Skip to content

Commit

Permalink
Merge "[FAB-3297] Generate PKCS8 compliant EC keys"
Browse files Browse the repository at this point in the history
  • Loading branch information
jimthematrix authored and Gerrit Code Review committed Apr 22, 2017
2 parents 0fd4f6f + 5bca81a commit 95d13d2
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 95d13d2

Please sign in to comment.