8c254edb9a
This addresses a subtle deadlock where an error during a copy prevented pipe closure to propagate correctly. By closing down the read end of the pipe rather than the write end, the waiting writer is properly signaled. A nice side-effect of this change is that errors encountered by io.Copy are no propagated to the verifier's Write method. A test to ensure validation errors for unsupported digest types has been added, as well. Signed-off-by: Stephen J Day <stephen.day@docker.com>
137 lines
2.9 KiB
Go
137 lines
2.9 KiB
Go
package digest
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"hash"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"github.com/docker/docker/pkg/tarsum"
|
|
)
|
|
|
|
// Verifier presents a general verification interface to be used with message
|
|
// digests and other byte stream verifications. Users instantiate a Verifier
|
|
// from one of the various methods, write the data under test to it then check
|
|
// the result with the Verified method.
|
|
type Verifier interface {
|
|
io.Writer
|
|
|
|
// Verified will return true if the content written to Verifier matches
|
|
// the digest.
|
|
Verified() bool
|
|
}
|
|
|
|
// NewDigestVerifier returns a verifier that compares the written bytes
|
|
// against a passed in digest.
|
|
func NewDigestVerifier(d Digest) (Verifier, error) {
|
|
if err := d.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
alg := d.Algorithm()
|
|
switch alg {
|
|
case "sha256", "sha384", "sha512":
|
|
return hashVerifier{
|
|
hash: newHash(alg),
|
|
digest: d,
|
|
}, nil
|
|
default:
|
|
// Assume we have a tarsum.
|
|
version, err := tarsum.GetVersionFromTarsum(string(d))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pr, pw := io.Pipe()
|
|
|
|
// TODO(stevvooe): We may actually want to ban the earlier versions of
|
|
// tarsum. That decision may not be the place of the verifier.
|
|
|
|
ts, err := tarsum.NewTarSum(pr, true, version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(sday): Ick! A goroutine per digest verification? We'll have to
|
|
// get the tarsum library to export an io.Writer variant.
|
|
go func() {
|
|
if _, err := io.Copy(ioutil.Discard, ts); err != nil {
|
|
pr.CloseWithError(err)
|
|
} else {
|
|
pr.Close()
|
|
}
|
|
}()
|
|
|
|
return &tarsumVerifier{
|
|
digest: d,
|
|
ts: ts,
|
|
pr: pr,
|
|
pw: pw,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// NewLengthVerifier returns a verifier that returns true when the number of
|
|
// read bytes equals the expected parameter.
|
|
func NewLengthVerifier(expected int64) Verifier {
|
|
return &lengthVerifier{
|
|
expected: expected,
|
|
}
|
|
}
|
|
|
|
type lengthVerifier struct {
|
|
expected int64 // expected bytes read
|
|
len int64 // bytes read
|
|
}
|
|
|
|
func (lv *lengthVerifier) Write(p []byte) (n int, err error) {
|
|
n = len(p)
|
|
lv.len += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
func (lv *lengthVerifier) Verified() bool {
|
|
return lv.expected == lv.len
|
|
}
|
|
|
|
func newHash(name string) hash.Hash {
|
|
switch name {
|
|
case "sha256":
|
|
return sha256.New()
|
|
case "sha384":
|
|
return sha512.New384()
|
|
case "sha512":
|
|
return sha512.New()
|
|
default:
|
|
panic("unsupport algorithm: " + name)
|
|
}
|
|
}
|
|
|
|
type hashVerifier struct {
|
|
digest Digest
|
|
hash hash.Hash
|
|
}
|
|
|
|
func (hv hashVerifier) Write(p []byte) (n int, err error) {
|
|
return hv.hash.Write(p)
|
|
}
|
|
|
|
func (hv hashVerifier) Verified() bool {
|
|
return hv.digest == NewDigest(hv.digest.Algorithm(), hv.hash)
|
|
}
|
|
|
|
type tarsumVerifier struct {
|
|
digest Digest
|
|
ts tarsum.TarSum
|
|
pr *io.PipeReader
|
|
pw *io.PipeWriter
|
|
}
|
|
|
|
func (tv *tarsumVerifier) Write(p []byte) (n int, err error) {
|
|
return tv.pw.Write(p)
|
|
}
|
|
|
|
func (tv *tarsumVerifier) Verified() bool {
|
|
return tv.digest == Digest(tv.ts.Sum(nil))
|
|
}
|