keyParser.js 47 KB

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