Skip to content

Commit 1da5a87

Browse files
authored
node-sshpk#27 PuTTY private key support (#76)
Reviewed by: Alex Wilson Reviewed by: Brian Bennett
1 parent 85f3c4e commit 1da5a87

15 files changed

Lines changed: 338 additions & 80 deletions

File tree

lib/formats/putty.js

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,30 @@ var assert = require('assert-plus');
99
var Buffer = require('safer-buffer').Buffer;
1010
var rfc4253 = require('./rfc4253');
1111
var Key = require('../key');
12+
var SSHBuffer = require('../ssh-buffer');
13+
var crypto = require('crypto');
14+
var PrivateKey = require('../private-key');
1215

1316
var errors = require('../errors');
1417

18+
// https://tartarus.org/~simon/putty-prerel-snapshots/htmldoc/AppendixC.html
1519
function read(buf, options) {
1620
var lines = buf.toString('ascii').split(/[\r\n]+/);
1721
var found = false;
1822
var parts;
1923
var si = 0;
24+
var formatVersion;
2025
while (si < lines.length) {
2126
parts = splitHeader(lines[si++]);
22-
if (parts &&
23-
parts[0].toLowerCase() === 'putty-user-key-file-2') {
24-
found = true;
25-
break;
27+
if (parts) {
28+
formatVersion = {
29+
'putty-user-key-file-2': 2,
30+
'putty-user-key-file-3': 3,
31+
}[parts[0].toLowerCase()]
32+
if (formatVersion) {
33+
found = true;
34+
break;
35+
}
2636
}
2737
}
2838
if (!found) {
@@ -32,6 +42,7 @@ function read(buf, options) {
3242

3343
parts = splitHeader(lines[si++]);
3444
assert.equal(parts[0].toLowerCase(), 'encryption');
45+
var encryption = parts[1];
3546

3647
parts = splitHeader(lines[si++]);
3748
assert.equal(parts[0].toLowerCase(), 'comment');
@@ -52,10 +63,93 @@ function read(buf, options) {
5263
if (key.type !== keyType) {
5364
throw (new Error('Outer key algorithm mismatch'));
5465
}
66+
67+
si += publicLines;
68+
if (lines[si]) {
69+
parts = splitHeader(lines[si++]);
70+
assert.equal(parts[0].toLowerCase(), 'private-lines');
71+
var privateLines = parseInt(parts[1], 10);
72+
if (!isFinite(privateLines) || privateLines < 0 ||
73+
privateLines > lines.length) {
74+
throw (new Error('Invalid private-lines count'));
75+
}
76+
77+
var privateBuf = Buffer.from(
78+
lines.slice(si, si + privateLines).join(''), 'base64');
79+
80+
if (encryption !== 'none' && formatVersion === 3) {
81+
throw new Error('Encrypted keys arenot supported for PuTTY format version 3');
82+
}
83+
84+
if (encryption === 'aes256-cbc') {
85+
if (!options.passphrase) {
86+
throw (new errors.KeyEncryptedError(
87+
options.filename, 'PEM'));
88+
}
89+
90+
var iv = Buffer.alloc(16, 0);
91+
var decipher = crypto.createDecipheriv(
92+
'aes-256-cbc',
93+
derivePPK2EncryptionKey(options.passphrase),
94+
iv);
95+
decipher.setAutoPadding(false);
96+
privateBuf = Buffer.concat([
97+
decipher.update(privateBuf), decipher.final()]);
98+
}
99+
100+
key = new PrivateKey(key);
101+
if (key.type !== keyType) {
102+
throw (new Error('Outer key algorithm mismatch'));
103+
}
104+
105+
var sshbuf = new SSHBuffer({buffer: privateBuf});
106+
var privateKeyParts;
107+
if (alg === 'ssh-dss') {
108+
privateKeyParts = [ {
109+
name: 'x',
110+
data: sshbuf.readBuffer()
111+
}];
112+
} else if (alg === 'ssh-rsa') {
113+
privateKeyParts = [
114+
{ name: 'd', data: sshbuf.readBuffer() },
115+
{ name: 'p', data: sshbuf.readBuffer() },
116+
{ name: 'q', data: sshbuf.readBuffer() },
117+
{ name: 'iqmp', data: sshbuf.readBuffer() }
118+
];
119+
} else if (alg.startsWith('ecdsa-sha2-nistp')) {
120+
privateKeyParts = [ {
121+
name: 'd', data: sshbuf.readBuffer()
122+
} ];
123+
} else if (alg === 'ssh-ed25519') {
124+
privateKeyParts = [ {
125+
name: 'k', data: sshbuf.readBuffer()
126+
} ];
127+
} else {
128+
throw new Error('Unsupported PPK key type: ' + alg);
129+
}
130+
131+
key = new PrivateKey({
132+
type: key.type,
133+
parts: key.parts.concat(privateKeyParts)
134+
});
135+
}
136+
55137
key.comment = comment;
56138
return (key);
57139
}
58140

141+
function derivePPK2EncryptionKey(passphrase) {
142+
var hash1 = crypto.createHash('sha1').update(Buffer.concat([
143+
Buffer.from([0, 0, 0, 0]),
144+
Buffer.from(passphrase)
145+
])).digest();
146+
var hash2 = crypto.createHash('sha1').update(Buffer.concat([
147+
Buffer.from([0, 0, 0, 1]),
148+
Buffer.from(passphrase)
149+
])).digest();
150+
return (Buffer.concat([hash1, hash2]).slice(0, 32));
151+
}
152+
59153
function splitHeader(line) {
60154
var idx = line.indexOf(':');
61155
if (idx === -1)

lib/private-key.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ formats['ssh-private'] = require('./formats/ssh-private');
3333
formats['openssh'] = formats['ssh-private'];
3434
formats['ssh'] = formats['ssh-private'];
3535
formats['dnssec'] = require('./formats/dnssec');
36+
formats['putty'] = require('./formats/putty');
3637

3738
function PrivateKey(opts) {
3839
assert.object(opts, 'options');

test/assets/dsa-ppk

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN DSA PRIVATE KEY-----
2+
MIIDPQIBAAKCAQEAm6ofqHnQBzlOXYK2o0o9eZi1YtmHksmdRefKAYpsIvO/OwMP
3+
6fLva+9TkUMdgu+B2n0w2vdbh1Qo1QtwRAiq9CKwY1iuK2CfciytvuYsKfLVTsgw
4+
sy4eXb8d4BSrBStvRVkAKjFVqIcXKBBejr30vVDFK0rN3bIZaTilXAdE66wuW19u
5+
mrFT9tiXKkTxPy2rnOrHE6qcgzD6EbfnWBqXOE3YSBzgOezoPExqkQX35cfVJdVI
6+
zX1iS/iJv7VJnr+4kf0zlcT80JGmjEAv17O/tSbcX88xV7R6t4hBsEuqAYhEWKob
7+
YtlJk66bS6FRpahwSNAcdx9flNCfxkygkk/GywIVAOTwYoGm15HYZFDWNnPwJCWv
8+
i92FAoIBAGyuCYSkZY693yb/2/hMdfrwsX/bNeqBz1Eu+uUlwPEjt/hgA5evvLQA
9+
D+EkRbeUI/k7bmzl+VDOzWQ7z9CbbAbeKz/rFg/kkxXhhyatsRJuGUGYdiQIaaKI
10+
51fk71crGzUVfgaQqpN5bE++ub0lJccKFhoNFjRBqesRXyfklTvYTx1BxCrhVf3r
11+
d5px4mDlM2v6UeCSLb6IEBscJgGzbRuuIeRoKYbSAxc2VnLHZMrvRUMVMu13sQPv
12+
gfWpPWR3spsv+bPqXAxtkd7fpxuRll0wJZLSao8oJgdy1XG8X3q+l6grQv2hqoGk
13+
XJUxPpvnkm30gCYMaqCd17UUrjRBjRkCggEAeCoNAd5nqIRV9sTXtxfcFb2o6VVT
14+
rCXt/MQ3CnK5SxDa/gEOhU8y6IwyE9Y7CN6i8ta/Qt4KZcanXS5ciOT+bGT5Y2qx
15+
HKgx8CcWv/J/GKROMxaEwaLvwT8qvI6Jv/x+3/+KwufaHA1w5ObOHL+UCaHzSuLK
16+
pPRWAbWc2GOZtyfMasmwA5tKzdRJ9VTjS4lVKKOI32AV70hhk7wHIBeRZgrxUw+N
17+
ZnlH5HWKZf2mWemPHhxsglKIczosCmg10vEVuFvIVzeSieV7y6/X/HIsAsjE6IMm
18+
RZDiU9h5rb9FJLrdgGgn6P8XIjJ7e3+P21EL0mi63sRQFQHfLEwA8F3UgQIUJ2Qi
19+
WkIsuhpeOTgyMLX2FWXPHZk=
20+
-----END DSA PRIVATE KEY-----

test/assets/dsa-ppk.pub

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/assets/dsa-pub.ppk

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
PuTTY-User-Key-File-2: ssh-dss
22
Encryption: none
3-
Comment: dsa-key-20170331
3+
Comment: dsa-key-20210515
44
Public-Lines: 18
5-
AAAAB3NzaC1kc3MAAAEBALPLp37KKWyxxCtV085ozT7tY1zQimFhPvD+rPO4SSfH
6-
quoRM/szwOXAqyavqYggajNyzSN/x5+gLu4p2iTmS0jC0Nf2FB5tyqxBKsrubmhV
7-
MZpK+dBLm2V7NTE6fJpGRmpTNCKWPOfdZP7wF0w8oRNaTz1ILjNlXh06zXrp4FU+
8-
wv0ums1PemF5Ff7u77vzkxXDsYFOO+btgAFEO8oTBapG1H0ZkcvTzIStCY6SnbzD
9-
ggPtaqasKLjh4zocvdR9M4E1IIFtMaFSZx19w6ccpzAFD7dqTqNVavSN2SzSzcjM
10-
eEdaLiSceof8qu5IcLdrofuQ0ltCVKNFrMm9pZK2kvUAAAAVANmWKeGafd0C0vAg
11-
GthSALs1q/q1AAABAQCtHTj9zydZ2VDhHxrge+WeSF/Ix0zUknwDo7luJGgSTb/B
12-
Zg42W+RIsWo+qFeK6XEzuaqlXIOL7ekBFkDvwOGt1l13T6YF4xH1XvjKg4+YP/U+
13-
QMqgiv/+eBodmm14vZqDUD37C1JXMr/vJ+6xDYlvJwwUdmBUcU0PMyw70YHEtWeG
14-
uzXbvMDsKLztXQrYJqzjKX7pyf0x2Rj3g7/HSdIssP32Da2lflNs0VPqCWjsG1bD
15-
iU8oqdOHhekfM+khqvVNIxmI/6nW/Izax/8ORv0QSmPxFp0FmTaRtNFInLDnOnsh
16-
XHf5h9+eC4vLmDs2P/65FLfS5WfQMABPTdifsLG6AAABAQCeT7XwWZ5+OMoGYgh2
17-
WeTAmHCykXnhnuH0LvyRoeuJq+oX3QJyU/I5L71mjQ1eCqMa/7RYZ3J2ct34ZQ7q
18-
iJzgbUgPvKuFuRgf12gn7cCc97Mu/Fo/Mjm0iisYigAR/QbT5uIpIASr5z8JxdNa
19-
EgEzhwP3o3+YviXXwEAbN36ZBwLp54MODbz+n5xGqG9avE5i4VCOkP5Q3ng6nnn+
20-
kelyaIHsWd498YJEQcs0eKaYlux03/D0hD/q6HLVtob6qjUN7xo0xFcYKQBzkBz0
21-
CgQ/CvgfKFOrS8Z/EdS6d6dFB8yKvEXygRPdE9MSepPlfy1d9Vvys8k2fs+Ny72w
22-
gg5I
5+
AAAAB3NzaC1kc3MAAAEBAJuqH6h50Ac5Tl2CtqNKPXmYtWLZh5LJnUXnygGKbCLz
6+
vzsDD+ny72vvU5FDHYLvgdp9MNr3W4dUKNULcEQIqvQisGNYritgn3Isrb7mLCny
7+
1U7IMLMuHl2/HeAUqwUrb0VZACoxVaiHFygQXo699L1QxStKzd2yGWk4pVwHROus
8+
LltfbpqxU/bYlypE8T8tq5zqxxOqnIMw+hG351galzhN2Egc4Dns6DxMapEF9+XH
9+
1SXVSM19Ykv4ib+1SZ6/uJH9M5XE/NCRpoxAL9ezv7Um3F/PMVe0ereIQbBLqgGI
10+
RFiqG2LZSZOum0uhUaWocEjQHHcfX5TQn8ZMoJJPxssAAAAVAOTwYoGm15HYZFDW
11+
NnPwJCWvi92FAAABAGyuCYSkZY693yb/2/hMdfrwsX/bNeqBz1Eu+uUlwPEjt/hg
12+
A5evvLQAD+EkRbeUI/k7bmzl+VDOzWQ7z9CbbAbeKz/rFg/kkxXhhyatsRJuGUGY
13+
diQIaaKI51fk71crGzUVfgaQqpN5bE++ub0lJccKFhoNFjRBqesRXyfklTvYTx1B
14+
xCrhVf3rd5px4mDlM2v6UeCSLb6IEBscJgGzbRuuIeRoKYbSAxc2VnLHZMrvRUMV
15+
Mu13sQPvgfWpPWR3spsv+bPqXAxtkd7fpxuRll0wJZLSao8oJgdy1XG8X3q+l6gr
16+
Qv2hqoGkXJUxPpvnkm30gCYMaqCd17UUrjRBjRkAAAEAeCoNAd5nqIRV9sTXtxfc
17+
Fb2o6VVTrCXt/MQ3CnK5SxDa/gEOhU8y6IwyE9Y7CN6i8ta/Qt4KZcanXS5ciOT+
18+
bGT5Y2qxHKgx8CcWv/J/GKROMxaEwaLvwT8qvI6Jv/x+3/+KwufaHA1w5ObOHL+U
19+
CaHzSuLKpPRWAbWc2GOZtyfMasmwA5tKzdRJ9VTjS4lVKKOI32AV70hhk7wHIBeR
20+
ZgrxUw+NZnlH5HWKZf2mWemPHhxsglKIczosCmg10vEVuFvIVzeSieV7y6/X/HIs
21+
AsjE6IMmRZDiU9h5rb9FJLrdgGgn6P8XIjJ7e3+P21EL0mi63sRQFQHfLEwA8F3U
22+
gQ==

test/assets/dsa.ppk

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
PuTTY-User-Key-File-2: ssh-dss
22
Encryption: aes256-cbc
3-
Comment: dsa-key-20170331
3+
Comment: dsa-key-20210515
44
Public-Lines: 18
5-
AAAAB3NzaC1kc3MAAAEBALPLp37KKWyxxCtV085ozT7tY1zQimFhPvD+rPO4SSfH
6-
quoRM/szwOXAqyavqYggajNyzSN/x5+gLu4p2iTmS0jC0Nf2FB5tyqxBKsrubmhV
7-
MZpK+dBLm2V7NTE6fJpGRmpTNCKWPOfdZP7wF0w8oRNaTz1ILjNlXh06zXrp4FU+
8-
wv0ums1PemF5Ff7u77vzkxXDsYFOO+btgAFEO8oTBapG1H0ZkcvTzIStCY6SnbzD
9-
ggPtaqasKLjh4zocvdR9M4E1IIFtMaFSZx19w6ccpzAFD7dqTqNVavSN2SzSzcjM
10-
eEdaLiSceof8qu5IcLdrofuQ0ltCVKNFrMm9pZK2kvUAAAAVANmWKeGafd0C0vAg
11-
GthSALs1q/q1AAABAQCtHTj9zydZ2VDhHxrge+WeSF/Ix0zUknwDo7luJGgSTb/B
12-
Zg42W+RIsWo+qFeK6XEzuaqlXIOL7ekBFkDvwOGt1l13T6YF4xH1XvjKg4+YP/U+
13-
QMqgiv/+eBodmm14vZqDUD37C1JXMr/vJ+6xDYlvJwwUdmBUcU0PMyw70YHEtWeG
14-
uzXbvMDsKLztXQrYJqzjKX7pyf0x2Rj3g7/HSdIssP32Da2lflNs0VPqCWjsG1bD
15-
iU8oqdOHhekfM+khqvVNIxmI/6nW/Izax/8ORv0QSmPxFp0FmTaRtNFInLDnOnsh
16-
XHf5h9+eC4vLmDs2P/65FLfS5WfQMABPTdifsLG6AAABAQCeT7XwWZ5+OMoGYgh2
17-
WeTAmHCykXnhnuH0LvyRoeuJq+oX3QJyU/I5L71mjQ1eCqMa/7RYZ3J2ct34ZQ7q
18-
iJzgbUgPvKuFuRgf12gn7cCc97Mu/Fo/Mjm0iisYigAR/QbT5uIpIASr5z8JxdNa
19-
EgEzhwP3o3+YviXXwEAbN36ZBwLp54MODbz+n5xGqG9avE5i4VCOkP5Q3ng6nnn+
20-
kelyaIHsWd498YJEQcs0eKaYlux03/D0hD/q6HLVtob6qjUN7xo0xFcYKQBzkBz0
21-
CgQ/CvgfKFOrS8Z/EdS6d6dFB8yKvEXygRPdE9MSepPlfy1d9Vvys8k2fs+Ny72w
22-
gg5I
5+
AAAAB3NzaC1kc3MAAAEBAJuqH6h50Ac5Tl2CtqNKPXmYtWLZh5LJnUXnygGKbCLz
6+
vzsDD+ny72vvU5FDHYLvgdp9MNr3W4dUKNULcEQIqvQisGNYritgn3Isrb7mLCny
7+
1U7IMLMuHl2/HeAUqwUrb0VZACoxVaiHFygQXo699L1QxStKzd2yGWk4pVwHROus
8+
LltfbpqxU/bYlypE8T8tq5zqxxOqnIMw+hG351galzhN2Egc4Dns6DxMapEF9+XH
9+
1SXVSM19Ykv4ib+1SZ6/uJH9M5XE/NCRpoxAL9ezv7Um3F/PMVe0ereIQbBLqgGI
10+
RFiqG2LZSZOum0uhUaWocEjQHHcfX5TQn8ZMoJJPxssAAAAVAOTwYoGm15HYZFDW
11+
NnPwJCWvi92FAAABAGyuCYSkZY693yb/2/hMdfrwsX/bNeqBz1Eu+uUlwPEjt/hg
12+
A5evvLQAD+EkRbeUI/k7bmzl+VDOzWQ7z9CbbAbeKz/rFg/kkxXhhyatsRJuGUGY
13+
diQIaaKI51fk71crGzUVfgaQqpN5bE++ub0lJccKFhoNFjRBqesRXyfklTvYTx1B
14+
xCrhVf3rd5px4mDlM2v6UeCSLb6IEBscJgGzbRuuIeRoKYbSAxc2VnLHZMrvRUMV
15+
Mu13sQPvgfWpPWR3spsv+bPqXAxtkd7fpxuRll0wJZLSao8oJgdy1XG8X3q+l6gr
16+
Qv2hqoGkXJUxPpvnkm30gCYMaqCd17UUrjRBjRkAAAEAeCoNAd5nqIRV9sTXtxfc
17+
Fb2o6VVTrCXt/MQ3CnK5SxDa/gEOhU8y6IwyE9Y7CN6i8ta/Qt4KZcanXS5ciOT+
18+
bGT5Y2qxHKgx8CcWv/J/GKROMxaEwaLvwT8qvI6Jv/x+3/+KwufaHA1w5ObOHL+U
19+
CaHzSuLKpPRWAbWc2GOZtyfMasmwA5tKzdRJ9VTjS4lVKKOI32AV70hhk7wHIBeR
20+
ZgrxUw+NZnlH5HWKZf2mWemPHhxsglKIczosCmg10vEVuFvIVzeSieV7y6/X/HIs
21+
AsjE6IMmRZDiU9h5rb9FJLrdgGgn6P8XIjJ7e3+P21EL0mi63sRQFQHfLEwA8F3U
22+
gQ==
2323
Private-Lines: 1
24-
KSl/bN/cgkBjVRhd91zBbTlSPNqRwQJ5xDF4qrehUtQ=
25-
Private-MAC: 3ab460bd517d8f4747f837a37710f88ad95be6ea
24+
ony1aHwujzT0bx1MCaH55NpCy6K/L+/rKAeb5CBF/D4=
25+
Private-MAC: 0e1508506fff981e5042d029429bfcb5765e80ba

test/assets/ecdsa-ppk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIH6319TQpT2OtWoyurNrC8t8RTMnKGkuF7Hg+iRAHaO5oAoGCCqGSM49
3+
AwEHoUQDQgAEKK8I4bJpFBon3SvTLM+ZMnXZIHgUHluna7SQP/XlrwGkNweyjUYg
4+
iMHJjYbrnkdH1BeQ4MaZNM8yFVmnhQoLAw==
5+
-----END EC PRIVATE KEY-----

test/assets/ecdsa.ppk

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
PuTTY-User-Key-File-2: ecdsa-sha2-nistp256
2+
Encryption: aes256-cbc
3+
Comment: ecdsa-key-20210515
4+
Public-Lines: 3
5+
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCivCOGyaRQa
6+
J90r0yzPmTJ12SB4FB5bp2u0kD/15a8BpDcHso1GIIjByY2G655HR9QXkODGmTTP
7+
MhVZp4UKCwM=
8+
Private-Lines: 1
9+
MW4fcSm+yRUR/19z3GrDV0dbjweegsEoWNIswwALYas8sV0l4S2k+b41qXuflP9N
10+
Private-MAC: 18146097d09025727b6facc1102aae201bd18364

test/assets/ed25519-ppk

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN EdDSA PRIVATE KEY-----
2+
MFECAQEEIJ4V2rGkAcGHBQnspmDrWJ9yncXAhJRi4NeE2FsQb0xMoAUGAytlcKEj
3+
AyEAVMkgHj6fUvLKiElOBtIMY27CldSJoLaTy0gmKs9rbIM=
4+
-----END EdDSA PRIVATE KEY-----

test/assets/ed25519.ppk

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
PuTTY-User-Key-File-2: ssh-ed25519
2+
Encryption: aes256-cbc
3+
Comment: ed25519-key-20210515
4+
Public-Lines: 2
5+
AAAAC3NzaC1lZDI1NTE5AAAAIFTJIB4+n1LyyohJTgbSDGNuwpXUiaC2k8tIJirP
6+
a2yD
7+
Private-Lines: 1
8+
Sth4e+fEa1lawlgah31ocn4Imf2pnocHG7jArPG2UxWNwmm7lUYm/SLfb5spucPt
9+
Private-MAC: aeec318aa251a446fb1d6f18d6288a264f3d0b41

0 commit comments

Comments
 (0)