You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by ut...@apache.org on 2018/01/02 15:18:50 UTC
[mynewt-newt] 03/03: newt: Add support for encrypted private keys
This is an automated email from the ASF dual-hosted git repository.
utzig pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git
commit 9e64e673c6568ffe1e31c101ab3a091eca0e8474
Author: David Brown <da...@linaro.org>
AuthorDate: Wed Nov 22 15:43:28 2017 -0700
newt: Add support for encrypted private keys
Allow both RSA and ECDSA keys to be encrypted with PKCS#5. This matches
the format used by both `imgtool.py` and OpenSSL (when using the
`genpkey` command) when encryption is requested.
The code supports both aes-128-cbc and aes-256-cbc, although the test
vectors only test the 256-bit variant (which is what `imgtool.py` uses).
This uses golang.org/x/crypto/ssh/terminal to prompt for the key
password, without echo.
---
newt/image/encrypted.go | 176 ++++++++++++++++++++++++++++++++++++++++++++++++
newt/image/image.go | 38 +++++++++--
newt/image/keys_test.go | 57 ++++++++++++++++
3 files changed, 264 insertions(+), 7 deletions(-)
diff --git a/newt/image/encrypted.go b/newt/image/encrypted.go
new file mode 100644
index 0000000..9d8ce58
--- /dev/null
+++ b/newt/image/encrypted.go
@@ -0,0 +1,176 @@
+/**
+ * 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 image
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/sha1"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "fmt"
+
+ "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}
+ 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
+ // Optional and default values omitted, and unsupported.
+}
+
+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
+ }
+
+ // 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, wrapper.Encrypted)
+}
+
+func unwrapPbes2Pbkdf2(param *pbkdf2Param, size int, iv []byte, encrypted []byte) (key interface{}, err error) {
+ pass, err := getPassword()
+ if err != nil {
+ return nil, err
+ }
+ cryptoKey := pbkdf2.Key(pass, param.Salt, param.IterCount, size, sha1.New)
+
+ 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)
+}
diff --git a/newt/image/image.go b/newt/image/image.go
index 0c20459..176939b 100644
--- a/newt/image/image.go
+++ b/newt/image/image.go
@@ -345,13 +345,22 @@ func (image *Image) SetSigningKey(fileName string, keyId uint8) error {
"failed: %s", err))
}
- switch priv := privateKey.(type) {
- case *rsa.PrivateKey:
- image.SigningRSA = priv
- case *ecdsa.PrivateKey:
- image.SigningEC = priv
- default:
- return util.NewNewtError("Unknown private key format")
+ err = image.storeKey(privateKey)
+ if err != nil {
+ return err
+ }
+ }
+ if block != nil && block.Type == "ENCRYPTED PRIVATE KEY" {
+ // This indicates a PKCS#8 key wrapped with PKCS#5
+ // encryption.
+ privateKey, err := parseEncryptedPrivateKey(block.Bytes)
+ if err != nil {
+ return util.FmtNewtError("Unable to decode encrypted private key: %s", err)
+ }
+
+ err = image.storeKey(privateKey)
+ if err != nil {
+ return err
}
}
if image.SigningEC == nil && image.SigningRSA == nil {
@@ -363,6 +372,21 @@ func (image *Image) SetSigningKey(fileName string, keyId uint8) error {
return nil
}
+// Store the given key in this image, determining the type of key and
+// storing it in the appropriate field.
+func (image *Image) storeKey(key interface{}) (err error) {
+ switch priv := key.(type) {
+ case *rsa.PrivateKey:
+ image.SigningRSA = priv
+ case *ecdsa.PrivateKey:
+ image.SigningEC = priv
+ default:
+ return util.NewNewtError("Unknown private key format")
+ }
+
+ return nil
+}
+
func (image *Image) sigHdrTypeV1() (uint32, error) {
if image.SigningRSA != nil {
if UseRsaPss {
diff --git a/newt/image/keys_test.go b/newt/image/keys_test.go
index 0b96e6e..ca24b98 100644
--- a/newt/image/keys_test.go
+++ b/newt/image/keys_test.go
@@ -25,6 +25,18 @@ func TestPlainEcdsaPkcs8(t *testing.T) {
signatureTest(t, ecdsaPkcs8Private)
}
+func TestEncryptedRSA(t *testing.T) {
+ image.KeyPassword = []byte("sample")
+ signatureTest(t, rsaEncryptedPrivate)
+ image.KeyPassword = []byte{}
+}
+
+func TestEncryptedEcdsa(t *testing.T) {
+ image.KeyPassword = []byte("sample")
+ signatureTest(t, ecdsaEncryptedPrivate)
+ image.KeyPassword = []byte{}
+}
+
func signatureTest(t *testing.T, privateKey []byte) {
tmpdir, err := ioutil.TempDir("", "newttest")
if err != nil {
@@ -168,3 +180,48 @@ j0AQZlBs7f4r67668eDCUB8aDR2hRANCAATyZPzsx+xn9JtlxdspevTrYisiMTjl
YuBJCrV1FZj2HkplEgO+ZIMuD7eRvyTEBS2bw6F1aCeKOMUmYVImAbpc
-----END PRIVATE KEY-----
`)
+
+// A password-protected RSA private key in PKCS#5/8 format. The
+// password for this key is "sample".
+var rsaEncryptedPrivate = []byte(`-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIRMifqJThk8kCAggA
+MB0GCWCGSAFlAwQBKgQQTMUBoFpzjJ5UNRnCIeqf4QSCBNDkQvXnUNmss8erKiDo
+Uqs2tf9ZD8MjDThLBmF/gV1dg1q6aDY+3fI2E4yLXJb2PmKcUq82YZ0FDeoCvJRJ
+BCurzM9slur5akpNBTFoFwtFsdHz7nKNS4MHUul22rGBnVFUUNTySmpjl/m+dxWO
+fa6tWpGTAr7tsCy9gF5PxpSw7NR/NpIL0PmpydHWhTs1tl2csqBqK6Tp014Kefi/
+pmmeb2eRl5cmprxW32rW2QBMtv4z91SsbnlVdz4r8txTG+3S4td9v9jD5kqcIiC2
+KQHrbH9y7okUk/ISsp9ANKPJt10fbYDxORiMK57XssXy1enGjpkIIrUGz1TMydkD
+USfwqkmPuIrrzOXnbxU4ef2wC/pA/h9Smby3WWYo8725/1kZyIediNDcgi/Qgrs4
+1VQAYzsD6duwyUNSo+tgmYVFGvZhsottus3fMWe/Ay1biJ6z6Vk8gqKWI1VV/REJ
+zK/I9hgKGxj2N2Ff6E/YkcwQenHWj/iDWLjvokyOBnPFNqzzM2Qqo1XFpzj4EO5D
+0WD4EzZYvUhk3lZZNydvXiuy8RrCVLLJMS08XgOqQaiFqqxj2hjRwv3nBesk7iA8
+5Tv8GMa5QkNrISCnp4/uGBh+v/CjwVRqPTcK3/mctPN2nLhI6H4pF4Y6apXkz1TN
+NMQqxaxmVVg8fyLaS4/xfUr8LAmiEtOwvs0XOhcqCTvvlsO4N+yec4VD4gmsTDY9
+/2b/+YwSlGMpA+GQQbg0FraaF8NyJRG1mSER6WiUGGM1cuKK44nzBbykQbZwzDSA
+kkhjDaadkhv/NPKAUR3sNy2GXVaNL/ItCpQUHRKKcIPp0HhdXsl0YebuwRlHjw/6
+UOdzNYe23e40X/Xl3vmOKRbzhLP/qO2DV21o0wI4ujF8Xu5h1h8s49HPp58G1ldy
+/hJ6durYKX8T5khiR2iXYewoy0YObuccV//Ov1/ySOp/x0/QuCl/swvs8Jf7awnu
+rpRrHPArpCvMmXmt5Y+TFYXFjkJGwsxTew5TcwBebBlIET2XNbo2pbz4WqJ3eVlK
+CNZVDEZ8mMrGT00FBi759Vfw9rhrnqXnLlNtJZ5VCXFUw8Tos302sLaQWXzHYyf8
+4awM8G9PSu5Q9lFcN9od4H95YrAAv/l8F+pcGgEKD8ZuzsgFIalqgx5wzmUMDcPM
+NKV5u9mtHjI92ru6NB8rGesM6sy6kBGvpotsDWawpV2SoCrkbyEkk+kXaGS+fsG7
+D2H37GfktN8R5Ktc0Uf/JJiNfDzq8lk1J4r7LBQlWUbhKbfGMYxt+7Xo0GsqAsLp
+PKSUwx+hTZb3BmW6s4Q6vivI1MdQbWVT1zh41StvfRSNlo70iOFxOM0lU1jjY989
+UKo+gcolddvZbMNwip0ILPO3dsa+he1jJ/gbo9qBHLy7plfsBLLakZP1Nu6xdlqQ
+TSSobaE8uxUMZk+wMWClA9AOZ1TcUr2yRV5GVj/bxG9ab+H37vF9F8vFE+jjJ7yN
+6pjdohm4gXeSVx7ON4SeZLsVwNYkCVYS89E81qLx1jP9F57+6IUGDZN5EMC0aJLT
+ny75MCCLT00KD7BFsb0KDLXxp++eu/L2hinorT3p6dXp/9mUoxmy6wJqEyqCFniZ
+N2GZN7+LDTIbHUxCijVWamU2DQ==
+-----END ENCRYPTED PRIVATE KEY-----
+`)
+
+// A password-protected ECDSA private key in PKCS#5/8 format. The
+// password for this key is "sample"
+var ecdsaEncryptedPrivate = []byte(`-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAjlKrDSKNg9QQICCAAw
+HQYJYIZIAWUDBAEqBBDliPNzQTNpdlppTcYpmuhWBIGQVhfWaVSzUvi/qIZLiZVn
+Nulfw5jDOlbn3UBX9kp/Z9Pro582Q0kjzLfm5UahvDINEJWxL4pc/28UnGQTBr0Q
+nSEg+RbqpuD099C38H0Gq/YkIM+RDG4aiQrkmzHXyVsHshIbG+z2LsLTIwmU69/Z
+v0nX6/hGErVR8YWcrOne086rCvfJVrxyO5+EUqrkLhEr
+-----END ENCRYPTED PRIVATE KEY-----
+`)
--
To stop receiving notification emails like this one, please contact
"commits@mynewt.apache.org" <co...@mynewt.apache.org>.