Skip to content

Commit

Permalink
[FAB-3441] bccsp/sw AES test coverage
Browse files Browse the repository at this point in the history
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 <adc@zurich.ibm.com>
  • Loading branch information
adecaro committed May 1, 2017
1 parent 1195f2f commit e1be7cd
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 44 deletions.
2 changes: 2 additions & 0 deletions bccsp/mocks/mocks.go
Expand Up @@ -160,3 +160,5 @@ func (*KeyImportOpts) Algorithm() string {
func (*KeyImportOpts) Ephemeral() bool {
panic("Not yet implemented")
}

type EncrypterOpts struct{}
34 changes: 27 additions & 7 deletions bccsp/sw/aes.go
Expand Up @@ -24,6 +24,8 @@ import (
"errors"
"fmt"
"io"

"github.com/hyperledger/fabric/bccsp"
)

// GetRandomBytes returns len random looking bytes
Expand Down Expand Up @@ -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)
}
}
62 changes: 62 additions & 0 deletions bccsp/sw/aes_test.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
}
50 changes: 50 additions & 0 deletions 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)
}
65 changes: 28 additions & 37 deletions bccsp/sw/impl.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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)
}
35 changes: 35 additions & 0 deletions 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)
}
47 changes: 47 additions & 0 deletions 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
}

0 comments on commit e1be7cd

Please sign in to comment.