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