Remove digest package's dependency on external sha implementation

The change relies on a refactor of the upstream resumable sha256/sha512 package
that opts to register implementations with the standard library. This allows
the resumable support to be detected where it matters, avoiding unnecessary and
complex code. It also ensures that consumers of the digest package don't need
to depend on the forked sha implementations.

We also get an optimization with this change. If the size of data written to a
digester is the same as the file size, we check to see if the digest has been
verified. This works if the blob is written and committed in a single request.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2015-05-20 23:44:08 -07:00
parent 07ff029506
commit eee6cad2cf
32 changed files with 431 additions and 459 deletions

View file

@ -1,87 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package crypto is a Subset of the Go `crypto` Package with a Resumable Hash
package crypto
import (
"hash"
"strconv"
)
// Hash identifies a cryptographic hash function that is implemented in another
// package.
type Hash uint
// HashFunc simply returns the value of h so that Hash implements SignerOpts.
func (h Hash) HashFunc() Hash {
return h
}
const (
SHA224 Hash = 1 + iota // import crypto/sha256
SHA256 // import crypto/sha256
SHA384 // import crypto/sha512
SHA512 // import crypto/sha512
maxHash
)
var digestSizes = []uint8{
SHA224: 28,
SHA256: 32,
SHA384: 48,
SHA512: 64,
}
// Size returns the length, in bytes, of a digest resulting from the given hash
// function. It doesn't require that the hash function in question be linked
// into the program.
func (h Hash) Size() int {
if h > 0 && h < maxHash {
return int(digestSizes[h])
}
panic("crypto: Size of unknown hash function")
}
// ResumableHash is the common interface implemented by all resumable hash
// functions.
type ResumableHash interface {
// ResumableHash is a superset of hash.Hash
hash.Hash
// Len returns the number of bytes written to the Hash so far.
Len() uint64
// State returns a snapshot of the state of the Hash.
State() ([]byte, error)
// Restore resets the Hash to the given state.
Restore(state []byte) error
}
var hashes = make([]func() ResumableHash, maxHash)
// New returns a new ResumableHash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() ResumableHash {
if h > 0 && h < maxHash {
f := hashes[h]
if f != nil {
return f()
}
}
panic("crypto: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
}
// Available reports whether the given hash function is linked into the binary.
func (h Hash) Available() bool {
return h < maxHash && hashes[h] != nil
}
// RegisterHash registers a function that returns a new instance of the given
// hash function. This is intended to be called from the init function in
// packages that implement hash functions.
func RegisterHash(h Hash, f func() ResumableHash) {
if h >= maxHash {
panic("crypto: RegisterHash of unknown hash function")
}
hashes[h] = f
}

View file

@ -1,23 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines flags attached to various functions
// and data objects. The compilers, assemblers, and linker must
// all agree on these values.
// Don't profile the marked routine. This flag is deprecated.
#define NOPROF 1
// It is ok for the linker to get multiple of these symbols. It will
// pick one of the duplicates to use.
#define DUPOK 2
// Don't insert stack check preamble.
#define NOSPLIT 4
// Put this data in a read-only section.
#define RODATA 8
// This data contains no pointers.
#define NOPTR 16
// This is a wrapper function and should not count as disabling 'recover'.
#define WRAPPER 32
// This function uses its incoming context register.
#define NEEDCTXT 64

View file

@ -3,4 +3,4 @@ A Subset of the Go `crypto` Package with a Resumable Hash Interface
### Documentation
GoDocs: http://godoc.org/github.com/jlhawn/go-crypto
GoDocs: http://godoc.org/github.com/stevvooe/resumable

View file

@ -0,0 +1,43 @@
// Package resumable registers resumable versions of hash functions. Resumable
// varieties of hash functions are available via the standard crypto package.
// Support can be checked by type assertion against the resumable.Hash
// interface.
//
// While one can use these sub-packages directly, it makes more sense to
// register them using side-effect imports:
//
// import _ "github.com/stevvooe/resumable/sha256"
//
// This will make the resumable hashes available to the application through
// the standard crypto package. For example, if a new sha256 is required, one
// should use the following:
//
// h := crypto.SHA256.New()
//
// Such a features allows one to control the inclusion of resumable hash
// support in a single file. Applications that require the resumable hash
// implementation can type switch to detect support, while other parts of the
// application can be completely oblivious to the presence of the alternative
// hash functions.
//
// Also note that the implementations available in this package are completely
// untouched from their Go counterparts in the standard library. Only an extra
// file is added to each package to implement the extra resumable hash
// functions.
package resumable
import "hash"
// Hash is the common interface implemented by all resumable hash functions.
type Hash interface {
hash.Hash
// Len returns the number of bytes written to the Hash so far.
Len() int64
// State returns a snapshot of the state of the Hash.
State() ([]byte, error)
// Restore resets the Hash to the given state.
Restore(state []byte) error
}

View file

@ -6,8 +6,8 @@ import (
)
// Len returns the number of bytes which have been written to the digest.
func (d *digest) Len() uint64 {
return d.len
func (d *digest) Len() int64 {
return int64(d.len)
}
// State returns a snapshot of the state of the digest.

View file

@ -7,7 +7,8 @@
package sha256
import (
"github.com/jlhawn/go-crypto"
"crypto"
"hash"
)
func init() {
@ -77,15 +78,15 @@ func (d *digest) Reset() {
d.len = 0
}
// New returns a new crypto.ResumableHash computing the SHA256 checksum.
func New() crypto.ResumableHash {
// New returns a new hash.Hash computing the SHA256 checksum.
func New() hash.Hash {
d := new(digest)
d.Reset()
return d
}
// New224 returns a new crypto.ResumableHash computing the SHA224 checksum.
func New224() crypto.ResumableHash {
// New224 returns a new hash.Hash computing the SHA224 checksum.
func New224() hash.Hash {
d := new(digest)
d.is224 = true
d.Reset()

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "../textflag.h"
#include "textflag.h"
// SHA256 block routine. See sha256block.go for Go equivalent.
//

View file

@ -2,15 +2,17 @@ package sha256
import (
"bytes"
stdlib "crypto"
"crypto"
"crypto/rand"
_ "crypto/sha256" // To register the stdlib sha224 and sha256 algs.
resumable "github.com/jlhawn/go-crypto"
"crypto/sha256" // To register the stdlib sha224 and sha256 algs.
"hash"
"io"
"testing"
"github.com/stevvooe/resumable"
)
func compareResumableHash(t *testing.T, r resumable.Hash, h stdlib.Hash) {
func compareResumableHash(t *testing.T, newResumable func() hash.Hash, newStdlib func() hash.Hash) {
// Read 3 Kilobytes of random data into a buffer.
buf := make([]byte, 3*1024)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
@ -20,8 +22,8 @@ func compareResumableHash(t *testing.T, r resumable.Hash, h stdlib.Hash) {
// Use two Hash objects to consume prefixes of the data. One will be
// snapshotted and resumed with each additional byte, then both will write
// that byte. The digests should be equal after each byte is digested.
resumableHasher := r.New()
stdlibHasher := h.New()
resumableHasher := newResumable().(resumable.Hash)
stdlibHasher := newStdlib()
// First, assert that the initial distest is the same.
if !bytes.Equal(resumableHasher.Sum(nil), stdlibHasher.Sum(nil)) {
@ -52,6 +54,21 @@ func compareResumableHash(t *testing.T, r resumable.Hash, h stdlib.Hash) {
}
func TestResumable(t *testing.T) {
compareResumableHash(t, resumable.SHA224, stdlib.SHA224)
compareResumableHash(t, resumable.SHA256, stdlib.SHA256)
compareResumableHash(t, New224, sha256.New224)
compareResumableHash(t, New, sha256.New)
}
func TestResumableRegistered(t *testing.T) {
for _, hf := range []crypto.Hash{crypto.SHA224, crypto.SHA256} {
// make sure that the hash gets the resumable version from the global
// registry in crypto library.
h := hf.New()
if rh, ok := h.(resumable.Hash); !ok {
t.Fatalf("non-resumable hash function registered: %#v %#v", rh, crypto.SHA256)
}
}
}

View file

@ -6,8 +6,8 @@ import (
)
// Len returns the number of bytes which have been written to the digest.
func (d *digest) Len() uint64 {
return d.len
func (d *digest) Len() int64 {
return int64(d.len)
}
// State returns a snapshot of the state of the digest.

View file

@ -7,7 +7,8 @@
package sha512
import (
"github.com/jlhawn/go-crypto"
"crypto"
"hash"
)
func init() {
@ -77,15 +78,15 @@ func (d *digest) Reset() {
d.len = 0
}
// New returns a new crypto.ResumableHash computing the SHA512 checksum.
func New() crypto.ResumableHash {
// New returns a new hash.Hash computing the SHA512 checksum.
func New() hash.Hash {
d := new(digest)
d.Reset()
return d
}
// New384 returns a new crypto.ResumableHash computing the SHA384 checksum.
func New384() crypto.ResumableHash {
// New384 returns a new hash.Hash computing the SHA384 checksum.
func New384() hash.Hash {
d := new(digest)
d.is384 = true
d.Reset()

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "../textflag.h"
#include "textflag.h"
// SHA512 block routine. See sha512block.go for Go equivalent.
//

View file

@ -2,15 +2,17 @@ package sha512
import (
"bytes"
stdlib "crypto"
"crypto/rand"
_ "crypto/sha512" // To register the stdlib sha224 and sha256 algs.
resumable "github.com/jlhawn/go-crypto"
"crypto"
"crypto/rand" // To register the stdlib sha224 and sha256 algs.
"crypto/sha512"
"hash"
"io"
"testing"
"github.com/stevvooe/resumable"
)
func compareResumableHash(t *testing.T, r resumable.Hash, h stdlib.Hash) {
func compareResumableHash(t *testing.T, newResumable func() hash.Hash, newStdlib func() hash.Hash) {
// Read 3 Kilobytes of random data into a buffer.
buf := make([]byte, 3*1024)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
@ -20,8 +22,8 @@ func compareResumableHash(t *testing.T, r resumable.Hash, h stdlib.Hash) {
// Use two Hash objects to consume prefixes of the data. One will be
// snapshotted and resumed with each additional byte, then both will write
// that byte. The digests should be equal after each byte is digested.
resumableHasher := r.New()
stdlibHasher := h.New()
resumableHasher := newResumable().(resumable.Hash)
stdlibHasher := newStdlib()
// First, assert that the initial distest is the same.
if !bytes.Equal(resumableHasher.Sum(nil), stdlibHasher.Sum(nil)) {
@ -52,6 +54,21 @@ func compareResumableHash(t *testing.T, r resumable.Hash, h stdlib.Hash) {
}
func TestResumable(t *testing.T) {
compareResumableHash(t, resumable.SHA384, stdlib.SHA384)
compareResumableHash(t, resumable.SHA512, stdlib.SHA512)
compareResumableHash(t, New384, sha512.New384)
compareResumableHash(t, New, sha512.New)
}
func TestResumableRegistered(t *testing.T) {
for _, hf := range []crypto.Hash{crypto.SHA384, crypto.SHA512} {
// make sure that the hash gets the resumable version from the global
// registry in crypto library.
h := hf.New()
if rh, ok := h.(resumable.Hash); !ok {
t.Fatalf("non-resumable hash function registered: %#v %#v", rh, crypto.SHA256)
}
}
}