@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.
@@ -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
+ };