diff --git a/digest/algorithm.go b/digest/algorithm.go new file mode 100644 index 00000000..9f74e552 --- /dev/null +++ b/digest/algorithm.go @@ -0,0 +1,140 @@ +package digest + +import ( + "crypto" + "fmt" + "hash" + "io" +) + +// Algorithm identifies and implementation of a digester by an identifier. +// Note the that this defines both the hash algorithm used and the string +// encoding. +type Algorithm string + +// supported digest types +const ( + SHA256 Algorithm = "sha256" // sha256 with hex encoding + SHA384 Algorithm = "sha384" // sha384 with hex encoding + SHA512 Algorithm = "sha512" // sha512 with hex encoding + + // Canonical is the primary digest algorithm used with the distribution + // project. Other digests may be used but this one is the primary storage + // digest. + Canonical = SHA256 +) + +var ( + // TODO(stevvooe): Follow the pattern of the standard crypto package for + // registration of digests. Effectively, we are a registerable set and + // common symbol access. + + // algorithms maps values to hash.Hash implementations. Other algorithms + // may be available but they cannot be calculated by the digest package. + algorithms = map[Algorithm]crypto.Hash{ + SHA256: crypto.SHA256, + SHA384: crypto.SHA384, + SHA512: crypto.SHA512, + } +) + +// Available returns true if the digest type is available for use. If this +// returns false, New and Hash will return nil. +func (a Algorithm) Available() bool { + h, ok := algorithms[a] + if !ok { + return false + } + + // check availability of the hash, as well + return h.Available() +} + +func (a Algorithm) String() string { + return string(a) +} + +// Size returns number of bytes returned by the hash. +func (a Algorithm) Size() int { + h, ok := algorithms[a] + if !ok { + return 0 + } + return h.Size() +} + +// Set implemented to allow use of Algorithm as a command line flag. +func (a *Algorithm) Set(value string) error { + if value == "" { + *a = Canonical + } else { + // just do a type conversion, support is queried with Available. + *a = Algorithm(value) + } + + return nil +} + +// New returns a new digester for the specified algorithm. If the algorithm +// does not have a digester implementation, nil will be returned. This can be +// checked by calling Available before calling New. +func (a Algorithm) Digester() Digester { + return &digester{ + alg: a, + hash: a.Hash(), + } +} + +// New is deprecated. Use Algorithm.Digester. +func (a Algorithm) New() Digester { + return a.Digester() +} + +// Hash returns a new hash as used by the algorithm. If not available, the +// method will panic. Check Algorithm.Available() before calling. +func (a Algorithm) Hash() hash.Hash { + if !a.Available() { + // NOTE(stevvooe): A missing hash is usually a programming error that + // must be resolved at compile time. We don't import in the digest + // package to allow users to choose their hash implementation (such as + // when using stevvooe/resumable or a hardware accelerated package). + // + // Applications that may want to resolve the hash at runtime should + // call Algorithm.Available before call Algorithm.Hash(). + panic(fmt.Sprintf("%v not available (make sure it is imported)", a)) + } + + return algorithms[a].New() +} + +// FromReader returns the digest of the reader using the algorithm. +func (a Algorithm) FromReader(rd io.Reader) (Digest, error) { + digester := a.New() + + if _, err := io.Copy(digester.Hash(), rd); err != nil { + return "", err + } + + return digester.Digest(), nil +} + +// FromBytes digests the input and returns a Digest. +func (a Algorithm) FromBytes(p []byte) Digest { + digester := a.New() + + if _, err := digester.Hash().Write(p); err != nil { + // Writes to a Hash should never fail. None of the existing + // hash implementations in the stdlib or hashes vendored + // here can return errors from Write. Having a panic in this + // condition instead of having FromBytes return an error value + // avoids unnecessary error handling paths in all callers. + panic("write to hash function returned error: " + err.Error()) + } + + return digester.Digest() +} + +// FromString digests the string input and returns a Digest. +func (a Algorithm) FromString(s string) Digest { + return a.FromBytes([]byte(s)) +} diff --git a/digest/digest.go b/digest/digest.go index 65c6f7f0..7e6e2d89 100644 --- a/digest/digest.go +++ b/digest/digest.go @@ -123,6 +123,20 @@ func (d Digest) Algorithm() Algorithm { return Algorithm(d[:d.sepIndex()]) } +// Verifier returns a writer object that can be used to verify a stream of +// content against the digest. If the digest is invalid, an error will be +// returned. +func (d Digest) Verifier() (Verifier, error) { + if err := d.Validate(); err != nil { + return nil, err + } + + return hashVerifier{ + hash: d.Algorithm().Hash(), + digest: d, + }, nil +} + // Hex returns the hex digest portion of the digest. This will panic if the // underlying digest is not in a valid format. func (d Digest) Hex() string { diff --git a/digest/digester.go b/digest/digester.go index 0435a1a6..918a3f91 100644 --- a/digest/digester.go +++ b/digest/digester.go @@ -1,141 +1,6 @@ package digest -import ( - "crypto" - "fmt" - "hash" - "io" -) - -// Algorithm identifies and implementation of a digester by an identifier. -// Note the that this defines both the hash algorithm used and the string -// encoding. -type Algorithm string - -// supported digest types -const ( - SHA256 Algorithm = "sha256" // sha256 with hex encoding - SHA384 Algorithm = "sha384" // sha384 with hex encoding - SHA512 Algorithm = "sha512" // sha512 with hex encoding - - // Canonical is the primary digest algorithm used with the distribution - // project. Other digests may be used but this one is the primary storage - // digest. - Canonical = SHA256 -) - -var ( - // TODO(stevvooe): Follow the pattern of the standard crypto package for - // registration of digests. Effectively, we are a registerable set and - // common symbol access. - - // algorithms maps values to hash.Hash implementations. Other algorithms - // may be available but they cannot be calculated by the digest package. - algorithms = map[Algorithm]crypto.Hash{ - SHA256: crypto.SHA256, - SHA384: crypto.SHA384, - SHA512: crypto.SHA512, - } -) - -// Available returns true if the digest type is available for use. If this -// returns false, New and Hash will return nil. -func (a Algorithm) Available() bool { - h, ok := algorithms[a] - if !ok { - return false - } - - // check availability of the hash, as well - return h.Available() -} - -func (a Algorithm) String() string { - return string(a) -} - -// Size returns number of bytes returned by the hash. -func (a Algorithm) Size() int { - h, ok := algorithms[a] - if !ok { - return 0 - } - return h.Size() -} - -// Set implemented to allow use of Algorithm as a command line flag. -func (a *Algorithm) Set(value string) error { - if value == "" { - *a = Canonical - } else { - // just do a type conversion, support is queried with Available. - *a = Algorithm(value) - } - - return nil -} - -// New returns a new digester for the specified algorithm. If the algorithm -// does not have a digester implementation, nil will be returned. This can be -// checked by calling Available before calling New. -func (a Algorithm) New() Digester { - return &digester{ - alg: a, - hash: a.Hash(), - } -} - -// Hash returns a new hash as used by the algorithm. If not available, the -// method will panic. Check Algorithm.Available() before calling. -func (a Algorithm) Hash() hash.Hash { - if !a.Available() { - // NOTE(stevvooe): A missing hash is usually a programming error that - // must be resolved at compile time. We don't import in the digest - // package to allow users to choose their hash implementation (such as - // when using stevvooe/resumable or a hardware accelerated package). - // - // Applications that may want to resolve the hash at runtime should - // call Algorithm.Available before call Algorithm.Hash(). - panic(fmt.Sprintf("%v not available (make sure it is imported)", a)) - } - - return algorithms[a].New() -} - -// FromReader returns the digest of the reader using the algorithm. -func (a Algorithm) FromReader(rd io.Reader) (Digest, error) { - digester := a.New() - - if _, err := io.Copy(digester.Hash(), rd); err != nil { - return "", err - } - - return digester.Digest(), nil -} - -// FromBytes digests the input and returns a Digest. -func (a Algorithm) FromBytes(p []byte) Digest { - digester := a.New() - - if _, err := digester.Hash().Write(p); err != nil { - // Writes to a Hash should never fail. None of the existing - // hash implementations in the stdlib or hashes vendored - // here can return errors from Write. Having a panic in this - // condition instead of having FromBytes return an error value - // avoids unnecessary error handling paths in all callers. - panic("write to hash function returned error: " + err.Error()) - } - - return digester.Digest() -} - -// FromString digests the string input and returns a Digest. -func (a Algorithm) FromString(s string) Digest { - return a.FromBytes([]byte(s)) -} - -// TODO(stevvooe): Allow resolution of verifiers using the digest type and -// this registration system. +import "hash" // Digester calculates the digest of written data. Writes should go directly // to the return value of Hash, while calling Digest will return the current diff --git a/digest/verifiers.go b/digest/verifiers.go index 9af3be13..89c1fb08 100644 --- a/digest/verifiers.go +++ b/digest/verifiers.go @@ -17,8 +17,7 @@ type Verifier interface { Verified() bool } -// NewDigestVerifier returns a verifier that compares the written bytes -// against a passed in digest. +// NewDigestVerifier is deprecated. Please use Digest.Verifier. func NewDigestVerifier(d Digest) (Verifier, error) { if err := d.Validate(); err != nil { return nil, err diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go index d8e93053..eff5c67b 100644 --- a/registry/handlers/api_test.go +++ b/registry/handlers/api_test.go @@ -552,7 +552,7 @@ func testBlobAPI(t *testing.T, env *testEnv, args blobArgs) *testEnv { }) // Verify the body - verifier, err := digest.NewDigestVerifier(layerDigest) + verifier, err := layerDigest.Verifier() if err != nil { t.Fatalf("unexpected error getting digest verifier: %s", err) } diff --git a/registry/storage/blobwriter.go b/registry/storage/blobwriter.go index 668a6fc9..7e42a59b 100644 --- a/registry/storage/blobwriter.go +++ b/registry/storage/blobwriter.go @@ -236,7 +236,7 @@ func (bw *blobWriter) validateBlob(ctx context.Context, desc distribution.Descri if !verified { digester := digest.Canonical.New() - digestVerifier, err := digest.NewDigestVerifier(desc.Digest) + digestVerifier, err := desc.Digest.Verifier() if err != nil { return distribution.Descriptor{}, err } diff --git a/registry/storage/filereader_test.go b/registry/storage/filereader_test.go index 5926020c..fa363f44 100644 --- a/registry/storage/filereader_test.go +++ b/registry/storage/filereader_test.go @@ -41,7 +41,7 @@ func TestSimpleRead(t *testing.T) { t.Fatalf("error allocating file reader: %v", err) } - verifier, err := digest.NewDigestVerifier(dgst) + verifier, err := dgst.Verifier() if err != nil { t.Fatalf("error getting digest verifier: %s", err) }