Skip to content

Commit

Permalink
Add PKCS#8 Support
Browse files Browse the repository at this point in the history
Migrate Private Encrypted Key to PKCS#8 format. Since PKCS#8 format
doesn't support headers so the key is being wrapped into another
ASN1 structure as DER format with Role and GUN values. So, the
key being saved to keystore will not be directly supported with
anything else like openssl.

There's no support for ED25519 in x509 package. So pkcs8 library
that is being used in here (https://github.com/youmark/pkcs8) has
been modified to support that as well.

Signed-off-by: Umayr Shahid <umayr.shahid@gmail.com>
  • Loading branch information
umayr committed Apr 6, 2017
1 parent 06e54a1 commit ee02b39
Show file tree
Hide file tree
Showing 4 changed files with 407 additions and 110 deletions.
23 changes: 10 additions & 13 deletions trustmanager/keystore.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package trustmanager

import (
"encoding/pem"
"fmt"
"path/filepath"
"strings"
Expand Down Expand Up @@ -114,11 +113,7 @@ func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
}
}

if chosenPassphrase != "" {
pemPrivKey, err = utils.EncryptPrivateKey(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)
} else {
pemPrivKey, err = utils.KeyToPEM(privKey, keyInfo.Role, keyInfo.Gun)
}
pemPrivKey, err = utils.ConvertPrivateKeyToPKCS8(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)

if err != nil {
return err
Expand Down Expand Up @@ -204,11 +199,11 @@ func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
var keyID string
keyID = filepath.Base(filename)
block, _ := pem.Decode(pemBytes)
if block == nil {
return "", KeyInfo{}, fmt.Errorf("could not decode PEM block for key %s", filename)
role, gun, err := utils.ExtractPrivateKeyAttributes(pemBytes)
if err != nil {
return "", KeyInfo{}, err
}
return keyID, KeyInfo{Gun: data.GUN(block.Headers["gun"]), Role: data.RoleName(block.Headers["role"])}, nil
return keyID, KeyInfo{Gun: gun, Role: role}, nil
}

// getKeyRole finds the role for the given keyID. It attempts to look
Expand All @@ -224,10 +219,12 @@ func getKeyRole(s Storage, keyID string) (data.RoleName, error) {
if err != nil {
return "", err
}
block, _ := pem.Decode(d)
if block != nil {
return data.RoleName(block.Headers["role"]), nil

role, _, err := utils.ExtractPrivateKeyAttributes(d)
if err != nil {
return "", err
}
return role, nil
}
}
return "", ErrKeyNotFound{KeyID: keyID}
Expand Down
292 changes: 292 additions & 0 deletions tuf/utils/pkcs8.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
package utils

import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"

"github.com/docker/notary/tuf/data"
"golang.org/x/crypto/pbkdf2"
)

// Copy from crypto/x509
var (
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1}
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
// crypto/x509 doesn't have support for ED25519
// http://www.oid-info.com/get/1.3.6.1.4.1.11591.15.1
oidPublicKeyED25519 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11591, 15, 1}
)

// Copy from crypto/x509
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}
)

// Copy from crypto/x509
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
}

// Unecrypted PKCS8
var (
oidPKCS5PBKDF2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12}
oidPBES2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
oidAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
)

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

type privateKeyInfo struct {
Version int
PrivateKeyAlgorithm []asn1.ObjectIdentifier
PrivateKey []byte
}

// Encrypted PKCS8
type pbkdf2Params struct {
Salt []byte
IterationCount int
}

type pbkdf2Algorithms struct {
IdPBKDF2 asn1.ObjectIdentifier
PBKDF2Params pbkdf2Params
}

type pbkdf2Encs struct {
EncryAlgo asn1.ObjectIdentifier
IV []byte
}

type pbes2Params struct {
KeyDerivationFunc pbkdf2Algorithms
EncryptionScheme pbkdf2Encs
}

type pbes2Algorithms struct {
IdPBES2 asn1.ObjectIdentifier
PBES2Params pbes2Params
}

type encryptedPrivateKeyInfo struct {
EncryptionAlgorithm pbes2Algorithms
EncryptedData []byte
}

func parsePKCS8ToTufKey(der []byte) (data.PrivateKey, error) {
var key struct {
Version int
Algo pkix.AlgorithmIdentifier
PrivateKey []byte
}

if _, err := asn1.Unmarshal(der, &key); err != nil {
return nil, err
}

if key.Algo.Algorithm.Equal(oidPublicKeyED25519) {
tufED25519PrivateKey, err := ED25519ToPrivateKey(key.PrivateKey)
if err != nil {
return nil, fmt.Errorf("could not convert ed25519.PrivateKey to data.PrivateKey: %v", err)
}

return tufED25519PrivateKey, nil
}

privKey, err := x509.ParsePKCS8PrivateKey(der)
if err != nil {
return nil, err
}

switch priv := privKey.(type) {
case *rsa.PrivateKey:
tufRSAPrivateKey, err := RSAToPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
}

return tufRSAPrivateKey, nil
case *ecdsa.PrivateKey:
tufECDSAPrivateKey, err := ECDSAToPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
}

return tufECDSAPrivateKey, nil
}

return nil, errors.New("unsupported key type")
}

func ParsePKCS8ToTufKey(der []byte, v ...[]byte) (data.PrivateKey, error) {
if v == nil {
return parsePKCS8ToTufKey(der)
}

// Use the password provided to decrypt the private key
password := v[0]
var privKey encryptedPrivateKeyInfo
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, errors.New("pkcs8: only PKCS #5 v2.0 supported")
}

if !privKey.EncryptionAlgorithm.IdPBES2.Equal(oidPBES2) {
return nil, errors.New("pkcs8: only PBES2 supported")
}

if !privKey.EncryptionAlgorithm.PBES2Params.KeyDerivationFunc.IdPBKDF2.Equal(oidPKCS5PBKDF2) {
return nil, errors.New("pkcs8: only PBKDF2 supported")
}

encParam := privKey.EncryptionAlgorithm.PBES2Params.EncryptionScheme
kdfParam := privKey.EncryptionAlgorithm.PBES2Params.KeyDerivationFunc.PBKDF2Params

switch {
case encParam.EncryAlgo.Equal(oidAES256CBC):
iv := encParam.IV
salt := kdfParam.Salt
iter := kdfParam.IterationCount

encryptedKey := privKey.EncryptedData
symkey := pbkdf2.Key(password, salt, iter, 32, sha1.New)
block, err := aes.NewCipher(symkey)
if err != nil {
return nil, err
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(encryptedKey, encryptedKey)

key, err := parsePKCS8ToTufKey(encryptedKey)
if err != nil {
return nil, errors.New("pkcs8: incorrect password")
}

return key, nil
default:
return nil, errors.New("pkcs8: only AES-256-CBC supported")
}

}

func convertTUFKeyToPKCS8(priv data.PrivateKey) ([]byte, error) {
var pkey privateKeyInfo

switch priv.Algorithm() {
case data.RSAKey, data.RSAx509Key:
// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
// But openssl set to v1 even publicKey is present
pkey.Version = 0
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyRSA
pkey.PrivateKey = priv.Private()
case data.ECDSAKey, data.ECDSAx509Key:
// To extract Curve value, parsing ECDSA key to *ecdsa.PrivateKey
eckey, err := x509.ParseECPrivateKey(priv.Private())
if err != nil {
return nil, err
}

oidNamedCurve, ok := oidFromNamedCurve(eckey.Curve)
if !ok {
return nil, errors.New("pkcs8: unknown elliptic curve")
}

// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
// But openssl set to v1 even publicKey is present
pkey.Version = 1
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 2)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyECDSA
pkey.PrivateKeyAlgorithm[1] = oidNamedCurve
pkey.PrivateKey = priv.Private()
case data.ED25519Key:
pkey.Version = 0
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyED25519
pkey.PrivateKey = priv.Private()
default:
return nil, fmt.Errorf("algorithm %s not supported", priv.Algorithm())
}

return asn1.Marshal(pkey)
}

func convertTUFKeyToPKCS8Encrypted(priv data.PrivateKey, password []byte) ([]byte, error) {
// Convert private key into PKCS8 format
pkey, err := convertTUFKeyToPKCS8(priv)
if err != nil {
return nil, err
}

// Calculate key from password based on PKCS5 algorithm
// Use 8 byte salt, 16 byte IV, and 2048 iteration
iter := 2048
salt := make([]byte, 8)
iv := make([]byte, 16)
rand.Reader.Read(salt)
rand.Reader.Read(iv)
key := pbkdf2.Key(password, salt, iter, 32, sha1.New)

// Use AES256-CBC mode, pad plaintext with PKCS5 padding scheme
padding := aes.BlockSize - len(pkey)%aes.BlockSize
if padding > 0 {
n := len(pkey)
pkey = append(pkey, make([]byte, padding)...)
for i := 0; i < padding; i++ {
pkey[n+i] = byte(padding)
}
}

encryptedKey := make([]byte, len(pkey))
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encryptedKey, pkey)

pbkdf2algo := pbkdf2Algorithms{oidPKCS5PBKDF2, pbkdf2Params{salt, iter}}
pbkdf2encs := pbkdf2Encs{oidAES256CBC, iv}
pbes2algo := pbes2Algorithms{oidPBES2, pbes2Params{pbkdf2algo, pbkdf2encs}}

encryptedPkey := encryptedPrivateKeyInfo{pbes2algo, encryptedKey}

return asn1.Marshal(encryptedPkey)
}

func ConvertTUFKeyToPKCS8(priv data.PrivateKey, v ...[]byte) ([]byte, error) {
if v == nil {
return convertTUFKeyToPKCS8(priv)
}
password := string(v[0])
return convertTUFKeyToPKCS8Encrypted(priv, []byte(password))
}
Loading

0 comments on commit ee02b39

Please sign in to comment.