blob: 83b9eb77a53041c2d3b9ce0a25ffd6e913333cd6 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Decoder for PKCS#5 encrypted PKCS#8 private keys.
package sec
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"hash"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/ssh/terminal"
)
var (
oidPbes2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
oidPbkdf2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12}
oidHmacWithSha1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 7}
oidHmacWithSha224 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 8}
oidHmacWithSha256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 9}
oidAes128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2}
oidAes256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
)
// We only support a narrow set of possible key types, namely the type
// generated by either MCUboot's `imgtool.py` command, or using an
// OpenSSL command such as:
//
// openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
// -aes-256-cbc > keyfile.pem
//
// or similar for ECDSA. Specifically, the encryption must be done
// with PBES2, and PBKDF2, and aes-256-cbc used as the cipher.
type pkcs5 struct {
Algo pkix.AlgorithmIdentifier
Encrypted []byte
}
// The parameters when the algorithm in pkcs5 is oidPbes2
type pbes2 struct {
KeyDerivationFunc pkix.AlgorithmIdentifier
EncryptionScheme pkix.AlgorithmIdentifier
}
// Salt is given as a choice, but we will only support the inlined
// octet string.
type pbkdf2Param struct {
Salt []byte
IterCount int
HashFunc pkix.AlgorithmIdentifier
// Optional and default values omitted, and unsupported.
}
type hashFunc func() hash.Hash
func parseEncryptedPrivateKey(der []byte) (key interface{}, err error) {
var wrapper pkcs5
if _, err = asn1.Unmarshal(der, &wrapper); err != nil {
return nil, err
}
if !wrapper.Algo.Algorithm.Equal(oidPbes2) {
return nil, fmt.Errorf("pkcs5: Unknown PKCS#5 wrapper algorithm: %v", wrapper.Algo.Algorithm)
}
var pbparm pbes2
if _, err = asn1.Unmarshal(wrapper.Algo.Parameters.FullBytes, &pbparm); err != nil {
return nil, err
}
if !pbparm.KeyDerivationFunc.Algorithm.Equal(oidPbkdf2) {
return nil, fmt.Errorf("pkcs5: Unknown KDF: %v", pbparm.KeyDerivationFunc.Algorithm)
}
var kdfParam pbkdf2Param
if _, err = asn1.Unmarshal(pbparm.KeyDerivationFunc.Parameters.FullBytes, &kdfParam); err != nil {
return nil, err
}
var hashNew hashFunc
switch {
case kdfParam.HashFunc.Algorithm.Equal(oidHmacWithSha1):
hashNew = sha1.New
case kdfParam.HashFunc.Algorithm.Equal(oidHmacWithSha224):
hashNew = sha256.New224
case kdfParam.HashFunc.Algorithm.Equal(oidHmacWithSha256):
hashNew = sha256.New
default:
return nil, fmt.Errorf("pkcs5: Unsupported hash: %v", pbparm.EncryptionScheme.Algorithm)
}
// Get the encryption used.
size := 0
var iv []byte
switch {
case pbparm.EncryptionScheme.Algorithm.Equal(oidAes256CBC):
size = 32
if _, err = asn1.Unmarshal(pbparm.EncryptionScheme.Parameters.FullBytes, &iv); err != nil {
return nil, err
}
case pbparm.EncryptionScheme.Algorithm.Equal(oidAes128CBC):
size = 16
if _, err = asn1.Unmarshal(pbparm.EncryptionScheme.Parameters.FullBytes, &iv); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("pkcs5: Unsupported cipher: %v", pbparm.EncryptionScheme.Algorithm)
}
return unwrapPbes2Pbkdf2(&kdfParam, size, iv, hashNew, wrapper.Encrypted)
}
func unwrapPbes2Pbkdf2(param *pbkdf2Param, size int, iv []byte, hashNew hashFunc, encrypted []byte) (key interface{}, err error) {
pass, err := getPassword()
if err != nil {
return nil, err
}
cryptoKey := pbkdf2.Key(pass, param.Salt, param.IterCount, size, hashNew)
block, err := aes.NewCipher(cryptoKey)
if err != nil {
return nil, err
}
enc := cipher.NewCBCDecrypter(block, iv)
plain := make([]byte, len(encrypted))
enc.CryptBlocks(plain, encrypted)
plain, err = checkPkcs7Padding(plain)
if err != nil {
return nil, err
}
return x509.ParsePKCS8PrivateKey(plain)
}
// Verify that PKCS#7 padding is correct on this plaintext message.
// Returns a new slice with the padding removed.
func checkPkcs7Padding(buf []byte) ([]byte, error) {
if len(buf) < 16 {
return nil, fmt.Errorf("Invalid padded buffer")
}
padLen := int(buf[len(buf)-1])
if padLen < 1 || padLen > 16 {
return nil, fmt.Errorf("Invalid padded buffer")
}
if padLen > len(buf) {
return nil, fmt.Errorf("Invalid padded buffer")
}
for pos := len(buf) - padLen; pos < len(buf); pos++ {
if int(buf[pos]) != padLen {
return nil, fmt.Errorf("Invalid padded buffer")
}
}
return buf[:len(buf)-padLen], nil
}
// For testing, a key can be set here. If this is empty, the key will
// be queried via prompt.
var KeyPassword = []byte{}
// Prompt the user for a password, unless we have stored one for
// testing.
func getPassword() ([]byte, error) {
if len(KeyPassword) != 0 {
return KeyPassword, nil
}
fmt.Printf("key password: ")
return terminal.ReadPassword(0)
}