1// Portions of this file are based on https://github.com/golang/crypto/blob/master/ssh/keys.go 2// 3// Copyright 2012 The Go Authors. All rights reserved. 4// Use of this source code is governed by a BSD-style 5// license that can be found in the LICENSE file. 6 7package sshkeys 8 9import ( 10 "bytes" 11 "crypto/aes" 12 "crypto/cipher" 13 "crypto/rsa" 14 "crypto/x509" 15 "encoding/pem" 16 "errors" 17 "fmt" 18 "math/big" 19 "strings" 20 21 "github.com/dchest/bcrypt_pbkdf" 22 "golang.org/x/crypto/ed25519" 23 "golang.org/x/crypto/ssh" 24) 25 26// ErrIncorrectPassword is returned when the supplied passphrase was not correct for an encrypted private key. 27var ErrIncorrectPassword = errors.New("sshkeys: Invalid Passphrase") 28 29const keySizeAES256 = 32 30 31// ParseEncryptedPrivateKey returns a Signer from an encrypted private key. It supports 32// the same keys as ParseEncryptedRawPrivateKey. 33func ParseEncryptedPrivateKey(data []byte, passphrase []byte) (ssh.Signer, error) { 34 key, err := ParseEncryptedRawPrivateKey(data, passphrase) 35 if err != nil { 36 return nil, err 37 } 38 39 return ssh.NewSignerFromKey(key) 40} 41 42// ParseEncryptedRawPrivateKey returns a private key from an encrypted private key. It 43// supports RSA (PKCS#1 or OpenSSH), DSA (OpenSSL), and ECDSA private keys. 44// 45// ErrIncorrectPassword will be returned if the supplied passphrase is wrong, 46// but some formats like RSA in PKCS#1 detecting a wrong passphrase is difficult, 47// and other parse errors may be returned. 48func ParseEncryptedRawPrivateKey(data []byte, passphrase []byte) (interface{}, error) { 49 var err error 50 51 block, _ := pem.Decode(data) 52 if block == nil { 53 return nil, errors.New("no PEM block found") 54 } 55 56 if x509.IsEncryptedPEMBlock(block) { 57 data, err = x509.DecryptPEMBlock(block, passphrase) 58 if err == x509.IncorrectPasswordError { 59 return nil, ErrIncorrectPassword 60 } 61 if err != nil { 62 return nil, err 63 } 64 } else { 65 data = block.Bytes 66 } 67 68 switch block.Type { 69 case "RSA PRIVATE KEY": 70 pk, err := x509.ParsePKCS1PrivateKey(data) 71 if err != nil { 72 // The Algos for PEM Encryption do not include strong message authentication, 73 // so sometimes DecryptPEMBlock works, but ParsePKCS1PrivateKey fails with an asn1 error. 74 // We are just catching the most common prefix here... 75 if strings.HasPrefix(err.Error(), "asn1: structure error") { 76 return nil, ErrIncorrectPassword 77 } 78 return nil, err 79 } 80 return pk, nil 81 case "EC PRIVATE KEY": 82 return x509.ParseECPrivateKey(data) 83 case "DSA PRIVATE KEY": 84 return ssh.ParseDSAPrivateKey(data) 85 case "OPENSSH PRIVATE KEY": 86 return parseOpenSSHPrivateKey(data, passphrase) 87 default: 88 return nil, fmt.Errorf("sshkeys: unsupported key type %q", block.Type) 89 } 90} 91 92func parseOpenSSHPrivateKey(data []byte, passphrase []byte) (interface{}, error) { 93 magic := append([]byte(opensshv1Magic), 0) 94 if !bytes.Equal(magic, data[0:len(magic)]) { 95 return nil, errors.New("sshkeys: invalid openssh private key format") 96 } 97 remaining := data[len(magic):] 98 99 w := opensshHeader{} 100 101 if err := ssh.Unmarshal(remaining, &w); err != nil { 102 return nil, err 103 } 104 105 if w.NumKeys != 1 { 106 return nil, fmt.Errorf("sshkeys: NumKeys must be 1: %d", w.NumKeys) 107 } 108 109 var privateKeyBytes []byte 110 var encrypted bool 111 112 switch { 113 // OpenSSH supports bcrypt KDF w/ AES256-CBC or AES256-CTR mode 114 case w.KdfName == "bcrypt" && w.CipherName == "aes256-cbc": 115 iv, block, err := extractBcryptIvBlock(passphrase, w) 116 if err != nil { 117 return nil, err 118 } 119 120 cbc := cipher.NewCBCDecrypter(block, iv) 121 privateKeyBytes = []byte(w.PrivKeyBlock) 122 cbc.CryptBlocks(privateKeyBytes, privateKeyBytes) 123 124 encrypted = true 125 126 case w.KdfName == "bcrypt" && w.CipherName == "aes256-ctr": 127 iv, block, err := extractBcryptIvBlock(passphrase, w) 128 if err != nil { 129 return nil, err 130 } 131 132 stream := cipher.NewCTR(block, iv) 133 privateKeyBytes = []byte(w.PrivKeyBlock) 134 stream.XORKeyStream(privateKeyBytes, privateKeyBytes) 135 136 encrypted = true 137 138 case w.KdfName == "none" && w.CipherName == "none": 139 privateKeyBytes = []byte(w.PrivKeyBlock) 140 141 default: 142 return nil, fmt.Errorf("sshkeys: unknown Cipher/KDF: %s:%s", w.CipherName, w.KdfName) 143 } 144 145 pk1 := opensshKey{} 146 147 if err := ssh.Unmarshal(privateKeyBytes, &pk1); err != nil { 148 if encrypted { 149 return nil, ErrIncorrectPassword 150 } 151 return nil, err 152 } 153 154 if pk1.Check1 != pk1.Check2 { 155 return nil, ErrIncorrectPassword 156 } 157 158 // we only handle ed25519 and rsa keys currently 159 switch pk1.Keytype { 160 case ssh.KeyAlgoRSA: 161 // https://github.com/openssh/openssh-portable/blob/V_7_4_P1/sshkey.c#L2760-L2773 162 key := opensshRsa{} 163 164 err := ssh.Unmarshal(pk1.Rest, &key) 165 if err != nil { 166 return nil, err 167 } 168 169 for i, b := range key.Pad { 170 if int(b) != i+1 { 171 return nil, errors.New("sshkeys: padding not as expected") 172 } 173 } 174 175 pk := &rsa.PrivateKey{ 176 PublicKey: rsa.PublicKey{ 177 N: key.N, 178 E: int(key.E.Int64()), 179 }, 180 D: key.D, 181 Primes: []*big.Int{key.P, key.Q}, 182 } 183 184 err = pk.Validate() 185 if err != nil { 186 return nil, err 187 } 188 189 pk.Precompute() 190 191 return pk, nil 192 case ssh.KeyAlgoED25519: 193 key := opensshED25519{} 194 195 err := ssh.Unmarshal(pk1.Rest, &key) 196 if err != nil { 197 return nil, err 198 } 199 200 if len(key.Priv) != ed25519.PrivateKeySize { 201 return nil, errors.New("sshkeys: private key unexpected length") 202 } 203 204 for i, b := range key.Pad { 205 if int(b) != i+1 { 206 return nil, errors.New("sshkeys: padding not as expected") 207 } 208 } 209 210 pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) 211 copy(pk, key.Priv) 212 return pk, nil 213 default: 214 return nil, errors.New("sshkeys: unhandled key type") 215 } 216} 217 218func extractBcryptIvBlock(passphrase []byte, w opensshHeader) ([]byte, cipher.Block, error) { 219 cipherKeylen := keySizeAES256 220 cipherIvLen := aes.BlockSize 221 222 var opts struct { 223 Salt []byte 224 Rounds uint32 225 } 226 227 if err := ssh.Unmarshal([]byte(w.KdfOpts), &opts); err != nil { 228 return nil, nil, err 229 } 230 kdfdata, err := bcrypt_pbkdf.Key(passphrase, opts.Salt, int(opts.Rounds), cipherKeylen+cipherIvLen) 231 if err != nil { 232 return nil, nil, err 233 } 234 235 iv := kdfdata[cipherKeylen : cipherIvLen+cipherKeylen] 236 aeskey := kdfdata[0:cipherKeylen] 237 block, err := aes.NewCipher(aeskey) 238 239 if err != nil { 240 return nil, nil, err 241 } 242 243 return iv, block, nil 244} 245