@electerm/ssh2 0.8.11 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/install.js +20 -0
- package/lib/Channel.js +236 -450
- package/lib/agent.js +1080 -376
- package/lib/client.js +1698 -1258
- package/lib/http-agents.js +72 -51
- package/lib/index.js +43 -0
- package/lib/protocol/Protocol.js +2077 -0
- package/lib/protocol/SFTP.js +3778 -0
- package/lib/protocol/constants.js +342 -0
- package/lib/protocol/crypto/binding.gyp +14 -0
- package/lib/protocol/crypto/poly1305.js +43 -0
- package/lib/protocol/crypto/src/binding.cc +2003 -0
- package/lib/protocol/crypto.js +1602 -0
- package/lib/protocol/handlers.js +16 -0
- package/lib/protocol/handlers.misc.js +1214 -0
- package/lib/protocol/kex.js +1831 -0
- package/lib/protocol/keyParser.js +1481 -0
- package/lib/protocol/node-fs-compat.js +115 -0
- package/lib/protocol/utils.js +356 -0
- package/lib/protocol/zlib.js +255 -0
- package/lib/server.js +1226 -1019
- package/lib/utils.js +336 -0
- package/package.json +42 -9
- package/lib/SFTPWrapper.js +0 -145
- package/lib/buffer-helpers.js +0 -22
- package/lib/keepalivemgr.js +0 -80
|
@@ -0,0 +1,1481 @@
|
|
|
1
|
+
// TODO:
|
|
2
|
+
// * utilize `crypto.create(Private|Public)Key()` and `keyObject.export()`
|
|
3
|
+
// * handle multi-line header values (OpenSSH)?
|
|
4
|
+
// * more thorough validation?
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
createDecipheriv,
|
|
9
|
+
createECDH,
|
|
10
|
+
createHash,
|
|
11
|
+
createHmac,
|
|
12
|
+
createSign,
|
|
13
|
+
createVerify,
|
|
14
|
+
getCiphers,
|
|
15
|
+
sign: sign_,
|
|
16
|
+
verify: verify_,
|
|
17
|
+
} = require('crypto');
|
|
18
|
+
const supportedOpenSSLCiphers = getCiphers();
|
|
19
|
+
|
|
20
|
+
const { Ber } = require('asn1');
|
|
21
|
+
const bcrypt_pbkdf = require('bcrypt-pbkdf').pbkdf;
|
|
22
|
+
|
|
23
|
+
const { CIPHER_INFO } = require('./crypto.js');
|
|
24
|
+
const { eddsaSupported, SUPPORTED_CIPHER } = require('./constants.js');
|
|
25
|
+
const {
|
|
26
|
+
bufferSlice,
|
|
27
|
+
makeBufferParser,
|
|
28
|
+
readString,
|
|
29
|
+
readUInt32BE,
|
|
30
|
+
writeUInt32BE,
|
|
31
|
+
} = require('./utils.js');
|
|
32
|
+
|
|
33
|
+
const SYM_HASH_ALGO = Symbol('Hash Algorithm');
|
|
34
|
+
const SYM_PRIV_PEM = Symbol('Private key PEM');
|
|
35
|
+
const SYM_PUB_PEM = Symbol('Public key PEM');
|
|
36
|
+
const SYM_PUB_SSH = Symbol('Public key SSH');
|
|
37
|
+
const SYM_DECRYPTED = Symbol('Decrypted Key');
|
|
38
|
+
|
|
39
|
+
// Create OpenSSL cipher name -> SSH cipher name conversion table
|
|
40
|
+
const CIPHER_INFO_OPENSSL = Object.create(null);
|
|
41
|
+
{
|
|
42
|
+
const keys = Object.keys(CIPHER_INFO);
|
|
43
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
44
|
+
const cipherName = CIPHER_INFO[keys[i]].sslName;
|
|
45
|
+
if (!cipherName || CIPHER_INFO_OPENSSL[cipherName])
|
|
46
|
+
continue;
|
|
47
|
+
CIPHER_INFO_OPENSSL[cipherName] = CIPHER_INFO[keys[i]];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const binaryKeyParser = makeBufferParser();
|
|
52
|
+
|
|
53
|
+
function makePEM(type, data) {
|
|
54
|
+
data = data.base64Slice(0, data.length);
|
|
55
|
+
let formatted = data.replace(/.{64}/g, '$&\n');
|
|
56
|
+
if (data.length & 63)
|
|
57
|
+
formatted += '\n';
|
|
58
|
+
return `-----BEGIN ${type} KEY-----\n${formatted}-----END ${type} KEY-----`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function combineBuffers(buf1, buf2) {
|
|
62
|
+
const result = Buffer.allocUnsafe(buf1.length + buf2.length);
|
|
63
|
+
result.set(buf1, 0);
|
|
64
|
+
result.set(buf2, buf1.length);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function skipFields(buf, nfields) {
|
|
69
|
+
const bufLen = buf.length;
|
|
70
|
+
let pos = (buf._pos || 0);
|
|
71
|
+
for (let i = 0; i < nfields; ++i) {
|
|
72
|
+
const left = (bufLen - pos);
|
|
73
|
+
if (pos >= bufLen || left < 4)
|
|
74
|
+
return false;
|
|
75
|
+
const len = readUInt32BE(buf, pos);
|
|
76
|
+
if (left < 4 + len)
|
|
77
|
+
return false;
|
|
78
|
+
pos += 4 + len;
|
|
79
|
+
}
|
|
80
|
+
buf._pos = pos;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function genOpenSSLRSAPub(n, e) {
|
|
85
|
+
const asnWriter = new Ber.Writer();
|
|
86
|
+
asnWriter.startSequence();
|
|
87
|
+
// algorithm
|
|
88
|
+
asnWriter.startSequence();
|
|
89
|
+
asnWriter.writeOID('1.2.840.113549.1.1.1'); // rsaEncryption
|
|
90
|
+
// algorithm parameters (RSA has none)
|
|
91
|
+
asnWriter.writeNull();
|
|
92
|
+
asnWriter.endSequence();
|
|
93
|
+
|
|
94
|
+
// subjectPublicKey
|
|
95
|
+
asnWriter.startSequence(Ber.BitString);
|
|
96
|
+
asnWriter.writeByte(0x00);
|
|
97
|
+
asnWriter.startSequence();
|
|
98
|
+
asnWriter.writeBuffer(n, Ber.Integer);
|
|
99
|
+
asnWriter.writeBuffer(e, Ber.Integer);
|
|
100
|
+
asnWriter.endSequence();
|
|
101
|
+
asnWriter.endSequence();
|
|
102
|
+
asnWriter.endSequence();
|
|
103
|
+
return makePEM('PUBLIC', asnWriter.buffer);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function genOpenSSHRSAPub(n, e) {
|
|
107
|
+
const publicKey = Buffer.allocUnsafe(4 + 7 + 4 + e.length + 4 + n.length);
|
|
108
|
+
|
|
109
|
+
writeUInt32BE(publicKey, 7, 0);
|
|
110
|
+
publicKey.utf8Write('ssh-rsa', 4, 7);
|
|
111
|
+
|
|
112
|
+
let i = 4 + 7;
|
|
113
|
+
writeUInt32BE(publicKey, e.length, i);
|
|
114
|
+
publicKey.set(e, i += 4);
|
|
115
|
+
|
|
116
|
+
writeUInt32BE(publicKey, n.length, i += e.length);
|
|
117
|
+
publicKey.set(n, i + 4);
|
|
118
|
+
|
|
119
|
+
return publicKey;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const genOpenSSLRSAPriv = (() => {
|
|
123
|
+
function genRSAASN1Buf(n, e, d, p, q, dmp1, dmq1, iqmp) {
|
|
124
|
+
const asnWriter = new Ber.Writer();
|
|
125
|
+
asnWriter.startSequence();
|
|
126
|
+
asnWriter.writeInt(0x00, Ber.Integer);
|
|
127
|
+
asnWriter.writeBuffer(n, Ber.Integer);
|
|
128
|
+
asnWriter.writeBuffer(e, Ber.Integer);
|
|
129
|
+
asnWriter.writeBuffer(d, Ber.Integer);
|
|
130
|
+
asnWriter.writeBuffer(p, Ber.Integer);
|
|
131
|
+
asnWriter.writeBuffer(q, Ber.Integer);
|
|
132
|
+
asnWriter.writeBuffer(dmp1, Ber.Integer);
|
|
133
|
+
asnWriter.writeBuffer(dmq1, Ber.Integer);
|
|
134
|
+
asnWriter.writeBuffer(iqmp, Ber.Integer);
|
|
135
|
+
asnWriter.endSequence();
|
|
136
|
+
return asnWriter.buffer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function bigIntFromBuffer(buf) {
|
|
140
|
+
return BigInt(`0x${buf.hexSlice(0, buf.length)}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function bigIntToBuffer(bn) {
|
|
144
|
+
let hex = bn.toString(16);
|
|
145
|
+
if ((hex.length & 1) !== 0) {
|
|
146
|
+
hex = `0${hex}`;
|
|
147
|
+
} else {
|
|
148
|
+
const sigbit = hex.charCodeAt(0);
|
|
149
|
+
// BER/DER integers require leading zero byte to denote a positive value
|
|
150
|
+
// when first byte >= 0x80
|
|
151
|
+
if (sigbit === 56/* '8' */
|
|
152
|
+
|| sigbit === 57/* '9' */
|
|
153
|
+
|| (sigbit >= 97/* 'a' */ && sigbit <= 102/* 'f' */)) {
|
|
154
|
+
hex = `00${hex}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return Buffer.from(hex, 'hex');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return function genOpenSSLRSAPriv(n, e, d, iqmp, p, q) {
|
|
161
|
+
const bn_d = bigIntFromBuffer(d);
|
|
162
|
+
const dmp1 = bigIntToBuffer(bn_d % (bigIntFromBuffer(p) - 1n));
|
|
163
|
+
const dmq1 = bigIntToBuffer(bn_d % (bigIntFromBuffer(q) - 1n));
|
|
164
|
+
return makePEM('RSA PRIVATE',
|
|
165
|
+
genRSAASN1Buf(n, e, d, p, q, dmp1, dmq1, iqmp));
|
|
166
|
+
};
|
|
167
|
+
})();
|
|
168
|
+
|
|
169
|
+
function genOpenSSLDSAPub(p, q, g, y) {
|
|
170
|
+
const asnWriter = new Ber.Writer();
|
|
171
|
+
asnWriter.startSequence();
|
|
172
|
+
// algorithm
|
|
173
|
+
asnWriter.startSequence();
|
|
174
|
+
asnWriter.writeOID('1.2.840.10040.4.1'); // id-dsa
|
|
175
|
+
// algorithm parameters
|
|
176
|
+
asnWriter.startSequence();
|
|
177
|
+
asnWriter.writeBuffer(p, Ber.Integer);
|
|
178
|
+
asnWriter.writeBuffer(q, Ber.Integer);
|
|
179
|
+
asnWriter.writeBuffer(g, Ber.Integer);
|
|
180
|
+
asnWriter.endSequence();
|
|
181
|
+
asnWriter.endSequence();
|
|
182
|
+
|
|
183
|
+
// subjectPublicKey
|
|
184
|
+
asnWriter.startSequence(Ber.BitString);
|
|
185
|
+
asnWriter.writeByte(0x00);
|
|
186
|
+
asnWriter.writeBuffer(y, Ber.Integer);
|
|
187
|
+
asnWriter.endSequence();
|
|
188
|
+
asnWriter.endSequence();
|
|
189
|
+
return makePEM('PUBLIC', asnWriter.buffer);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function genOpenSSHDSAPub(p, q, g, y) {
|
|
193
|
+
const publicKey = Buffer.allocUnsafe(
|
|
194
|
+
4 + 7 + 4 + p.length + 4 + q.length + 4 + g.length + 4 + y.length
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
writeUInt32BE(publicKey, 7, 0);
|
|
198
|
+
publicKey.utf8Write('ssh-dss', 4, 7);
|
|
199
|
+
|
|
200
|
+
let i = 4 + 7;
|
|
201
|
+
writeUInt32BE(publicKey, p.length, i);
|
|
202
|
+
publicKey.set(p, i += 4);
|
|
203
|
+
|
|
204
|
+
writeUInt32BE(publicKey, q.length, i += p.length);
|
|
205
|
+
publicKey.set(q, i += 4);
|
|
206
|
+
|
|
207
|
+
writeUInt32BE(publicKey, g.length, i += q.length);
|
|
208
|
+
publicKey.set(g, i += 4);
|
|
209
|
+
|
|
210
|
+
writeUInt32BE(publicKey, y.length, i += g.length);
|
|
211
|
+
publicKey.set(y, i + 4);
|
|
212
|
+
|
|
213
|
+
return publicKey;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function genOpenSSLDSAPriv(p, q, g, y, x) {
|
|
217
|
+
const asnWriter = new Ber.Writer();
|
|
218
|
+
asnWriter.startSequence();
|
|
219
|
+
asnWriter.writeInt(0x00, Ber.Integer);
|
|
220
|
+
asnWriter.writeBuffer(p, Ber.Integer);
|
|
221
|
+
asnWriter.writeBuffer(q, Ber.Integer);
|
|
222
|
+
asnWriter.writeBuffer(g, Ber.Integer);
|
|
223
|
+
asnWriter.writeBuffer(y, Ber.Integer);
|
|
224
|
+
asnWriter.writeBuffer(x, Ber.Integer);
|
|
225
|
+
asnWriter.endSequence();
|
|
226
|
+
return makePEM('DSA PRIVATE', asnWriter.buffer);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function genOpenSSLEdPub(pub) {
|
|
230
|
+
const asnWriter = new Ber.Writer();
|
|
231
|
+
asnWriter.startSequence();
|
|
232
|
+
// algorithm
|
|
233
|
+
asnWriter.startSequence();
|
|
234
|
+
asnWriter.writeOID('1.3.101.112'); // id-Ed25519
|
|
235
|
+
asnWriter.endSequence();
|
|
236
|
+
|
|
237
|
+
// PublicKey
|
|
238
|
+
asnWriter.startSequence(Ber.BitString);
|
|
239
|
+
asnWriter.writeByte(0x00);
|
|
240
|
+
// XXX: hack to write a raw buffer without a tag -- yuck
|
|
241
|
+
asnWriter._ensure(pub.length);
|
|
242
|
+
asnWriter._buf.set(pub, asnWriter._offset);
|
|
243
|
+
asnWriter._offset += pub.length;
|
|
244
|
+
asnWriter.endSequence();
|
|
245
|
+
asnWriter.endSequence();
|
|
246
|
+
return makePEM('PUBLIC', asnWriter.buffer);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function genOpenSSHEdPub(pub) {
|
|
250
|
+
const publicKey = Buffer.allocUnsafe(4 + 11 + 4 + pub.length);
|
|
251
|
+
|
|
252
|
+
writeUInt32BE(publicKey, 11, 0);
|
|
253
|
+
publicKey.utf8Write('ssh-ed25519', 4, 11);
|
|
254
|
+
|
|
255
|
+
writeUInt32BE(publicKey, pub.length, 15);
|
|
256
|
+
publicKey.set(pub, 19);
|
|
257
|
+
|
|
258
|
+
return publicKey;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function genOpenSSLEdPriv(priv) {
|
|
262
|
+
const asnWriter = new Ber.Writer();
|
|
263
|
+
asnWriter.startSequence();
|
|
264
|
+
// version
|
|
265
|
+
asnWriter.writeInt(0x00, Ber.Integer);
|
|
266
|
+
|
|
267
|
+
// algorithm
|
|
268
|
+
asnWriter.startSequence();
|
|
269
|
+
asnWriter.writeOID('1.3.101.112'); // id-Ed25519
|
|
270
|
+
asnWriter.endSequence();
|
|
271
|
+
|
|
272
|
+
// PrivateKey
|
|
273
|
+
asnWriter.startSequence(Ber.OctetString);
|
|
274
|
+
asnWriter.writeBuffer(priv, Ber.OctetString);
|
|
275
|
+
asnWriter.endSequence();
|
|
276
|
+
asnWriter.endSequence();
|
|
277
|
+
return makePEM('PRIVATE', asnWriter.buffer);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function genOpenSSLECDSAPub(oid, Q) {
|
|
281
|
+
const asnWriter = new Ber.Writer();
|
|
282
|
+
asnWriter.startSequence();
|
|
283
|
+
// algorithm
|
|
284
|
+
asnWriter.startSequence();
|
|
285
|
+
asnWriter.writeOID('1.2.840.10045.2.1'); // id-ecPublicKey
|
|
286
|
+
// algorithm parameters (namedCurve)
|
|
287
|
+
asnWriter.writeOID(oid);
|
|
288
|
+
asnWriter.endSequence();
|
|
289
|
+
|
|
290
|
+
// subjectPublicKey
|
|
291
|
+
asnWriter.startSequence(Ber.BitString);
|
|
292
|
+
asnWriter.writeByte(0x00);
|
|
293
|
+
// XXX: hack to write a raw buffer without a tag -- yuck
|
|
294
|
+
asnWriter._ensure(Q.length);
|
|
295
|
+
asnWriter._buf.set(Q, asnWriter._offset);
|
|
296
|
+
asnWriter._offset += Q.length;
|
|
297
|
+
// end hack
|
|
298
|
+
asnWriter.endSequence();
|
|
299
|
+
asnWriter.endSequence();
|
|
300
|
+
return makePEM('PUBLIC', asnWriter.buffer);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function genOpenSSHECDSAPub(oid, Q) {
|
|
304
|
+
let curveName;
|
|
305
|
+
switch (oid) {
|
|
306
|
+
case '1.2.840.10045.3.1.7':
|
|
307
|
+
// prime256v1/secp256r1
|
|
308
|
+
curveName = 'nistp256';
|
|
309
|
+
break;
|
|
310
|
+
case '1.3.132.0.34':
|
|
311
|
+
// secp384r1
|
|
312
|
+
curveName = 'nistp384';
|
|
313
|
+
break;
|
|
314
|
+
case '1.3.132.0.35':
|
|
315
|
+
// secp521r1
|
|
316
|
+
curveName = 'nistp521';
|
|
317
|
+
break;
|
|
318
|
+
default:
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const publicKey = Buffer.allocUnsafe(4 + 19 + 4 + 8 + 4 + Q.length);
|
|
323
|
+
|
|
324
|
+
writeUInt32BE(publicKey, 19, 0);
|
|
325
|
+
publicKey.utf8Write(`ecdsa-sha2-${curveName}`, 4, 19);
|
|
326
|
+
|
|
327
|
+
writeUInt32BE(publicKey, 8, 23);
|
|
328
|
+
publicKey.utf8Write(curveName, 27, 8);
|
|
329
|
+
|
|
330
|
+
writeUInt32BE(publicKey, Q.length, 35);
|
|
331
|
+
publicKey.set(Q, 39);
|
|
332
|
+
|
|
333
|
+
return publicKey;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function genOpenSSLECDSAPriv(oid, pub, priv) {
|
|
337
|
+
const asnWriter = new Ber.Writer();
|
|
338
|
+
asnWriter.startSequence();
|
|
339
|
+
// version
|
|
340
|
+
asnWriter.writeInt(0x01, Ber.Integer);
|
|
341
|
+
// privateKey
|
|
342
|
+
asnWriter.writeBuffer(priv, Ber.OctetString);
|
|
343
|
+
// parameters (optional)
|
|
344
|
+
asnWriter.startSequence(0xA0);
|
|
345
|
+
asnWriter.writeOID(oid);
|
|
346
|
+
asnWriter.endSequence();
|
|
347
|
+
// publicKey (optional)
|
|
348
|
+
asnWriter.startSequence(0xA1);
|
|
349
|
+
asnWriter.startSequence(Ber.BitString);
|
|
350
|
+
asnWriter.writeByte(0x00);
|
|
351
|
+
// XXX: hack to write a raw buffer without a tag -- yuck
|
|
352
|
+
asnWriter._ensure(pub.length);
|
|
353
|
+
asnWriter._buf.set(pub, asnWriter._offset);
|
|
354
|
+
asnWriter._offset += pub.length;
|
|
355
|
+
// end hack
|
|
356
|
+
asnWriter.endSequence();
|
|
357
|
+
asnWriter.endSequence();
|
|
358
|
+
asnWriter.endSequence();
|
|
359
|
+
return makePEM('EC PRIVATE', asnWriter.buffer);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function genOpenSSLECDSAPubFromPriv(curveName, priv) {
|
|
363
|
+
const tempECDH = createECDH(curveName);
|
|
364
|
+
tempECDH.setPrivateKey(priv);
|
|
365
|
+
return tempECDH.getPublicKey();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const BaseKey = {
|
|
369
|
+
sign: (() => {
|
|
370
|
+
if (typeof sign_ === 'function') {
|
|
371
|
+
return function sign(data, algo) {
|
|
372
|
+
const pem = this[SYM_PRIV_PEM];
|
|
373
|
+
if (pem === null)
|
|
374
|
+
return new Error('No private key available');
|
|
375
|
+
if (!algo || typeof algo !== 'string')
|
|
376
|
+
algo = this[SYM_HASH_ALGO];
|
|
377
|
+
try {
|
|
378
|
+
return sign_(algo, data, pem);
|
|
379
|
+
} catch (ex) {
|
|
380
|
+
return ex;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return function sign(data, algo) {
|
|
385
|
+
const pem = this[SYM_PRIV_PEM];
|
|
386
|
+
if (pem === null)
|
|
387
|
+
return new Error('No private key available');
|
|
388
|
+
if (!algo || typeof algo !== 'string')
|
|
389
|
+
algo = this[SYM_HASH_ALGO];
|
|
390
|
+
const signature = createSign(algo);
|
|
391
|
+
signature.update(data);
|
|
392
|
+
try {
|
|
393
|
+
return signature.sign(pem);
|
|
394
|
+
} catch (ex) {
|
|
395
|
+
return ex;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
})(),
|
|
399
|
+
verify: (() => {
|
|
400
|
+
if (typeof verify_ === 'function') {
|
|
401
|
+
return function verify(data, signature, algo) {
|
|
402
|
+
const pem = this[SYM_PUB_PEM];
|
|
403
|
+
if (pem === null)
|
|
404
|
+
return new Error('No public key available');
|
|
405
|
+
if (!algo || typeof algo !== 'string')
|
|
406
|
+
algo = this[SYM_HASH_ALGO];
|
|
407
|
+
try {
|
|
408
|
+
return verify_(algo, data, pem, signature);
|
|
409
|
+
} catch (ex) {
|
|
410
|
+
return ex;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return function verify(data, signature, algo) {
|
|
415
|
+
const pem = this[SYM_PUB_PEM];
|
|
416
|
+
if (pem === null)
|
|
417
|
+
return new Error('No public key available');
|
|
418
|
+
if (!algo || typeof algo !== 'string')
|
|
419
|
+
algo = this[SYM_HASH_ALGO];
|
|
420
|
+
const verifier = createVerify(algo);
|
|
421
|
+
verifier.update(data);
|
|
422
|
+
try {
|
|
423
|
+
return verifier.verify(pem, signature);
|
|
424
|
+
} catch (ex) {
|
|
425
|
+
return ex;
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
})(),
|
|
429
|
+
isPrivateKey: function isPrivateKey() {
|
|
430
|
+
return (this[SYM_PRIV_PEM] !== null);
|
|
431
|
+
},
|
|
432
|
+
getPrivatePEM: function getPrivatePEM() {
|
|
433
|
+
return this[SYM_PRIV_PEM];
|
|
434
|
+
},
|
|
435
|
+
getPublicPEM: function getPublicPEM() {
|
|
436
|
+
return this[SYM_PUB_PEM];
|
|
437
|
+
},
|
|
438
|
+
getPublicSSH: function getPublicSSH() {
|
|
439
|
+
return this[SYM_PUB_SSH];
|
|
440
|
+
},
|
|
441
|
+
equals: function equals(key) {
|
|
442
|
+
const parsed = parseKey(key);
|
|
443
|
+
if (parsed instanceof Error)
|
|
444
|
+
return false;
|
|
445
|
+
return (
|
|
446
|
+
this.type === parsed.type
|
|
447
|
+
&& this[SYM_PRIV_PEM] === parsed[SYM_PRIV_PEM]
|
|
448
|
+
&& this[SYM_PUB_PEM] === parsed[SYM_PUB_PEM]
|
|
449
|
+
&& this[SYM_PUB_SSH] === parsed[SYM_PUB_SSH]
|
|
450
|
+
);
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
function OpenSSH_Private(type, comment, privPEM, pubPEM, pubSSH, algo,
|
|
456
|
+
decrypted) {
|
|
457
|
+
this.type = type;
|
|
458
|
+
this.comment = comment;
|
|
459
|
+
this[SYM_PRIV_PEM] = privPEM;
|
|
460
|
+
this[SYM_PUB_PEM] = pubPEM;
|
|
461
|
+
this[SYM_PUB_SSH] = pubSSH;
|
|
462
|
+
this[SYM_HASH_ALGO] = algo;
|
|
463
|
+
this[SYM_DECRYPTED] = decrypted;
|
|
464
|
+
}
|
|
465
|
+
OpenSSH_Private.prototype = BaseKey;
|
|
466
|
+
{
|
|
467
|
+
const regexp = /^-----BEGIN OPENSSH PRIVATE KEY-----(?:\r\n|\n)([\s\S]+)(?:\r\n|\n)-----END OPENSSH PRIVATE KEY-----$/;
|
|
468
|
+
OpenSSH_Private.parse = (str, passphrase) => {
|
|
469
|
+
const m = regexp.exec(str);
|
|
470
|
+
if (m === null)
|
|
471
|
+
return null;
|
|
472
|
+
let ret;
|
|
473
|
+
const data = Buffer.from(m[1], 'base64');
|
|
474
|
+
if (data.length < 31) // magic (+ magic null term.) + minimum field lengths
|
|
475
|
+
return new Error('Malformed OpenSSH private key');
|
|
476
|
+
const magic = data.utf8Slice(0, 15);
|
|
477
|
+
if (magic !== 'openssh-key-v1\0')
|
|
478
|
+
return new Error(`Unsupported OpenSSH key magic: ${magic}`);
|
|
479
|
+
|
|
480
|
+
const cipherName = readString(data, 15, true);
|
|
481
|
+
if (cipherName === undefined)
|
|
482
|
+
return new Error('Malformed OpenSSH private key');
|
|
483
|
+
if (cipherName !== 'none' && SUPPORTED_CIPHER.indexOf(cipherName) === -1)
|
|
484
|
+
return new Error(`Unsupported cipher for OpenSSH key: ${cipherName}`);
|
|
485
|
+
|
|
486
|
+
const kdfName = readString(data, data._pos, true);
|
|
487
|
+
if (kdfName === undefined)
|
|
488
|
+
return new Error('Malformed OpenSSH private key');
|
|
489
|
+
if (kdfName !== 'none') {
|
|
490
|
+
if (cipherName === 'none')
|
|
491
|
+
return new Error('Malformed OpenSSH private key');
|
|
492
|
+
if (kdfName !== 'bcrypt')
|
|
493
|
+
return new Error(`Unsupported kdf name for OpenSSH key: ${kdfName}`);
|
|
494
|
+
if (!passphrase) {
|
|
495
|
+
return new Error(
|
|
496
|
+
'Encrypted private OpenSSH key detected, but no passphrase given'
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
} else if (cipherName !== 'none') {
|
|
500
|
+
return new Error('Malformed OpenSSH private key');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let encInfo;
|
|
504
|
+
let cipherKey;
|
|
505
|
+
let cipherIV;
|
|
506
|
+
if (cipherName !== 'none')
|
|
507
|
+
encInfo = CIPHER_INFO[cipherName];
|
|
508
|
+
const kdfOptions = readString(data, data._pos);
|
|
509
|
+
if (kdfOptions === undefined)
|
|
510
|
+
return new Error('Malformed OpenSSH private key');
|
|
511
|
+
if (kdfOptions.length) {
|
|
512
|
+
switch (kdfName) {
|
|
513
|
+
case 'none':
|
|
514
|
+
return new Error('Malformed OpenSSH private key');
|
|
515
|
+
case 'bcrypt':
|
|
516
|
+
/*
|
|
517
|
+
string salt
|
|
518
|
+
uint32 rounds
|
|
519
|
+
*/
|
|
520
|
+
const salt = readString(kdfOptions, 0);
|
|
521
|
+
if (salt === undefined || kdfOptions._pos + 4 > kdfOptions.length)
|
|
522
|
+
return new Error('Malformed OpenSSH private key');
|
|
523
|
+
const rounds = readUInt32BE(kdfOptions, kdfOptions._pos);
|
|
524
|
+
const gen = Buffer.allocUnsafe(encInfo.keyLen + encInfo.ivLen);
|
|
525
|
+
const r = bcrypt_pbkdf(passphrase,
|
|
526
|
+
passphrase.length,
|
|
527
|
+
salt,
|
|
528
|
+
salt.length,
|
|
529
|
+
gen,
|
|
530
|
+
gen.length,
|
|
531
|
+
rounds);
|
|
532
|
+
if (r !== 0)
|
|
533
|
+
return new Error('Failed to generate information to decrypt key');
|
|
534
|
+
cipherKey = bufferSlice(gen, 0, encInfo.keyLen);
|
|
535
|
+
cipherIV = bufferSlice(gen, encInfo.keyLen, gen.length);
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
} else if (kdfName !== 'none') {
|
|
539
|
+
return new Error('Malformed OpenSSH private key');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (data._pos + 3 >= data.length)
|
|
543
|
+
return new Error('Malformed OpenSSH private key');
|
|
544
|
+
const keyCount = readUInt32BE(data, data._pos);
|
|
545
|
+
data._pos += 4;
|
|
546
|
+
|
|
547
|
+
if (keyCount > 0) {
|
|
548
|
+
// TODO: place sensible limit on max `keyCount`
|
|
549
|
+
|
|
550
|
+
// Read public keys first
|
|
551
|
+
for (let i = 0; i < keyCount; ++i) {
|
|
552
|
+
const pubData = readString(data, data._pos);
|
|
553
|
+
if (pubData === undefined)
|
|
554
|
+
return new Error('Malformed OpenSSH private key');
|
|
555
|
+
const type = readString(pubData, 0, true);
|
|
556
|
+
if (type === undefined)
|
|
557
|
+
return new Error('Malformed OpenSSH private key');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
let privBlob = readString(data, data._pos);
|
|
561
|
+
if (privBlob === undefined)
|
|
562
|
+
return new Error('Malformed OpenSSH private key');
|
|
563
|
+
|
|
564
|
+
if (cipherKey !== undefined) {
|
|
565
|
+
// Encrypted private key(s)
|
|
566
|
+
if (privBlob.length < encInfo.blockLen
|
|
567
|
+
|| (privBlob.length % encInfo.blockLen) !== 0) {
|
|
568
|
+
return new Error('Malformed OpenSSH private key');
|
|
569
|
+
}
|
|
570
|
+
try {
|
|
571
|
+
const options = { authTagLength: encInfo.authLen };
|
|
572
|
+
const decipher = createDecipheriv(encInfo.sslName,
|
|
573
|
+
cipherKey,
|
|
574
|
+
cipherIV,
|
|
575
|
+
options);
|
|
576
|
+
if (encInfo.authLen > 0) {
|
|
577
|
+
if (data.length - data._pos < encInfo.authLen)
|
|
578
|
+
return new Error('Malformed OpenSSH private key');
|
|
579
|
+
decipher.setAuthTag(
|
|
580
|
+
bufferSlice(data, data._pos, data._pos += encInfo.authLen)
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
privBlob = combineBuffers(decipher.update(privBlob),
|
|
584
|
+
decipher.final());
|
|
585
|
+
} catch (ex) {
|
|
586
|
+
return ex;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// Nothing should we follow the private key(s), except a possible
|
|
590
|
+
// authentication tag for relevant ciphers
|
|
591
|
+
if (data._pos !== data.length)
|
|
592
|
+
return new Error('Malformed OpenSSH private key');
|
|
593
|
+
|
|
594
|
+
ret = parseOpenSSHPrivKeys(privBlob, keyCount, cipherKey !== undefined);
|
|
595
|
+
} else {
|
|
596
|
+
ret = [];
|
|
597
|
+
}
|
|
598
|
+
// This will need to change if/when OpenSSH ever starts storing multiple
|
|
599
|
+
// keys in their key files
|
|
600
|
+
return ret[0];
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
function parseOpenSSHPrivKeys(data, nkeys, decrypted) {
|
|
604
|
+
const keys = [];
|
|
605
|
+
/*
|
|
606
|
+
uint32 checkint
|
|
607
|
+
uint32 checkint
|
|
608
|
+
string privatekey1
|
|
609
|
+
string comment1
|
|
610
|
+
string privatekey2
|
|
611
|
+
string comment2
|
|
612
|
+
...
|
|
613
|
+
string privatekeyN
|
|
614
|
+
string commentN
|
|
615
|
+
char 1
|
|
616
|
+
char 2
|
|
617
|
+
char 3
|
|
618
|
+
...
|
|
619
|
+
char padlen % 255
|
|
620
|
+
*/
|
|
621
|
+
if (data.length < 8)
|
|
622
|
+
return new Error('Malformed OpenSSH private key');
|
|
623
|
+
const check1 = readUInt32BE(data, 0);
|
|
624
|
+
const check2 = readUInt32BE(data, 4);
|
|
625
|
+
if (check1 !== check2) {
|
|
626
|
+
if (decrypted) {
|
|
627
|
+
return new Error(
|
|
628
|
+
'OpenSSH key integrity check failed -- bad passphrase?'
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
return new Error('OpenSSH key integrity check failed');
|
|
632
|
+
}
|
|
633
|
+
data._pos = 8;
|
|
634
|
+
let i;
|
|
635
|
+
let oid;
|
|
636
|
+
for (i = 0; i < nkeys; ++i) {
|
|
637
|
+
let algo;
|
|
638
|
+
let privPEM;
|
|
639
|
+
let pubPEM;
|
|
640
|
+
let pubSSH;
|
|
641
|
+
// The OpenSSH documentation for the key format actually lies, the
|
|
642
|
+
// entirety of the private key content is not contained with a string
|
|
643
|
+
// field, it's actually the literal contents of the private key, so to be
|
|
644
|
+
// able to find the end of the key data you need to know the layout/format
|
|
645
|
+
// of each key type ...
|
|
646
|
+
const type = readString(data, data._pos, true);
|
|
647
|
+
if (type === undefined)
|
|
648
|
+
return new Error('Malformed OpenSSH private key');
|
|
649
|
+
|
|
650
|
+
switch (type) {
|
|
651
|
+
case 'ssh-rsa': {
|
|
652
|
+
/*
|
|
653
|
+
string n -- public
|
|
654
|
+
string e -- public
|
|
655
|
+
string d -- private
|
|
656
|
+
string iqmp -- private
|
|
657
|
+
string p -- private
|
|
658
|
+
string q -- private
|
|
659
|
+
*/
|
|
660
|
+
const n = readString(data, data._pos);
|
|
661
|
+
if (n === undefined)
|
|
662
|
+
return new Error('Malformed OpenSSH private key');
|
|
663
|
+
const e = readString(data, data._pos);
|
|
664
|
+
if (e === undefined)
|
|
665
|
+
return new Error('Malformed OpenSSH private key');
|
|
666
|
+
const d = readString(data, data._pos);
|
|
667
|
+
if (d === undefined)
|
|
668
|
+
return new Error('Malformed OpenSSH private key');
|
|
669
|
+
const iqmp = readString(data, data._pos);
|
|
670
|
+
if (iqmp === undefined)
|
|
671
|
+
return new Error('Malformed OpenSSH private key');
|
|
672
|
+
const p = readString(data, data._pos);
|
|
673
|
+
if (p === undefined)
|
|
674
|
+
return new Error('Malformed OpenSSH private key');
|
|
675
|
+
const q = readString(data, data._pos);
|
|
676
|
+
if (q === undefined)
|
|
677
|
+
return new Error('Malformed OpenSSH private key');
|
|
678
|
+
|
|
679
|
+
pubPEM = genOpenSSLRSAPub(n, e);
|
|
680
|
+
pubSSH = genOpenSSHRSAPub(n, e);
|
|
681
|
+
privPEM = genOpenSSLRSAPriv(n, e, d, iqmp, p, q);
|
|
682
|
+
algo = 'sha1';
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
case 'ssh-dss': {
|
|
686
|
+
/*
|
|
687
|
+
string p -- public
|
|
688
|
+
string q -- public
|
|
689
|
+
string g -- public
|
|
690
|
+
string y -- public
|
|
691
|
+
string x -- private
|
|
692
|
+
*/
|
|
693
|
+
const p = readString(data, data._pos);
|
|
694
|
+
if (p === undefined)
|
|
695
|
+
return new Error('Malformed OpenSSH private key');
|
|
696
|
+
const q = readString(data, data._pos);
|
|
697
|
+
if (q === undefined)
|
|
698
|
+
return new Error('Malformed OpenSSH private key');
|
|
699
|
+
const g = readString(data, data._pos);
|
|
700
|
+
if (g === undefined)
|
|
701
|
+
return new Error('Malformed OpenSSH private key');
|
|
702
|
+
const y = readString(data, data._pos);
|
|
703
|
+
if (y === undefined)
|
|
704
|
+
return new Error('Malformed OpenSSH private key');
|
|
705
|
+
const x = readString(data, data._pos);
|
|
706
|
+
if (x === undefined)
|
|
707
|
+
return new Error('Malformed OpenSSH private key');
|
|
708
|
+
|
|
709
|
+
pubPEM = genOpenSSLDSAPub(p, q, g, y);
|
|
710
|
+
pubSSH = genOpenSSHDSAPub(p, q, g, y);
|
|
711
|
+
privPEM = genOpenSSLDSAPriv(p, q, g, y, x);
|
|
712
|
+
algo = 'sha1';
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
case 'ssh-ed25519': {
|
|
716
|
+
if (!eddsaSupported)
|
|
717
|
+
return new Error(`Unsupported OpenSSH private key type: ${type}`);
|
|
718
|
+
/*
|
|
719
|
+
* string public key
|
|
720
|
+
* string private key + public key
|
|
721
|
+
*/
|
|
722
|
+
const edpub = readString(data, data._pos);
|
|
723
|
+
if (edpub === undefined || edpub.length !== 32)
|
|
724
|
+
return new Error('Malformed OpenSSH private key');
|
|
725
|
+
const edpriv = readString(data, data._pos);
|
|
726
|
+
if (edpriv === undefined || edpriv.length !== 64)
|
|
727
|
+
return new Error('Malformed OpenSSH private key');
|
|
728
|
+
|
|
729
|
+
pubPEM = genOpenSSLEdPub(edpub);
|
|
730
|
+
pubSSH = genOpenSSHEdPub(edpub);
|
|
731
|
+
privPEM = genOpenSSLEdPriv(bufferSlice(edpriv, 0, 32));
|
|
732
|
+
algo = null;
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
case 'ecdsa-sha2-nistp256':
|
|
736
|
+
algo = 'sha256';
|
|
737
|
+
oid = '1.2.840.10045.3.1.7';
|
|
738
|
+
// FALLTHROUGH
|
|
739
|
+
case 'ecdsa-sha2-nistp384':
|
|
740
|
+
if (algo === undefined) {
|
|
741
|
+
algo = 'sha384';
|
|
742
|
+
oid = '1.3.132.0.34';
|
|
743
|
+
}
|
|
744
|
+
// FALLTHROUGH
|
|
745
|
+
case 'ecdsa-sha2-nistp521': {
|
|
746
|
+
if (algo === undefined) {
|
|
747
|
+
algo = 'sha512';
|
|
748
|
+
oid = '1.3.132.0.35';
|
|
749
|
+
}
|
|
750
|
+
/*
|
|
751
|
+
string curve name
|
|
752
|
+
string Q -- public
|
|
753
|
+
string d -- private
|
|
754
|
+
*/
|
|
755
|
+
// TODO: validate curve name against type
|
|
756
|
+
if (!skipFields(data, 1)) // Skip curve name
|
|
757
|
+
return new Error('Malformed OpenSSH private key');
|
|
758
|
+
const ecpub = readString(data, data._pos);
|
|
759
|
+
if (ecpub === undefined)
|
|
760
|
+
return new Error('Malformed OpenSSH private key');
|
|
761
|
+
const ecpriv = readString(data, data._pos);
|
|
762
|
+
if (ecpriv === undefined)
|
|
763
|
+
return new Error('Malformed OpenSSH private key');
|
|
764
|
+
|
|
765
|
+
pubPEM = genOpenSSLECDSAPub(oid, ecpub);
|
|
766
|
+
pubSSH = genOpenSSHECDSAPub(oid, ecpub);
|
|
767
|
+
privPEM = genOpenSSLECDSAPriv(oid, ecpub, ecpriv);
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
default:
|
|
771
|
+
return new Error(`Unsupported OpenSSH private key type: ${type}`);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const privComment = readString(data, data._pos, true);
|
|
775
|
+
if (privComment === undefined)
|
|
776
|
+
return new Error('Malformed OpenSSH private key');
|
|
777
|
+
|
|
778
|
+
keys.push(
|
|
779
|
+
new OpenSSH_Private(type, privComment, privPEM, pubPEM, pubSSH, algo,
|
|
780
|
+
decrypted)
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
let cnt = 0;
|
|
784
|
+
for (i = data._pos; i < data.length; ++i) {
|
|
785
|
+
if (data[i] !== (++cnt % 255))
|
|
786
|
+
return new Error('Malformed OpenSSH private key');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return keys;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
function OpenSSH_Old_Private(type, comment, privPEM, pubPEM, pubSSH, algo,
|
|
795
|
+
decrypted) {
|
|
796
|
+
this.type = type;
|
|
797
|
+
this.comment = comment;
|
|
798
|
+
this[SYM_PRIV_PEM] = privPEM;
|
|
799
|
+
this[SYM_PUB_PEM] = pubPEM;
|
|
800
|
+
this[SYM_PUB_SSH] = pubSSH;
|
|
801
|
+
this[SYM_HASH_ALGO] = algo;
|
|
802
|
+
this[SYM_DECRYPTED] = decrypted;
|
|
803
|
+
}
|
|
804
|
+
OpenSSH_Old_Private.prototype = BaseKey;
|
|
805
|
+
{
|
|
806
|
+
const regexp = /^-----BEGIN (RSA|DSA|EC) PRIVATE KEY-----(?:\r\n|\n)((?:[^:]+:\s*[\S].*(?:\r\n|\n))*)([\s\S]+)(?:\r\n|\n)-----END (RSA|DSA|EC) PRIVATE KEY-----$/;
|
|
807
|
+
OpenSSH_Old_Private.parse = (str, passphrase) => {
|
|
808
|
+
const m = regexp.exec(str);
|
|
809
|
+
if (m === null)
|
|
810
|
+
return null;
|
|
811
|
+
let privBlob = Buffer.from(m[3], 'base64');
|
|
812
|
+
let headers = m[2];
|
|
813
|
+
let decrypted = false;
|
|
814
|
+
if (headers !== undefined) {
|
|
815
|
+
// encrypted key
|
|
816
|
+
headers = headers.split(/\r\n|\n/g);
|
|
817
|
+
for (let i = 0; i < headers.length; ++i) {
|
|
818
|
+
const header = headers[i];
|
|
819
|
+
let sepIdx = header.indexOf(':');
|
|
820
|
+
if (header.slice(0, sepIdx) === 'DEK-Info') {
|
|
821
|
+
const val = header.slice(sepIdx + 2);
|
|
822
|
+
sepIdx = val.indexOf(',');
|
|
823
|
+
if (sepIdx === -1)
|
|
824
|
+
continue;
|
|
825
|
+
const cipherName = val.slice(0, sepIdx).toLowerCase();
|
|
826
|
+
if (supportedOpenSSLCiphers.indexOf(cipherName) === -1) {
|
|
827
|
+
return new Error(
|
|
828
|
+
`Cipher (${cipherName}) not supported `
|
|
829
|
+
+ 'for encrypted OpenSSH private key'
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
const encInfo = CIPHER_INFO_OPENSSL[cipherName];
|
|
833
|
+
if (!encInfo) {
|
|
834
|
+
return new Error(
|
|
835
|
+
`Cipher (${cipherName}) not supported `
|
|
836
|
+
+ 'for encrypted OpenSSH private key'
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const cipherIV = Buffer.from(val.slice(sepIdx + 1), 'hex');
|
|
840
|
+
if (cipherIV.length !== encInfo.ivLen)
|
|
841
|
+
return new Error('Malformed encrypted OpenSSH private key');
|
|
842
|
+
if (!passphrase) {
|
|
843
|
+
return new Error(
|
|
844
|
+
'Encrypted OpenSSH private key detected, but no passphrase given'
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
const ivSlice = bufferSlice(cipherIV, 0, 8);
|
|
848
|
+
let cipherKey = createHash('md5')
|
|
849
|
+
.update(passphrase)
|
|
850
|
+
.update(ivSlice)
|
|
851
|
+
.digest();
|
|
852
|
+
while (cipherKey.length < encInfo.keyLen) {
|
|
853
|
+
cipherKey = combineBuffers(
|
|
854
|
+
cipherKey,
|
|
855
|
+
createHash('md5')
|
|
856
|
+
.update(cipherKey)
|
|
857
|
+
.update(passphrase)
|
|
858
|
+
.update(ivSlice)
|
|
859
|
+
.digest()
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (cipherKey.length > encInfo.keyLen)
|
|
863
|
+
cipherKey = bufferSlice(cipherKey, 0, encInfo.keyLen);
|
|
864
|
+
try {
|
|
865
|
+
const decipher = createDecipheriv(cipherName, cipherKey, cipherIV);
|
|
866
|
+
decipher.setAutoPadding(false);
|
|
867
|
+
privBlob = combineBuffers(decipher.update(privBlob),
|
|
868
|
+
decipher.final());
|
|
869
|
+
decrypted = true;
|
|
870
|
+
} catch (ex) {
|
|
871
|
+
return ex;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
let type;
|
|
878
|
+
let privPEM;
|
|
879
|
+
let pubPEM;
|
|
880
|
+
let pubSSH;
|
|
881
|
+
let algo;
|
|
882
|
+
let reader;
|
|
883
|
+
let errMsg = 'Malformed OpenSSH private key';
|
|
884
|
+
if (decrypted)
|
|
885
|
+
errMsg += '. Bad passphrase?';
|
|
886
|
+
switch (m[1]) {
|
|
887
|
+
case 'RSA':
|
|
888
|
+
type = 'ssh-rsa';
|
|
889
|
+
privPEM = makePEM('RSA PRIVATE', privBlob);
|
|
890
|
+
try {
|
|
891
|
+
reader = new Ber.Reader(privBlob);
|
|
892
|
+
reader.readSequence();
|
|
893
|
+
reader.readInt(); // skip version
|
|
894
|
+
const n = reader.readString(Ber.Integer, true);
|
|
895
|
+
if (n === null)
|
|
896
|
+
return new Error(errMsg);
|
|
897
|
+
const e = reader.readString(Ber.Integer, true);
|
|
898
|
+
if (e === null)
|
|
899
|
+
return new Error(errMsg);
|
|
900
|
+
pubPEM = genOpenSSLRSAPub(n, e);
|
|
901
|
+
pubSSH = genOpenSSHRSAPub(n, e);
|
|
902
|
+
} catch {
|
|
903
|
+
return new Error(errMsg);
|
|
904
|
+
}
|
|
905
|
+
algo = 'sha1';
|
|
906
|
+
break;
|
|
907
|
+
case 'DSA':
|
|
908
|
+
type = 'ssh-dss';
|
|
909
|
+
privPEM = makePEM('DSA PRIVATE', privBlob);
|
|
910
|
+
try {
|
|
911
|
+
reader = new Ber.Reader(privBlob);
|
|
912
|
+
reader.readSequence();
|
|
913
|
+
reader.readInt(); // skip version
|
|
914
|
+
const p = reader.readString(Ber.Integer, true);
|
|
915
|
+
if (p === null)
|
|
916
|
+
return new Error(errMsg);
|
|
917
|
+
const q = reader.readString(Ber.Integer, true);
|
|
918
|
+
if (q === null)
|
|
919
|
+
return new Error(errMsg);
|
|
920
|
+
const g = reader.readString(Ber.Integer, true);
|
|
921
|
+
if (g === null)
|
|
922
|
+
return new Error(errMsg);
|
|
923
|
+
const y = reader.readString(Ber.Integer, true);
|
|
924
|
+
if (y === null)
|
|
925
|
+
return new Error(errMsg);
|
|
926
|
+
pubPEM = genOpenSSLDSAPub(p, q, g, y);
|
|
927
|
+
pubSSH = genOpenSSHDSAPub(p, q, g, y);
|
|
928
|
+
} catch {
|
|
929
|
+
return new Error(errMsg);
|
|
930
|
+
}
|
|
931
|
+
algo = 'sha1';
|
|
932
|
+
break;
|
|
933
|
+
case 'EC':
|
|
934
|
+
let ecSSLName;
|
|
935
|
+
let ecPriv;
|
|
936
|
+
let ecOID;
|
|
937
|
+
try {
|
|
938
|
+
reader = new Ber.Reader(privBlob);
|
|
939
|
+
reader.readSequence();
|
|
940
|
+
reader.readInt(); // skip version
|
|
941
|
+
ecPriv = reader.readString(Ber.OctetString, true);
|
|
942
|
+
reader.readByte(); // Skip "complex" context type byte
|
|
943
|
+
const offset = reader.readLength(); // Skip context length
|
|
944
|
+
if (offset !== null) {
|
|
945
|
+
reader._offset = offset;
|
|
946
|
+
ecOID = reader.readOID();
|
|
947
|
+
if (ecOID === null)
|
|
948
|
+
return new Error(errMsg);
|
|
949
|
+
switch (ecOID) {
|
|
950
|
+
case '1.2.840.10045.3.1.7':
|
|
951
|
+
// prime256v1/secp256r1
|
|
952
|
+
ecSSLName = 'prime256v1';
|
|
953
|
+
type = 'ecdsa-sha2-nistp256';
|
|
954
|
+
algo = 'sha256';
|
|
955
|
+
break;
|
|
956
|
+
case '1.3.132.0.34':
|
|
957
|
+
// secp384r1
|
|
958
|
+
ecSSLName = 'secp384r1';
|
|
959
|
+
type = 'ecdsa-sha2-nistp384';
|
|
960
|
+
algo = 'sha384';
|
|
961
|
+
break;
|
|
962
|
+
case '1.3.132.0.35':
|
|
963
|
+
// secp521r1
|
|
964
|
+
ecSSLName = 'secp521r1';
|
|
965
|
+
type = 'ecdsa-sha2-nistp521';
|
|
966
|
+
algo = 'sha512';
|
|
967
|
+
break;
|
|
968
|
+
default:
|
|
969
|
+
return new Error(`Unsupported private key EC OID: ${ecOID}`);
|
|
970
|
+
}
|
|
971
|
+
} else {
|
|
972
|
+
return new Error(errMsg);
|
|
973
|
+
}
|
|
974
|
+
} catch {
|
|
975
|
+
return new Error(errMsg);
|
|
976
|
+
}
|
|
977
|
+
privPEM = makePEM('EC PRIVATE', privBlob);
|
|
978
|
+
const pubBlob = genOpenSSLECDSAPubFromPriv(ecSSLName, ecPriv);
|
|
979
|
+
pubPEM = genOpenSSLECDSAPub(ecOID, pubBlob);
|
|
980
|
+
pubSSH = genOpenSSHECDSAPub(ecOID, pubBlob);
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return new OpenSSH_Old_Private(type, '', privPEM, pubPEM, pubSSH, algo,
|
|
985
|
+
decrypted);
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
function PPK_Private(type, comment, privPEM, pubPEM, pubSSH, algo, decrypted) {
|
|
991
|
+
this.type = type;
|
|
992
|
+
this.comment = comment;
|
|
993
|
+
this[SYM_PRIV_PEM] = privPEM;
|
|
994
|
+
this[SYM_PUB_PEM] = pubPEM;
|
|
995
|
+
this[SYM_PUB_SSH] = pubSSH;
|
|
996
|
+
this[SYM_HASH_ALGO] = algo;
|
|
997
|
+
this[SYM_DECRYPTED] = decrypted;
|
|
998
|
+
}
|
|
999
|
+
PPK_Private.prototype = BaseKey;
|
|
1000
|
+
{
|
|
1001
|
+
const EMPTY_PASSPHRASE = Buffer.alloc(0);
|
|
1002
|
+
const PPK_IV = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
1003
|
+
const PPK_PP1 = Buffer.from([0, 0, 0, 0]);
|
|
1004
|
+
const PPK_PP2 = Buffer.from([0, 0, 0, 1]);
|
|
1005
|
+
const regexp = /^PuTTY-User-Key-File-2: (ssh-(?:rsa|dss))\r?\nEncryption: (aes256-cbc|none)\r?\nComment: ([^\r\n]*)\r?\nPublic-Lines: \d+\r?\n([\s\S]+?)\r?\nPrivate-Lines: \d+\r?\n([\s\S]+?)\r?\nPrivate-MAC: ([^\r\n]+)/;
|
|
1006
|
+
PPK_Private.parse = (str, passphrase) => {
|
|
1007
|
+
const m = regexp.exec(str);
|
|
1008
|
+
if (m === null)
|
|
1009
|
+
return null;
|
|
1010
|
+
// m[1] = key type
|
|
1011
|
+
// m[2] = encryption type
|
|
1012
|
+
// m[3] = comment
|
|
1013
|
+
// m[4] = base64-encoded public key data:
|
|
1014
|
+
// for "ssh-rsa":
|
|
1015
|
+
// string "ssh-rsa"
|
|
1016
|
+
// mpint e (public exponent)
|
|
1017
|
+
// mpint n (modulus)
|
|
1018
|
+
// for "ssh-dss":
|
|
1019
|
+
// string "ssh-dss"
|
|
1020
|
+
// mpint p (modulus)
|
|
1021
|
+
// mpint q (prime)
|
|
1022
|
+
// mpint g (base number)
|
|
1023
|
+
// mpint y (public key parameter: g^x mod p)
|
|
1024
|
+
// m[5] = base64-encoded private key data:
|
|
1025
|
+
// for "ssh-rsa":
|
|
1026
|
+
// mpint d (private exponent)
|
|
1027
|
+
// mpint p (prime 1)
|
|
1028
|
+
// mpint q (prime 2)
|
|
1029
|
+
// mpint iqmp ([inverse of q] mod p)
|
|
1030
|
+
// for "ssh-dss":
|
|
1031
|
+
// mpint x (private key parameter)
|
|
1032
|
+
// m[6] = SHA1 HMAC over:
|
|
1033
|
+
// string name of algorithm ("ssh-dss", "ssh-rsa")
|
|
1034
|
+
// string encryption type
|
|
1035
|
+
// string comment
|
|
1036
|
+
// string public key data
|
|
1037
|
+
// string private-plaintext (including the final padding)
|
|
1038
|
+
const cipherName = m[2];
|
|
1039
|
+
const encrypted = (cipherName !== 'none');
|
|
1040
|
+
if (encrypted && !passphrase) {
|
|
1041
|
+
return new Error(
|
|
1042
|
+
'Encrypted PPK private key detected, but no passphrase given'
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
let privBlob = Buffer.from(m[5], 'base64');
|
|
1047
|
+
|
|
1048
|
+
if (encrypted) {
|
|
1049
|
+
const encInfo = CIPHER_INFO[cipherName];
|
|
1050
|
+
let cipherKey = combineBuffers(
|
|
1051
|
+
createHash('sha1').update(PPK_PP1).update(passphrase).digest(),
|
|
1052
|
+
createHash('sha1').update(PPK_PP2).update(passphrase).digest()
|
|
1053
|
+
);
|
|
1054
|
+
if (cipherKey.length > encInfo.keyLen)
|
|
1055
|
+
cipherKey = bufferSlice(cipherKey, 0, encInfo.keyLen);
|
|
1056
|
+
try {
|
|
1057
|
+
const decipher = createDecipheriv(encInfo.sslName,
|
|
1058
|
+
cipherKey,
|
|
1059
|
+
PPK_IV);
|
|
1060
|
+
decipher.setAutoPadding(false);
|
|
1061
|
+
privBlob = combineBuffers(decipher.update(privBlob),
|
|
1062
|
+
decipher.final());
|
|
1063
|
+
} catch (ex) {
|
|
1064
|
+
return ex;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const type = m[1];
|
|
1069
|
+
const comment = m[3];
|
|
1070
|
+
const pubBlob = Buffer.from(m[4], 'base64');
|
|
1071
|
+
|
|
1072
|
+
const mac = m[6];
|
|
1073
|
+
const typeLen = type.length;
|
|
1074
|
+
const cipherNameLen = cipherName.length;
|
|
1075
|
+
const commentLen = Buffer.byteLength(comment);
|
|
1076
|
+
const pubLen = pubBlob.length;
|
|
1077
|
+
const privLen = privBlob.length;
|
|
1078
|
+
const macData = Buffer.allocUnsafe(4 + typeLen
|
|
1079
|
+
+ 4 + cipherNameLen
|
|
1080
|
+
+ 4 + commentLen
|
|
1081
|
+
+ 4 + pubLen
|
|
1082
|
+
+ 4 + privLen);
|
|
1083
|
+
let p = 0;
|
|
1084
|
+
|
|
1085
|
+
writeUInt32BE(macData, typeLen, p);
|
|
1086
|
+
macData.utf8Write(type, p += 4, typeLen);
|
|
1087
|
+
writeUInt32BE(macData, cipherNameLen, p += typeLen);
|
|
1088
|
+
macData.utf8Write(cipherName, p += 4, cipherNameLen);
|
|
1089
|
+
writeUInt32BE(macData, commentLen, p += cipherNameLen);
|
|
1090
|
+
macData.utf8Write(comment, p += 4, commentLen);
|
|
1091
|
+
writeUInt32BE(macData, pubLen, p += commentLen);
|
|
1092
|
+
macData.set(pubBlob, p += 4);
|
|
1093
|
+
writeUInt32BE(macData, privLen, p += pubLen);
|
|
1094
|
+
macData.set(privBlob, p + 4);
|
|
1095
|
+
|
|
1096
|
+
if (!passphrase)
|
|
1097
|
+
passphrase = EMPTY_PASSPHRASE;
|
|
1098
|
+
|
|
1099
|
+
const calcMAC = createHmac(
|
|
1100
|
+
'sha1',
|
|
1101
|
+
createHash('sha1')
|
|
1102
|
+
.update('putty-private-key-file-mac-key')
|
|
1103
|
+
.update(passphrase)
|
|
1104
|
+
.digest()
|
|
1105
|
+
).update(macData).digest('hex');
|
|
1106
|
+
|
|
1107
|
+
if (calcMAC !== mac) {
|
|
1108
|
+
if (encrypted) {
|
|
1109
|
+
return new Error(
|
|
1110
|
+
'PPK private key integrity check failed -- bad passphrase?'
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
return new Error('PPK private key integrity check failed');
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
let pubPEM;
|
|
1117
|
+
let pubSSH;
|
|
1118
|
+
let privPEM;
|
|
1119
|
+
pubBlob._pos = 0;
|
|
1120
|
+
skipFields(pubBlob, 1); // skip (duplicate) key type
|
|
1121
|
+
switch (type) {
|
|
1122
|
+
case 'ssh-rsa': {
|
|
1123
|
+
const e = readString(pubBlob, pubBlob._pos);
|
|
1124
|
+
if (e === undefined)
|
|
1125
|
+
return new Error('Malformed PPK public key');
|
|
1126
|
+
const n = readString(pubBlob, pubBlob._pos);
|
|
1127
|
+
if (n === undefined)
|
|
1128
|
+
return new Error('Malformed PPK public key');
|
|
1129
|
+
const d = readString(privBlob, 0);
|
|
1130
|
+
if (d === undefined)
|
|
1131
|
+
return new Error('Malformed PPK private key');
|
|
1132
|
+
const p = readString(privBlob, privBlob._pos);
|
|
1133
|
+
if (p === undefined)
|
|
1134
|
+
return new Error('Malformed PPK private key');
|
|
1135
|
+
const q = readString(privBlob, privBlob._pos);
|
|
1136
|
+
if (q === undefined)
|
|
1137
|
+
return new Error('Malformed PPK private key');
|
|
1138
|
+
const iqmp = readString(privBlob, privBlob._pos);
|
|
1139
|
+
if (iqmp === undefined)
|
|
1140
|
+
return new Error('Malformed PPK private key');
|
|
1141
|
+
pubPEM = genOpenSSLRSAPub(n, e);
|
|
1142
|
+
pubSSH = genOpenSSHRSAPub(n, e);
|
|
1143
|
+
privPEM = genOpenSSLRSAPriv(n, e, d, iqmp, p, q);
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
case 'ssh-dss': {
|
|
1147
|
+
const p = readString(pubBlob, pubBlob._pos);
|
|
1148
|
+
if (p === undefined)
|
|
1149
|
+
return new Error('Malformed PPK public key');
|
|
1150
|
+
const q = readString(pubBlob, pubBlob._pos);
|
|
1151
|
+
if (q === undefined)
|
|
1152
|
+
return new Error('Malformed PPK public key');
|
|
1153
|
+
const g = readString(pubBlob, pubBlob._pos);
|
|
1154
|
+
if (g === undefined)
|
|
1155
|
+
return new Error('Malformed PPK public key');
|
|
1156
|
+
const y = readString(pubBlob, pubBlob._pos);
|
|
1157
|
+
if (y === undefined)
|
|
1158
|
+
return new Error('Malformed PPK public key');
|
|
1159
|
+
const x = readString(privBlob, 0);
|
|
1160
|
+
if (x === undefined)
|
|
1161
|
+
return new Error('Malformed PPK private key');
|
|
1162
|
+
|
|
1163
|
+
pubPEM = genOpenSSLDSAPub(p, q, g, y);
|
|
1164
|
+
pubSSH = genOpenSSHDSAPub(p, q, g, y);
|
|
1165
|
+
privPEM = genOpenSSLDSAPriv(p, q, g, y, x);
|
|
1166
|
+
break;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return new PPK_Private(type, comment, privPEM, pubPEM, pubSSH, 'sha1',
|
|
1171
|
+
encrypted);
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
function OpenSSH_Public(type, comment, pubPEM, pubSSH, algo) {
|
|
1177
|
+
this.type = type;
|
|
1178
|
+
this.comment = comment;
|
|
1179
|
+
this[SYM_PRIV_PEM] = null;
|
|
1180
|
+
this[SYM_PUB_PEM] = pubPEM;
|
|
1181
|
+
this[SYM_PUB_SSH] = pubSSH;
|
|
1182
|
+
this[SYM_HASH_ALGO] = algo;
|
|
1183
|
+
this[SYM_DECRYPTED] = false;
|
|
1184
|
+
}
|
|
1185
|
+
OpenSSH_Public.prototype = BaseKey;
|
|
1186
|
+
{
|
|
1187
|
+
let regexp;
|
|
1188
|
+
if (eddsaSupported)
|
|
1189
|
+
regexp = /^(((?:ssh-(?:rsa|dss|ed25519))|ecdsa-sha2-nistp(?:256|384|521))(?:-cert-v0[01]@openssh.com)?) ([A-Z0-9a-z/+=]+)(?:$|\s+([\S].*)?)$/;
|
|
1190
|
+
else
|
|
1191
|
+
regexp = /^(((?:ssh-(?:rsa|dss))|ecdsa-sha2-nistp(?:256|384|521))(?:-cert-v0[01]@openssh.com)?) ([A-Z0-9a-z/+=]+)(?:$|\s+([\S].*)?)$/;
|
|
1192
|
+
OpenSSH_Public.parse = (str) => {
|
|
1193
|
+
const m = regexp.exec(str);
|
|
1194
|
+
if (m === null)
|
|
1195
|
+
return null;
|
|
1196
|
+
// m[1] = full type
|
|
1197
|
+
// m[2] = base type
|
|
1198
|
+
// m[3] = base64-encoded public key
|
|
1199
|
+
// m[4] = comment
|
|
1200
|
+
|
|
1201
|
+
const fullType = m[1];
|
|
1202
|
+
const baseType = m[2];
|
|
1203
|
+
const data = Buffer.from(m[3], 'base64');
|
|
1204
|
+
const comment = (m[4] || '');
|
|
1205
|
+
|
|
1206
|
+
const type = readString(data, data._pos, true);
|
|
1207
|
+
if (type === undefined || type.indexOf(baseType) !== 0)
|
|
1208
|
+
return new Error('Malformed OpenSSH public key');
|
|
1209
|
+
|
|
1210
|
+
return parseDER(data, baseType, comment, fullType);
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
function RFC4716_Public(type, comment, pubPEM, pubSSH, algo) {
|
|
1216
|
+
this.type = type;
|
|
1217
|
+
this.comment = comment;
|
|
1218
|
+
this[SYM_PRIV_PEM] = null;
|
|
1219
|
+
this[SYM_PUB_PEM] = pubPEM;
|
|
1220
|
+
this[SYM_PUB_SSH] = pubSSH;
|
|
1221
|
+
this[SYM_HASH_ALGO] = algo;
|
|
1222
|
+
this[SYM_DECRYPTED] = false;
|
|
1223
|
+
}
|
|
1224
|
+
RFC4716_Public.prototype = BaseKey;
|
|
1225
|
+
{
|
|
1226
|
+
const regexp = /^---- BEGIN SSH2 PUBLIC KEY ----(?:\r?\n)((?:.{0,72}\r?\n)+)---- END SSH2 PUBLIC KEY ----$/;
|
|
1227
|
+
const RE_DATA = /^[A-Z0-9a-z/+=\r\n]+$/;
|
|
1228
|
+
const RE_HEADER = /^([\x21-\x39\x3B-\x7E]{1,64}): ((?:[^\\]*\\\r?\n)*[^\r\n]+)\r?\n/gm;
|
|
1229
|
+
const RE_HEADER_ENDS = /\\\r?\n/g;
|
|
1230
|
+
RFC4716_Public.parse = (str) => {
|
|
1231
|
+
let m = regexp.exec(str);
|
|
1232
|
+
if (m === null)
|
|
1233
|
+
return null;
|
|
1234
|
+
|
|
1235
|
+
const body = m[1];
|
|
1236
|
+
let dataStart = 0;
|
|
1237
|
+
let comment = '';
|
|
1238
|
+
|
|
1239
|
+
while (m = RE_HEADER.exec(body)) {
|
|
1240
|
+
const headerName = m[1];
|
|
1241
|
+
const headerValue = m[2].replace(RE_HEADER_ENDS, '');
|
|
1242
|
+
if (headerValue.length > 1024) {
|
|
1243
|
+
RE_HEADER.lastIndex = 0;
|
|
1244
|
+
return new Error('Malformed RFC4716 public key');
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
dataStart = RE_HEADER.lastIndex;
|
|
1248
|
+
|
|
1249
|
+
if (headerName.toLowerCase() === 'comment') {
|
|
1250
|
+
comment = headerValue;
|
|
1251
|
+
if (comment.length > 1
|
|
1252
|
+
&& comment.charCodeAt(0) === 34/* '"' */
|
|
1253
|
+
&& comment.charCodeAt(comment.length - 1) === 34/* '"' */) {
|
|
1254
|
+
comment = comment.slice(1, -1);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
let data = body.slice(dataStart);
|
|
1260
|
+
if (!RE_DATA.test(data))
|
|
1261
|
+
return new Error('Malformed RFC4716 public key');
|
|
1262
|
+
|
|
1263
|
+
data = Buffer.from(data, 'base64');
|
|
1264
|
+
|
|
1265
|
+
const type = readString(data, 0, true);
|
|
1266
|
+
if (type === undefined)
|
|
1267
|
+
return new Error('Malformed RFC4716 public key');
|
|
1268
|
+
|
|
1269
|
+
let pubPEM = null;
|
|
1270
|
+
let pubSSH = null;
|
|
1271
|
+
switch (type) {
|
|
1272
|
+
case 'ssh-rsa': {
|
|
1273
|
+
const e = readString(data, data._pos);
|
|
1274
|
+
if (e === undefined)
|
|
1275
|
+
return new Error('Malformed RFC4716 public key');
|
|
1276
|
+
const n = readString(data, data._pos);
|
|
1277
|
+
if (n === undefined)
|
|
1278
|
+
return new Error('Malformed RFC4716 public key');
|
|
1279
|
+
pubPEM = genOpenSSLRSAPub(n, e);
|
|
1280
|
+
pubSSH = genOpenSSHRSAPub(n, e);
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
case 'ssh-dss': {
|
|
1284
|
+
const p = readString(data, data._pos);
|
|
1285
|
+
if (p === undefined)
|
|
1286
|
+
return new Error('Malformed RFC4716 public key');
|
|
1287
|
+
const q = readString(data, data._pos);
|
|
1288
|
+
if (q === undefined)
|
|
1289
|
+
return new Error('Malformed RFC4716 public key');
|
|
1290
|
+
const g = readString(data, data._pos);
|
|
1291
|
+
if (g === undefined)
|
|
1292
|
+
return new Error('Malformed RFC4716 public key');
|
|
1293
|
+
const y = readString(data, data._pos);
|
|
1294
|
+
if (y === undefined)
|
|
1295
|
+
return new Error('Malformed RFC4716 public key');
|
|
1296
|
+
pubPEM = genOpenSSLDSAPub(p, q, g, y);
|
|
1297
|
+
pubSSH = genOpenSSHDSAPub(p, q, g, y);
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
default:
|
|
1301
|
+
return new Error('Malformed RFC4716 public key');
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return new RFC4716_Public(type, comment, pubPEM, pubSSH, 'sha1');
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
function parseDER(data, baseType, comment, fullType) {
|
|
1310
|
+
if (!isSupportedKeyType(baseType))
|
|
1311
|
+
return new Error(`Unsupported OpenSSH public key type: ${baseType}`);
|
|
1312
|
+
|
|
1313
|
+
let algo;
|
|
1314
|
+
let oid;
|
|
1315
|
+
let pubPEM = null;
|
|
1316
|
+
let pubSSH = null;
|
|
1317
|
+
|
|
1318
|
+
switch (baseType) {
|
|
1319
|
+
case 'ssh-rsa': {
|
|
1320
|
+
const e = readString(data, data._pos || 0);
|
|
1321
|
+
if (e === undefined)
|
|
1322
|
+
return new Error('Malformed OpenSSH public key');
|
|
1323
|
+
const n = readString(data, data._pos);
|
|
1324
|
+
if (n === undefined)
|
|
1325
|
+
return new Error('Malformed OpenSSH public key');
|
|
1326
|
+
pubPEM = genOpenSSLRSAPub(n, e);
|
|
1327
|
+
pubSSH = genOpenSSHRSAPub(n, e);
|
|
1328
|
+
algo = 'sha1';
|
|
1329
|
+
break;
|
|
1330
|
+
}
|
|
1331
|
+
case 'ssh-dss': {
|
|
1332
|
+
const p = readString(data, data._pos || 0);
|
|
1333
|
+
if (p === undefined)
|
|
1334
|
+
return new Error('Malformed OpenSSH public key');
|
|
1335
|
+
const q = readString(data, data._pos);
|
|
1336
|
+
if (q === undefined)
|
|
1337
|
+
return new Error('Malformed OpenSSH public key');
|
|
1338
|
+
const g = readString(data, data._pos);
|
|
1339
|
+
if (g === undefined)
|
|
1340
|
+
return new Error('Malformed OpenSSH public key');
|
|
1341
|
+
const y = readString(data, data._pos);
|
|
1342
|
+
if (y === undefined)
|
|
1343
|
+
return new Error('Malformed OpenSSH public key');
|
|
1344
|
+
pubPEM = genOpenSSLDSAPub(p, q, g, y);
|
|
1345
|
+
pubSSH = genOpenSSHDSAPub(p, q, g, y);
|
|
1346
|
+
algo = 'sha1';
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
case 'ssh-ed25519': {
|
|
1350
|
+
const edpub = readString(data, data._pos || 0);
|
|
1351
|
+
if (edpub === undefined || edpub.length !== 32)
|
|
1352
|
+
return new Error('Malformed OpenSSH public key');
|
|
1353
|
+
pubPEM = genOpenSSLEdPub(edpub);
|
|
1354
|
+
pubSSH = genOpenSSHEdPub(edpub);
|
|
1355
|
+
algo = null;
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
case 'ecdsa-sha2-nistp256':
|
|
1359
|
+
algo = 'sha256';
|
|
1360
|
+
oid = '1.2.840.10045.3.1.7';
|
|
1361
|
+
// FALLTHROUGH
|
|
1362
|
+
case 'ecdsa-sha2-nistp384':
|
|
1363
|
+
if (algo === undefined) {
|
|
1364
|
+
algo = 'sha384';
|
|
1365
|
+
oid = '1.3.132.0.34';
|
|
1366
|
+
}
|
|
1367
|
+
// FALLTHROUGH
|
|
1368
|
+
case 'ecdsa-sha2-nistp521': {
|
|
1369
|
+
if (algo === undefined) {
|
|
1370
|
+
algo = 'sha512';
|
|
1371
|
+
oid = '1.3.132.0.35';
|
|
1372
|
+
}
|
|
1373
|
+
// TODO: validate curve name against type
|
|
1374
|
+
if (!skipFields(data, 1)) // Skip curve name
|
|
1375
|
+
return new Error('Malformed OpenSSH public key');
|
|
1376
|
+
const ecpub = readString(data, data._pos || 0);
|
|
1377
|
+
if (ecpub === undefined)
|
|
1378
|
+
return new Error('Malformed OpenSSH public key');
|
|
1379
|
+
pubPEM = genOpenSSLECDSAPub(oid, ecpub);
|
|
1380
|
+
pubSSH = genOpenSSHECDSAPub(oid, ecpub);
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
default:
|
|
1384
|
+
return new Error(`Unsupported OpenSSH public key type: ${baseType}`);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
return new OpenSSH_Public(fullType, comment, pubPEM, pubSSH, algo);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function isSupportedKeyType(type) {
|
|
1391
|
+
switch (type) {
|
|
1392
|
+
case 'ssh-rsa':
|
|
1393
|
+
case 'ssh-dss':
|
|
1394
|
+
case 'ecdsa-sha2-nistp256':
|
|
1395
|
+
case 'ecdsa-sha2-nistp384':
|
|
1396
|
+
case 'ecdsa-sha2-nistp521':
|
|
1397
|
+
return true;
|
|
1398
|
+
case 'ssh-ed25519':
|
|
1399
|
+
if (eddsaSupported)
|
|
1400
|
+
return true;
|
|
1401
|
+
// FALLTHROUGH
|
|
1402
|
+
default:
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
function isParsedKey(val) {
|
|
1408
|
+
if (!val)
|
|
1409
|
+
return false;
|
|
1410
|
+
return (typeof val[SYM_DECRYPTED] === 'boolean');
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
function parseKey(data, passphrase) {
|
|
1414
|
+
if (isParsedKey(data))
|
|
1415
|
+
return data;
|
|
1416
|
+
|
|
1417
|
+
let origBuffer;
|
|
1418
|
+
if (Buffer.isBuffer(data)) {
|
|
1419
|
+
origBuffer = data;
|
|
1420
|
+
data = data.utf8Slice(0, data.length).trim();
|
|
1421
|
+
} else if (typeof data === 'string') {
|
|
1422
|
+
data = data.trim();
|
|
1423
|
+
} else {
|
|
1424
|
+
return new Error('Key data must be a Buffer or string');
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// eslint-disable-next-line eqeqeq
|
|
1428
|
+
if (passphrase != undefined) {
|
|
1429
|
+
if (typeof passphrase === 'string')
|
|
1430
|
+
passphrase = Buffer.from(passphrase);
|
|
1431
|
+
else if (!Buffer.isBuffer(passphrase))
|
|
1432
|
+
return new Error('Passphrase must be a string or Buffer when supplied');
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
let ret;
|
|
1436
|
+
|
|
1437
|
+
// First try as printable string format (e.g. PEM)
|
|
1438
|
+
|
|
1439
|
+
// Private keys
|
|
1440
|
+
if ((ret = OpenSSH_Private.parse(data, passphrase)) !== null)
|
|
1441
|
+
return ret;
|
|
1442
|
+
if ((ret = OpenSSH_Old_Private.parse(data, passphrase)) !== null)
|
|
1443
|
+
return ret;
|
|
1444
|
+
if ((ret = PPK_Private.parse(data, passphrase)) !== null)
|
|
1445
|
+
return ret;
|
|
1446
|
+
|
|
1447
|
+
// Public keys
|
|
1448
|
+
if ((ret = OpenSSH_Public.parse(data)) !== null)
|
|
1449
|
+
return ret;
|
|
1450
|
+
if ((ret = RFC4716_Public.parse(data)) !== null)
|
|
1451
|
+
return ret;
|
|
1452
|
+
|
|
1453
|
+
// Finally try as a binary format if we were originally passed binary data
|
|
1454
|
+
if (origBuffer) {
|
|
1455
|
+
binaryKeyParser.init(origBuffer, 0);
|
|
1456
|
+
const type = binaryKeyParser.readString(true);
|
|
1457
|
+
if (type !== undefined) {
|
|
1458
|
+
data = binaryKeyParser.readRaw();
|
|
1459
|
+
if (data !== undefined) {
|
|
1460
|
+
ret = parseDER(data, type, '', type);
|
|
1461
|
+
// Ignore potentially useless errors in case the data was not actually
|
|
1462
|
+
// in the binary format
|
|
1463
|
+
if (ret instanceof Error)
|
|
1464
|
+
ret = null;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
binaryKeyParser.clear();
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
if (ret)
|
|
1471
|
+
return ret;
|
|
1472
|
+
|
|
1473
|
+
return new Error('Unsupported key format');
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
module.exports = {
|
|
1477
|
+
isParsedKey,
|
|
1478
|
+
isSupportedKeyType,
|
|
1479
|
+
parseDERKey: (data, type) => parseDER(data, type, '', type),
|
|
1480
|
+
parseKey,
|
|
1481
|
+
};
|