Skip to content

Commit e1be7cd

Browse files
committed
[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 <[email protected]>
1 parent 1195f2f commit e1be7cd

File tree

7 files changed

+251
-44
lines changed

7 files changed

+251
-44
lines changed

bccsp/mocks/mocks.go

+2
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,5 @@ func (*KeyImportOpts) Algorithm() string {
160160
func (*KeyImportOpts) Ephemeral() bool {
161161
panic("Not yet implemented")
162162
}
163+
164+
type EncrypterOpts struct{}

bccsp/sw/aes.go

+27-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"errors"
2525
"fmt"
2626
"io"
27+
28+
"github.com/hyperledger/fabric/bccsp"
2729
)
2830

2931
// GetRandomBytes returns len random looking bytes
@@ -123,15 +125,33 @@ func AESCBCPKCS7Encrypt(key, src []byte) ([]byte, error) {
123125
func AESCBCPKCS7Decrypt(key, src []byte) ([]byte, error) {
124126
// First decrypt
125127
pt, err := aesCBCDecrypt(key, src)
126-
if err != nil {
127-
return nil, err
128+
if err == nil {
129+
return pkcs7UnPadding(pt)
128130
}
131+
return nil, err
132+
}
129133

130-
// Then remove padding
131-
original, err := pkcs7UnPadding(pt)
132-
if err != nil {
133-
return nil, err
134+
type aescbcpkcs7Encryptor struct{}
135+
136+
func (*aescbcpkcs7Encryptor) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error) {
137+
switch opts.(type) {
138+
case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts:
139+
// AES in CBC mode with PKCS7 padding
140+
return AESCBCPKCS7Encrypt(k.(*aesPrivateKey).privKey, plaintext)
141+
default:
142+
return nil, fmt.Errorf("Mode not recognized [%s]", opts)
134143
}
144+
}
135145

136-
return original, nil
146+
type aescbcpkcs7Decryptor struct{}
147+
148+
func (*aescbcpkcs7Decryptor) Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error) {
149+
// check for mode
150+
switch opts.(type) {
151+
case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts:
152+
// AES in CBC mode with PKCS7 padding
153+
return AESCBCPKCS7Decrypt(k.(*aesPrivateKey).privKey, ciphertext)
154+
default:
155+
return nil, fmt.Errorf("Mode not recognized [%s]", opts)
156+
}
137157
}

bccsp/sw/aes_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"math/big"
2323
"testing"
2424

25+
"github.com/hyperledger/fabric/bccsp"
26+
"github.com/hyperledger/fabric/bccsp/mocks"
2527
"github.com/hyperledger/fabric/bccsp/utils"
2628
"github.com/stretchr/testify/assert"
2729
)
@@ -476,3 +478,63 @@ func TestVariousAESKeyEncoding(t *testing.T) {
476478
t.Fatalf("Failed converting encrypted PEM to AES key. Keys are different [%x][%x]", key, keyFromPEM)
477479
}
478480
}
481+
482+
func TestPkcs7UnPaddingInvalidInputs(t *testing.T) {
483+
_, err := pkcs7UnPadding([]byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
484+
assert.Error(t, err)
485+
assert.Equal(t, "Invalid pkcs7 padding (pad[i] != unpadding)", err.Error())
486+
}
487+
488+
func TestAESCBCEncryptInvalidInputs(t *testing.T) {
489+
_, err := aesCBCEncrypt(nil, []byte{0, 1, 2, 3})
490+
assert.Error(t, err)
491+
assert.Equal(t, "Invalid plaintext. It must be a multiple of the block size", err.Error())
492+
493+
_, err = aesCBCEncrypt([]byte{0}, []byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})
494+
assert.Error(t, err)
495+
}
496+
497+
func TestAESCBCDecryptInvalidInputs(t *testing.T) {
498+
_, err := aesCBCDecrypt([]byte{0}, []byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})
499+
assert.Error(t, err)
500+
501+
_, err = aesCBCDecrypt([]byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, []byte{0})
502+
assert.Error(t, err)
503+
504+
_, err = aesCBCDecrypt([]byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
505+
[]byte{1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
506+
assert.Error(t, err)
507+
}
508+
509+
// TestAESCBCPKCS7EncryptorDecrypt tests the integration of
510+
// aescbcpkcs7Encryptor and aescbcpkcs7Decryptor
511+
func TestAESCBCPKCS7EncryptorDecrypt(t *testing.T) {
512+
raw, err := GetRandomBytes(32)
513+
assert.NoError(t, err)
514+
515+
k := &aesPrivateKey{privKey: raw, exportable: false}
516+
517+
msg := []byte("Hello World")
518+
encryptor := &aescbcpkcs7Encryptor{}
519+
520+
_, err = encryptor.Encrypt(k, msg, nil)
521+
assert.Error(t, err)
522+
523+
_, err = encryptor.Encrypt(k, msg, &mocks.EncrypterOpts{})
524+
assert.Error(t, err)
525+
526+
ct, err := encryptor.Encrypt(k, msg, &bccsp.AESCBCPKCS7ModeOpts{})
527+
assert.NoError(t, err)
528+
529+
decryptor := &aescbcpkcs7Decryptor{}
530+
531+
_, err = decryptor.Decrypt(k, ct, nil)
532+
assert.Error(t, err)
533+
534+
_, err = decryptor.Decrypt(k, ct, &mocks.EncrypterOpts{})
535+
assert.Error(t, err)
536+
537+
msg2, err := decryptor.Decrypt(k, ct, &bccsp.AESCBCPKCS7ModeOpts{})
538+
assert.NoError(t, err)
539+
assert.Equal(t, msg, msg2)
540+
}

bccsp/sw/enc_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package sw
18+
19+
import (
20+
"errors"
21+
"reflect"
22+
"testing"
23+
24+
mocks2 "github.com/hyperledger/fabric/bccsp/mocks"
25+
"github.com/hyperledger/fabric/bccsp/sw/mocks"
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
func TestEncrypt(t *testing.T) {
30+
expectedKey := &mocks2.MockKey{}
31+
expectedPlaintext := []byte{1, 2, 3, 4}
32+
expectedOpts := &mocks2.EncrypterOpts{}
33+
expectedCiphertext := []byte{0, 1, 2, 3, 4}
34+
expectedErr := errors.New("no error")
35+
36+
encryptors := make(map[reflect.Type]Encryptor)
37+
encryptors[reflect.TypeOf(&mocks2.MockKey{})] = &mocks.Encryptor{
38+
KeyArg: expectedKey,
39+
PlaintextArg: expectedPlaintext,
40+
OptsArg: expectedOpts,
41+
EncValue: expectedCiphertext,
42+
EncErr: expectedErr,
43+
}
44+
45+
csp := impl{encryptors: encryptors}
46+
47+
ct, err := csp.Encrypt(expectedKey, expectedPlaintext, expectedOpts)
48+
assert.Equal(t, expectedCiphertext, ct)
49+
assert.Equal(t, expectedErr, err)
50+
}

bccsp/sw/impl.go

+28-37
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,18 @@ package sw
1717

1818
import (
1919
"crypto/ecdsa"
20+
"crypto/elliptic"
21+
"crypto/hmac"
2022
"crypto/rand"
21-
"errors"
22-
"fmt"
23-
"math/big"
24-
2523
"crypto/rsa"
26-
27-
"hash"
28-
29-
"crypto/x509"
30-
31-
"crypto/hmac"
32-
33-
"crypto/elliptic"
3424
"crypto/sha256"
3525
"crypto/sha512"
26+
"crypto/x509"
27+
"errors"
28+
"fmt"
29+
"hash"
30+
"math/big"
31+
"reflect"
3632

3733
"github.com/hyperledger/fabric/bccsp"
3834
"github.com/hyperledger/fabric/bccsp/utils"
@@ -76,13 +72,24 @@ func New(securityLevel int, hashFamily string, keyStore bccsp.KeyStore) (bccsp.B
7672
return nil, errors.New("Invalid bccsp.KeyStore instance. It must be different from nil.")
7773
}
7874

79-
return &impl{conf, keyStore}, nil
75+
// Set the encryptors
76+
encryptors := make(map[reflect.Type]Encryptor)
77+
encryptors[reflect.TypeOf(&aesPrivateKey{})] = &aescbcpkcs7Encryptor{}
78+
79+
// Set the decryptors
80+
decryptors := make(map[reflect.Type]Decryptor)
81+
decryptors[reflect.TypeOf(&aesPrivateKey{})] = &aescbcpkcs7Decryptor{}
82+
83+
return &impl{conf, keyStore, encryptors, decryptors}, nil
8084
}
8185

8286
// SoftwareBasedBCCSP is the software-based implementation of the BCCSP.
8387
type impl struct {
8488
conf *config
8589
ks bccsp.KeyStore
90+
91+
encryptors map[reflect.Type]Encryptor
92+
decryptors map[reflect.Type]Decryptor
8693
}
8794

8895
// KeyGen generates a key using opts.
@@ -729,20 +736,12 @@ func (csp *impl) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts
729736
return nil, errors.New("Invalid Key. It must not be nil.")
730737
}
731738

732-
// Check key type
733-
switch k.(type) {
734-
case *aesPrivateKey:
735-
// check for mode
736-
switch opts.(type) {
737-
case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts:
738-
// AES in CBC mode with PKCS7 padding
739-
return AESCBCPKCS7Encrypt(k.(*aesPrivateKey).privKey, plaintext)
740-
default:
741-
return nil, fmt.Errorf("Mode not recognized [%s]", opts)
742-
}
743-
default:
739+
encryptor, found := csp.encryptors[reflect.TypeOf(k)]
740+
if !found {
744741
return nil, fmt.Errorf("Unsupported 'EncryptKey' provided [%v]", k)
745742
}
743+
744+
return encryptor.Encrypt(k, plaintext, opts)
746745
}
747746

748747
// Decrypt decrypts ciphertext using key k.
@@ -753,18 +752,10 @@ func (csp *impl) Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpt
753752
return nil, errors.New("Invalid Key. It must not be nil.")
754753
}
755754

756-
// Check key type
757-
switch k.(type) {
758-
case *aesPrivateKey:
759-
// check for mode
760-
switch opts.(type) {
761-
case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts:
762-
// AES in CBC mode with PKCS7 padding
763-
return AESCBCPKCS7Decrypt(k.(*aesPrivateKey).privKey, ciphertext)
764-
default:
765-
return nil, fmt.Errorf("Mode not recognized [%s]", opts)
766-
}
767-
default:
755+
decryptor, found := csp.decryptors[reflect.TypeOf(k)]
756+
if !found {
768757
return nil, fmt.Errorf("Unsupported 'DecryptKey' provided [%v]", k)
769758
}
759+
760+
return decryptor.Decrypt(k, ciphertext, opts)
770761
}

bccsp/sw/internals.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package sw
18+
19+
import "github.com/hyperledger/fabric/bccsp"
20+
21+
// Encryptor is a BCCSP-like interface that provides encryption algorithms
22+
type Encryptor interface {
23+
24+
// Encrypt encrypts plaintext using key k.
25+
// The opts argument should be appropriate for the algorithm used.
26+
Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error)
27+
}
28+
29+
// Decryptor is a BCCSP-like interface that provides decryption algorithms
30+
type Decryptor interface {
31+
32+
// Decrypt decrypts ciphertext using key k.
33+
// The opts argument should be appropriate for the algorithm used.
34+
Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error)
35+
}

bccsp/sw/mocks/mocks.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package mocks
18+
19+
import (
20+
"errors"
21+
"reflect"
22+
23+
"github.com/hyperledger/fabric/bccsp"
24+
)
25+
26+
type Encryptor struct {
27+
KeyArg bccsp.Key
28+
PlaintextArg []byte
29+
OptsArg bccsp.EncrypterOpts
30+
31+
EncValue []byte
32+
EncErr error
33+
}
34+
35+
func (e *Encryptor) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error) {
36+
if !reflect.DeepEqual(e.KeyArg, k) {
37+
return nil, errors.New("invalid key")
38+
}
39+
if !reflect.DeepEqual(e.PlaintextArg, plaintext) {
40+
return nil, errors.New("invalid plaintext")
41+
}
42+
if !reflect.DeepEqual(e.OptsArg, opts) {
43+
return nil, errors.New("invalid opts")
44+
}
45+
46+
return e.EncValue, e.EncErr
47+
}

0 commit comments

Comments
 (0)