docker/swarmkit

View on GitHub
ca/pkcs8/pkcs8.go

Summary

Maintainability
A
1 hr
Test Coverage
// Package pkcs8 implements functions to encrypt, decrypt, parse and to convert
// EC private keys to PKCS#8 format. However this package is hard forked from
// https://github.com/youmark/pkcs8 and modified function signatures to match
// signatures of crypto/x509 and cloudflare/cfssl/helpers to simplify package
// swapping. License for original package is as follow:

// The MIT License (MIT)
//
// Copyright (c) 2014 youmark
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package pkcs8

import (
    "bytes"
    "crypto"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/sha1"
    "encoding/asn1"
    "encoding/pem"
    "errors"

    "github.com/cloudflare/cfssl/helpers/derhelpers"
    "golang.org/x/crypto/pbkdf2"
)

// Copy from crypto/x509
var (
    oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
)

// Unencrypted PKCS#8
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
}

// ParsePrivateKeyPEMWithPassword parses an encrypted or a decrypted PKCS#8 PEM to crypto.signer
func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) {
    block, _ := pem.Decode(pemBytes)
    if block == nil {
        return nil, errors.New("invalid pem file")
    }

    var (
        der []byte
        err error
    )
    der = block.Bytes

    if ok := IsEncryptedPEMBlock(block); ok {
        der, err = DecryptPEMBlock(block, password)
        if err != nil {
            return nil, err
        }
    }

    return derhelpers.ParsePrivateKeyDER(der)
}

// IsEncryptedPEMBlock checks if a PKCS#8 PEM-block is encrypted or not
func IsEncryptedPEMBlock(block *pem.Block) bool {
    der := block.Bytes

    var privKey encryptedPrivateKeyInfo
    if _, err := asn1.Unmarshal(der, &privKey); err != nil {
        return false
    }

    return true
}

// DecryptPEMBlock requires PKCS#8 PEM Block and password to decrypt and return unencrypted der []byte
func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) {
    der := block.Bytes

    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)

        if _, err := derhelpers.ParsePrivateKeyDER(encryptedKey); err != nil {
            return nil, errors.New("pkcs8: incorrect password")
        }

        // Remove padding from key as it might be used to encode to memory as pem
        keyLen := len(encryptedKey)
        padLen := int(encryptedKey[keyLen-1])
        if padLen > keyLen || padLen > aes.BlockSize {
            return nil, errors.New("pkcs8: invalid padding size")
        }
        encryptedKey = encryptedKey[:keyLen-padLen]

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

func encryptPrivateKey(pkey, password []byte) ([]byte, error) {
    // 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)

    if _, err := rand.Reader.Read(salt); err != nil {
        return nil, err
    }

    if _, err := rand.Reader.Read(iv); err != nil {
        return nil, err
    }

    key := pbkdf2.Key(password, salt, iter, 32, sha1.New)

    // Use AES256-CBC mode, pad plaintext with PKCS5 padding scheme
    n := len(pkey)
    padLen := aes.BlockSize - n%aes.BlockSize
    if padLen > 0 {
        padValue := []byte{byte(padLen)}
        padding := bytes.Repeat(padValue, padLen)
        pkey = append(pkey, 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)
}

// EncryptPEMBlock takes DER-format bytes and password to return an encrypted PKCS#8 PEM-block
func EncryptPEMBlock(data, password []byte) (*pem.Block, error) {
    encryptedBytes, err := encryptPrivateKey(data, password)
    if err != nil {
        return nil, err
    }

    return &pem.Block{
        Type:    "ENCRYPTED PRIVATE KEY",
        Headers: map[string]string{},
        Bytes:   encryptedBytes,
    }, nil
}

// ConvertECPrivateKeyPEM takes an EC Private Key as input and returns PKCS#8 version of it
func ConvertECPrivateKeyPEM(inPEM []byte) ([]byte, error) {
    block, _ := pem.Decode(inPEM)
    if block == nil {
        return nil, errors.New("invalid pem bytes")
    }

    var ecPrivKey ecPrivateKey
    if _, err := asn1.Unmarshal(block.Bytes, &ecPrivKey); err != nil {
        return nil, errors.New("invalid ec private key")
    }

    var pkey privateKeyInfo
    pkey.Version = 0
    pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 2)
    pkey.PrivateKeyAlgorithm[0] = oidPublicKeyECDSA
    pkey.PrivateKeyAlgorithm[1] = ecPrivKey.NamedCurveOID

    // remove curve oid from private bytes as it is already mentioned in algorithm
    ecPrivKey.NamedCurveOID = nil

    privatekey, err := asn1.Marshal(ecPrivKey)
    if err != nil {
        return nil, err
    }
    pkey.PrivateKey = privatekey

    der, err := asn1.Marshal(pkey)
    if err != nil {
        return nil, err
    }

    return pem.EncodeToMemory(&pem.Block{
        Type:  "PRIVATE KEY",
        Bytes: der,
    }), nil
}

// ConvertToECPrivateKeyPEM takes an unencrypted PKCS#8 PEM and converts it to
// EC Private Key
func ConvertToECPrivateKeyPEM(inPEM []byte) ([]byte, error) {
    block, _ := pem.Decode(inPEM)
    if block == nil {
        return nil, errors.New("invalid pem bytes")
    }

    var pkey privateKeyInfo
    if _, err := asn1.Unmarshal(block.Bytes, &pkey); err != nil {
        return nil, errors.New("invalid pkcs8 key")
    }

    var ecPrivKey ecPrivateKey
    if _, err := asn1.Unmarshal(pkey.PrivateKey, &ecPrivKey); err != nil {
        return nil, errors.New("invalid private key")
    }

    ecPrivKey.NamedCurveOID = pkey.PrivateKeyAlgorithm[1]
    key, err := asn1.Marshal(ecPrivKey)
    if err != nil {
        return nil, err
    }

    return pem.EncodeToMemory(&pem.Block{
        Type:  "EC PRIVATE KEY",
        Bytes: key,
    }), nil
}