sec/pkcs.go (130 lines of code) (raw):

/** * 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) }