From e1be7cd21d36a46efc0f6635fa9c9e8e7aa40f9d Mon Sep 17 00:00:00 2001 From: Angelo De Caro Date: Fri, 28 Apr 2017 10:50:34 +0200 Subject: [PATCH] [FAB-3441] bccsp/sw AES test coverage Using the approach discussed in FAB-3465, this change-sets refactors the way AES encryption and decryption 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 AES is now at more than 90% Change-Id: I828d882b727321a3ffa52d2aa0a73e7dd075e92c Signed-off-by: Angelo De Caro --- bccsp/mocks/mocks.go | 2 ++ bccsp/sw/aes.go | 34 ++++++++++++++++----- bccsp/sw/aes_test.go | 62 +++++++++++++++++++++++++++++++++++++++ bccsp/sw/enc_test.go | 50 +++++++++++++++++++++++++++++++ bccsp/sw/impl.go | 65 ++++++++++++++++++----------------------- bccsp/sw/internals.go | 35 ++++++++++++++++++++++ bccsp/sw/mocks/mocks.go | 47 +++++++++++++++++++++++++++++ 7 files changed, 251 insertions(+), 44 deletions(-) create mode 100644 bccsp/sw/enc_test.go create mode 100644 bccsp/sw/internals.go create mode 100644 bccsp/sw/mocks/mocks.go diff --git a/bccsp/mocks/mocks.go b/bccsp/mocks/mocks.go index 17e9508b11e..86d342b1d45 100644 --- a/bccsp/mocks/mocks.go +++ b/bccsp/mocks/mocks.go @@ -160,3 +160,5 @@ func (*KeyImportOpts) Algorithm() string { func (*KeyImportOpts) Ephemeral() bool { panic("Not yet implemented") } + +type EncrypterOpts struct{} diff --git a/bccsp/sw/aes.go b/bccsp/sw/aes.go index 32a05b92808..97b6f0fc1a9 100644 --- a/bccsp/sw/aes.go +++ b/bccsp/sw/aes.go @@ -24,6 +24,8 @@ import ( "errors" "fmt" "io" + + "github.com/hyperledger/fabric/bccsp" ) // GetRandomBytes returns len random looking bytes @@ -123,15 +125,33 @@ func AESCBCPKCS7Encrypt(key, src []byte) ([]byte, error) { func AESCBCPKCS7Decrypt(key, src []byte) ([]byte, error) { // First decrypt pt, err := aesCBCDecrypt(key, src) - if err != nil { - return nil, err + if err == nil { + return pkcs7UnPadding(pt) } + return nil, err +} - // Then remove padding - original, err := pkcs7UnPadding(pt) - if err != nil { - return nil, err +type aescbcpkcs7Encryptor struct{} + +func (*aescbcpkcs7Encryptor) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error) { + switch opts.(type) { + case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts: + // AES in CBC mode with PKCS7 padding + return AESCBCPKCS7Encrypt(k.(*aesPrivateKey).privKey, plaintext) + default: + return nil, fmt.Errorf("Mode not recognized [%s]", opts) } +} - return original, nil +type aescbcpkcs7Decryptor struct{} + +func (*aescbcpkcs7Decryptor) Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error) { + // check for mode + switch opts.(type) { + case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts: + // AES in CBC mode with PKCS7 padding + return AESCBCPKCS7Decrypt(k.(*aesPrivateKey).privKey, ciphertext) + default: + return nil, fmt.Errorf("Mode not recognized [%s]", opts) + } } diff --git a/bccsp/sw/aes_test.go b/bccsp/sw/aes_test.go index 41c682236e2..a4176697c81 100644 --- a/bccsp/sw/aes_test.go +++ b/bccsp/sw/aes_test.go @@ -22,6 +22,8 @@ import ( "math/big" "testing" + "github.com/hyperledger/fabric/bccsp" + "github.com/hyperledger/fabric/bccsp/mocks" "github.com/hyperledger/fabric/bccsp/utils" "github.com/stretchr/testify/assert" ) @@ -476,3 +478,63 @@ func TestVariousAESKeyEncoding(t *testing.T) { t.Fatalf("Failed converting encrypted PEM to AES key. Keys are different [%x][%x]", key, keyFromPEM) } } + +func TestPkcs7UnPaddingInvalidInputs(t *testing.T) { + _, err := pkcs7UnPadding([]byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + assert.Error(t, err) + assert.Equal(t, "Invalid pkcs7 padding (pad[i] != unpadding)", err.Error()) +} + +func TestAESCBCEncryptInvalidInputs(t *testing.T) { + _, err := aesCBCEncrypt(nil, []byte{0, 1, 2, 3}) + assert.Error(t, err) + assert.Equal(t, "Invalid plaintext. It must be a multiple of the block size", err.Error()) + + _, err = aesCBCEncrypt([]byte{0}, []byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + assert.Error(t, err) +} + +func TestAESCBCDecryptInvalidInputs(t *testing.T) { + _, err := aesCBCDecrypt([]byte{0}, []byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + assert.Error(t, err) + + _, err = aesCBCDecrypt([]byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, []byte{0}) + assert.Error(t, err) + + _, err = aesCBCDecrypt([]byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + []byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + assert.Error(t, err) +} + +// TestAESCBCPKCS7EncryptorDecrypt tests the integration of +// aescbcpkcs7Encryptor and aescbcpkcs7Decryptor +func TestAESCBCPKCS7EncryptorDecrypt(t *testing.T) { + raw, err := GetRandomBytes(32) + assert.NoError(t, err) + + k := &aesPrivateKey{privKey: raw, exportable: false} + + msg := []byte("Hello World") + encryptor := &aescbcpkcs7Encryptor{} + + _, err = encryptor.Encrypt(k, msg, nil) + assert.Error(t, err) + + _, err = encryptor.Encrypt(k, msg, &mocks.EncrypterOpts{}) + assert.Error(t, err) + + ct, err := encryptor.Encrypt(k, msg, &bccsp.AESCBCPKCS7ModeOpts{}) + assert.NoError(t, err) + + decryptor := &aescbcpkcs7Decryptor{} + + _, err = decryptor.Decrypt(k, ct, nil) + assert.Error(t, err) + + _, err = decryptor.Decrypt(k, ct, &mocks.EncrypterOpts{}) + assert.Error(t, err) + + msg2, err := decryptor.Decrypt(k, ct, &bccsp.AESCBCPKCS7ModeOpts{}) + assert.NoError(t, err) + assert.Equal(t, msg, msg2) +} diff --git a/bccsp/sw/enc_test.go b/bccsp/sw/enc_test.go new file mode 100644 index 00000000000..81ec70b6551 --- /dev/null +++ b/bccsp/sw/enc_test.go @@ -0,0 +1,50 @@ +/* +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 ( + "errors" + "reflect" + "testing" + + mocks2 "github.com/hyperledger/fabric/bccsp/mocks" + "github.com/hyperledger/fabric/bccsp/sw/mocks" + "github.com/stretchr/testify/assert" +) + +func TestEncrypt(t *testing.T) { + expectedKey := &mocks2.MockKey{} + expectedPlaintext := []byte{1, 2, 3, 4} + expectedOpts := &mocks2.EncrypterOpts{} + expectedCiphertext := []byte{0, 1, 2, 3, 4} + expectedErr := errors.New("no error") + + encryptors := make(map[reflect.Type]Encryptor) + encryptors[reflect.TypeOf(&mocks2.MockKey{})] = &mocks.Encryptor{ + KeyArg: expectedKey, + PlaintextArg: expectedPlaintext, + OptsArg: expectedOpts, + EncValue: expectedCiphertext, + EncErr: expectedErr, + } + + csp := impl{encryptors: encryptors} + + ct, err := csp.Encrypt(expectedKey, expectedPlaintext, expectedOpts) + assert.Equal(t, expectedCiphertext, ct) + assert.Equal(t, expectedErr, err) +} diff --git a/bccsp/sw/impl.go b/bccsp/sw/impl.go index cf1219b7d3d..9fc91e7931c 100644 --- a/bccsp/sw/impl.go +++ b/bccsp/sw/impl.go @@ -17,22 +17,18 @@ package sw import ( "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" "crypto/rand" - "errors" - "fmt" - "math/big" - "crypto/rsa" - - "hash" - - "crypto/x509" - - "crypto/hmac" - - "crypto/elliptic" "crypto/sha256" "crypto/sha512" + "crypto/x509" + "errors" + "fmt" + "hash" + "math/big" + "reflect" "github.com/hyperledger/fabric/bccsp" "github.com/hyperledger/fabric/bccsp/utils" @@ -76,13 +72,24 @@ func New(securityLevel int, hashFamily string, keyStore bccsp.KeyStore) (bccsp.B return nil, errors.New("Invalid bccsp.KeyStore instance. It must be different from nil.") } - return &impl{conf, keyStore}, nil + // Set the encryptors + encryptors := make(map[reflect.Type]Encryptor) + encryptors[reflect.TypeOf(&aesPrivateKey{})] = &aescbcpkcs7Encryptor{} + + // Set the decryptors + decryptors := make(map[reflect.Type]Decryptor) + decryptors[reflect.TypeOf(&aesPrivateKey{})] = &aescbcpkcs7Decryptor{} + + return &impl{conf, keyStore, encryptors, decryptors}, nil } // SoftwareBasedBCCSP is the software-based implementation of the BCCSP. type impl struct { conf *config ks bccsp.KeyStore + + encryptors map[reflect.Type]Encryptor + decryptors map[reflect.Type]Decryptor } // KeyGen generates a key using opts. @@ -729,20 +736,12 @@ func (csp *impl) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts return nil, errors.New("Invalid Key. It must not be nil.") } - // Check key type - switch k.(type) { - case *aesPrivateKey: - // check for mode - switch opts.(type) { - case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts: - // AES in CBC mode with PKCS7 padding - return AESCBCPKCS7Encrypt(k.(*aesPrivateKey).privKey, plaintext) - default: - return nil, fmt.Errorf("Mode not recognized [%s]", opts) - } - default: + encryptor, found := csp.encryptors[reflect.TypeOf(k)] + if !found { return nil, fmt.Errorf("Unsupported 'EncryptKey' provided [%v]", k) } + + return encryptor.Encrypt(k, plaintext, opts) } // Decrypt decrypts ciphertext using key k. @@ -753,18 +752,10 @@ func (csp *impl) Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpt return nil, errors.New("Invalid Key. It must not be nil.") } - // Check key type - switch k.(type) { - case *aesPrivateKey: - // check for mode - switch opts.(type) { - case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts: - // AES in CBC mode with PKCS7 padding - return AESCBCPKCS7Decrypt(k.(*aesPrivateKey).privKey, ciphertext) - default: - return nil, fmt.Errorf("Mode not recognized [%s]", opts) - } - default: + decryptor, found := csp.decryptors[reflect.TypeOf(k)] + if !found { return nil, fmt.Errorf("Unsupported 'DecryptKey' provided [%v]", k) } + + return decryptor.Decrypt(k, ciphertext, opts) } diff --git a/bccsp/sw/internals.go b/bccsp/sw/internals.go new file mode 100644 index 00000000000..b0b443b9bea --- /dev/null +++ b/bccsp/sw/internals.go @@ -0,0 +1,35 @@ +/* +Copyright IBM Corp. 2016 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 "github.com/hyperledger/fabric/bccsp" + +// Encryptor is a BCCSP-like interface that provides encryption algorithms +type Encryptor interface { + + // Encrypt encrypts plaintext using key k. + // The opts argument should be appropriate for the algorithm used. + Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error) +} + +// Decryptor is a BCCSP-like interface that provides decryption algorithms +type Decryptor interface { + + // Decrypt decrypts ciphertext using key k. + // The opts argument should be appropriate for the algorithm used. + Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error) +} diff --git a/bccsp/sw/mocks/mocks.go b/bccsp/sw/mocks/mocks.go new file mode 100644 index 00000000000..fd61f12eec5 --- /dev/null +++ b/bccsp/sw/mocks/mocks.go @@ -0,0 +1,47 @@ +/* +Copyright IBM Corp. 2016 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 mocks + +import ( + "errors" + "reflect" + + "github.com/hyperledger/fabric/bccsp" +) + +type Encryptor struct { + KeyArg bccsp.Key + PlaintextArg []byte + OptsArg bccsp.EncrypterOpts + + EncValue []byte + EncErr error +} + +func (e *Encryptor) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error) { + if !reflect.DeepEqual(e.KeyArg, k) { + return nil, errors.New("invalid key") + } + if !reflect.DeepEqual(e.PlaintextArg, plaintext) { + return nil, errors.New("invalid plaintext") + } + if !reflect.DeepEqual(e.OptsArg, opts) { + return nil, errors.New("invalid opts") + } + + return e.EncValue, e.EncErr +}