]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: add DecryptBlock function for loading password protected keys
authorJeff Wendling <jeff@spacemonkey.com>
Thu, 4 Oct 2012 19:42:57 +0000 (15:42 -0400)
committerAdam Langley <agl@golang.org>
Thu, 4 Oct 2012 19:42:57 +0000 (15:42 -0400)
Adds a DecryptBlock function which takes a password and a *pem.Block and
returns the decrypted DER bytes suitable for passing into other crypto/x509
functions.

R=golang-dev, agl, leterip
CC=golang-dev
https://golang.org/cl/6555052

src/pkg/crypto/x509/pem_decrypt.go [new file with mode: 0644]
src/pkg/crypto/x509/pem_decrypt_test.go [new file with mode: 0644]
src/pkg/go/build/deps_test.go

diff --git a/src/pkg/crypto/x509/pem_decrypt.go b/src/pkg/crypto/x509/pem_decrypt.go
new file mode 100644 (file)
index 0000000..21f62e5
--- /dev/null
@@ -0,0 +1,133 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x509
+
+// RFC 1423 describes the encryption of PEM blocks. The algorithm used to
+// generate a key from the password was derived by looking at the OpenSSL
+// implementation.
+
+import (
+       "crypto/aes"
+       "crypto/cipher"
+       "crypto/des"
+       "crypto/md5"
+       "encoding/hex"
+       "encoding/pem"
+       "errors"
+       "strings"
+)
+
+// rfc1423Algos represents how to create a block cipher for a decryption mode.
+type rfc1423Algo struct {
+       cipherFunc func([]byte) (cipher.Block, error)
+       keySize    int
+}
+
+// deriveKey uses a key derivation function to stretch the password into a key
+// with the number of bits our cipher requires. This algorithm was derived from
+// the OpenSSL source.
+func (c rfc1423Algo) deriveKey(password, salt []byte) []byte {
+       hash := md5.New()
+       out := make([]byte, c.keySize)
+       var digest []byte
+
+       for i := 0; i < len(out); i += len(digest) {
+               hash.Reset()
+               hash.Write(digest)
+               hash.Write(password)
+               hash.Write(salt)
+               digest = hash.Sum(digest[:0])
+               copy(out[i:], digest)
+       }
+
+       return out
+}
+
+// rfc1423Algos is a mapping of encryption algorithm to an rfc1423Algo that can
+// create block ciphers for that mode.
+var rfc1423Algos = map[string]rfc1423Algo{
+       "DES-CBC":      {des.NewCipher, 8},
+       "DES-EDE3-CBC": {des.NewTripleDESCipher, 24},
+       "AES-128-CBC":  {aes.NewCipher, 16},
+       "AES-192-CBC":  {aes.NewCipher, 24},
+       "AES-256-CBC":  {aes.NewCipher, 32},
+}
+
+// IsEncryptedPEMBlock returns if the PEM block is password encrypted.
+func IsEncryptedPEMBlock(b *pem.Block) bool {
+       _, ok := b.Headers["DEK-Info"]
+       return ok
+}
+
+// IncorrectPasswordError is returned when an incorrect password is detected.
+var IncorrectPasswordError = errors.New("x509: decryption password incorrect")
+
+// DecryptPEMBlock takes a password encrypted PEM block and the password used to
+// encrypt it and returns a slice of decrypted DER encoded bytes. It inspects
+// the DEK-Info header to determine the algorithm used for decryption. If no
+// DEK-Info header is present, an error is returned. If an incorrect password
+// is detected an IncorrectPasswordError is returned.
+func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
+       dek, ok := b.Headers["DEK-Info"]
+       if !ok {
+               return nil, errors.New("x509: no DEK-Info header in block")
+       }
+
+       idx := strings.Index(dek, ",")
+       if idx == -1 {
+               return nil, errors.New("x509: malformed DEK-Info header")
+       }
+
+       mode, hexIV := dek[:idx], dek[idx+1:]
+       iv, err := hex.DecodeString(hexIV)
+       if err != nil {
+               return nil, err
+       }
+       if len(iv) < 8 {
+               return nil, errors.New("x509: not enough bytes in IV")
+       }
+
+       ciph, ok := rfc1423Algos[mode]
+       if !ok {
+               return nil, errors.New("x509: unknown encryption mode")
+       }
+
+       // Based on the OpenSSL implementation. The salt is the first 8 bytes
+       // of the initialization vector.
+       key := ciph.deriveKey(password, iv[:8])
+       block, err := ciph.cipherFunc(key)
+       if err != nil {
+               return nil, err
+       }
+
+       data := make([]byte, len(b.Bytes))
+       dec := cipher.NewCBCDecrypter(block, iv)
+       dec.CryptBlocks(data, b.Bytes)
+
+       // Blocks are padded using a scheme where the last n bytes of padding are all
+       // equal to n. It can pad from 1 to 8 bytes inclusive. See RFC 1423.
+       // For example:
+       //      [x y z 2 2]
+       //      [x y 7 7 7 7 7 7 7]
+       // If we detect a bad padding, we assume it is an invalid password.
+       dlen := len(data)
+       if dlen == 0 {
+               return nil, errors.New("x509: invalid padding")
+       }
+       last := data[dlen-1]
+       if dlen < int(last) {
+               return nil, IncorrectPasswordError
+       }
+       if last == 0 || last > 8 {
+               return nil, IncorrectPasswordError
+       }
+       for _, val := range data[dlen-int(last):] {
+               if val != last {
+                       return nil, IncorrectPasswordError
+               }
+       }
+
+       return data[:dlen-int(last)], nil
+}
diff --git a/src/pkg/crypto/x509/pem_decrypt_test.go b/src/pkg/crypto/x509/pem_decrypt_test.go
new file mode 100644 (file)
index 0000000..2cb9983
--- /dev/null
@@ -0,0 +1,119 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x509
+
+import (
+       "encoding/pem"
+       "testing"
+)
+
+func TestDecrypt(t *testing.T) {
+       for _, data := range testData {
+               block, rest := pem.Decode(data.pemData)
+               if len(rest) > 0 {
+                       t.Error(data.kind, "extra data")
+               }
+               der, err := DecryptPEMBlock(block, data.password)
+               if err != nil {
+                       t.Error(data.kind, err)
+                       continue
+               }
+               if _, err := ParsePKCS1PrivateKey(der); err != nil {
+                       t.Error(data.kind, "Invalid private key")
+               }
+       }
+}
+
+var testData = []struct {
+       kind     string
+       password []byte
+       pemData  []byte
+}{
+       {
+               kind:     "DES-CBC",
+               password: []byte("asdf"),
+               pemData: []byte(`
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-CBC,34F09A4FC8DE22B5
+
+WXxy8kbZdiZvANtKvhmPBLV7eVFj2A5z6oAxvI9KGyhG0ZK0skfnt00C24vfU7m5
+ICXeoqP67lzJ18xCzQfHjDaBNs53DSDT+Iz4e8QUep1xQ30+8QKX2NA2coee3nwc
+6oM1cuvhNUDemBH2i3dKgMVkfaga0zQiiOq6HJyGSncCMSruQ7F9iWEfRbFcxFCx
+qtHb1kirfGKEtgWTF+ynyco6+2gMXNu70L7nJcnxnV/RLFkHt7AUU1yrclxz7eZz
+XOH9VfTjb52q/I8Suozq9coVQwg4tXfIoYUdT//O+mB7zJb9HI9Ps77b9TxDE6Gm
+4C9brwZ3zg2vqXcwwV6QRZMtyll9rOpxkbw6NPlpfBqkc3xS51bbxivbO/Nve4KD
+r12ymjFNF4stXCfJnNqKoZ50BHmEEUDu5Wb0fpVn82XrGw7CYc4iug==
+-----END RSA PRIVATE KEY-----`),
+       },
+       {
+               kind:     "DES-EDE3-CBC",
+               password: []byte("asdf"),
+               pemData: []byte(`
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,C1F4A6A03682C2C7
+
+0JqVdBEH6iqM7drTkj+e2W/bE3LqakaiWhb9WUVonFkhyu8ca/QzebY3b5gCvAZQ
+YwBvDcT/GHospKqPx+cxDHJNsUASDZws6bz8ZXWJGwZGExKzr0+Qx5fgXn44Ms3x
+8g1ENFuTXtxo+KoNK0zuAMAqp66Llcds3Fjl4XR18QaD0CrVNAfOdgATWZm5GJxk
+Fgx5f84nT+/ovvreG+xeOzWgvtKo0UUZVrhGOgfKLpa57adumcJ6SkUuBtEFpZFB
+ldw5w7WC7d13x2LsRkwo8ZrDKgIV+Y9GNvhuCCkTzNP0V3gNeJpd201HZHR+9n3w
+3z0VjR/MGqsfcy1ziEWMNOO53At3zlG6zP05aHMnMcZoVXadEK6L1gz++inSSDCq
+gI0UJP4e3JVB7AkgYymYAwiYALAkoEIuanxoc50njJk=
+-----END RSA PRIVATE KEY-----`),
+       },
+       {
+               kind:     "AES-128-CBC",
+               password: []byte("asdf"),
+               pemData: []byte(`
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,D4492E793FC835CC038A728ED174F78A
+
+EyfQSzXSjv6BaNH+NHdXRlkHdimpF9izWlugVJAPApgXrq5YldPe2aGIOFXyJ+QE
+ZIG20DYqaPzJRjTEbPNZ6Es0S2JJ5yCpKxwJuDkgJZKtF39Q2i36JeGbSZQIuWJE
+GZbBpf1jDH/pr0iGonuAdl2PCCZUiy+8eLsD2tyviHUkFLOB+ykYoJ5t8ngZ/B6D
+33U43LLb7+9zD4y3Q9OVHqBFGyHcxCY9+9Qh4ZnFp7DTf6RY5TNEvE3s4g6aDpBs
+3NbvRVvYTgs8K9EPk4K+5R+P2kD8J8KvEIGxVa1vz8QoCJ/jr7Ka2rvNgPCex5/E
+080LzLHPCrXKdlr/f50yhNWq08ZxMWQFkui+FDHPDUaEELKAXV8/5PDxw80Rtybo
+AVYoCVIbZXZCuCO81op8UcOgEpTtyU5Lgh3Mw5scQL0=
+-----END RSA PRIVATE KEY-----`),
+       },
+       {
+               kind:     "AES-192-CBC",
+               password: []byte("asdf"),
+               pemData: []byte(`
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-192-CBC,E2C9FB02BCA23ADE1829F8D8BC5F5369
+
+cqVslvHqDDM6qwU6YjezCRifXmKsrgEev7ng6Qs7UmDJOpHDgJQZI9fwMFUhIyn5
+FbCu1SHkLMW52Ld3CuEqMnzWMlhPrW8tFvUOrMWPYSisv7nNq88HobZEJcUNL2MM
+Y15XmHW6IJwPqhKyLHpWXyOCVEh4ODND2nV15PCoi18oTa475baxSk7+1qH7GuIs
+Rb7tshNTMqHbCpyo9Rn3UxeFIf9efdl8YLiMoIqc7J8E5e9VlbeQSdLMQOgDAQJG
+ReUtTw8exmKsY4gsSjhkg5uiw7/ZB1Ihto0qnfQJgjGc680qGkT1d6JfvOfeYAk6
+xn5RqS/h8rYAYm64KnepfC9vIujo4NqpaREDmaLdX5MJPQ+SlytITQvgUsUq3q/t
+Ss85xjQEZH3hzwjQqdJvmA4hYP6SUjxYpBM+02xZ1Xw=
+-----END RSA PRIVATE KEY-----`),
+       },
+       {
+               kind:     "AES-256-CBC",
+               password: []byte("asdf"),
+               pemData: []byte(`
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,8E7ED5CD731902CE938957A886A5FFBD
+
+4Mxr+KIzRVwoOP0wwq6caSkvW0iS+GE2h2Ov/u+n9ZTMwL83PRnmjfjzBgfRZLVf
+JFPXxUK26kMNpIdssNnqGOds+DhB+oSrsNKoxgxSl5OBoYv9eJTVYm7qOyAFIsjr
+DRKAcjYCmzfesr7PVTowwy0RtHmYwyXMGDlAzzZrEvaiySFFmMyKKvtoavwaFoc7
+Pz3RZScwIuubzTGJ1x8EzdffYOsdCa9Mtgpp3L136+23dOd6L/qK2EG2fzrJSHs/
+2XugkleBFSMKzEp9mxXKRfa++uidQvMZTFLDK9w5YjrRvMBo/l2BoZIsq0jAIE1N
+sv5Z/KwlX+3MDEpPQpUwGPlGGdLnjI3UZ+cjgqBcoMiNc6HfgbBgYJSU6aDSHuCk
+clCwByxWkBNgJ2GrkwNrF26v+bGJJJNR4SKouY1jQf0=
+-----END RSA PRIVATE KEY-----`),
+       },
+}
index 0505a4304e848703c68d37337f9da00de63fc534..e1f4f8c63e4bdd470669f64e67a72494db37df34 100644 (file)
@@ -300,7 +300,10 @@ var pkgDeps = map[string][]string{
                "L4", "CRYPTO-MATH", "CGO", "OS",
                "crypto/x509", "encoding/pem", "net", "syscall",
        },
-       "crypto/x509":      {"L4", "CRYPTO-MATH", "OS", "CGO", "crypto/x509/pkix", "encoding/pem", "syscall"},
+       "crypto/x509": {
+               "L4", "CRYPTO-MATH", "OS", "CGO",
+               "crypto/x509/pkix", "encoding/pem", "encoding/hex", "syscall",
+       },
        "crypto/x509/pkix": {"L4", "CRYPTO-MATH"},
 
        // Simple net+crypto-aware packages.