From 1b7b1630b0471132cb0bef04da19df12d90ecfa2 Mon Sep 17 00:00:00 2001 From: Angelo De Caro Date: Mon, 1 May 2017 10:57:46 +0200 Subject: [PATCH] [FAB-3441] bccsp/sw Hash test coverage Using the approach discussed in FAB-3465, this change-sets refactors the way hashing is done at bccsp/sw. Essentially, the switch has been replaced by a map. The approach decouples the testing of the bccsp interface implementation from the cryptographic algorithms. Test-coverage of Hash is now at more than 85% Change-Id: Ib9c5b45da54241d959c09f7b0351f3163be0c6ef Signed-off-by: Angelo De Caro --- bccsp/mocks/mocks.go | 6 ++ bccsp/sw/hash.go | 37 ++++++++++++ bccsp/sw/hash_test.go | 84 ++++++++++++++++++++++++++ bccsp/sw/impl.go | 69 ++++++++++------------ bccsp/sw/impl_test.go | 128 +++------------------------------------- bccsp/sw/internals.go | 18 +++++- bccsp/sw/mocks/mocks.go | 29 +++++++++ bccsp/sw/sw_test.go | 26 ++++++++ 8 files changed, 240 insertions(+), 157 deletions(-) create mode 100644 bccsp/sw/hash.go create mode 100644 bccsp/sw/hash_test.go diff --git a/bccsp/mocks/mocks.go b/bccsp/mocks/mocks.go index 86d342b1d45..816456005a5 100644 --- a/bccsp/mocks/mocks.go +++ b/bccsp/mocks/mocks.go @@ -162,3 +162,9 @@ func (*KeyImportOpts) Ephemeral() bool { } type EncrypterOpts struct{} + +type HashOpts struct{} + +func (HashOpts) Algorithm() string { + return "Mock HashOpts" +} diff --git a/bccsp/sw/hash.go b/bccsp/sw/hash.go new file mode 100644 index 00000000000..a4b2dbc028e --- /dev/null +++ b/bccsp/sw/hash.go @@ -0,0 +1,37 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sw + +import ( + "hash" + + "github.com/hyperledger/fabric/bccsp" +) + +type hasher struct { + hash func() hash.Hash +} + +func (c *hasher) Hash(msg []byte, opts bccsp.HashOpts) (hash []byte, err error) { + h := c.hash() + h.Write(msg) + return h.Sum(nil), nil +} + +func (c *hasher) GetHash(opts bccsp.HashOpts) (h hash.Hash, err error) { + return c.hash(), nil +} diff --git a/bccsp/sw/hash_test.go b/bccsp/sw/hash_test.go new file mode 100644 index 00000000000..b7a50f174fc --- /dev/null +++ b/bccsp/sw/hash_test.go @@ -0,0 +1,84 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sw + +import ( + "crypto/sha256" + "errors" + "reflect" + "testing" + + mocks2 "github.com/hyperledger/fabric/bccsp/mocks" + "github.com/hyperledger/fabric/bccsp/sw/mocks" + "github.com/stretchr/testify/assert" +) + +func TestHash(t *testing.T) { + expectetMsg := []byte{1, 2, 3, 4} + expectedOpts := &mocks2.HashOpts{} + expectetValue := []byte{1, 2, 3, 4, 5} + expectedErr := errors.New("no error") + + hashers := make(map[reflect.Type]Hasher) + hashers[reflect.TypeOf(&mocks2.HashOpts{})] = &mocks.Hasher{ + MsgArg: expectetMsg, + OptsArg: expectedOpts, + Value: expectetValue, + Err: expectedErr, + } + + csp := impl{hashers: hashers} + + value, err := csp.Hash(expectetMsg, expectedOpts) + assert.Equal(t, expectetValue, value) + assert.Equal(t, expectedErr, err) +} + +func TestGetHash(t *testing.T) { + expectedOpts := &mocks2.HashOpts{} + expectetValue := sha256.New() + expectedErr := errors.New("no error") + + hashers := make(map[reflect.Type]Hasher) + hashers[reflect.TypeOf(&mocks2.HashOpts{})] = &mocks.Hasher{ + OptsArg: expectedOpts, + ValueHash: expectetValue, + Err: expectedErr, + } + + csp := impl{hashers: hashers} + + value, err := csp.GetHash(expectedOpts) + assert.Equal(t, expectetValue, value) + assert.Equal(t, expectedErr, err) +} + +func TestHasher(t *testing.T) { + hasher := &hasher{hash: sha256.New} + + msg := []byte("Hello World") + out, err := hasher.Hash(msg, nil) + assert.NoError(t, err) + h := sha256.New() + h.Write(msg) + out2 := h.Sum(nil) + assert.Equal(t, out, out2) + + hf, err := hasher.GetHash(nil) + assert.NoError(t, err) + assert.Equal(t, hf, sha256.New()) +} diff --git a/bccsp/sw/impl.go b/bccsp/sw/impl.go index 1e00470cf12..2661f03f53c 100644 --- a/bccsp/sw/impl.go +++ b/bccsp/sw/impl.go @@ -21,8 +21,6 @@ import ( "crypto/hmac" "crypto/rand" "crypto/rsa" - "crypto/sha256" - "crypto/sha512" "crypto/x509" "errors" "fmt" @@ -30,6 +28,9 @@ import ( "math/big" "reflect" + "crypto/sha256" + "crypto/sha512" + "github.com/hyperledger/fabric/bccsp" "github.com/hyperledger/fabric/bccsp/utils" "github.com/hyperledger/fabric/common/flogging" @@ -92,13 +93,24 @@ func New(securityLevel int, hashFamily string, keyStore bccsp.KeyStore) (bccsp.B verifiers[reflect.TypeOf(&rsaPrivateKey{})] = &rsaPrivateKeyVerifier{} verifiers[reflect.TypeOf(&rsaPublicKey{})] = &rsaPublicKeyKeyVerifier{} - return &impl{ + // Set the hashers + hashers := make(map[reflect.Type]Hasher) + hashers[reflect.TypeOf(&bccsp.SHAOpts{})] = &hasher{hash: conf.hashFunction} + hashers[reflect.TypeOf(&bccsp.SHA256Opts{})] = &hasher{hash: sha256.New} + hashers[reflect.TypeOf(&bccsp.SHA384Opts{})] = &hasher{hash: sha512.New384} + hashers[reflect.TypeOf(&bccsp.SHA3_256Opts{})] = &hasher{hash: sha3.New256} + hashers[reflect.TypeOf(&bccsp.SHA3_384Opts{})] = &hasher{hash: sha3.New384} + + impl := &impl{ conf: conf, ks: keyStore, encryptors: encryptors, decryptors: decryptors, signers: signers, - verifiers: verifiers}, nil + verifiers: verifiers, + hashers: hashers} + + return impl, nil } // SoftwareBasedBCCSP is the software-based implementation of the BCCSP. @@ -110,6 +122,7 @@ type impl struct { decryptors map[reflect.Type]Decryptor signers map[reflect.Type]Signer verifiers map[reflect.Type]Verifier + hashers map[reflect.Type]Hasher } // KeyGen generates a key using opts. @@ -619,51 +632,33 @@ func (csp *impl) GetKey(ski []byte) (k bccsp.Key, err error) { // Hash hashes messages msg using options opts. func (csp *impl) Hash(msg []byte, opts bccsp.HashOpts) (digest []byte, err error) { - var h hash.Hash + // Validate arguments if opts == nil { - h = csp.conf.hashFunction() - } else { - switch opts.(type) { - case *bccsp.SHAOpts: - h = csp.conf.hashFunction() - case *bccsp.SHA256Opts: - h = sha256.New() - case *bccsp.SHA384Opts: - h = sha512.New384() - case *bccsp.SHA3_256Opts: - h = sha3.New256() - case *bccsp.SHA3_384Opts: - h = sha3.New384() - default: - return nil, fmt.Errorf("Algorithm not recognized [%s]", opts.Algorithm()) - } + return nil, errors.New("Invalid opts. It must not be nil.") } - h.Write(msg) - return h.Sum(nil), nil + hasher, found := csp.hashers[reflect.TypeOf(opts)] + if !found { + return nil, fmt.Errorf("Unsupported 'HashOpt' provided [%v]", opts) + } + + return hasher.Hash(msg, opts) } // GetHash returns and instance of hash.Hash using options opts. // If opts is nil then the default hash function is returned. func (csp *impl) GetHash(opts bccsp.HashOpts) (h hash.Hash, err error) { + // Validate arguments if opts == nil { - return csp.conf.hashFunction(), nil + return nil, errors.New("Invalid opts. It must not be nil.") } - switch opts.(type) { - case *bccsp.SHAOpts: - return csp.conf.hashFunction(), nil - case *bccsp.SHA256Opts: - return sha256.New(), nil - case *bccsp.SHA384Opts: - return sha512.New384(), nil - case *bccsp.SHA3_256Opts: - return sha3.New256(), nil - case *bccsp.SHA3_384Opts: - return sha3.New384(), nil - default: - return nil, fmt.Errorf("Algorithm not recognized [%s]", opts.Algorithm()) + hasher, found := csp.hashers[reflect.TypeOf(opts)] + if !found { + return nil, fmt.Errorf("Unsupported 'HashOpt' provided [%v]", opts) } + + return hasher.GetHash(opts) } // Sign signs digest using key k. diff --git a/bccsp/sw/impl_test.go b/bccsp/sw/impl_test.go index 52e08aca623..2802a9e295a 100644 --- a/bccsp/sw/impl_test.go +++ b/bccsp/sw/impl_test.go @@ -17,29 +17,24 @@ package sw import ( "bytes" - "os" - "testing" - "crypto" - "crypto/rsa" - + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/sha512" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "fmt" + "hash" "math/big" "net" + "os" + "testing" "time" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/sha256" - - "fmt" - - "crypto/sha512" - "hash" - "github.com/hyperledger/fabric/bccsp" "github.com/hyperledger/fabric/bccsp/signer" "github.com/hyperledger/fabric/bccsp/utils" @@ -375,66 +370,6 @@ func TestKeyGenAESOpts(t *testing.T) { } } -func TestHashOpts(t *testing.T) { - msg := []byte("abcd") - - // SHA256 - digest1, err := currentBCCSP.Hash(msg, &bccsp.SHA256Opts{}) - if err != nil { - t.Fatalf("Failed computing SHA256 [%s]", err) - } - - h := sha256.New() - h.Write(msg) - digest2 := h.Sum(nil) - - if !bytes.Equal(digest1, digest2) { - t.Fatalf("Different SHA256 computed. [%x][%x]", digest1, digest2) - } - - // SHA384 - digest1, err = currentBCCSP.Hash(msg, &bccsp.SHA384Opts{}) - if err != nil { - t.Fatalf("Failed computing SHA384 [%s]", err) - } - - h = sha512.New384() - h.Write(msg) - digest2 = h.Sum(nil) - - if !bytes.Equal(digest1, digest2) { - t.Fatalf("Different SHA384 computed. [%x][%x]", digest1, digest2) - } - - // SHA3_256O - digest1, err = currentBCCSP.Hash(msg, &bccsp.SHA3_256Opts{}) - if err != nil { - t.Fatalf("Failed computing SHA3_256 [%s]", err) - } - - h = sha3.New256() - h.Write(msg) - digest2 = h.Sum(nil) - - if !bytes.Equal(digest1, digest2) { - t.Fatalf("Different SHA3_256 computed. [%x][%x]", digest1, digest2) - } - - // SHA3_384 - digest1, err = currentBCCSP.Hash(msg, &bccsp.SHA3_384Opts{}) - if err != nil { - t.Fatalf("Failed computing SHA3_384 [%s]", err) - } - - h = sha3.New384() - h.Write(msg) - digest2 = h.Sum(nil) - - if !bytes.Equal(digest1, digest2) { - t.Fatalf("Different SHA3_384 computed. [%x][%x]", digest1, digest2) - } -} - func TestECDSAKeyGenEphemeral(t *testing.T) { k, err := currentBCCSP.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) if err != nil { @@ -1848,51 +1783,6 @@ func TestKeyImportFromX509RSAPublicKey(t *testing.T) { } } -func TestGetHashAndHashCompatibility(t *testing.T) { - - msg1 := []byte("abcd") - msg2 := []byte("efgh") - msg := []byte("abcdefgh") - - digest1, err := currentBCCSP.Hash(msg, &bccsp.SHAOpts{}) - if err != nil { - t.Fatalf("Failed computing HASH [%s]", err) - } - - digest2, err := currentBCCSP.Hash(msg, nil) - if err != nil { - t.Fatalf("Failed computing HASH [%s]", err) - } - - if !bytes.Equal(digest1, digest2) { - t.Fatalf("Different hash computed. [%x][%x]", digest1, digest2) - } - - h, err := currentBCCSP.GetHash(nil) - if err != nil { - t.Fatalf("Failed getting hash.Hash instance [%s]", err) - } - h.Write(msg1) - h.Write(msg2) - digest3 := h.Sum(nil) - - h2, err := currentBCCSP.GetHash(&bccsp.SHAOpts{}) - if err != nil { - t.Fatalf("Failed getting SHA hash.Hash instance [%s]", err) - } - h2.Write(msg1) - h2.Write(msg2) - digest4 := h2.Sum(nil) - - if !bytes.Equal(digest3, digest4) { - t.Fatalf("Different hash computed. [%x][%x]", digest3, digest4) - } - - if !bytes.Equal(digest1, digest3) { - t.Fatalf("Different hash computed. [%x][%x]", digest1, digest3) - } -} - func getCryptoHashIndex(t *testing.T) crypto.Hash { switch currentTestConfig.hashFamily { case "SHA2": diff --git a/bccsp/sw/internals.go b/bccsp/sw/internals.go index 1917a662816..b41837b07ba 100644 --- a/bccsp/sw/internals.go +++ b/bccsp/sw/internals.go @@ -16,7 +16,11 @@ limitations under the License. package sw -import "github.com/hyperledger/fabric/bccsp" +import ( + "hash" + + "github.com/hyperledger/fabric/bccsp" +) // Encryptor is a BCCSP-like interface that provides encryption algorithms type Encryptor interface { @@ -53,3 +57,15 @@ type Verifier interface { // The opts argument should be appropriate for the algorithm used. Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) } + +// Hasher is a BCCSP-like interface that provides hash algorithms +type Hasher interface { + + // Hash hashes messages msg using options opts. + // If opts is nil, the default hash function will be used. + Hash(msg []byte, opts bccsp.HashOpts) (hash []byte, err error) + + // GetHash returns and instance of hash.Hash using options opts. + // If opts is nil, the default hash function will be returned. + GetHash(opts bccsp.HashOpts) (h hash.Hash, err error) +} diff --git a/bccsp/sw/mocks/mocks.go b/bccsp/sw/mocks/mocks.go index 50f2560c4ae..359b6a908f2 100644 --- a/bccsp/sw/mocks/mocks.go +++ b/bccsp/sw/mocks/mocks.go @@ -18,6 +18,7 @@ package mocks import ( "errors" + "hash" "reflect" "github.com/hyperledger/fabric/bccsp" @@ -95,3 +96,31 @@ func (s *Verifier) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.Sign return s.Value, s.Err } + +type Hasher struct { + MsgArg []byte + OptsArg bccsp.HashOpts + + Value []byte + ValueHash hash.Hash + Err error +} + +func (h *Hasher) Hash(msg []byte, opts bccsp.HashOpts) (hash []byte, err error) { + if !reflect.DeepEqual(h.MsgArg, msg) { + return nil, errors.New("invalid message") + } + if !reflect.DeepEqual(h.OptsArg, opts) { + return nil, errors.New("invalid opts") + } + + return h.Value, h.Err +} + +func (h *Hasher) GetHash(opts bccsp.HashOpts) (hash.Hash, error) { + if !reflect.DeepEqual(h.OptsArg, opts) { + return nil, errors.New("invalid opts") + } + + return h.ValueHash, h.Err +} diff --git a/bccsp/sw/sw_test.go b/bccsp/sw/sw_test.go index dd35186d689..6ff4219cfd8 100644 --- a/bccsp/sw/sw_test.go +++ b/bccsp/sw/sw_test.go @@ -156,3 +156,29 @@ func TestDecryptInvalidInputs(t *testing.T) { assert.Error(t, err) assert.True(t, strings.Contains(err.Error(), "Unsupported 'DecryptKey' provided [")) } + +func TestHashInvalidInputs(t *testing.T) { + csp, err := New(256, "SHA2", &mocks.KeyStore{}) + assert.NoError(t, err) + + _, err = csp.Hash(nil, nil) + assert.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "Invalid opts. It must not be nil.")) + + _, err = csp.Hash(nil, &mocks.HashOpts{}) + assert.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "Unsupported 'HashOpt' provided [")) +} + +func TestGetHashInvalidInputs(t *testing.T) { + csp, err := New(256, "SHA2", &mocks.KeyStore{}) + assert.NoError(t, err) + + _, err = csp.GetHash(nil) + assert.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "Invalid opts. It must not be nil.")) + + _, err = csp.GetHash(&mocks.HashOpts{}) + assert.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "Unsupported 'HashOpt' provided [")) +}