From cd435064f0e95bcd647e8c096bc69ee789a185c0 Mon Sep 17 00:00:00 2001 From: Angelo De Caro Date: Mon, 31 Oct 2016 10:36:57 +0100 Subject: [PATCH] Default Fabric Crypto Service Provider This change-set introduces a default implementation of the BCCSP whose interfaces were introduced in https://gerrit.hyperledger.org/r/#/c/2027/ The implementation is based on the crypto/primitives package and provides the following capabilities: 1. ecdsa key generation, sign, verify and public key marshalling, 2. HMAC, 3. Hashing, 4. AES key generation, encryption/decryption in CBC mode with PKCS7 padding. This change-set comes in the context of: https://jira.hyperledger.org/browse/FAB-354 Change-Id: Ica504c36756209ea540d010b4f48a801290a7be2 Signed-off-by: Angelo De Caro --- core/crypto/bccsp/bccsp.go | 7 +- core/crypto/bccsp/sw/aeskey.go | 46 +++ core/crypto/bccsp/sw/conf.go | 53 +++ core/crypto/bccsp/sw/ecdsakey.go | 86 +++++ core/crypto/bccsp/sw/impl.go | 417 +++++++++++++++++++++++ core/crypto/bccsp/sw/impl_test.go | 540 ++++++++++++++++++++++++++++++ core/crypto/bccsp/sw/ks.go | 236 +++++++++++++ 7 files changed, 1381 insertions(+), 4 deletions(-) create mode 100644 core/crypto/bccsp/sw/aeskey.go create mode 100644 core/crypto/bccsp/sw/conf.go create mode 100644 core/crypto/bccsp/sw/ecdsakey.go create mode 100644 core/crypto/bccsp/sw/impl.go create mode 100644 core/crypto/bccsp/sw/impl_test.go create mode 100644 core/crypto/bccsp/sw/ks.go diff --git a/core/crypto/bccsp/bccsp.go b/core/crypto/bccsp/bccsp.go index b267a3fd033..15ceb68d8d7 100644 --- a/core/crypto/bccsp/bccsp.go +++ b/core/crypto/bccsp/bccsp.go @@ -32,13 +32,12 @@ type Key interface { // false is this key is asymmetric Symmetric() bool - // Private returns true if this key is an asymmetric private key, + // Private returns true if this key is a private key, // false otherwise. Private() bool - // PublicKey returns the corresponding public key if this key - // is an asymmetric private key. If this key is already public, - // PublicKey returns this key itself. + // PublicKey returns the corresponding public key part of an asymmetric public/private key pair. + // This method returns an error in symmetric key schemes. PublicKey() (Key, error) } diff --git a/core/crypto/bccsp/sw/aeskey.go b/core/crypto/bccsp/sw/aeskey.go new file mode 100644 index 00000000000..dc277684862 --- /dev/null +++ b/core/crypto/bccsp/sw/aeskey.go @@ -0,0 +1,46 @@ +package sw + +import ( + "errors" + + "github.com/hyperledger/fabric/core/crypto/bccsp" + "github.com/hyperledger/fabric/core/crypto/primitives" +) + +type aesPrivateKey struct { + k []byte + exportable bool +} + +// Bytes converts this key to its byte representation, +// if this operation is allowed. +func (k *aesPrivateKey) Bytes() (raw []byte, err error) { + if k.exportable { + return k.k, nil + } + + return nil, errors.New("Not supported.") +} + +// SKI returns the subject key identifier of this key. +func (k *aesPrivateKey) SKI() (ski []byte) { + return primitives.Hash(k.k) +} + +// Symmetric returns true if this key is a symmetric key, +// false if this key is asymmetric +func (k *aesPrivateKey) Symmetric() bool { + return true +} + +// Private returns true if this key is a private key, +// false otherwise. +func (k *aesPrivateKey) Private() bool { + return true +} + +// PublicKey returns the corresponding public key part of an asymmetric public/private key pair. +// This method returns an error in symmetric key schemes. +func (k *aesPrivateKey) PublicKey() (bccsp.Key, error) { + return nil, errors.New("Cannot call this method on a symmetric key.") +} diff --git a/core/crypto/bccsp/sw/conf.go b/core/crypto/bccsp/sw/conf.go new file mode 100644 index 00000000000..08e31b18a16 --- /dev/null +++ b/core/crypto/bccsp/sw/conf.go @@ -0,0 +1,53 @@ +package sw + +import ( + "errors" + "path/filepath" + + "os" + + "github.com/spf13/viper" +) + +type config struct { + keystorePath string + + configurationPathProperty string +} + +func (conf *config) init() error { + conf.configurationPathProperty = "security.bccsp.default.keyStorePath" + + // Check mandatory fields + var rootPath string + if err := conf.checkProperty(conf.configurationPathProperty); err != nil { + logger.Warning("'security.bccsp.default.keyStorePath' not set. Using the default directory [%s] for temporary files", os.TempDir()) + rootPath = os.TempDir() + } else { + rootPath = viper.GetString(conf.configurationPathProperty) + } + logger.Infof("Root Path [%s]", rootPath) + // Set configuration path + rootPath = filepath.Join(rootPath, "crypto") + + // Set ks path + conf.keystorePath = filepath.Join(rootPath, "ks") + + return nil +} + +func (conf *config) checkProperty(property string) error { + res := viper.GetString(property) + if res == "" { + return errors.New("Property not specified in configuration file. Please check that property is set: " + property) + } + return nil +} + +func (conf *config) getKeyStorePath() string { + return conf.keystorePath +} + +func (conf *config) getPathForAlias(alias, suffix string) string { + return filepath.Join(conf.getKeyStorePath(), alias+"_"+suffix) +} diff --git a/core/crypto/bccsp/sw/ecdsakey.go b/core/crypto/bccsp/sw/ecdsakey.go new file mode 100644 index 00000000000..a96eed67994 --- /dev/null +++ b/core/crypto/bccsp/sw/ecdsakey.go @@ -0,0 +1,86 @@ +package sw + +import ( + "crypto/ecdsa" + "crypto/x509" + "fmt" + + "github.com/hyperledger/fabric/core/crypto/bccsp" + "github.com/hyperledger/fabric/core/crypto/primitives" +) + +type ecdsaPrivateKey struct { + k *ecdsa.PrivateKey +} + +// ToByte converts this key to its byte representation, +// if this operation is allowed. +func (k *ecdsaPrivateKey) Bytes() (raw []byte, err error) { + return +} + +// SKI returns the subject key identifier of this key. +func (k *ecdsaPrivateKey) SKI() (ski []byte) { + raw, _ := primitives.PrivateKeyToDER(k.k) + // TODO: Error should not be thrown. Anyway, move the marshalling at initialization. + + return primitives.Hash(raw) +} + +// Symmetric returns true if this key is a symmetric key, +// false if this key is asymmetric +func (k *ecdsaPrivateKey) Symmetric() bool { + return false +} + +// Private returns true if this key is a private key, +// false otherwise. +func (k *ecdsaPrivateKey) Private() bool { + return true +} + +// PublicKey returns the corresponding public key part of an asymmetric public/private key pair. +// This method returns an error in symmetric key schemes. +func (k *ecdsaPrivateKey) PublicKey() (bccsp.Key, error) { + return &ecdsaPublicKey{&k.k.PublicKey}, nil +} + +type ecdsaPublicKey struct { + k *ecdsa.PublicKey +} + +// ToByte converts this key to its byte representation, +// if this operation is allowed. +func (k *ecdsaPublicKey) Bytes() (raw []byte, err error) { + raw, err = x509.MarshalPKIXPublicKey(k.k) + if err != nil { + return nil, fmt.Errorf("Failed marshalling key [%s]", err) + } + return +} + +// SKI returns the subject key identifier of this key. +func (k *ecdsaPublicKey) SKI() (ski []byte) { + raw, _ := primitives.PublicKeyToPEM(k.k, nil) + // TODO: Error should not be thrown. Anyway, move the marshalling at initialization. + + return primitives.Hash(raw) +} + +// Symmetric returns true if this key is a symmetric key, +// false if this key is asymmetric +func (k *ecdsaPublicKey) Symmetric() bool { + return false +} + +// Private returns true if this key is a private key, +// false otherwise. +func (k *ecdsaPublicKey) Private() bool { + return false +} + +// PublicKey returns the corresponding public key part of an asymmetric public/private key pair. +// This method returns an error in symmetric key schemes. +func (k *ecdsaPublicKey) PublicKey() (bccsp.Key, error) { + return k, nil +} diff --git a/core/crypto/bccsp/sw/impl.go b/core/crypto/bccsp/sw/impl.go new file mode 100644 index 00000000000..f215462a387 --- /dev/null +++ b/core/crypto/bccsp/sw/impl.go @@ -0,0 +1,417 @@ +package sw + +import ( + "crypto/ecdsa" + "crypto/rand" + "encoding/asn1" + "encoding/hex" + "errors" + "fmt" + "math/big" + + "github.com/hyperledger/fabric/core/crypto/bccsp" + "github.com/hyperledger/fabric/core/crypto/primitives" + "github.com/hyperledger/fabric/core/crypto/utils" + "github.com/op/go-logging" +) + +var ( + logger = logging.MustGetLogger("SW_BCCSP") +) + +// New returns a new instance of the software-based BCCSP. +func New() (bccsp.BCCSP, error) { + conf := &config{} + err := conf.init() + if err != nil { + return nil, fmt.Errorf("Failed initializing configuration [%s]", err) + } + + ks := &keyStore{} + if err := ks.init(nil, conf); err != nil { + return nil, fmt.Errorf("Failed initializing key store [%s]", err) + } + return &impl{ks}, nil +} + +// SoftwareBasedBCCSP is the software-based implementation of the BCCSP. +// It is based on code used in the primitives package. +// It can be configured via viper. +type impl struct { + ks *keyStore +} + +// KeyGen generates a key using opts. +func (csp *impl) KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, err error) { + // Validate arguments + if opts == nil { + return nil, errors.New("Invalid Opts parameter. It must not be nil.") + } + + // Parse algorithm + switch opts.Algorithm() { + case bccsp.ECDSA: + lowLevelKey, err := primitives.NewECDSAKey() + if err != nil { + return nil, fmt.Errorf("Failed generating ECDSA key [%s]", err) + } + + k = &ecdsaPrivateKey{lowLevelKey} + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.storePrivateKey(hex.EncodeToString(k.SKI()), lowLevelKey) + if err != nil { + return nil, fmt.Errorf("Failed storing ECDSA key [%s]", err) + } + } + + return k, nil + case bccsp.AES: + lowLevelKey, err := primitives.GenAESKey() + + if err != nil { + return nil, fmt.Errorf("Failed generating AES key [%s]", err) + } + + k = &aesPrivateKey{lowLevelKey, false} + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.storeKey(hex.EncodeToString(k.SKI()), lowLevelKey) + if err != nil { + return nil, fmt.Errorf("Failed storing AES key [%s]", err) + } + } + + return k, nil + default: + return nil, fmt.Errorf("Algorithm not recognized [%s]", opts.Algorithm()) + } +} + +// KeyDeriv derives a key from k using opts. +// The opts argument should be appropriate for the primitive used. +func (csp *impl) KeyDeriv(k bccsp.Key, opts bccsp.KeyDerivOpts) (dk bccsp.Key, err error) { + // Validate arguments + if k == nil { + return nil, errors.New("Invalid Key. It must not be nil.") + } + + // Derive key + switch k.(type) { + case *ecdsaPrivateKey: + // Validate opts + if opts == nil { + return nil, errors.New("Invalid Opts parameter. It must not be nil.") + } + + ecdsaK := k.(*ecdsaPrivateKey) + + switch opts.(type) { + + // Re-randomized an ECDSA private key + case *bccsp.ECDSAReRandKeyOpts: + reRandOpts := opts.(*bccsp.ECDSAReRandKeyOpts) + tempSK := &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: ecdsaK.k.Curve, + X: new(big.Int), + Y: new(big.Int), + }, + D: new(big.Int), + } + + var k = new(big.Int).SetBytes(reRandOpts.ExpansionValue()) + var one = new(big.Int).SetInt64(1) + n := new(big.Int).Sub(ecdsaK.k.Params().N, one) + k.Mod(k, n) + k.Add(k, one) + + tempSK.D.Add(ecdsaK.k.D, k) + tempSK.D.Mod(tempSK.D, ecdsaK.k.PublicKey.Params().N) + + // Compute temporary public key + tempX, tempY := ecdsaK.k.PublicKey.ScalarBaseMult(k.Bytes()) + tempSK.PublicKey.X, tempSK.PublicKey.Y = + tempSK.PublicKey.Add( + ecdsaK.k.PublicKey.X, ecdsaK.k.PublicKey.Y, + tempX, tempY, + ) + + // Verify temporary public key is a valid point on the reference curve + isOn := tempSK.Curve.IsOnCurve(tempSK.PublicKey.X, tempSK.PublicKey.Y) + if !isOn { + return nil, errors.New("Failed temporary public key IsOnCurve check. This is an foreign key.") + } + + reRandomizedKey := &ecdsaPrivateKey{tempSK} + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.storePrivateKey(hex.EncodeToString(reRandomizedKey.SKI()), tempSK) + if err != nil { + return nil, fmt.Errorf("Failed storing ECDSA key [%s]", err) + } + } + + return reRandomizedKey, nil + + default: + return nil, errors.New("Opts not supported") + + } + case *aesPrivateKey: + // Validate opts + if opts == nil { + return nil, errors.New("Invalid Opts parameter. It must not be nil.") + } + + aesK := k.(*aesPrivateKey) + + switch opts.(type) { + case *bccsp.HMACTruncated256AESDeriveKeyOpts: + hmacOpts := opts.(*bccsp.HMACTruncated256AESDeriveKeyOpts) + + hmacedKey := &aesPrivateKey{primitives.HMACAESTruncated(aesK.k, hmacOpts.Argument()), false} + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.storeKey(hex.EncodeToString(hmacedKey.SKI()), hmacedKey.k) + if err != nil { + return nil, fmt.Errorf("Failed storing ECDSA key [%s]", err) + } + } + + return hmacedKey, nil + + case *bccsp.HMACDeriveKeyOpts: + + hmacOpts := opts.(*bccsp.HMACDeriveKeyOpts) + + hmacedKey := &aesPrivateKey{primitives.HMAC(aesK.k, hmacOpts.Argument()), true} + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.storeKey(hex.EncodeToString(hmacedKey.SKI()), hmacedKey.k) + if err != nil { + return nil, fmt.Errorf("Failed storing ECDSA key [%s]", err) + } + } + + return hmacedKey, nil + + default: + return nil, errors.New("Opts not supported") + + } + + default: + return nil, fmt.Errorf("Key type not recognized [%s]", k) + } +} + +// KeyImport imports a key from its raw representation using opts. +// The opts argument should be appropriate for the primitive used. +func (csp *impl) KeyImport(raw []byte, opts bccsp.KeyImportOpts) (k bccsp.Key, err error) { + // Validate arguments + if len(raw) == 0 { + return nil, errors.New("Invalid raw. Zero length.") + } + if opts == nil { + return nil, errors.New("Invalid Opts parameter. It must not be nil.") + } + + switch opts.(type) { + case *bccsp.AES256ImportKeyOpts: + + if len(raw) != 32 { + return nil, fmt.Errorf("Invalid Key Length [%d]. Must be 32 bytes", len(raw)) + } + + aesK := &aesPrivateKey{utils.Clone(raw), false} + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.storeKey(hex.EncodeToString(aesK.SKI()), aesK.k) + if err != nil { + return nil, fmt.Errorf("Failed storing AES key [%s]", err) + } + } + + return aesK, nil + case *bccsp.HMACImportKeyOpts: + + aesK := &aesPrivateKey{utils.Clone(raw), false} + + // If the key is not Ephemeral, store it. + if !opts.Ephemeral() { + // Store the key + err = csp.ks.storeKey(hex.EncodeToString(aesK.SKI()), aesK.k) + if err != nil { + return nil, fmt.Errorf("Failed storing AES key [%s]", err) + } + } + + return aesK, nil + default: + return nil, errors.New("Import Key Options not recognized") + } +} + +// GetKey returns the key this CSP associates to +// the Subject Key Identifier ski. +func (csp *impl) GetKey(ski []byte) (k bccsp.Key, err error) { + // Validate arguments + if len(ski) == 0 { + return nil, errors.New("Invalid SKI. Cannot be of zero length.") + } + + suffix := csp.ks.getSuffix(hex.EncodeToString(ski)) + + switch suffix { + case "key": + // Load the key + key, err := csp.ks.loadKey(hex.EncodeToString(ski)) + if err != nil { + return nil, fmt.Errorf("Failed loading key [%x] [%s]", ski, err) + } + + return &aesPrivateKey{key, false}, nil + case "sk": + // Load the private key + key, err := csp.ks.loadPrivateKey(hex.EncodeToString(ski)) + if err != nil { + return nil, fmt.Errorf("Failed loading key [%x] [%s]", ski, err) + } + + switch key.(type) { + case *ecdsa.PrivateKey: + return &ecdsaPrivateKey{key.(*ecdsa.PrivateKey)}, nil + default: + return nil, errors.New("Key type not recognized") + } + default: + return nil, errors.New("Key not recognized") + } +} + +// Hash hashes messages msg using options opts. +func (csp *impl) Hash(msg []byte, opts bccsp.HashOpts) (hash []byte, err error) { + if opts == nil { + return nil, errors.New("Invalid Opts parameter. It must not be nil.") + } + + switch opts.Algorithm() { + case bccsp.SHA: + return primitives.Hash(msg), nil + default: + return nil, fmt.Errorf("Algorithm not recognized [%s]", opts.Algorithm()) + } +} + +// Sign signs digest using key k. +// The opts argument should be appropriate for the primitive 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). +func (csp *impl) Sign(k bccsp.Key, digest []byte, opts bccsp.SignerOpts) (signature []byte, err error) { + // Validate arguments + if k == nil { + return nil, errors.New("Invalid Key. It must not be nil.") + } + if len(digest) == 0 { + return nil, errors.New("Invalid digest. Cannot be empty.") + } + + // Check key type + switch k.(type) { + case *ecdsaPrivateKey: + return k.(*ecdsaPrivateKey).k.Sign(rand.Reader, digest, nil) + default: + return nil, fmt.Errorf("Key type not recognized [%s]", k) + } +} + +// Verify verifies signature against key k and digest +func (csp *impl) Verify(k bccsp.Key, signature, digest []byte) (valid bool, err error) { + // Validate arguments + if k == nil { + return false, errors.New("Invalid Key. It must not be nil.") + } + if len(signature) == 0 { + return false, errors.New("Invalid signature. Cannot be empty.") + } + if len(digest) == 0 { + return false, errors.New("Invalid digest. Cannot be empty.") + } + + // Check key type + switch k.(type) { + case *ecdsaPrivateKey: + ecdsaSignature := new(primitives.ECDSASignature) + _, err := asn1.Unmarshal(signature, ecdsaSignature) + if err != nil { + return false, fmt.Errorf("Failed unmashalling signature [%s]", err) + } + + return ecdsa.Verify(&(k.(*ecdsaPrivateKey).k.PublicKey), digest, ecdsaSignature.R, ecdsaSignature.S), nil + default: + return false, fmt.Errorf("Key type not recognized [%s]", k) + } +} + +// Encrypt encrypts plaintext using key k. +// The opts argument should be appropriate for the primitive used. +func (csp *impl) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) (ciphertext []byte, err error) { + // Validate arguments + if k == nil { + 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 primitives.CBCPKCS7Encrypt(k.(*aesPrivateKey).k, plaintext) + default: + return nil, fmt.Errorf("Mode not recognized [%s]", opts) + } + default: + return nil, fmt.Errorf("Key type not recognized [%s]", k) + } +} + +// Decrypt decrypts ciphertext using key k. +// The opts argument should be appropriate for the primitive used. +func (csp *impl) Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) (plaintext []byte, err error) { + // Validate arguments + if k == nil { + 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 primitives.CBCPKCS7Decrypt(k.(*aesPrivateKey).k, ciphertext) + default: + return nil, fmt.Errorf("Mode not recognized [%s]", opts) + } + default: + return nil, fmt.Errorf("Key type not recognized [%s]", k) + } +} diff --git a/core/crypto/bccsp/sw/impl_test.go b/core/crypto/bccsp/sw/impl_test.go new file mode 100644 index 00000000000..a4e15037bf2 --- /dev/null +++ b/core/crypto/bccsp/sw/impl_test.go @@ -0,0 +1,540 @@ +package sw + +import ( + "bytes" + "os" + "testing" + + "github.com/hyperledger/fabric/core/crypto/bccsp" + "github.com/hyperledger/fabric/core/crypto/primitives" + "github.com/spf13/viper" +) + +var ( + swBCCSPInstance bccsp.BCCSP +) + +func getBCCSP(t *testing.T) bccsp.BCCSP { + if swBCCSPInstance == nil { + primitives.InitSecurityLevel("SHA2", 256) + viper.Set("security.bccsp.default.keyStorePath", os.TempDir()) + + var err error + swBCCSPInstance, err = New() + if err != nil { + t.Fatalf("Failed initializing key store [%s]", err) + } + } + + return swBCCSPInstance +} + +func TestECDSAKeyGenEphemeral(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + if k == nil { + t.Fatal("Failed generating ECDSA key. Key must be different from nil") + } + if !k.Private() { + t.Fatal("Failed generating ECDSA key. Key should be private") + } + if k.Symmetric() { + t.Fatal("Failed generating ECDSA key. Key should be asymmetric") + } +} + +func TestECDSAPrivateKeySKI(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + ski := k.SKI() + if len(ski) == 0 { + t.Fatal("SKI not valid. Zero length.") + } +} + +func TestECDSAKeyGenNonEphemeral(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + if k == nil { + t.Fatal("Failed generating ECDSA key. Key must be different from nil") + } + if !k.Private() { + t.Fatal("Failed generating ECDSA key. Key should be private") + } + if k.Symmetric() { + t.Fatal("Failed generating ECDSA key. Key should be asymmetric") + } +} + +func TestECDSAGetKeyBySKI(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + k2, err := csp.GetKey(k.SKI()) + if err != nil { + t.Fatalf("Failed getting ECDSA key [%s]", err) + } + if k2 == nil { + t.Fatal("Failed getting ECDSA key. Key must be different from nil") + } + if !k2.Private() { + t.Fatal("Failed getting ECDSA key. Key should be private") + } + if k2.Symmetric() { + t.Fatal("Failed getting ECDSA key. Key should be asymmetric") + } + + // Check that the SKIs are the same + if !bytes.Equal(k.SKI(), k2.SKI()) { + t.Fatalf("SKIs are different [%x]!=[%x]", k.SKI(), k2.SKI()) + } +} + +func TestECDSAPublicKeyFromPrivateKey(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + pk, err := k.PublicKey() + if err != nil { + t.Fatalf("Failed getting public key from private ECDSA key [%s]", err) + } + if pk == nil { + t.Fatal("Failed getting public key from private ECDSA key. Key must be different from nil") + } + if pk.Private() { + t.Fatal("Failed generating ECDSA key. Key should be public") + } + if pk.Symmetric() { + t.Fatal("Failed generating ECDSA key. Key should be asymmetric") + } +} + +func TestECDSAPublicKeyBytes(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + pk, err := k.PublicKey() + if err != nil { + t.Fatalf("Failed getting public key from private ECDSA key [%s]", err) + } + + raw, err := pk.Bytes() + if err != nil { + t.Fatalf("Failed marshalling ECDSA public key [%s]", err) + } + if len(raw) == 0 { + t.Fatal("Failed marshalling ECDSA public key. Zero length") + } +} + +func TestECDSAPublicKeySKI(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + pk, err := k.PublicKey() + if err != nil { + t.Fatalf("Failed getting public key from private ECDSA key [%s]", err) + } + + ski := pk.SKI() + if len(ski) == 0 { + t.Fatal("SKI not valid. Zero length.") + } +} + +func TestECDSAKeyReRand(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + reRandomizedKey, err := csp.KeyDeriv(k, &bccsp.ECDSAReRandKeyOpts{Temporary: false, Expansion: []byte{1}}) + if err != nil { + t.Fatalf("Failed re-randomizing ECDSA key [%s]", err) + } + if k == nil { + t.Fatal("Failed re-randomizing ECDSA key. Re-randomized Key must be different from nil") + } + if !reRandomizedKey.Private() { + t.Fatal("Failed re-randomizing ECDSA key. Re-randomized Key should be private") + } + if reRandomizedKey.Symmetric() { + t.Fatal("Failed re-randomizing ECDSA key. Re-randomized Key should be asymmetric") + } +} + +func TestECDSASign(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + msg := []byte("Hello World") + + digest, err := csp.Hash(msg, &bccsp.SHAOpts{}) + if err != nil { + t.Fatalf("Failed computing HASH [%s]", err) + } + + signature, err := csp.Sign(k, digest, nil) + if err != nil { + t.Fatalf("Failed generating ECDSA signature [%s]", err) + } + if len(signature) == 0 { + t.Fatal("Failed generating ECDSA key. Signature must be different from nil") + } +} + +func TestECDSAVerify(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + msg := []byte("Hello World") + + digest, err := csp.Hash(msg, &bccsp.SHAOpts{}) + if err != nil { + t.Fatalf("Failed computing HASH [%s]", err) + } + + signature, err := csp.Sign(k, digest, nil) + if err != nil { + t.Fatalf("Failed generating ECDSA signature [%s]", err) + } + + valid, err := csp.Verify(k, signature, digest) + if err != nil { + t.Fatalf("Failed verifying ECDSA signature [%s]", err) + } + if !valid { + t.Fatal("Failed verifying ECDSA signature. Signature not valid.") + } +} + +func TestECDSAKeyDeriv(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false}) + if err != nil { + t.Fatalf("Failed generating ECDSA key [%s]", err) + } + + reRandomizedKey, err := csp.KeyDeriv(k, &bccsp.ECDSAReRandKeyOpts{Temporary: false, Expansion: []byte{1}}) + if err != nil { + t.Fatalf("Failed re-randomizing ECDSA key [%s]", err) + } + + msg := []byte("Hello World") + + digest, err := csp.Hash(msg, &bccsp.SHAOpts{}) + if err != nil { + t.Fatalf("Failed computing HASH [%s]", err) + } + + signature, err := csp.Sign(reRandomizedKey, digest, nil) + if err != nil { + t.Fatalf("Failed generating ECDSA signature [%s]", err) + } + + valid, err := csp.Verify(reRandomizedKey, signature, digest) + if err != nil { + t.Fatalf("Failed verifying ECDSA signature [%s]", err) + } + if !valid { + t.Fatal("Failed verifying ECDSA signature. Signature not valid.") + } +} + +func TestAESKeyGen(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.AESKeyGenOpts{Temporary: false}) + if err != nil { + t.Fatalf("Failed generating AES_256 key [%s]", err) + } + if k == nil { + t.Fatal("Failed generating AES_256 key. Key must be different from nil") + } + if !k.Private() { + t.Fatal("Failed generating AES_256 key. Key should be private") + } + if !k.Symmetric() { + t.Fatal("Failed generating AES_256 key. Key should be symmetric") + } +} + +func TestAESEncrypt(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.AESKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating AES_256 key [%s]", err) + } + + ct, err := csp.Encrypt(k, []byte("Hello World"), &bccsp.AESCBCPKCS7ModeOpts{}) + if err != nil { + t.Fatalf("Failed encrypting [%s]", err) + } + if len(ct) == 0 { + t.Fatal("Failed encrypting. Nil ciphertext") + } +} + +func TestAESDecrypt(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.AESKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating AES_256 key [%s]", err) + } + + msg := []byte("Hello World") + + ct, err := csp.Encrypt(k, msg, &bccsp.AESCBCPKCS7ModeOpts{}) + if err != nil { + t.Fatalf("Failed encrypting [%s]", err) + } + + pt, err := csp.Decrypt(k, ct, bccsp.AESCBCPKCS7ModeOpts{}) + if err != nil { + t.Fatalf("Failed decrypting [%s]", err) + } + if len(ct) == 0 { + t.Fatal("Failed decrypting. Nil plaintext") + } + + if !bytes.Equal(msg, pt) { + t.Fatalf("Failed decrypting. Decrypted plaintext is different from the original. [%x][%x]", msg, pt) + } +} + +func TestHMACTruncated256KeyDerivOverAES256Key(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.AESKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating AES_256 key [%s]", err) + } + + hmcaedKey, err := csp.KeyDeriv(k, &bccsp.HMACTruncated256AESDeriveKeyOpts{Temporary: false, Arg: []byte{1}}) + if err != nil { + t.Fatalf("Failed HMACing AES_256 key [%s]", err) + } + if k == nil { + t.Fatal("Failed HMACing AES_256 key. HMACed Key must be different from nil") + } + if !hmcaedKey.Private() { + t.Fatal("Failed HMACing AES_256 key. HMACed Key should be private") + } + if !hmcaedKey.Symmetric() { + t.Fatal("Failed HMACing AES_256 key. HMACed Key should be asymmetric") + } + raw, err := hmcaedKey.Bytes() + if err == nil { + t.Fatal("Failed marshalling to bytes. Operation must be forbidden") + } + if len(raw) != 0 { + t.Fatal("Failed marshalling to bytes. Operation must return 0 bytes") + } + + msg := []byte("Hello World") + + ct, err := csp.Encrypt(hmcaedKey, msg, &bccsp.AESCBCPKCS7ModeOpts{}) + if err != nil { + t.Fatalf("Failed encrypting [%s]", err) + } + + pt, err := csp.Decrypt(hmcaedKey, ct, bccsp.AESCBCPKCS7ModeOpts{}) + if err != nil { + t.Fatalf("Failed decrypting [%s]", err) + } + if len(ct) == 0 { + t.Fatal("Failed decrypting. Nil plaintext") + } + + if !bytes.Equal(msg, pt) { + t.Fatalf("Failed decrypting. Decrypted plaintext is different from the original. [%x][%x]", msg, pt) + } + +} + +func TestHMACKeyDerivOverAES256Key(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.AESKeyGenOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed generating AES_256 key [%s]", err) + } + + hmcaedKey, err := csp.KeyDeriv(k, &bccsp.HMACDeriveKeyOpts{Temporary: false, Arg: []byte{1}}) + + if err != nil { + t.Fatalf("Failed HMACing AES_256 key [%s]", err) + } + if k == nil { + t.Fatal("Failed HMACing AES_256 key. HMACed Key must be different from nil") + } + if !hmcaedKey.Private() { + t.Fatal("Failed HMACing AES_256 key. HMACed Key should be private") + } + if !hmcaedKey.Symmetric() { + t.Fatal("Failed HMACing AES_256 key. HMACed Key should be asymmetric") + } + raw, err := hmcaedKey.Bytes() + if err != nil { + t.Fatalf("Failed marshalling to bytes [%s]", err) + } + if len(raw) == 0 { + t.Fatal("Failed marshalling to bytes. 0 bytes") + } +} + +func TestAES256KeyImport(t *testing.T) { + csp := getBCCSP(t) + + raw, err := primitives.GenAESKey() + if err != nil { + t.Fatalf("Failed generating AES key [%s]", err) + } + + k, err := csp.KeyImport(raw, &bccsp.AES256ImportKeyOpts{Temporary: true}) + if err != nil { + t.Fatalf("Failed importing AES_256 key [%s]", err) + } + if k == nil { + t.Fatal("Failed importing AES_256 key. Imported Key must be different from nil") + } + if !k.Private() { + t.Fatal("Failed HMACing AES_256 key. Imported Key should be private") + } + if !k.Symmetric() { + t.Fatal("Failed HMACing AES_256 key. Imported Key should be asymmetric") + } + raw, err = k.Bytes() + if err == nil { + t.Fatal("Failed marshalling to bytes. Marshalling must fail.") + } + if len(raw) != 0 { + t.Fatal("Failed marshalling to bytes. Output should be 0 bytes") + } + + msg := []byte("Hello World") + + ct, err := csp.Encrypt(k, msg, &bccsp.AESCBCPKCS7ModeOpts{}) + if err != nil { + t.Fatalf("Failed encrypting [%s]", err) + } + + pt, err := csp.Decrypt(k, ct, bccsp.AESCBCPKCS7ModeOpts{}) + if err != nil { + t.Fatalf("Failed decrypting [%s]", err) + } + if len(ct) == 0 { + t.Fatal("Failed decrypting. Nil plaintext") + } + + if !bytes.Equal(msg, pt) { + t.Fatalf("Failed decrypting. Decrypted plaintext is different from the original. [%x][%x]", msg, pt) + } +} + +func TestAES256KeyImportBadPaths(t *testing.T) { + csp := getBCCSP(t) + + _, err := csp.KeyImport(nil, &bccsp.AES256ImportKeyOpts{Temporary: true}) + if err == nil { + t.Fatal("Failed importing key. Must fail on importing nil key") + } + + _, err = csp.KeyImport([]byte{1}, &bccsp.AES256ImportKeyOpts{Temporary: true}) + if err == nil { + t.Fatal("Failed importing key. Must fail on importing a key with an invalid length") + } +} + +func TestAES256KeyGenSKI(t *testing.T) { + csp := getBCCSP(t) + + k, err := csp.KeyGen(&bccsp.AESKeyGenOpts{Temporary: false}) + if err != nil { + t.Fatalf("Failed generating AES_256 key [%s]", err) + } + + k2, err := csp.GetKey(k.SKI()) + if err != nil { + t.Fatalf("Failed getting AES_256 key [%s]", err) + } + if k2 == nil { + t.Fatal("Failed getting AES_256 key. Key must be different from nil") + } + if !k2.Private() { + t.Fatal("Failed getting AES_256 key. Key should be private") + } + if !k2.Symmetric() { + t.Fatal("Failed getting AES_256 key. Key should be symmetric") + } + + // Check that the SKIs are the same + if !bytes.Equal(k.SKI(), k2.SKI()) { + t.Fatalf("SKIs are different [%x]!=[%x]", k.SKI(), k2.SKI()) + } + +} + +func TestSHA(t *testing.T) { + csp := getBCCSP(t) + + for i := 0; i < 100; i++ { + b, err := primitives.GetRandomBytes(i) + if err != nil { + t.Fatalf("Failed getting random bytes [%s]", err) + } + + h1, err := csp.Hash(b, &bccsp.SHAOpts{}) + if err != nil { + t.Fatalf("Failed computing SHA [%s]", err) + } + + h2 := primitives.Hash(b[:]) + + if !bytes.Equal(h1, h2) { + t.Fatalf("Discrempancy found in HASH result [%x], [%x]!=[%x]", b, h1, h2) + } + } +} diff --git a/core/crypto/bccsp/sw/ks.go b/core/crypto/bccsp/sw/ks.go new file mode 100644 index 00000000000..9cdbc0bec6c --- /dev/null +++ b/core/crypto/bccsp/sw/ks.go @@ -0,0 +1,236 @@ +package sw + +import ( + "io/ioutil" + "os" + "sync" + + "github.com/hyperledger/fabric/core/crypto/utils" + + "errors" + "strings" + + "github.com/hyperledger/fabric/core/crypto/primitives" +) + +type keyStore struct { + conf *config + + isOpen bool + + pwd []byte + + // Sync + m sync.Mutex +} + +func (ks *keyStore) init(pwd []byte, conf *config) error { + // Validate inputs + // pwd can be nil + + if conf == nil { + return errors.New("Invalid config. Nil.") + } + + ks.m.Lock() + defer ks.m.Unlock() + + if ks.isOpen { + return errors.New("Keystore already Initilized.") + } + + ks.conf = conf + ks.pwd = utils.Clone(pwd) + + err := ks.createKeyStoreIfNotExists() + if err != nil { + return err + } + + err = ks.openKeyStore() + if err != nil { + return err + } + + return nil +} + +func (ks *keyStore) getSuffix(alias string) string { + files, _ := ioutil.ReadDir(ks.conf.getKeyStorePath()) + for _, f := range files { + if strings.HasPrefix(f.Name(), alias) { + if strings.HasSuffix(f.Name(), "sk") { + return "sk" + } + if strings.HasSuffix(f.Name(), "pk") { + return "pk" + } + if strings.HasSuffix(f.Name(), "key") { + return "key" + } + break + } + } + return "" +} + +func (ks *keyStore) storePrivateKey(alias string, privateKey interface{}) error { + rawKey, err := primitives.PrivateKeyToPEM(privateKey, ks.pwd) + if err != nil { + logger.Errorf("Failed converting private key to PEM [%s]: [%s]", alias, err) + return err + } + + err = ioutil.WriteFile(ks.conf.getPathForAlias(alias, "sk"), rawKey, 0700) + if err != nil { + logger.Errorf("Failed storing private key [%s]: [%s]", alias, err) + return err + } + + return nil +} + +func (ks *keyStore) loadPrivateKey(alias string) (interface{}, error) { + path := ks.conf.getPathForAlias(alias, "sk") + logger.Debugf("Loading private key [%s] at [%s]...", alias, path) + + raw, err := ioutil.ReadFile(path) + if err != nil { + logger.Errorf("Failed loading private key [%s]: [%s].", alias, err.Error()) + + return nil, err + } + + privateKey, err := primitives.PEMtoPrivateKey(raw, ks.pwd) + if err != nil { + logger.Errorf("Failed parsing private key [%s]: [%s].", alias, err.Error()) + + return nil, err + } + + return privateKey, nil +} + +func (ks *keyStore) storePublicKey(alias string, publicKey interface{}) error { + rawKey, err := primitives.PublicKeyToPEM(publicKey, ks.pwd) + if err != nil { + logger.Errorf("Failed converting public key to PEM [%s]: [%s]", alias, err) + return err + } + + err = ioutil.WriteFile(ks.conf.getPathForAlias(alias, "pk"), rawKey, 0700) + if err != nil { + logger.Errorf("Failed storing private key [%s]: [%s]", alias, err) + return err + } + + return nil +} + +func (ks *keyStore) loadPublicKey(alias string) (interface{}, error) { + path := ks.conf.getPathForAlias(alias, "pk") + logger.Debugf("Loading public key [%s] at [%s]...", alias, path) + + raw, err := ioutil.ReadFile(path) + if err != nil { + logger.Errorf("Failed loading public key [%s]: [%s].", alias, err.Error()) + + return nil, err + } + + privateKey, err := primitives.PEMtoPublicKey(raw, ks.pwd) + if err != nil { + logger.Errorf("Failed parsing private key [%s]: [%s].", alias, err.Error()) + + return nil, err + } + + return privateKey, nil +} + +func (ks *keyStore) storeKey(alias string, key []byte) error { + pem, err := primitives.AEStoEncryptedPEM(key, ks.pwd) + if err != nil { + logger.Errorf("Failed converting key to PEM [%s]: [%s]", alias, err) + return err + } + + err = ioutil.WriteFile(ks.conf.getPathForAlias(alias, "key"), pem, 0700) + if err != nil { + logger.Errorf("Failed storing key [%s]: [%s]", alias, err) + return err + } + + return nil +} + +func (ks *keyStore) loadKey(alias string) ([]byte, error) { + path := ks.conf.getPathForAlias(alias, "key") + logger.Debugf("Loading key [%s] at [%s]...", alias, path) + + pem, err := ioutil.ReadFile(path) + if err != nil { + logger.Errorf("Failed loading key [%s]: [%s].", alias, err.Error()) + + return nil, err + } + + key, err := primitives.PEMtoAES(pem, ks.pwd) + if err != nil { + logger.Errorf("Failed parsing key [%s]: [%s]", alias, err) + + return nil, err + } + + return key, nil +} + +func (ks *keyStore) close() error { + ks.isOpen = false + logger.Debug("Closing keystore...done!") + return nil +} + +func (ks *keyStore) createKeyStoreIfNotExists() error { + // Check keystore directory + ksPath := ks.conf.getKeyStorePath() + missing, err := utils.DirMissingOrEmpty(ksPath) + logger.Infof("Keystore path [%s] missing [%t]: [%s]", ksPath, missing, utils.ErrToString(err)) + + if missing { + err := ks.createKeyStore() + if err != nil { + logger.Errorf("Failed creating ks At [%s]: [%s]", ksPath, err.Error()) + return nil + } + } + + return nil +} + +func (ks *keyStore) createKeyStore() error { + // Create keystore directory root if it doesn't exist yet + ksPath := ks.conf.getKeyStorePath() + logger.Debugf("Creating Keystore at [%s]...", ksPath) + + os.MkdirAll(ksPath, 0755) + + logger.Debugf("Keystore created at [%s].", ksPath) + return nil +} + +func (ks *keyStore) deleteKeyStore() error { + logger.Debugf("Removing KeyStore at [%s].", ks.conf.getKeyStorePath()) + + return os.RemoveAll(ks.conf.getKeyStorePath()) +} + +func (ks *keyStore) openKeyStore() error { + if ks.isOpen { + return nil + } + + logger.Debugf("Keystore opened at [%s]...done", ks.conf.getKeyStorePath()) + + return nil +}