]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: implement EncryptPEMBlock
authorRoger Peppe <rogpeppe@gmail.com>
Mon, 12 Nov 2012 15:31:23 +0000 (15:31 +0000)
committerRoger Peppe <rogpeppe@gmail.com>
Mon, 12 Nov 2012 15:31:23 +0000 (15:31 +0000)
Arbitrary decisions: order of the arguments and the
fact it takes a block-type argument (rather than
leaving to user to fill it in later); I'm happy whatever
colour we want to paint it.

We also change DecryptPEMBlock so that it won't
panic when the IV has the wrong size.

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

src/pkg/crypto/x509/pem_decrypt.go
src/pkg/crypto/x509/pem_decrypt_test.go

index 0dddd7ff9a8d2334024f3001c699fadb77615a96..194c81bf688c8eb5be8ab03bb4878bf15d049f5d 100644 (file)
@@ -16,23 +16,64 @@ import (
        "encoding/hex"
        "encoding/pem"
        "errors"
+       "io"
        "strings"
 )
 
-// rfc1423Algos represents how to create a block cipher for a decryption mode.
+type PEMCipher int
+
+// Possible values for the EncryptPEMBlock encryption algorithm.
+const (
+       _ PEMCipher = iota
+       PEMCipherDES
+       PEMCipher3DES
+       PEMCipherAES128
+       PEMCipherAES192
+       PEMCipherAES256
+)
+
+// rfc1423Algo holds a method for enciphering a PEM block.
 type rfc1423Algo struct {
-       cipherFunc func([]byte) (cipher.Block, error)
+       cipher     PEMCipher
+       name       string
+       cipherFunc func(key []byte) (cipher.Block, error)
        keySize    int
+       blockSize  int
 }
 
-// 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},
+// rfc1423Algos holds a slice of the possible ways to encrypt a PEM
+// block.  The ivSize numbers were taken from the OpenSSL source.
+var rfc1423Algos = []rfc1423Algo{{
+       cipher:     PEMCipherDES,
+       name:       "DES-CBC",
+       cipherFunc: des.NewCipher,
+       keySize:    8,
+       blockSize:  des.BlockSize,
+}, {
+       cipher:     PEMCipher3DES,
+       name:       "DES-EDE3-CBC",
+       cipherFunc: des.NewTripleDESCipher,
+       keySize:    24,
+       blockSize:  des.BlockSize,
+}, {
+       cipher:     PEMCipherAES128,
+       name:       "AES-128-CBC",
+       cipherFunc: aes.NewCipher,
+       keySize:    16,
+       blockSize:  aes.BlockSize,
+}, {
+       cipher:     PEMCipherAES192,
+       name:       "AES-192-CBC",
+       cipherFunc: aes.NewCipher,
+       keySize:    24,
+       blockSize:  aes.BlockSize,
+}, {
+       cipher:     PEMCipherAES256,
+       name:       "AES-256-CBC",
+       cipherFunc: aes.NewCipher,
+       keySize:    32,
+       blockSize:  aes.BlockSize,
+},
 }
 
 // deriveKey uses a key derivation function to stretch the password into a key
@@ -51,7 +92,6 @@ func (c rfc1423Algo) deriveKey(password, salt []byte) []byte {
                digest = hash.Sum(digest[:0])
                copy(out[i:], digest)
        }
-
        return out
 }
 
@@ -81,16 +121,16 @@ func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
        }
 
        mode, hexIV := dek[:idx], dek[idx+1:]
-       ciph, ok := rfc1423Algos[mode]
-       if !ok {
+       ciph := cipherByName(mode)
+       if ciph == nil {
                return nil, errors.New("x509: unknown encryption mode")
        }
        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")
+       if len(iv) != ciph.blockSize {
+               return nil, errors.New("x509: incorrect IV size")
        }
 
        // Based on the OpenSSL implementation. The salt is the first 8 bytes
@@ -112,15 +152,14 @@ func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
        //      [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)
-       blockSize := block.BlockSize()
-       if dlen == 0 || dlen%blockSize != 0 {
+       if dlen == 0 || dlen%ciph.blockSize != 0 {
                return nil, errors.New("x509: invalid padding")
        }
        last := int(data[dlen-1])
        if dlen < last {
                return nil, IncorrectPasswordError
        }
-       if last == 0 || last > blockSize {
+       if last == 0 || last > ciph.blockSize {
                return nil, IncorrectPasswordError
        }
        for _, val := range data[dlen-last:] {
@@ -130,3 +169,65 @@ func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
        }
        return data[:dlen-last], nil
 }
+
+// EncryptPEMBlock returns a PEM block of the specified type holding the
+// given DER-encoded data encrypted with the specified algorithm and
+// password.
+func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
+       ciph := cipherByKey(alg)
+       if ciph == nil {
+               return nil, errors.New("x509: unknown encryption mode")
+       }
+       iv := make([]byte, ciph.blockSize)
+       if _, err := io.ReadFull(rand, iv); err != nil {
+               return nil, errors.New("x509: cannot generate IV: " + err.Error())
+       }
+       // The salt is the first 8 bytes of the initialization vector,
+       // matching the key derivation in DecryptPEMBlock.
+       key := ciph.deriveKey(password, iv[:8])
+       block, err := ciph.cipherFunc(key)
+       if err != nil {
+               return nil, err
+       }
+       enc := cipher.NewCBCEncrypter(block, iv)
+       pad := ciph.blockSize - len(data)%ciph.blockSize
+       encrypted := make([]byte, len(data), len(data)+pad)
+       // We could save this copy by encrypting all the whole blocks in
+       // the data separately, but it doesn't seem worth the additional
+       // code.
+       copy(encrypted, data)
+       // See RFC 1423, section 1.1
+       for i := 0; i < pad; i++ {
+               encrypted = append(encrypted, byte(pad))
+       }
+       enc.CryptBlocks(encrypted, encrypted)
+
+       return &pem.Block{
+               Type: blockType,
+               Headers: map[string]string{
+                       "Proc-Type": "4,ENCRYPTED",
+                       "DEK-Info":  ciph.name + "," + hex.EncodeToString(iv),
+               },
+               Bytes: encrypted,
+       }, nil
+}
+
+func cipherByName(name string) *rfc1423Algo {
+       for i := range rfc1423Algos {
+               alg := &rfc1423Algos[i]
+               if alg.name == name {
+                       return alg
+               }
+       }
+       return nil
+}
+
+func cipherByKey(key PEMCipher) *rfc1423Algo {
+       for i := range rfc1423Algos {
+               alg := &rfc1423Algos[i]
+               if alg.cipher == key {
+                       return alg
+               }
+       }
+       return nil
+}
index 0eb6d08f7930ac7caae84f84b7af5ae805f08862..59ba6f90019a2d58955b8416a91d82ae60bfe4dc 100644 (file)
@@ -5,34 +5,79 @@
 package x509
 
 import (
+       "bytes"
+       "crypto/rand"
+       "encoding/base64"
        "encoding/pem"
        "testing"
 )
 
 func TestDecrypt(t *testing.T) {
-       for _, data := range testData {
+       for i, data := range testData {
+               t.Logf("test %d. %s", i, data.kind)
                block, rest := pem.Decode(data.pemData)
                if len(rest) > 0 {
-                       t.Error(data.kind, "extra data")
+                       t.Error("extra data")
                }
                der, err := DecryptPEMBlock(block, data.password)
                if err != nil {
-                       t.Error(data.kind, err)
+                       t.Error("decrypt failed: ", err)
                        continue
                }
                if _, err := ParsePKCS1PrivateKey(der); err != nil {
-                       t.Error(data.kind, "Invalid private key")
+                       t.Error("invalid private key: ", err)
+               }
+               plainDER, err := base64.StdEncoding.DecodeString(data.plainDER)
+               if err != nil {
+                       t.Fatal("cannot decode test DER data: ", err)
+               }
+               if !bytes.Equal(der, plainDER) {
+                       t.Error("data mismatch")
+               }
+       }
+}
+
+func TestEncrypt(t *testing.T) {
+       for i, data := range testData {
+               t.Logf("test %d. %s", i, data.kind)
+               plainDER, err := base64.StdEncoding.DecodeString(data.plainDER)
+               if err != nil {
+                       t.Fatal("cannot decode test DER data: ", err)
+               }
+               password := []byte("kremvax1")
+               block, err := EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", plainDER, password, data.kind)
+               if err != nil {
+                       t.Error("encrypt: ", err)
+                       continue
+               }
+               if !IsEncryptedPEMBlock(block) {
+                       t.Error("PEM block does not appear to be encrypted")
+               }
+               if block.Type != "RSA PRIVATE KEY" {
+                       t.Errorf("unexpected block type; got %q want %q", block.Type, "RSA PRIVATE KEY")
+               }
+               if block.Headers["Proc-Type"] != "4,ENCRYPTED" {
+                       t.Errorf("block does not have correct Proc-Type header")
+               }
+               der, err := DecryptPEMBlock(block, password)
+               if err != nil {
+                       t.Error("decrypt: ", err)
+                       continue
+               }
+               if !bytes.Equal(der, plainDER) {
+                       t.Errorf("data mismatch")
                }
        }
 }
 
 var testData = []struct {
-       kind     string
+       kind     PEMCipher
        password []byte
        pemData  []byte
+       plainDER string
 }{
        {
-               kind:     "DES-CBC",
+               kind:     PEMCipherDES,
                password: []byte("asdf"),
                pemData: []byte(`
 -----BEGIN RSA PRIVATE KEY-----
@@ -47,9 +92,17 @@ XOH9VfTjb52q/I8Suozq9coVQwg4tXfIoYUdT//O+mB7zJb9HI9Ps77b9TxDE6Gm
 4C9brwZ3zg2vqXcwwV6QRZMtyll9rOpxkbw6NPlpfBqkc3xS51bbxivbO/Nve4KD
 r12ymjFNF4stXCfJnNqKoZ50BHmEEUDu5Wb0fpVn82XrGw7CYc4iug==
 -----END RSA PRIVATE KEY-----`),
+               plainDER: `
+MIIBPAIBAAJBAPASZe+tCPU6p80AjHhDkVsLYa51D35e/YGa8QcZyooeZM8EHozo
+KD0fNiKI+53bHdy07N+81VQ8/ejPcRoXPlsCAwEAAQJBAMTxIuSq27VpR+zZ7WJf
+c6fvv1OBvpMZ0/d1pxL/KnOAgq2rD5hDtk9b0LGhTPgQAmrrMTKuSeGoIuYE+gKQ
+QvkCIQD+GC1m+/do+QRurr0uo46Kx1LzLeSCrjBk34wiOp2+dwIhAPHfTLRXS2fv
+7rljm0bYa4+eDZpz+E8RcXEgzhhvcQQ9AiAI5eHZJGOyml3MXnQjiPi55WcDOw0w
+glcRgT6QCEtz2wIhANSyqaFtosIkHKqrDUGfz/bb5tqMYTAnBruVPaf/WEOBAiEA
+9xORWeRG1tRpso4+dYy4KdDkuLPIO01KY6neYGm3BCM=`,
        },
        {
-               kind:     "DES-EDE3-CBC",
+               kind:     PEMCipher3DES,
                password: []byte("asdf"),
                pemData: []byte(`
 -----BEGIN RSA PRIVATE KEY-----
@@ -64,9 +117,17 @@ ldw5w7WC7d13x2LsRkwo8ZrDKgIV+Y9GNvhuCCkTzNP0V3gNeJpd201HZHR+9n3w
 3z0VjR/MGqsfcy1ziEWMNOO53At3zlG6zP05aHMnMcZoVXadEK6L1gz++inSSDCq
 gI0UJP4e3JVB7AkgYymYAwiYALAkoEIuanxoc50njJk=
 -----END RSA PRIVATE KEY-----`),
+               plainDER: `
+MIIBOwIBAAJBANOCXKdoNS/iP/MAbl9cf1/SF3P+Ns7ZeNL27CfmDh0O6Zduaax5
+NBiumd2PmjkaCu7lQ5JOibHfWn+xJsc3kw0CAwEAAQJANX/W8d1Q/sCqzkuAn4xl
+B5a7qfJWaLHndu1QRLNTRJPn0Ee7OKJ4H0QKOhQM6vpjRrz+P2u9thn6wUxoPsef
+QQIhAP/jCkfejFcy4v15beqKzwz08/tslVjF+Yq41eJGejmxAiEA05pMoqfkyjcx
+fyvGhpoOyoCp71vSGUfR2I9CR65oKh0CIC1Msjs66LlfJtQctRq6bCEtFCxEcsP+
+eEjYo/Sk6WphAiEAxpgWPMJeU/shFT28gS+tmhjPZLpEoT1qkVlC14u0b3ECIQDX
+tZZZxCtPAm7shftEib0VU77Lk8MsXJcx2C4voRsjEw==`,
        },
        {
-               kind:     "AES-128-CBC",
+               kind:     PEMCipherAES128,
                password: []byte("asdf"),
                pemData: []byte(`
 -----BEGIN RSA PRIVATE KEY-----
@@ -81,9 +142,17 @@ GZbBpf1jDH/pr0iGonuAdl2PCCZUiy+8eLsD2tyviHUkFLOB+ykYoJ5t8ngZ/B6D
 080LzLHPCrXKdlr/f50yhNWq08ZxMWQFkui+FDHPDUaEELKAXV8/5PDxw80Rtybo
 AVYoCVIbZXZCuCO81op8UcOgEpTtyU5Lgh3Mw5scQL0=
 -----END RSA PRIVATE KEY-----`),
+               plainDER: `
+MIIBOgIBAAJBAMBlj5FxYtqbcy8wY89d/S7n0+r5MzD9F63BA/Lpl78vQKtdJ5dT
+cDGh/rBt1ufRrNp0WihcmZi7Mpl/3jHjiWECAwEAAQJABNOHYnKhtDIqFYj1OAJ3
+k3GlU0OlERmIOoeY/cL2V4lgwllPBEs7r134AY4wMmZSBUj8UR/O4SNO668ElKPE
+cQIhAOuqY7/115x5KCdGDMWi+jNaMxIvI4ETGwV40ykGzqlzAiEA0P9oEC3m9tHB
+kbpjSTxaNkrXxDgdEOZz8X0uOUUwHNsCIAwzcSCiGLyYJTULUmP1ESERfW1mlV78
+XzzESaJpIM/zAiBQkSTcl9VhcJreQqvjn5BnPZLP4ZHS4gPwJAGdsj5J4QIhAOVR
+B3WlRNTXR2WsJ5JdByezg9xzdXzULqmga0OE339a`,
        },
        {
-               kind:     "AES-192-CBC",
+               kind:     PEMCipherAES192,
                password: []byte("asdf"),
                pemData: []byte(`
 -----BEGIN RSA PRIVATE KEY-----
@@ -98,9 +167,17 @@ ReUtTw8exmKsY4gsSjhkg5uiw7/ZB1Ihto0qnfQJgjGc680qGkT1d6JfvOfeYAk6
 xn5RqS/h8rYAYm64KnepfC9vIujo4NqpaREDmaLdX5MJPQ+SlytITQvgUsUq3q/t
 Ss85xjQEZH3hzwjQqdJvmA4hYP6SUjxYpBM+02xZ1Xw=
 -----END RSA PRIVATE KEY-----`),
+               plainDER: `
+MIIBOwIBAAJBAMGcRrZiNNmtF20zyS6MQ7pdGx17aFDl+lTl+qnLuJRUCMUG05xs
+OmxmL/O1Qlf+bnqR8Bgg65SfKg21SYuLhiMCAwEAAQJBAL94uuHyO4wux2VC+qpj
+IzPykjdU7XRcDHbbvksf4xokSeUFjjD3PB0Qa83M94y89ZfdILIqS9x5EgSB4/lX
+qNkCIQD6cCIqLfzq/lYbZbQgAAjpBXeQVYsbvVtJrPrXJAlVVQIhAMXpDKMeFPMn
+J0g2rbx1gngx0qOa5r5iMU5w/noN4W2XAiBjf+WzCG5yFvazD+dOx3TC0A8+4x3P
+uZ3pWbaXf5PNuQIgAcdXarvhelH2w2piY1g3BPeFqhzBSCK/yLGxR82KIh8CIQDD
++qGKsd09NhQ/G27y/DARzOYtml1NvdmCQAgsDIIOLA==`,
        },
        {
-               kind:     "AES-256-CBC",
+               kind:     PEMCipherAES256,
                password: []byte("asdf"),
                pemData: []byte(`
 -----BEGIN RSA PRIVATE KEY-----
@@ -115,11 +192,19 @@ Pz3RZScwIuubzTGJ1x8EzdffYOsdCa9Mtgpp3L136+23dOd6L/qK2EG2fzrJSHs/
 sv5Z/KwlX+3MDEpPQpUwGPlGGdLnjI3UZ+cjgqBcoMiNc6HfgbBgYJSU6aDSHuCk
 clCwByxWkBNgJ2GrkwNrF26v+bGJJJNR4SKouY1jQf0=
 -----END RSA PRIVATE KEY-----`),
+               plainDER: `
+MIIBOgIBAAJBAKy3GFkstoCHIEeUU/qO8207m8WSrjksR+p9B4tf1w5k+2O1V/GY
+AQ5WFCApItcOkQe/I0yZZJk/PmCqMzSxrc8CAwEAAQJAOCAz0F7AW9oNelVQSP8F
+Sfzx7O1yom+qWyAQQJF/gFR11gpf9xpVnnyu1WxIRnDUh1LZwUsjwlDYb7MB74id
+oQIhANPcOiLwOPT4sIUpRM5HG6BF1BI7L77VpyGVk8xNP7X/AiEA0LMHZtk4I+lJ
+nClgYp4Yh2JZ1Znbu7IoQMCEJCjwKDECIGd8Dzm5tViTkUW6Hs3Tlf73nNs65duF
+aRnSglss8I3pAiEAonEnKruawgD8RavDFR+fUgmQiPz4FnGGeVgfwpGG1JECIBYq
+PXHYtPqxQIbD2pScR5qum7iGUh11lEUPkmt+2uqS`,
        },
        {
                // generated with:
                // openssl genrsa -aes128 -passout pass:asdf -out server.orig.key 128
-               kind:     "AES-128-CBC",
+               kind:     PEMCipherAES128,
                password: []byte("asdf"),
                pemData: []byte(`
 -----BEGIN RSA PRIVATE KEY-----
@@ -130,5 +215,9 @@ DEK-Info: AES-128-CBC,74611ABC2571AF11B1BF9B69E62C89E7
 eND9l7C9meCirWovjj9QWVHrXyugFuDIqgdhQ8iHTgCfF3lrmcttVrbIfMDw+smD
 hTP8O1mS/MHl92NE0nhv0w==
 -----END RSA PRIVATE KEY-----`),
+               plainDER: `
+MGMCAQACEQC6ssxmYuauuHGOCDAI54RdAgMBAAECEQCWIn6Yv2O+kBcDF7STctKB
+AgkA8SEfu/2i3g0CCQDGNlXbBHX7kQIIK3Ww5o0cYbECCQDCimPb0dYGsQIIeQ7A
+jryIst8=`,
        },
 }