Skip to content

Commit

Permalink
Refactor specification of supported digests
Browse files Browse the repository at this point in the history
To make the definition of supported digests more clear, we have refactored the
digest package to have a special Algorithm type. This represents the digest's
prefix and we associated various supported hash implementations through
function calls.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
  • Loading branch information
stevvooe committed May 23, 2015
1 parent a418a41 commit 2d4d92c
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 45 deletions.
16 changes: 6 additions & 10 deletions digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package digest

import (
"bytes"
"crypto"
"fmt"
"hash"
"io"
Expand All @@ -19,9 +18,6 @@ const (

// DigestSha256EmptyTar is the canonical sha256 digest of empty data
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

CanonicalAlgorithm = "sha256"
CanonicalHash = crypto.SHA256 // main digest algorithm used through distribution
)

// Digest allows simple protection of hex formatted digest strings, prefixed
Expand All @@ -43,7 +39,7 @@ const (
type Digest string

// NewDigest returns a Digest from alg and a hash.Hash object.
func NewDigest(alg string, h hash.Hash) Digest {
func NewDigest(alg Algorithm, h hash.Hash) Digest {
return Digest(fmt.Sprintf("%s:%x", alg, h.Sum(nil)))
}

Expand Down Expand Up @@ -76,7 +72,7 @@ func ParseDigest(s string) (Digest, error) {

// FromReader returns the most valid digest for the underlying content.
func FromReader(rd io.Reader) (Digest, error) {
digester := NewCanonicalDigester()
digester := Canonical.New()

if _, err := io.Copy(digester.Hash(), rd); err != nil {
return "", err
Expand Down Expand Up @@ -135,8 +131,8 @@ func (d Digest) Validate() error {
return ErrDigestInvalidFormat
}

switch s[:i] {
case "sha256", "sha384", "sha512":
switch Algorithm(s[:i]) {
case SHA256, SHA384, SHA512:
break
default:
return ErrDigestUnsupported
Expand All @@ -147,8 +143,8 @@ func (d Digest) Validate() error {

// Algorithm returns the algorithm portion of the digest. This will panic if
// the underlying digest is not in a valid format.
func (d Digest) Algorithm() string {
return string(d[:d.sepIndex()])
func (d Digest) Algorithm() Algorithm {
return Algorithm(d[:d.sepIndex()])
}

// Hex returns the hex digest portion of the digest. This will panic if the
Expand Down
2 changes: 1 addition & 1 deletion digest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestParseDigest(t *testing.T) {
for _, testcase := range []struct {
input string
err error
algorithm string
algorithm Algorithm
hex string
}{
{
Expand Down
91 changes: 73 additions & 18 deletions digester.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,88 @@
package digest

import "hash"
import (
"crypto"
"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
// value of the digest.
type Digester interface {
Hash() hash.Hash // provides direct access to underlying hash instance.
Digest() Digest
// 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
TarsumV1SHA256 Algorithm = "tarsum+v1+sha256" // supported tarsum version, verification only

// 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()
}

// NewDigester create a new Digester with the given hashing algorithm and
// instance of that algo's hasher.
func NewDigester(alg string, h hash.Hash) Digester {
// 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: alg,
hash: h,
alg: a,
hash: a.Hash(),
}
}

// Hash returns a new hash as used by the algorithm. If not available, nil is
// returned. Make sure to check Available before calling.
func (a Algorithm) Hash() hash.Hash {
if !a.Available() {
return nil
}

return algorithms[a].New()
}

// NewCanonicalDigester is a convenience function to create a new Digester with
// our default settings.
func NewCanonicalDigester() Digester {
return NewDigester(CanonicalAlgorithm, CanonicalHash.New())
// TODO(stevvooe): Allow resolution of verifiers using the digest type and
// this registration system.

// Digester calculates the digest of written data. Writes should go directly
// to the return value of Hash, while calling Digest will return the current
// value of the digest.
type Digester interface {
Hash() hash.Hash // provides direct access to underlying hash instance.
Digest() Digest
}

// digester provides a simple digester definition that embeds a hasher.
type digester struct {
alg string
alg Algorithm
hash hash.Hash
}

Expand All @@ -36,5 +91,5 @@ func (d *digester) Hash() hash.Hash {
}

func (d *digester) Digest() Digest {
return NewDigest(d.alg, d.Hash())
return NewDigest(d.alg, d.hash)
}
17 changes: 1 addition & 16 deletions verifiers.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package digest

import (
"crypto/sha256"
"crypto/sha512"
"hash"
"io"
"io/ioutil"
Expand Down Expand Up @@ -33,7 +31,7 @@ func NewDigestVerifier(d Digest) (Verifier, error) {
switch alg {
case "sha256", "sha384", "sha512":
return hashVerifier{
hash: newHash(alg),
hash: alg.Hash(),
digest: d,
}, nil
default:
Expand Down Expand Up @@ -95,19 +93,6 @@ 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
Expand Down

0 comments on commit 2d4d92c

Please sign in to comment.