Skip to content

Commit

Permalink
[FAB-3297] Generate PKCS8 compliant EC keys
Browse files Browse the repository at this point in the history
When BCCSP serialize EC private and public keys
to PEM format, it currently includes "ECDSA" in
the header.  While this is not illegal, it is
incompatible with libraries which expect PKCS8
format.

This change only uses PKCS8 for EC keys as most
libraries and tools support PKCS1 for RSA.

Needed to modify the based asn1 marshaling for
EC private keys as the Go implementation
includes an optional element which is
incompatible with several libraries with the
most important being the jsrsasign package
used by fabric-node-sdk project

Change-Id: I802dcdb28b46bd353efe1ed3868feb78f92e92c9
Signed-off-by: Gari Singh <gari.r.singh@gmail.com>
  • Loading branch information
mastersingh24 committed Apr 22, 2017
1 parent fb5183a commit 5bca81a
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 12 deletions.
83 changes: 75 additions & 8 deletions bccsp/utils/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,53 @@ package utils

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
)

// struct to hold info required for PKCS#8
type pkcs8Info struct {
Version int
PrivateKeyAlgorithm []asn1.ObjectIdentifier
PrivateKey []byte
}

type ecPrivateKey struct {
Version int
PrivateKey []byte
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}

var (
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
)

var oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}

func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) {
switch curve {
case elliptic.P224():
return oidNamedCurveP224, true
case elliptic.P256():
return oidNamedCurveP256, true
case elliptic.P384():
return oidNamedCurveP384, true
case elliptic.P521():
return oidNamedCurveP521, true
}
return nil, false
}

// PrivateKeyToDER marshals a private key to der
func PrivateKeyToDER(privateKey *ecdsa.PrivateKey) ([]byte, error) {
if privateKey == nil {
Expand All @@ -35,7 +74,9 @@ func PrivateKeyToDER(privateKey *ecdsa.PrivateKey) ([]byte, error) {
return x509.MarshalECPrivateKey(privateKey)
}

// PrivateKeyToPEM converts a private key to PEM
// PrivateKeyToPEM converts the private key to PEM format.
// EC private keys are converted to PKCS#8 format.
// RSA private keys are converted to PKCS#1 format.
func PrivateKeyToPEM(privateKey interface{}, pwd []byte) ([]byte, error) {
// Validate inputs
if len(pwd) != 0 {
Expand All @@ -48,16 +89,42 @@ func PrivateKeyToPEM(privateKey interface{}, pwd []byte) ([]byte, error) {
return nil, errors.New("Invalid ecdsa private key. It must be different from nil.")
}

raw, err := x509.MarshalECPrivateKey(k)
// get the oid for the curve
oidNamedCurve, ok := oidFromNamedCurve(k.Curve)
if !ok {
return nil, errors.New("unknown elliptic curve")
}

// based on https://golang.org/src/crypto/x509/sec1.go
privateKeyBytes := k.D.Bytes()
paddedPrivateKey := make([]byte, (k.Curve.Params().N.BitLen()+7)/8)
copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes)
// omit NamedCurveOID for compatibility as it's optional
asn1Bytes, err := asn1.Marshal(ecPrivateKey{
Version: 1,
PrivateKey: paddedPrivateKey,
PublicKey: asn1.BitString{Bytes: elliptic.Marshal(k.Curve, k.X, k.Y)},
})

if err != nil {
return nil, err
return nil, fmt.Errorf("error marshaling EC key to asn1 [%s]", err)
}

var pkcs8Key pkcs8Info
pkcs8Key.Version = 1
pkcs8Key.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 2)
pkcs8Key.PrivateKeyAlgorithm[0] = oidPublicKeyECDSA
pkcs8Key.PrivateKeyAlgorithm[1] = oidNamedCurve
pkcs8Key.PrivateKey = asn1Bytes

pkcs8Bytes, err := asn1.Marshal(pkcs8Key)
if err != nil {
return nil, fmt.Errorf("error marshaling EC key to asn1 [%s]", err)
}
return pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PRIVATE KEY",
Bytes: raw,
Type: "PRIVATE KEY",
Bytes: pkcs8Bytes,
},
), nil
case *rsa.PrivateKey:
Expand Down Expand Up @@ -94,7 +161,7 @@ func PrivateKeyToEncryptedPEM(privateKey interface{}, pwd []byte) ([]byte, error

block, err := x509.EncryptPEMBlock(
rand.Reader,
"ECDSA PRIVATE KEY",
"PRIVATE KEY",
raw,
pwd,
x509.PEMCipherAES256)
Expand Down Expand Up @@ -241,7 +308,7 @@ func PublicKeyToPEM(publicKey interface{}, pwd []byte) ([]byte, error) {

return pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PUBLIC KEY",
Type: "PUBLIC KEY",
Bytes: PubASN1,
},
), nil
Expand Down Expand Up @@ -302,7 +369,7 @@ func PublicKeyToEncryptedPEM(publicKey interface{}, pwd []byte) ([]byte, error)

block, err := x509.EncryptPEMBlock(
rand.Reader,
"ECDSA PUBLIC KEY",
"PUBLIC KEY",
raw,
pwd,
x509.PEMCipherAES256)
Expand Down
96 changes: 92 additions & 4 deletions bccsp/utils/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,85 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"testing"

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

func TestOidFromNamedCurve(t *testing.T) {

var (
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
)

type result struct {
oid asn1.ObjectIdentifier
ok bool
}

var tests = []struct {
name string
curve elliptic.Curve
expected result
}{
{
name: "P224",
curve: elliptic.P224(),
expected: result{
oid: oidNamedCurveP224,
ok: true,
},
},
{
name: "P256",
curve: elliptic.P256(),
expected: result{
oid: oidNamedCurveP256,
ok: true,
},
},
{
name: "P384",
curve: elliptic.P384(),
expected: result{
oid: oidNamedCurveP384,
ok: true,
},
},
{
name: "P521",
curve: elliptic.P521(),
expected: result{
oid: oidNamedCurveP521,
ok: true,
},
},
{
name: "T-1000",
curve: &elliptic.CurveParams{Name: "T-1000"},
expected: result{
oid: nil,
ok: false,
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
oid, ok := oidFromNamedCurve(test.curve)
assert.Equal(t, oid, test.expected.oid)
assert.Equal(t, ok, test.expected.ok)
})
}

}

func TestECDSAKeys(t *testing.T) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
Expand Down Expand Up @@ -35,11 +111,19 @@ func TestECDSAKeys(t *testing.T) {
}

// Private Key PEM format
pem, err := PrivateKeyToPEM(key, nil)
rawPEM, err := PrivateKeyToPEM(key, nil)
if err != nil {
t.Fatalf("Failed converting private key to PEM [%s]", err)
}
keyFromPEM, err := PEMtoPrivateKey(pem, nil)
pemBlock, _ := pem.Decode(rawPEM)
if pemBlock.Type != "PRIVATE KEY" {
t.Fatalf("Expected type 'PRIVATE KEY' but found '%s'", pemBlock.Type)
}
_, err = x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
if err != nil {
t.Fatalf("Failed to parse PKCS#8 private key [%s]", err)
}
keyFromPEM, err := PEMtoPrivateKey(rawPEM, nil)
if err != nil {
t.Fatalf("Failed converting DER to private key [%s]", err)
}
Expand Down Expand Up @@ -108,11 +192,15 @@ func TestECDSAKeys(t *testing.T) {
}

// Public Key PEM format
pem, err = PublicKeyToPEM(&key.PublicKey, nil)
rawPEM, err = PublicKeyToPEM(&key.PublicKey, nil)
if err != nil {
t.Fatalf("Failed converting public key to PEM [%s]", err)
}
keyFromPEM, err = PEMtoPublicKey(pem, nil)
pemBlock, _ = pem.Decode(rawPEM)
if pemBlock.Type != "PUBLIC KEY" {
t.Fatalf("Expected type 'PUBLIC KEY' but found '%s'", pemBlock.Type)
}
keyFromPEM, err = PEMtoPublicKey(rawPEM, nil)
if err != nil {
t.Fatalf("Failed converting DER to public key [%s]", err)
}
Expand Down

0 comments on commit 5bca81a

Please sign in to comment.