Skip to content

Commit

Permalink
[FAB-3441] bccsp/sw ECDSA/RSA sign 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
ECDSA/RSA sign 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 ECDSA/RSA is now at more than 90%

Change-Id: I08d1b7b840384fc052f8912d7e331c2d28784a44
Signed-off-by: Angelo De Caro <adc@zurich.ibm.com>
  • Loading branch information
adecaro committed May 2, 2017
1 parent f3c61e6 commit b17e846
Show file tree
Hide file tree
Showing 8 changed files with 496 additions and 16 deletions.
10 changes: 8 additions & 2 deletions bccsp/sw/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func unmarshalECDSASignature(raw []byte) (*big.Int, *big.Int, error) {
return sig.R, sig.S, nil
}

func (csp *impl) signECDSA(k *ecdsa.PrivateKey, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) {
func signECDSA(k *ecdsa.PrivateKey, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) {
r, s, err := ecdsa.Sign(rand.Reader, k, digest)
if err != nil {
return nil, err
Expand All @@ -96,7 +96,7 @@ func (csp *impl) signECDSA(k *ecdsa.PrivateKey, digest []byte, opts bccsp.Signer
return marshalECDSASignature(r, s)
}

func (csp *impl) verifyECDSA(k *ecdsa.PublicKey, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) {
func verifyECDSA(k *ecdsa.PublicKey, signature, digest []byte, opts bccsp.SignerOpts) (valid bool, err error) {
r, s, err := unmarshalECDSASignature(signature)
if err != nil {
return false, fmt.Errorf("Failed unmashalling signature [%s]", err)
Expand All @@ -115,3 +115,9 @@ func (csp *impl) verifyECDSA(k *ecdsa.PublicKey, signature, digest []byte, opts

return ecdsa.Verify(k, digest, r, s), nil
}

type ecdsaSigner struct{}

func (s *ecdsaSigner) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) {
return signECDSA(k.(*ecdsaPrivateKey).privKey, digest, opts)
}
226 changes: 226 additions & 0 deletions bccsp/sw/ecdsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
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/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"math/big"
"testing"

"github.com/stretchr/testify/assert"
)

func TestUnmarshalECDSASignature(t *testing.T) {
_, _, err := unmarshalECDSASignature(nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Failed unmashalling signature [")

_, _, err = unmarshalECDSASignature([]byte{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "Failed unmashalling signature [")

_, _, err = unmarshalECDSASignature([]byte{0})
assert.Error(t, err)
assert.Contains(t, err.Error(), "Failed unmashalling signature [")

sigma, err := marshalECDSASignature(big.NewInt(-1), big.NewInt(1))
assert.NoError(t, err)
_, _, err = unmarshalECDSASignature(sigma)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Invalid signature. R must be larger than zero")

sigma, err = marshalECDSASignature(big.NewInt(0), big.NewInt(1))
assert.NoError(t, err)
_, _, err = unmarshalECDSASignature(sigma)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Invalid signature. R must be larger than zero")

sigma, err = marshalECDSASignature(big.NewInt(1), big.NewInt(0))
assert.NoError(t, err)
_, _, err = unmarshalECDSASignature(sigma)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Invalid signature. S must be larger than zero")

sigma, err = marshalECDSASignature(big.NewInt(1), big.NewInt(-1))
assert.NoError(t, err)
_, _, err = unmarshalECDSASignature(sigma)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Invalid signature. S must be larger than zero")

sigma, err = marshalECDSASignature(big.NewInt(1), big.NewInt(1))
assert.NoError(t, err)
R, S, err := unmarshalECDSASignature(sigma)
assert.NoError(t, err)
assert.Equal(t, big.NewInt(1), R)
assert.Equal(t, big.NewInt(1), S)
}

func TestSignECDSA(t *testing.T) {
// Generate a key
lowLevelKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.NoError(t, err)

// Induce an error on the underlying ecdsa algorithm
msg := []byte("hello world")
oldN := lowLevelKey.Params().N
defer func() { lowLevelKey.Params().N = oldN }()
lowLevelKey.Params().N = big.NewInt(0)
_, err = signECDSA(lowLevelKey, msg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "zero parameter")
lowLevelKey.Params().N = oldN

oldCurveHalfOrders := curveHalfOrders
curveHalfOrders = nil
defer func() { curveHalfOrders = oldCurveHalfOrders }()
_, err = signECDSA(lowLevelKey, msg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Curve not recognized [")
curveHalfOrders = oldCurveHalfOrders
}

func TestVerifyECDSA(t *testing.T) {
// Generate a key
lowLevelKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.NoError(t, err)

msg := []byte("hello world")
sigma, err := signECDSA(lowLevelKey, msg, nil)
assert.NoError(t, err)

valid, err := verifyECDSA(&lowLevelKey.PublicKey, sigma, msg, nil)
assert.NoError(t, err)
assert.True(t, valid)

_, err = verifyECDSA(&lowLevelKey.PublicKey, nil, msg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Failed unmashalling signature [")

oldCurveHalfOrders := curveHalfOrders
curveHalfOrders = nil
defer func() { curveHalfOrders = oldCurveHalfOrders }()
_, err = verifyECDSA(&lowLevelKey.PublicKey, sigma, msg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Curve not recognized [")
curveHalfOrders = oldCurveHalfOrders

_, err = verifyECDSA(&lowLevelKey.PublicKey, nil, msg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Failed unmashalling signature [")

R, S, err := unmarshalECDSASignature(sigma)
assert.NoError(t, err)
S.Add(curveHalfOrders[elliptic.P256()], big.NewInt(1))
sigmaWrongS, err := marshalECDSASignature(R, S)
assert.NoError(t, err)
_, err = verifyECDSA(&lowLevelKey.PublicKey, sigmaWrongS, msg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Invalid S. Must be smaller than half the order [")
}

func TestEcdsaSignerSign(t *testing.T) {
signer := &ecdsaSigner{}

// Generate a key
lowLevelKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.NoError(t, err)
k := &ecdsaPrivateKey{lowLevelKey}

// Sign
msg := []byte("Hello World")
sigma, err := signer.Sign(k, msg, nil)
assert.NoError(t, err)
assert.NotNil(t, sigma)

// Verify
valid, err := verifyECDSA(&lowLevelKey.PublicKey, sigma, msg, nil)
assert.NoError(t, err)
assert.True(t, valid)
}

func TestEcdsaPrivateKey(t *testing.T) {
lowLevelKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.NoError(t, err)
k := &ecdsaPrivateKey{lowLevelKey}

assert.False(t, k.Symmetric())
assert.True(t, k.Private())

_, err = k.Bytes()
assert.Error(t, err)
assert.Contains(t, err.Error(), "Not supported.")

k.privKey = nil
ski := k.SKI()
assert.Nil(t, ski)

k.privKey = lowLevelKey
ski = k.SKI()
raw := elliptic.Marshal(k.privKey.Curve, k.privKey.PublicKey.X, k.privKey.PublicKey.Y)
hash := sha256.New()
hash.Write(raw)
ski2 := hash.Sum(nil)
assert.Equal(t, ski2, ski, "SKI is not computed in the right way.")

pk, err := k.PublicKey()
assert.NoError(t, err)
assert.NotNil(t, pk)
ecdsaPK, ok := pk.(*ecdsaPublicKey)
assert.True(t, ok)
assert.Equal(t, &lowLevelKey.PublicKey, ecdsaPK.pubKey)
}

func TestEcdsaPublicKey(t *testing.T) {
lowLevelKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.NoError(t, err)
k := &ecdsaPublicKey{&lowLevelKey.PublicKey}

assert.False(t, k.Symmetric())
assert.False(t, k.Private())

k.pubKey = nil
ski := k.SKI()
assert.Nil(t, ski)

k.pubKey = &lowLevelKey.PublicKey
ski = k.SKI()
raw := elliptic.Marshal(k.pubKey.Curve, k.pubKey.X, k.pubKey.Y)
hash := sha256.New()
hash.Write(raw)
ski2 := hash.Sum(nil)
assert.Equal(t, ski, ski2, "SKI is not computed in the right way.")

pk, err := k.PublicKey()
assert.NoError(t, err)
assert.Equal(t, k, pk)

bytes, err := k.Bytes()
assert.NoError(t, err)
bytes2, err := x509.MarshalPKIXPublicKey(k.pubKey)
assert.Equal(t, bytes2, bytes, "bytes are not computed in the right way.")

invalidCurve := &elliptic.CurveParams{Name: "P-Invalid"}
invalidCurve.BitSize = 1024
k.pubKey = &ecdsa.PublicKey{Curve: invalidCurve, X: big.NewInt(1), Y: big.NewInt(1)}
_, err = k.Bytes()
assert.Error(t, err)
assert.Contains(t, err.Error(), "Failed marshalling key [")
}
32 changes: 18 additions & 14 deletions bccsp/sw/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,17 @@ func New(securityLevel int, hashFamily string, keyStore bccsp.KeyStore) (bccsp.B
decryptors := make(map[reflect.Type]Decryptor)
decryptors[reflect.TypeOf(&aesPrivateKey{})] = &aescbcpkcs7Decryptor{}

return &impl{conf, keyStore, encryptors, decryptors}, nil
// Set the signers
signers := make(map[reflect.Type]Signer)
signers[reflect.TypeOf(&ecdsaPrivateKey{})] = &ecdsaSigner{}
signers[reflect.TypeOf(&rsaPrivateKey{})] = &rsaSigner{}

return &impl{
conf,
keyStore,
encryptors,
decryptors,
signers}, nil
}

// SoftwareBasedBCCSP is the software-based implementation of the BCCSP.
Expand All @@ -90,6 +100,7 @@ type impl struct {

encryptors map[reflect.Type]Encryptor
decryptors map[reflect.Type]Decryptor
signers map[reflect.Type]Signer
}

// KeyGen generates a key using opts.
Expand Down Expand Up @@ -661,19 +672,12 @@ func (csp *impl) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signat
return nil, errors.New("Invalid digest. Cannot be empty.")
}

// Check key type
switch k.(type) {
case *ecdsaPrivateKey:
return csp.signECDSA(k.(*ecdsaPrivateKey).privKey, digest, opts)
case *rsaPrivateKey:
if opts == nil {
return nil, errors.New("Invalid options. Nil.")
}

return k.(*rsaPrivateKey).privKey.Sign(rand.Reader, digest, opts)
default:
signer, found := csp.signers[reflect.TypeOf(k)]
if !found {
return nil, fmt.Errorf("Unsupported 'SignKey' provided [%v]", k)
}

return signer.Sign(k, digest, opts)
}

// Verify verifies signature against key k and digest
Expand All @@ -692,9 +696,9 @@ func (csp *impl) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.Signer
// Check key type
switch k.(type) {
case *ecdsaPrivateKey:
return csp.verifyECDSA(&(k.(*ecdsaPrivateKey).privKey.PublicKey), signature, digest, opts)
return verifyECDSA(&(k.(*ecdsaPrivateKey).privKey.PublicKey), signature, digest, opts)
case *ecdsaPublicKey:
return csp.verifyECDSA(k.(*ecdsaPublicKey).pubKey, signature, digest, opts)
return verifyECDSA(k.(*ecdsaPublicKey).pubKey, signature, digest, opts)
case *rsaPrivateKey:
if opts == nil {
return false, errors.New("Invalid options. It must not be nil.")
Expand Down
12 changes: 12 additions & 0 deletions bccsp/sw/internals.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ type Decryptor interface {
// The opts argument should be appropriate for the algorithm used.
Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error)
}

// Signer is a BCCSP-like interface that provides signing algorithms
type Signer interface {

// Sign signs digest using key k.
// The opts argument should be appropriate for the algorithm used.
//
// Note that when a signature of a hash of a larger message is needed,
// the caller is responsible for hashing the larger message and passing
// the hash (as digest).
Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error)
}
23 changes: 23 additions & 0 deletions bccsp/sw/mocks/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,26 @@ func (e *Encryptor) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterO

return e.EncValue, e.EncErr
}

type Signer struct {
KeyArg bccsp.Key
DigestArg []byte
OptsArg bccsp.SignerOpts

Value []byte
Err error
}

func (s *Signer) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) {
if !reflect.DeepEqual(s.KeyArg, k) {
return nil, errors.New("invalid key")
}
if !reflect.DeepEqual(s.DigestArg, digest) {
return nil, errors.New("invalid digest")
}
if !reflect.DeepEqual(s.OptsArg, opts) {
return nil, errors.New("invalid opts")
}

return s.Value, s.Err
}
34 changes: 34 additions & 0 deletions bccsp/sw/rsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
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/rand"
"errors"

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

type rsaSigner struct{}

func (s *rsaSigner) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) {
if opts == nil {
return nil, errors.New("Invalid options. Must be different from nil.")
}

return k.(*rsaPrivateKey).privKey.Sign(rand.Reader, digest, opts)
}
Loading

0 comments on commit b17e846

Please sign in to comment.