Skip to content

Commit 5bca81a

Browse files
committed
[FAB-3297] Generate PKCS8 compliant EC keys
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 <[email protected]>
1 parent fb5183a commit 5bca81a

File tree

2 files changed

+167
-12
lines changed

2 files changed

+167
-12
lines changed

bccsp/utils/keys.go

+75-8
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,53 @@ package utils
1818

1919
import (
2020
"crypto/ecdsa"
21+
"crypto/elliptic"
2122
"crypto/rand"
2223
"crypto/rsa"
2324
"crypto/x509"
25+
"encoding/asn1"
2426
"encoding/pem"
2527
"errors"
2628
"fmt"
2729
)
2830

31+
// struct to hold info required for PKCS#8
32+
type pkcs8Info struct {
33+
Version int
34+
PrivateKeyAlgorithm []asn1.ObjectIdentifier
35+
PrivateKey []byte
36+
}
37+
38+
type ecPrivateKey struct {
39+
Version int
40+
PrivateKey []byte
41+
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
42+
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
43+
}
44+
45+
var (
46+
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
47+
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
48+
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
49+
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
50+
)
51+
52+
var oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
53+
54+
func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) {
55+
switch curve {
56+
case elliptic.P224():
57+
return oidNamedCurveP224, true
58+
case elliptic.P256():
59+
return oidNamedCurveP256, true
60+
case elliptic.P384():
61+
return oidNamedCurveP384, true
62+
case elliptic.P521():
63+
return oidNamedCurveP521, true
64+
}
65+
return nil, false
66+
}
67+
2968
// PrivateKeyToDER marshals a private key to der
3069
func PrivateKeyToDER(privateKey *ecdsa.PrivateKey) ([]byte, error) {
3170
if privateKey == nil {
@@ -35,7 +74,9 @@ func PrivateKeyToDER(privateKey *ecdsa.PrivateKey) ([]byte, error) {
3574
return x509.MarshalECPrivateKey(privateKey)
3675
}
3776

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

51-
raw, err := x509.MarshalECPrivateKey(k)
92+
// get the oid for the curve
93+
oidNamedCurve, ok := oidFromNamedCurve(k.Curve)
94+
if !ok {
95+
return nil, errors.New("unknown elliptic curve")
96+
}
97+
98+
// based on https://golang.org/src/crypto/x509/sec1.go
99+
privateKeyBytes := k.D.Bytes()
100+
paddedPrivateKey := make([]byte, (k.Curve.Params().N.BitLen()+7)/8)
101+
copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes)
102+
// omit NamedCurveOID for compatibility as it's optional
103+
asn1Bytes, err := asn1.Marshal(ecPrivateKey{
104+
Version: 1,
105+
PrivateKey: paddedPrivateKey,
106+
PublicKey: asn1.BitString{Bytes: elliptic.Marshal(k.Curve, k.X, k.Y)},
107+
})
52108

53109
if err != nil {
54-
return nil, err
110+
return nil, fmt.Errorf("error marshaling EC key to asn1 [%s]", err)
55111
}
56112

113+
var pkcs8Key pkcs8Info
114+
pkcs8Key.Version = 1
115+
pkcs8Key.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 2)
116+
pkcs8Key.PrivateKeyAlgorithm[0] = oidPublicKeyECDSA
117+
pkcs8Key.PrivateKeyAlgorithm[1] = oidNamedCurve
118+
pkcs8Key.PrivateKey = asn1Bytes
119+
120+
pkcs8Bytes, err := asn1.Marshal(pkcs8Key)
121+
if err != nil {
122+
return nil, fmt.Errorf("error marshaling EC key to asn1 [%s]", err)
123+
}
57124
return pem.EncodeToMemory(
58125
&pem.Block{
59-
Type: "ECDSA PRIVATE KEY",
60-
Bytes: raw,
126+
Type: "PRIVATE KEY",
127+
Bytes: pkcs8Bytes,
61128
},
62129
), nil
63130
case *rsa.PrivateKey:
@@ -94,7 +161,7 @@ func PrivateKeyToEncryptedPEM(privateKey interface{}, pwd []byte) ([]byte, error
94161

95162
block, err := x509.EncryptPEMBlock(
96163
rand.Reader,
97-
"ECDSA PRIVATE KEY",
164+
"PRIVATE KEY",
98165
raw,
99166
pwd,
100167
x509.PEMCipherAES256)
@@ -241,7 +308,7 @@ func PublicKeyToPEM(publicKey interface{}, pwd []byte) ([]byte, error) {
241308

242309
return pem.EncodeToMemory(
243310
&pem.Block{
244-
Type: "ECDSA PUBLIC KEY",
311+
Type: "PUBLIC KEY",
245312
Bytes: PubASN1,
246313
},
247314
), nil
@@ -302,7 +369,7 @@ func PublicKeyToEncryptedPEM(publicKey interface{}, pwd []byte) ([]byte, error)
302369

303370
block, err := x509.EncryptPEMBlock(
304371
rand.Reader,
305-
"ECDSA PUBLIC KEY",
372+
"PUBLIC KEY",
306373
raw,
307374
pwd,
308375
x509.PEMCipherAES256)

bccsp/utils/keys_test.go

+92-4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,85 @@ import (
44
"crypto/ecdsa"
55
"crypto/elliptic"
66
"crypto/rand"
7+
"crypto/x509"
8+
"encoding/asn1"
9+
"encoding/pem"
710
"testing"
11+
12+
"github.com/stretchr/testify/assert"
813
)
914

15+
func TestOidFromNamedCurve(t *testing.T) {
16+
17+
var (
18+
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
19+
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
20+
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
21+
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
22+
)
23+
24+
type result struct {
25+
oid asn1.ObjectIdentifier
26+
ok bool
27+
}
28+
29+
var tests = []struct {
30+
name string
31+
curve elliptic.Curve
32+
expected result
33+
}{
34+
{
35+
name: "P224",
36+
curve: elliptic.P224(),
37+
expected: result{
38+
oid: oidNamedCurveP224,
39+
ok: true,
40+
},
41+
},
42+
{
43+
name: "P256",
44+
curve: elliptic.P256(),
45+
expected: result{
46+
oid: oidNamedCurveP256,
47+
ok: true,
48+
},
49+
},
50+
{
51+
name: "P384",
52+
curve: elliptic.P384(),
53+
expected: result{
54+
oid: oidNamedCurveP384,
55+
ok: true,
56+
},
57+
},
58+
{
59+
name: "P521",
60+
curve: elliptic.P521(),
61+
expected: result{
62+
oid: oidNamedCurveP521,
63+
ok: true,
64+
},
65+
},
66+
{
67+
name: "T-1000",
68+
curve: &elliptic.CurveParams{Name: "T-1000"},
69+
expected: result{
70+
oid: nil,
71+
ok: false,
72+
},
73+
},
74+
}
75+
76+
for _, test := range tests {
77+
t.Run(test.name, func(t *testing.T) {
78+
oid, ok := oidFromNamedCurve(test.curve)
79+
assert.Equal(t, oid, test.expected.oid)
80+
assert.Equal(t, ok, test.expected.ok)
81+
})
82+
}
83+
84+
}
85+
1086
func TestECDSAKeys(t *testing.T) {
1187
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
1288
if err != nil {
@@ -35,11 +111,19 @@ func TestECDSAKeys(t *testing.T) {
35111
}
36112

37113
// Private Key PEM format
38-
pem, err := PrivateKeyToPEM(key, nil)
114+
rawPEM, err := PrivateKeyToPEM(key, nil)
39115
if err != nil {
40116
t.Fatalf("Failed converting private key to PEM [%s]", err)
41117
}
42-
keyFromPEM, err := PEMtoPrivateKey(pem, nil)
118+
pemBlock, _ := pem.Decode(rawPEM)
119+
if pemBlock.Type != "PRIVATE KEY" {
120+
t.Fatalf("Expected type 'PRIVATE KEY' but found '%s'", pemBlock.Type)
121+
}
122+
_, err = x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
123+
if err != nil {
124+
t.Fatalf("Failed to parse PKCS#8 private key [%s]", err)
125+
}
126+
keyFromPEM, err := PEMtoPrivateKey(rawPEM, nil)
43127
if err != nil {
44128
t.Fatalf("Failed converting DER to private key [%s]", err)
45129
}
@@ -108,11 +192,15 @@ func TestECDSAKeys(t *testing.T) {
108192
}
109193

110194
// Public Key PEM format
111-
pem, err = PublicKeyToPEM(&key.PublicKey, nil)
195+
rawPEM, err = PublicKeyToPEM(&key.PublicKey, nil)
112196
if err != nil {
113197
t.Fatalf("Failed converting public key to PEM [%s]", err)
114198
}
115-
keyFromPEM, err = PEMtoPublicKey(pem, nil)
199+
pemBlock, _ = pem.Decode(rawPEM)
200+
if pemBlock.Type != "PUBLIC KEY" {
201+
t.Fatalf("Expected type 'PUBLIC KEY' but found '%s'", pemBlock.Type)
202+
}
203+
keyFromPEM, err = PEMtoPublicKey(rawPEM, nil)
116204
if err != nil {
117205
t.Fatalf("Failed converting DER to public key [%s]", err)
118206
}

0 commit comments

Comments
 (0)