12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481 |
- // TODO:
- // * utilize `crypto.create(Private|Public)Key()` and `keyObject.export()`
- // * handle multi-line header values (OpenSSH)?
- // * more thorough validation?
- 'use strict';
- const {
- createDecipheriv,
- createECDH,
- createHash,
- createHmac,
- createSign,
- createVerify,
- getCiphers,
- sign: sign_,
- verify: verify_,
- } = require('crypto');
- const supportedOpenSSLCiphers = getCiphers();
- const { Ber } = require('asn1');
- const bcrypt_pbkdf = require('bcrypt-pbkdf').pbkdf;
- const { CIPHER_INFO } = require('./crypto.js');
- const { eddsaSupported, SUPPORTED_CIPHER } = require('./constants.js');
- const {
- bufferSlice,
- makeBufferParser,
- readString,
- readUInt32BE,
- writeUInt32BE,
- } = require('./utils.js');
- const SYM_HASH_ALGO = Symbol('Hash Algorithm');
- const SYM_PRIV_PEM = Symbol('Private key PEM');
- const SYM_PUB_PEM = Symbol('Public key PEM');
- const SYM_PUB_SSH = Symbol('Public key SSH');
- const SYM_DECRYPTED = Symbol('Decrypted Key');
- // Create OpenSSL cipher name -> SSH cipher name conversion table
- const CIPHER_INFO_OPENSSL = Object.create(null);
- {
- const keys = Object.keys(CIPHER_INFO);
- for (let i = 0; i < keys.length; ++i) {
- const cipherName = CIPHER_INFO[keys[i]].sslName;
- if (!cipherName || CIPHER_INFO_OPENSSL[cipherName])
- continue;
- CIPHER_INFO_OPENSSL[cipherName] = CIPHER_INFO[keys[i]];
- }
- }
- const binaryKeyParser = makeBufferParser();
- function makePEM(type, data) {
- data = data.base64Slice(0, data.length);
- let formatted = data.replace(/.{64}/g, '$&\n');
- if (data.length & 63)
- formatted += '\n';
- return `-----BEGIN ${type} KEY-----\n${formatted}-----END ${type} KEY-----`;
- }
- function combineBuffers(buf1, buf2) {
- const result = Buffer.allocUnsafe(buf1.length + buf2.length);
- result.set(buf1, 0);
- result.set(buf2, buf1.length);
- return result;
- }
- function skipFields(buf, nfields) {
- const bufLen = buf.length;
- let pos = (buf._pos || 0);
- for (let i = 0; i < nfields; ++i) {
- const left = (bufLen - pos);
- if (pos >= bufLen || left < 4)
- return false;
- const len = readUInt32BE(buf, pos);
- if (left < 4 + len)
- return false;
- pos += 4 + len;
- }
- buf._pos = pos;
- return true;
- }
- function genOpenSSLRSAPub(n, e) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- // algorithm
- asnWriter.startSequence();
- asnWriter.writeOID('1.2.840.113549.1.1.1'); // rsaEncryption
- // algorithm parameters (RSA has none)
- asnWriter.writeNull();
- asnWriter.endSequence();
- // subjectPublicKey
- asnWriter.startSequence(Ber.BitString);
- asnWriter.writeByte(0x00);
- asnWriter.startSequence();
- asnWriter.writeBuffer(n, Ber.Integer);
- asnWriter.writeBuffer(e, Ber.Integer);
- asnWriter.endSequence();
- asnWriter.endSequence();
- asnWriter.endSequence();
- return makePEM('PUBLIC', asnWriter.buffer);
- }
- function genOpenSSHRSAPub(n, e) {
- const publicKey = Buffer.allocUnsafe(4 + 7 + 4 + e.length + 4 + n.length);
- writeUInt32BE(publicKey, 7, 0);
- publicKey.utf8Write('ssh-rsa', 4, 7);
- let i = 4 + 7;
- writeUInt32BE(publicKey, e.length, i);
- publicKey.set(e, i += 4);
- writeUInt32BE(publicKey, n.length, i += e.length);
- publicKey.set(n, i + 4);
- return publicKey;
- }
- const genOpenSSLRSAPriv = (() => {
- function genRSAASN1Buf(n, e, d, p, q, dmp1, dmq1, iqmp) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- asnWriter.writeInt(0x00, Ber.Integer);
- asnWriter.writeBuffer(n, Ber.Integer);
- asnWriter.writeBuffer(e, Ber.Integer);
- asnWriter.writeBuffer(d, Ber.Integer);
- asnWriter.writeBuffer(p, Ber.Integer);
- asnWriter.writeBuffer(q, Ber.Integer);
- asnWriter.writeBuffer(dmp1, Ber.Integer);
- asnWriter.writeBuffer(dmq1, Ber.Integer);
- asnWriter.writeBuffer(iqmp, Ber.Integer);
- asnWriter.endSequence();
- return asnWriter.buffer;
- }
- function bigIntFromBuffer(buf) {
- return BigInt(`0x${buf.hexSlice(0, buf.length)}`);
- }
- function bigIntToBuffer(bn) {
- let hex = bn.toString(16);
- if ((hex.length & 1) !== 0) {
- hex = `0${hex}`;
- } else {
- const sigbit = hex.charCodeAt(0);
- // BER/DER integers require leading zero byte to denote a positive value
- // when first byte >= 0x80
- if (sigbit === 56/* '8' */
- || sigbit === 57/* '9' */
- || (sigbit >= 97/* 'a' */ && sigbit <= 102/* 'f' */)) {
- hex = `00${hex}`;
- }
- }
- return Buffer.from(hex, 'hex');
- }
- return function genOpenSSLRSAPriv(n, e, d, iqmp, p, q) {
- const bn_d = bigIntFromBuffer(d);
- const dmp1 = bigIntToBuffer(bn_d % (bigIntFromBuffer(p) - 1n));
- const dmq1 = bigIntToBuffer(bn_d % (bigIntFromBuffer(q) - 1n));
- return makePEM('RSA PRIVATE',
- genRSAASN1Buf(n, e, d, p, q, dmp1, dmq1, iqmp));
- };
- })();
- function genOpenSSLDSAPub(p, q, g, y) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- // algorithm
- asnWriter.startSequence();
- asnWriter.writeOID('1.2.840.10040.4.1'); // id-dsa
- // algorithm parameters
- asnWriter.startSequence();
- asnWriter.writeBuffer(p, Ber.Integer);
- asnWriter.writeBuffer(q, Ber.Integer);
- asnWriter.writeBuffer(g, Ber.Integer);
- asnWriter.endSequence();
- asnWriter.endSequence();
- // subjectPublicKey
- asnWriter.startSequence(Ber.BitString);
- asnWriter.writeByte(0x00);
- asnWriter.writeBuffer(y, Ber.Integer);
- asnWriter.endSequence();
- asnWriter.endSequence();
- return makePEM('PUBLIC', asnWriter.buffer);
- }
- function genOpenSSHDSAPub(p, q, g, y) {
- const publicKey = Buffer.allocUnsafe(
- 4 + 7 + 4 + p.length + 4 + q.length + 4 + g.length + 4 + y.length
- );
- writeUInt32BE(publicKey, 7, 0);
- publicKey.utf8Write('ssh-dss', 4, 7);
- let i = 4 + 7;
- writeUInt32BE(publicKey, p.length, i);
- publicKey.set(p, i += 4);
- writeUInt32BE(publicKey, q.length, i += p.length);
- publicKey.set(q, i += 4);
- writeUInt32BE(publicKey, g.length, i += q.length);
- publicKey.set(g, i += 4);
- writeUInt32BE(publicKey, y.length, i += g.length);
- publicKey.set(y, i + 4);
- return publicKey;
- }
- function genOpenSSLDSAPriv(p, q, g, y, x) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- asnWriter.writeInt(0x00, Ber.Integer);
- asnWriter.writeBuffer(p, Ber.Integer);
- asnWriter.writeBuffer(q, Ber.Integer);
- asnWriter.writeBuffer(g, Ber.Integer);
- asnWriter.writeBuffer(y, Ber.Integer);
- asnWriter.writeBuffer(x, Ber.Integer);
- asnWriter.endSequence();
- return makePEM('DSA PRIVATE', asnWriter.buffer);
- }
- function genOpenSSLEdPub(pub) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- // algorithm
- asnWriter.startSequence();
- asnWriter.writeOID('1.3.101.112'); // id-Ed25519
- asnWriter.endSequence();
- // PublicKey
- asnWriter.startSequence(Ber.BitString);
- asnWriter.writeByte(0x00);
- // XXX: hack to write a raw buffer without a tag -- yuck
- asnWriter._ensure(pub.length);
- asnWriter._buf.set(pub, asnWriter._offset);
- asnWriter._offset += pub.length;
- asnWriter.endSequence();
- asnWriter.endSequence();
- return makePEM('PUBLIC', asnWriter.buffer);
- }
- function genOpenSSHEdPub(pub) {
- const publicKey = Buffer.allocUnsafe(4 + 11 + 4 + pub.length);
- writeUInt32BE(publicKey, 11, 0);
- publicKey.utf8Write('ssh-ed25519', 4, 11);
- writeUInt32BE(publicKey, pub.length, 15);
- publicKey.set(pub, 19);
- return publicKey;
- }
- function genOpenSSLEdPriv(priv) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- // version
- asnWriter.writeInt(0x00, Ber.Integer);
- // algorithm
- asnWriter.startSequence();
- asnWriter.writeOID('1.3.101.112'); // id-Ed25519
- asnWriter.endSequence();
- // PrivateKey
- asnWriter.startSequence(Ber.OctetString);
- asnWriter.writeBuffer(priv, Ber.OctetString);
- asnWriter.endSequence();
- asnWriter.endSequence();
- return makePEM('PRIVATE', asnWriter.buffer);
- }
- function genOpenSSLECDSAPub(oid, Q) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- // algorithm
- asnWriter.startSequence();
- asnWriter.writeOID('1.2.840.10045.2.1'); // id-ecPublicKey
- // algorithm parameters (namedCurve)
- asnWriter.writeOID(oid);
- asnWriter.endSequence();
- // subjectPublicKey
- asnWriter.startSequence(Ber.BitString);
- asnWriter.writeByte(0x00);
- // XXX: hack to write a raw buffer without a tag -- yuck
- asnWriter._ensure(Q.length);
- asnWriter._buf.set(Q, asnWriter._offset);
- asnWriter._offset += Q.length;
- // end hack
- asnWriter.endSequence();
- asnWriter.endSequence();
- return makePEM('PUBLIC', asnWriter.buffer);
- }
- function genOpenSSHECDSAPub(oid, Q) {
- let curveName;
- switch (oid) {
- case '1.2.840.10045.3.1.7':
- // prime256v1/secp256r1
- curveName = 'nistp256';
- break;
- case '1.3.132.0.34':
- // secp384r1
- curveName = 'nistp384';
- break;
- case '1.3.132.0.35':
- // secp521r1
- curveName = 'nistp521';
- break;
- default:
- return;
- }
- const publicKey = Buffer.allocUnsafe(4 + 19 + 4 + 8 + 4 + Q.length);
- writeUInt32BE(publicKey, 19, 0);
- publicKey.utf8Write(`ecdsa-sha2-${curveName}`, 4, 19);
- writeUInt32BE(publicKey, 8, 23);
- publicKey.utf8Write(curveName, 27, 8);
- writeUInt32BE(publicKey, Q.length, 35);
- publicKey.set(Q, 39);
- return publicKey;
- }
- function genOpenSSLECDSAPriv(oid, pub, priv) {
- const asnWriter = new Ber.Writer();
- asnWriter.startSequence();
- // version
- asnWriter.writeInt(0x01, Ber.Integer);
- // privateKey
- asnWriter.writeBuffer(priv, Ber.OctetString);
- // parameters (optional)
- asnWriter.startSequence(0xA0);
- asnWriter.writeOID(oid);
- asnWriter.endSequence();
- // publicKey (optional)
- asnWriter.startSequence(0xA1);
- asnWriter.startSequence(Ber.BitString);
- asnWriter.writeByte(0x00);
- // XXX: hack to write a raw buffer without a tag -- yuck
- asnWriter._ensure(pub.length);
- asnWriter._buf.set(pub, asnWriter._offset);
- asnWriter._offset += pub.length;
- // end hack
- asnWriter.endSequence();
- asnWriter.endSequence();
- asnWriter.endSequence();
- return makePEM('EC PRIVATE', asnWriter.buffer);
- }
- function genOpenSSLECDSAPubFromPriv(curveName, priv) {
- const tempECDH = createECDH(curveName);
- tempECDH.setPrivateKey(priv);
- return tempECDH.getPublicKey();
- }
- const BaseKey = {
- sign: (() => {
- if (typeof sign_ === 'function') {
- return function sign(data, algo) {
- const pem = this[SYM_PRIV_PEM];
- if (pem === null)
- return new Error('No private key available');
- if (!algo || typeof algo !== 'string')
- algo = this[SYM_HASH_ALGO];
- try {
- return sign_(algo, data, pem);
- } catch (ex) {
- return ex;
- }
- };
- }
- return function sign(data, algo) {
- const pem = this[SYM_PRIV_PEM];
- if (pem === null)
- return new Error('No private key available');
- if (!algo || typeof algo !== 'string')
- algo = this[SYM_HASH_ALGO];
- const signature = createSign(algo);
- signature.update(data);
- try {
- return signature.sign(pem);
- } catch (ex) {
- return ex;
- }
- };
- })(),
- verify: (() => {
- if (typeof verify_ === 'function') {
- return function verify(data, signature, algo) {
- const pem = this[SYM_PUB_PEM];
- if (pem === null)
- return new Error('No public key available');
- if (!algo || typeof algo !== 'string')
- algo = this[SYM_HASH_ALGO];
- try {
- return verify_(algo, data, pem, signature);
- } catch (ex) {
- return ex;
- }
- };
- }
- return function verify(data, signature, algo) {
- const pem = this[SYM_PUB_PEM];
- if (pem === null)
- return new Error('No public key available');
- if (!algo || typeof algo !== 'string')
- algo = this[SYM_HASH_ALGO];
- const verifier = createVerify(algo);
- verifier.update(data);
- try {
- return verifier.verify(pem, signature);
- } catch (ex) {
- return ex;
- }
- };
- })(),
- isPrivateKey: function isPrivateKey() {
- return (this[SYM_PRIV_PEM] !== null);
- },
- getPrivatePEM: function getPrivatePEM() {
- return this[SYM_PRIV_PEM];
- },
- getPublicPEM: function getPublicPEM() {
- return this[SYM_PUB_PEM];
- },
- getPublicSSH: function getPublicSSH() {
- return this[SYM_PUB_SSH];
- },
- equals: function equals(key) {
- const parsed = parseKey(key);
- if (parsed instanceof Error)
- return false;
- return (
- this.type === parsed.type
- && this[SYM_PRIV_PEM] === parsed[SYM_PRIV_PEM]
- && this[SYM_PUB_PEM] === parsed[SYM_PUB_PEM]
- && this[SYM_PUB_SSH] === parsed[SYM_PUB_SSH]
- );
- },
- };
- function OpenSSH_Private(type, comment, privPEM, pubPEM, pubSSH, algo,
- decrypted) {
- this.type = type;
- this.comment = comment;
- this[SYM_PRIV_PEM] = privPEM;
- this[SYM_PUB_PEM] = pubPEM;
- this[SYM_PUB_SSH] = pubSSH;
- this[SYM_HASH_ALGO] = algo;
- this[SYM_DECRYPTED] = decrypted;
- }
- OpenSSH_Private.prototype = BaseKey;
- {
- const regexp = /^-----BEGIN OPENSSH PRIVATE KEY-----(?:\r\n|\n)([\s\S]+)(?:\r\n|\n)-----END OPENSSH PRIVATE KEY-----$/;
- OpenSSH_Private.parse = (str, passphrase) => {
- const m = regexp.exec(str);
- if (m === null)
- return null;
- let ret;
- const data = Buffer.from(m[1], 'base64');
- if (data.length < 31) // magic (+ magic null term.) + minimum field lengths
- return new Error('Malformed OpenSSH private key');
- const magic = data.utf8Slice(0, 15);
- if (magic !== 'openssh-key-v1\0')
- return new Error(`Unsupported OpenSSH key magic: ${magic}`);
- const cipherName = readString(data, 15, true);
- if (cipherName === undefined)
- return new Error('Malformed OpenSSH private key');
- if (cipherName !== 'none' && SUPPORTED_CIPHER.indexOf(cipherName) === -1)
- return new Error(`Unsupported cipher for OpenSSH key: ${cipherName}`);
- const kdfName = readString(data, data._pos, true);
- if (kdfName === undefined)
- return new Error('Malformed OpenSSH private key');
- if (kdfName !== 'none') {
- if (cipherName === 'none')
- return new Error('Malformed OpenSSH private key');
- if (kdfName !== 'bcrypt')
- return new Error(`Unsupported kdf name for OpenSSH key: ${kdfName}`);
- if (!passphrase) {
- return new Error(
- 'Encrypted private OpenSSH key detected, but no passphrase given'
- );
- }
- } else if (cipherName !== 'none') {
- return new Error('Malformed OpenSSH private key');
- }
- let encInfo;
- let cipherKey;
- let cipherIV;
- if (cipherName !== 'none')
- encInfo = CIPHER_INFO[cipherName];
- const kdfOptions = readString(data, data._pos);
- if (kdfOptions === undefined)
- return new Error('Malformed OpenSSH private key');
- if (kdfOptions.length) {
- switch (kdfName) {
- case 'none':
- return new Error('Malformed OpenSSH private key');
- case 'bcrypt':
- /*
- string salt
- uint32 rounds
- */
- const salt = readString(kdfOptions, 0);
- if (salt === undefined || kdfOptions._pos + 4 > kdfOptions.length)
- return new Error('Malformed OpenSSH private key');
- const rounds = readUInt32BE(kdfOptions, kdfOptions._pos);
- const gen = Buffer.allocUnsafe(encInfo.keyLen + encInfo.ivLen);
- const r = bcrypt_pbkdf(passphrase,
- passphrase.length,
- salt,
- salt.length,
- gen,
- gen.length,
- rounds);
- if (r !== 0)
- return new Error('Failed to generate information to decrypt key');
- cipherKey = bufferSlice(gen, 0, encInfo.keyLen);
- cipherIV = bufferSlice(gen, encInfo.keyLen, gen.length);
- break;
- }
- } else if (kdfName !== 'none') {
- return new Error('Malformed OpenSSH private key');
- }
- if (data._pos + 3 >= data.length)
- return new Error('Malformed OpenSSH private key');
- const keyCount = readUInt32BE(data, data._pos);
- data._pos += 4;
- if (keyCount > 0) {
- // TODO: place sensible limit on max `keyCount`
- // Read public keys first
- for (let i = 0; i < keyCount; ++i) {
- const pubData = readString(data, data._pos);
- if (pubData === undefined)
- return new Error('Malformed OpenSSH private key');
- const type = readString(pubData, 0, true);
- if (type === undefined)
- return new Error('Malformed OpenSSH private key');
- }
- let privBlob = readString(data, data._pos);
- if (privBlob === undefined)
- return new Error('Malformed OpenSSH private key');
- if (cipherKey !== undefined) {
- // Encrypted private key(s)
- if (privBlob.length < encInfo.blockLen
- || (privBlob.length % encInfo.blockLen) !== 0) {
- return new Error('Malformed OpenSSH private key');
- }
- try {
- const options = { authTagLength: encInfo.authLen };
- const decipher = createDecipheriv(encInfo.sslName,
- cipherKey,
- cipherIV,
- options);
- if (encInfo.authLen > 0) {
- if (data.length - data._pos < encInfo.authLen)
- return new Error('Malformed OpenSSH private key');
- decipher.setAuthTag(
- bufferSlice(data, data._pos, data._pos += encInfo.authLen)
- );
- }
- privBlob = combineBuffers(decipher.update(privBlob),
- decipher.final());
- } catch (ex) {
- return ex;
- }
- }
- // Nothing should we follow the private key(s), except a possible
- // authentication tag for relevant ciphers
- if (data._pos !== data.length)
- return new Error('Malformed OpenSSH private key');
- ret = parseOpenSSHPrivKeys(privBlob, keyCount, cipherKey !== undefined);
- } else {
- ret = [];
- }
- // This will need to change if/when OpenSSH ever starts storing multiple
- // keys in their key files
- return ret[0];
- };
- function parseOpenSSHPrivKeys(data, nkeys, decrypted) {
- const keys = [];
- /*
- uint32 checkint
- uint32 checkint
- string privatekey1
- string comment1
- string privatekey2
- string comment2
- ...
- string privatekeyN
- string commentN
- char 1
- char 2
- char 3
- ...
- char padlen % 255
- */
- if (data.length < 8)
- return new Error('Malformed OpenSSH private key');
- const check1 = readUInt32BE(data, 0);
- const check2 = readUInt32BE(data, 4);
- if (check1 !== check2) {
- if (decrypted) {
- return new Error(
- 'OpenSSH key integrity check failed -- bad passphrase?'
- );
- }
- return new Error('OpenSSH key integrity check failed');
- }
- data._pos = 8;
- let i;
- let oid;
- for (i = 0; i < nkeys; ++i) {
- let algo;
- let privPEM;
- let pubPEM;
- let pubSSH;
- // The OpenSSH documentation for the key format actually lies, the
- // entirety of the private key content is not contained with a string
- // field, it's actually the literal contents of the private key, so to be
- // able to find the end of the key data you need to know the layout/format
- // of each key type ...
- const type = readString(data, data._pos, true);
- if (type === undefined)
- return new Error('Malformed OpenSSH private key');
- switch (type) {
- case 'ssh-rsa': {
- /*
- string n -- public
- string e -- public
- string d -- private
- string iqmp -- private
- string p -- private
- string q -- private
- */
- const n = readString(data, data._pos);
- if (n === undefined)
- return new Error('Malformed OpenSSH private key');
- const e = readString(data, data._pos);
- if (e === undefined)
- return new Error('Malformed OpenSSH private key');
- const d = readString(data, data._pos);
- if (d === undefined)
- return new Error('Malformed OpenSSH private key');
- const iqmp = readString(data, data._pos);
- if (iqmp === undefined)
- return new Error('Malformed OpenSSH private key');
- const p = readString(data, data._pos);
- if (p === undefined)
- return new Error('Malformed OpenSSH private key');
- const q = readString(data, data._pos);
- if (q === undefined)
- return new Error('Malformed OpenSSH private key');
- pubPEM = genOpenSSLRSAPub(n, e);
- pubSSH = genOpenSSHRSAPub(n, e);
- privPEM = genOpenSSLRSAPriv(n, e, d, iqmp, p, q);
- algo = 'sha1';
- break;
- }
- case 'ssh-dss': {
- /*
- string p -- public
- string q -- public
- string g -- public
- string y -- public
- string x -- private
- */
- const p = readString(data, data._pos);
- if (p === undefined)
- return new Error('Malformed OpenSSH private key');
- const q = readString(data, data._pos);
- if (q === undefined)
- return new Error('Malformed OpenSSH private key');
- const g = readString(data, data._pos);
- if (g === undefined)
- return new Error('Malformed OpenSSH private key');
- const y = readString(data, data._pos);
- if (y === undefined)
- return new Error('Malformed OpenSSH private key');
- const x = readString(data, data._pos);
- if (x === undefined)
- return new Error('Malformed OpenSSH private key');
- pubPEM = genOpenSSLDSAPub(p, q, g, y);
- pubSSH = genOpenSSHDSAPub(p, q, g, y);
- privPEM = genOpenSSLDSAPriv(p, q, g, y, x);
- algo = 'sha1';
- break;
- }
- case 'ssh-ed25519': {
- if (!eddsaSupported)
- return new Error(`Unsupported OpenSSH private key type: ${type}`);
- /*
- * string public key
- * string private key + public key
- */
- const edpub = readString(data, data._pos);
- if (edpub === undefined || edpub.length !== 32)
- return new Error('Malformed OpenSSH private key');
- const edpriv = readString(data, data._pos);
- if (edpriv === undefined || edpriv.length !== 64)
- return new Error('Malformed OpenSSH private key');
- pubPEM = genOpenSSLEdPub(edpub);
- pubSSH = genOpenSSHEdPub(edpub);
- privPEM = genOpenSSLEdPriv(bufferSlice(edpriv, 0, 32));
- algo = null;
- break;
- }
- case 'ecdsa-sha2-nistp256':
- algo = 'sha256';
- oid = '1.2.840.10045.3.1.7';
- // FALLTHROUGH
- case 'ecdsa-sha2-nistp384':
- if (algo === undefined) {
- algo = 'sha384';
- oid = '1.3.132.0.34';
- }
- // FALLTHROUGH
- case 'ecdsa-sha2-nistp521': {
- if (algo === undefined) {
- algo = 'sha512';
- oid = '1.3.132.0.35';
- }
- /*
- string curve name
- string Q -- public
- string d -- private
- */
- // TODO: validate curve name against type
- if (!skipFields(data, 1)) // Skip curve name
- return new Error('Malformed OpenSSH private key');
- const ecpub = readString(data, data._pos);
- if (ecpub === undefined)
- return new Error('Malformed OpenSSH private key');
- const ecpriv = readString(data, data._pos);
- if (ecpriv === undefined)
- return new Error('Malformed OpenSSH private key');
- pubPEM = genOpenSSLECDSAPub(oid, ecpub);
- pubSSH = genOpenSSHECDSAPub(oid, ecpub);
- privPEM = genOpenSSLECDSAPriv(oid, ecpub, ecpriv);
- break;
- }
- default:
- return new Error(`Unsupported OpenSSH private key type: ${type}`);
- }
- const privComment = readString(data, data._pos, true);
- if (privComment === undefined)
- return new Error('Malformed OpenSSH private key');
- keys.push(
- new OpenSSH_Private(type, privComment, privPEM, pubPEM, pubSSH, algo,
- decrypted)
- );
- }
- let cnt = 0;
- for (i = data._pos; i < data.length; ++i) {
- if (data[i] !== (++cnt % 255))
- return new Error('Malformed OpenSSH private key');
- }
- return keys;
- }
- }
- function OpenSSH_Old_Private(type, comment, privPEM, pubPEM, pubSSH, algo,
- decrypted) {
- this.type = type;
- this.comment = comment;
- this[SYM_PRIV_PEM] = privPEM;
- this[SYM_PUB_PEM] = pubPEM;
- this[SYM_PUB_SSH] = pubSSH;
- this[SYM_HASH_ALGO] = algo;
- this[SYM_DECRYPTED] = decrypted;
- }
- OpenSSH_Old_Private.prototype = BaseKey;
- {
- 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-----$/;
- OpenSSH_Old_Private.parse = (str, passphrase) => {
- const m = regexp.exec(str);
- if (m === null)
- return null;
- let privBlob = Buffer.from(m[3], 'base64');
- let headers = m[2];
- let decrypted = false;
- if (headers !== undefined) {
- // encrypted key
- headers = headers.split(/\r\n|\n/g);
- for (let i = 0; i < headers.length; ++i) {
- const header = headers[i];
- let sepIdx = header.indexOf(':');
- if (header.slice(0, sepIdx) === 'DEK-Info') {
- const val = header.slice(sepIdx + 2);
- sepIdx = val.indexOf(',');
- if (sepIdx === -1)
- continue;
- const cipherName = val.slice(0, sepIdx).toLowerCase();
- if (supportedOpenSSLCiphers.indexOf(cipherName) === -1) {
- return new Error(
- `Cipher (${cipherName}) not supported `
- + 'for encrypted OpenSSH private key'
- );
- }
- const encInfo = CIPHER_INFO_OPENSSL[cipherName];
- if (!encInfo) {
- return new Error(
- `Cipher (${cipherName}) not supported `
- + 'for encrypted OpenSSH private key'
- );
- }
- const cipherIV = Buffer.from(val.slice(sepIdx + 1), 'hex');
- if (cipherIV.length !== encInfo.ivLen)
- return new Error('Malformed encrypted OpenSSH private key');
- if (!passphrase) {
- return new Error(
- 'Encrypted OpenSSH private key detected, but no passphrase given'
- );
- }
- const ivSlice = bufferSlice(cipherIV, 0, 8);
- let cipherKey = createHash('md5')
- .update(passphrase)
- .update(ivSlice)
- .digest();
- while (cipherKey.length < encInfo.keyLen) {
- cipherKey = combineBuffers(
- cipherKey,
- createHash('md5')
- .update(cipherKey)
- .update(passphrase)
- .update(ivSlice)
- .digest()
- );
- }
- if (cipherKey.length > encInfo.keyLen)
- cipherKey = bufferSlice(cipherKey, 0, encInfo.keyLen);
- try {
- const decipher = createDecipheriv(cipherName, cipherKey, cipherIV);
- decipher.setAutoPadding(false);
- privBlob = combineBuffers(decipher.update(privBlob),
- decipher.final());
- decrypted = true;
- } catch (ex) {
- return ex;
- }
- }
- }
- }
- let type;
- let privPEM;
- let pubPEM;
- let pubSSH;
- let algo;
- let reader;
- let errMsg = 'Malformed OpenSSH private key';
- if (decrypted)
- errMsg += '. Bad passphrase?';
- switch (m[1]) {
- case 'RSA':
- type = 'ssh-rsa';
- privPEM = makePEM('RSA PRIVATE', privBlob);
- try {
- reader = new Ber.Reader(privBlob);
- reader.readSequence();
- reader.readInt(); // skip version
- const n = reader.readString(Ber.Integer, true);
- if (n === null)
- return new Error(errMsg);
- const e = reader.readString(Ber.Integer, true);
- if (e === null)
- return new Error(errMsg);
- pubPEM = genOpenSSLRSAPub(n, e);
- pubSSH = genOpenSSHRSAPub(n, e);
- } catch {
- return new Error(errMsg);
- }
- algo = 'sha1';
- break;
- case 'DSA':
- type = 'ssh-dss';
- privPEM = makePEM('DSA PRIVATE', privBlob);
- try {
- reader = new Ber.Reader(privBlob);
- reader.readSequence();
- reader.readInt(); // skip version
- const p = reader.readString(Ber.Integer, true);
- if (p === null)
- return new Error(errMsg);
- const q = reader.readString(Ber.Integer, true);
- if (q === null)
- return new Error(errMsg);
- const g = reader.readString(Ber.Integer, true);
- if (g === null)
- return new Error(errMsg);
- const y = reader.readString(Ber.Integer, true);
- if (y === null)
- return new Error(errMsg);
- pubPEM = genOpenSSLDSAPub(p, q, g, y);
- pubSSH = genOpenSSHDSAPub(p, q, g, y);
- } catch {
- return new Error(errMsg);
- }
- algo = 'sha1';
- break;
- case 'EC':
- let ecSSLName;
- let ecPriv;
- let ecOID;
- try {
- reader = new Ber.Reader(privBlob);
- reader.readSequence();
- reader.readInt(); // skip version
- ecPriv = reader.readString(Ber.OctetString, true);
- reader.readByte(); // Skip "complex" context type byte
- const offset = reader.readLength(); // Skip context length
- if (offset !== null) {
- reader._offset = offset;
- ecOID = reader.readOID();
- if (ecOID === null)
- return new Error(errMsg);
- switch (ecOID) {
- case '1.2.840.10045.3.1.7':
- // prime256v1/secp256r1
- ecSSLName = 'prime256v1';
- type = 'ecdsa-sha2-nistp256';
- algo = 'sha256';
- break;
- case '1.3.132.0.34':
- // secp384r1
- ecSSLName = 'secp384r1';
- type = 'ecdsa-sha2-nistp384';
- algo = 'sha384';
- break;
- case '1.3.132.0.35':
- // secp521r1
- ecSSLName = 'secp521r1';
- type = 'ecdsa-sha2-nistp521';
- algo = 'sha512';
- break;
- default:
- return new Error(`Unsupported private key EC OID: ${ecOID}`);
- }
- } else {
- return new Error(errMsg);
- }
- } catch {
- return new Error(errMsg);
- }
- privPEM = makePEM('EC PRIVATE', privBlob);
- const pubBlob = genOpenSSLECDSAPubFromPriv(ecSSLName, ecPriv);
- pubPEM = genOpenSSLECDSAPub(ecOID, pubBlob);
- pubSSH = genOpenSSHECDSAPub(ecOID, pubBlob);
- break;
- }
- return new OpenSSH_Old_Private(type, '', privPEM, pubPEM, pubSSH, algo,
- decrypted);
- };
- }
- function PPK_Private(type, comment, privPEM, pubPEM, pubSSH, algo, decrypted) {
- this.type = type;
- this.comment = comment;
- this[SYM_PRIV_PEM] = privPEM;
- this[SYM_PUB_PEM] = pubPEM;
- this[SYM_PUB_SSH] = pubSSH;
- this[SYM_HASH_ALGO] = algo;
- this[SYM_DECRYPTED] = decrypted;
- }
- PPK_Private.prototype = BaseKey;
- {
- const EMPTY_PASSPHRASE = Buffer.alloc(0);
- const PPK_IV = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
- const PPK_PP1 = Buffer.from([0, 0, 0, 0]);
- const PPK_PP2 = Buffer.from([0, 0, 0, 1]);
- 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]+)/;
- PPK_Private.parse = (str, passphrase) => {
- const m = regexp.exec(str);
- if (m === null)
- return null;
- // m[1] = key type
- // m[2] = encryption type
- // m[3] = comment
- // m[4] = base64-encoded public key data:
- // for "ssh-rsa":
- // string "ssh-rsa"
- // mpint e (public exponent)
- // mpint n (modulus)
- // for "ssh-dss":
- // string "ssh-dss"
- // mpint p (modulus)
- // mpint q (prime)
- // mpint g (base number)
- // mpint y (public key parameter: g^x mod p)
- // m[5] = base64-encoded private key data:
- // for "ssh-rsa":
- // mpint d (private exponent)
- // mpint p (prime 1)
- // mpint q (prime 2)
- // mpint iqmp ([inverse of q] mod p)
- // for "ssh-dss":
- // mpint x (private key parameter)
- // m[6] = SHA1 HMAC over:
- // string name of algorithm ("ssh-dss", "ssh-rsa")
- // string encryption type
- // string comment
- // string public key data
- // string private-plaintext (including the final padding)
- const cipherName = m[2];
- const encrypted = (cipherName !== 'none');
- if (encrypted && !passphrase) {
- return new Error(
- 'Encrypted PPK private key detected, but no passphrase given'
- );
- }
- let privBlob = Buffer.from(m[5], 'base64');
- if (encrypted) {
- const encInfo = CIPHER_INFO[cipherName];
- let cipherKey = combineBuffers(
- createHash('sha1').update(PPK_PP1).update(passphrase).digest(),
- createHash('sha1').update(PPK_PP2).update(passphrase).digest()
- );
- if (cipherKey.length > encInfo.keyLen)
- cipherKey = bufferSlice(cipherKey, 0, encInfo.keyLen);
- try {
- const decipher = createDecipheriv(encInfo.sslName,
- cipherKey,
- PPK_IV);
- decipher.setAutoPadding(false);
- privBlob = combineBuffers(decipher.update(privBlob),
- decipher.final());
- } catch (ex) {
- return ex;
- }
- }
- const type = m[1];
- const comment = m[3];
- const pubBlob = Buffer.from(m[4], 'base64');
- const mac = m[6];
- const typeLen = type.length;
- const cipherNameLen = cipherName.length;
- const commentLen = Buffer.byteLength(comment);
- const pubLen = pubBlob.length;
- const privLen = privBlob.length;
- const macData = Buffer.allocUnsafe(4 + typeLen
- + 4 + cipherNameLen
- + 4 + commentLen
- + 4 + pubLen
- + 4 + privLen);
- let p = 0;
- writeUInt32BE(macData, typeLen, p);
- macData.utf8Write(type, p += 4, typeLen);
- writeUInt32BE(macData, cipherNameLen, p += typeLen);
- macData.utf8Write(cipherName, p += 4, cipherNameLen);
- writeUInt32BE(macData, commentLen, p += cipherNameLen);
- macData.utf8Write(comment, p += 4, commentLen);
- writeUInt32BE(macData, pubLen, p += commentLen);
- macData.set(pubBlob, p += 4);
- writeUInt32BE(macData, privLen, p += pubLen);
- macData.set(privBlob, p + 4);
- if (!passphrase)
- passphrase = EMPTY_PASSPHRASE;
- const calcMAC = createHmac(
- 'sha1',
- createHash('sha1')
- .update('putty-private-key-file-mac-key')
- .update(passphrase)
- .digest()
- ).update(macData).digest('hex');
- if (calcMAC !== mac) {
- if (encrypted) {
- return new Error(
- 'PPK private key integrity check failed -- bad passphrase?'
- );
- }
- return new Error('PPK private key integrity check failed');
- }
- let pubPEM;
- let pubSSH;
- let privPEM;
- pubBlob._pos = 0;
- skipFields(pubBlob, 1); // skip (duplicate) key type
- switch (type) {
- case 'ssh-rsa': {
- const e = readString(pubBlob, pubBlob._pos);
- if (e === undefined)
- return new Error('Malformed PPK public key');
- const n = readString(pubBlob, pubBlob._pos);
- if (n === undefined)
- return new Error('Malformed PPK public key');
- const d = readString(privBlob, 0);
- if (d === undefined)
- return new Error('Malformed PPK private key');
- const p = readString(privBlob, privBlob._pos);
- if (p === undefined)
- return new Error('Malformed PPK private key');
- const q = readString(privBlob, privBlob._pos);
- if (q === undefined)
- return new Error('Malformed PPK private key');
- const iqmp = readString(privBlob, privBlob._pos);
- if (iqmp === undefined)
- return new Error('Malformed PPK private key');
- pubPEM = genOpenSSLRSAPub(n, e);
- pubSSH = genOpenSSHRSAPub(n, e);
- privPEM = genOpenSSLRSAPriv(n, e, d, iqmp, p, q);
- break;
- }
- case 'ssh-dss': {
- const p = readString(pubBlob, pubBlob._pos);
- if (p === undefined)
- return new Error('Malformed PPK public key');
- const q = readString(pubBlob, pubBlob._pos);
- if (q === undefined)
- return new Error('Malformed PPK public key');
- const g = readString(pubBlob, pubBlob._pos);
- if (g === undefined)
- return new Error('Malformed PPK public key');
- const y = readString(pubBlob, pubBlob._pos);
- if (y === undefined)
- return new Error('Malformed PPK public key');
- const x = readString(privBlob, 0);
- if (x === undefined)
- return new Error('Malformed PPK private key');
- pubPEM = genOpenSSLDSAPub(p, q, g, y);
- pubSSH = genOpenSSHDSAPub(p, q, g, y);
- privPEM = genOpenSSLDSAPriv(p, q, g, y, x);
- break;
- }
- }
- return new PPK_Private(type, comment, privPEM, pubPEM, pubSSH, 'sha1',
- encrypted);
- };
- }
- function OpenSSH_Public(type, comment, pubPEM, pubSSH, algo) {
- this.type = type;
- this.comment = comment;
- this[SYM_PRIV_PEM] = null;
- this[SYM_PUB_PEM] = pubPEM;
- this[SYM_PUB_SSH] = pubSSH;
- this[SYM_HASH_ALGO] = algo;
- this[SYM_DECRYPTED] = false;
- }
- OpenSSH_Public.prototype = BaseKey;
- {
- let regexp;
- if (eddsaSupported)
- regexp = /^(((?:ssh-(?:rsa|dss|ed25519))|ecdsa-sha2-nistp(?:256|384|521))(?:-cert-v0[01]@openssh.com)?) ([A-Z0-9a-z/+=]+)(?:$|\s+([\S].*)?)$/;
- else
- regexp = /^(((?:ssh-(?:rsa|dss))|ecdsa-sha2-nistp(?:256|384|521))(?:-cert-v0[01]@openssh.com)?) ([A-Z0-9a-z/+=]+)(?:$|\s+([\S].*)?)$/;
- OpenSSH_Public.parse = (str) => {
- const m = regexp.exec(str);
- if (m === null)
- return null;
- // m[1] = full type
- // m[2] = base type
- // m[3] = base64-encoded public key
- // m[4] = comment
- const fullType = m[1];
- const baseType = m[2];
- const data = Buffer.from(m[3], 'base64');
- const comment = (m[4] || '');
- const type = readString(data, data._pos, true);
- if (type === undefined || type.indexOf(baseType) !== 0)
- return new Error('Malformed OpenSSH public key');
- return parseDER(data, baseType, comment, fullType);
- };
- }
- function RFC4716_Public(type, comment, pubPEM, pubSSH, algo) {
- this.type = type;
- this.comment = comment;
- this[SYM_PRIV_PEM] = null;
- this[SYM_PUB_PEM] = pubPEM;
- this[SYM_PUB_SSH] = pubSSH;
- this[SYM_HASH_ALGO] = algo;
- this[SYM_DECRYPTED] = false;
- }
- RFC4716_Public.prototype = BaseKey;
- {
- const regexp = /^---- BEGIN SSH2 PUBLIC KEY ----(?:\r?\n)((?:.{0,72}\r?\n)+)---- END SSH2 PUBLIC KEY ----$/;
- const RE_DATA = /^[A-Z0-9a-z/+=\r\n]+$/;
- const RE_HEADER = /^([\x21-\x39\x3B-\x7E]{1,64}): ((?:[^\\]*\\\r?\n)*[^\r\n]+)\r?\n/gm;
- const RE_HEADER_ENDS = /\\\r?\n/g;
- RFC4716_Public.parse = (str) => {
- let m = regexp.exec(str);
- if (m === null)
- return null;
- const body = m[1];
- let dataStart = 0;
- let comment = '';
- while (m = RE_HEADER.exec(body)) {
- const headerName = m[1];
- const headerValue = m[2].replace(RE_HEADER_ENDS, '');
- if (headerValue.length > 1024) {
- RE_HEADER.lastIndex = 0;
- return new Error('Malformed RFC4716 public key');
- }
- dataStart = RE_HEADER.lastIndex;
- if (headerName.toLowerCase() === 'comment') {
- comment = headerValue;
- if (comment.length > 1
- && comment.charCodeAt(0) === 34/* '"' */
- && comment.charCodeAt(comment.length - 1) === 34/* '"' */) {
- comment = comment.slice(1, -1);
- }
- }
- }
- let data = body.slice(dataStart);
- if (!RE_DATA.test(data))
- return new Error('Malformed RFC4716 public key');
- data = Buffer.from(data, 'base64');
- const type = readString(data, 0, true);
- if (type === undefined)
- return new Error('Malformed RFC4716 public key');
- let pubPEM = null;
- let pubSSH = null;
- switch (type) {
- case 'ssh-rsa': {
- const e = readString(data, data._pos);
- if (e === undefined)
- return new Error('Malformed RFC4716 public key');
- const n = readString(data, data._pos);
- if (n === undefined)
- return new Error('Malformed RFC4716 public key');
- pubPEM = genOpenSSLRSAPub(n, e);
- pubSSH = genOpenSSHRSAPub(n, e);
- break;
- }
- case 'ssh-dss': {
- const p = readString(data, data._pos);
- if (p === undefined)
- return new Error('Malformed RFC4716 public key');
- const q = readString(data, data._pos);
- if (q === undefined)
- return new Error('Malformed RFC4716 public key');
- const g = readString(data, data._pos);
- if (g === undefined)
- return new Error('Malformed RFC4716 public key');
- const y = readString(data, data._pos);
- if (y === undefined)
- return new Error('Malformed RFC4716 public key');
- pubPEM = genOpenSSLDSAPub(p, q, g, y);
- pubSSH = genOpenSSHDSAPub(p, q, g, y);
- break;
- }
- default:
- return new Error('Malformed RFC4716 public key');
- }
- return new RFC4716_Public(type, comment, pubPEM, pubSSH, 'sha1');
- };
- }
- function parseDER(data, baseType, comment, fullType) {
- if (!isSupportedKeyType(baseType))
- return new Error(`Unsupported OpenSSH public key type: ${baseType}`);
- let algo;
- let oid;
- let pubPEM = null;
- let pubSSH = null;
- switch (baseType) {
- case 'ssh-rsa': {
- const e = readString(data, data._pos || 0);
- if (e === undefined)
- return new Error('Malformed OpenSSH public key');
- const n = readString(data, data._pos);
- if (n === undefined)
- return new Error('Malformed OpenSSH public key');
- pubPEM = genOpenSSLRSAPub(n, e);
- pubSSH = genOpenSSHRSAPub(n, e);
- algo = 'sha1';
- break;
- }
- case 'ssh-dss': {
- const p = readString(data, data._pos || 0);
- if (p === undefined)
- return new Error('Malformed OpenSSH public key');
- const q = readString(data, data._pos);
- if (q === undefined)
- return new Error('Malformed OpenSSH public key');
- const g = readString(data, data._pos);
- if (g === undefined)
- return new Error('Malformed OpenSSH public key');
- const y = readString(data, data._pos);
- if (y === undefined)
- return new Error('Malformed OpenSSH public key');
- pubPEM = genOpenSSLDSAPub(p, q, g, y);
- pubSSH = genOpenSSHDSAPub(p, q, g, y);
- algo = 'sha1';
- break;
- }
- case 'ssh-ed25519': {
- const edpub = readString(data, data._pos || 0);
- if (edpub === undefined || edpub.length !== 32)
- return new Error('Malformed OpenSSH public key');
- pubPEM = genOpenSSLEdPub(edpub);
- pubSSH = genOpenSSHEdPub(edpub);
- algo = null;
- break;
- }
- case 'ecdsa-sha2-nistp256':
- algo = 'sha256';
- oid = '1.2.840.10045.3.1.7';
- // FALLTHROUGH
- case 'ecdsa-sha2-nistp384':
- if (algo === undefined) {
- algo = 'sha384';
- oid = '1.3.132.0.34';
- }
- // FALLTHROUGH
- case 'ecdsa-sha2-nistp521': {
- if (algo === undefined) {
- algo = 'sha512';
- oid = '1.3.132.0.35';
- }
- // TODO: validate curve name against type
- if (!skipFields(data, 1)) // Skip curve name
- return new Error('Malformed OpenSSH public key');
- const ecpub = readString(data, data._pos || 0);
- if (ecpub === undefined)
- return new Error('Malformed OpenSSH public key');
- pubPEM = genOpenSSLECDSAPub(oid, ecpub);
- pubSSH = genOpenSSHECDSAPub(oid, ecpub);
- break;
- }
- default:
- return new Error(`Unsupported OpenSSH public key type: ${baseType}`);
- }
- return new OpenSSH_Public(fullType, comment, pubPEM, pubSSH, algo);
- }
- function isSupportedKeyType(type) {
- switch (type) {
- case 'ssh-rsa':
- case 'ssh-dss':
- case 'ecdsa-sha2-nistp256':
- case 'ecdsa-sha2-nistp384':
- case 'ecdsa-sha2-nistp521':
- return true;
- case 'ssh-ed25519':
- if (eddsaSupported)
- return true;
- // FALLTHROUGH
- default:
- return false;
- }
- }
- function isParsedKey(val) {
- if (!val)
- return false;
- return (typeof val[SYM_DECRYPTED] === 'boolean');
- }
- function parseKey(data, passphrase) {
- if (isParsedKey(data))
- return data;
- let origBuffer;
- if (Buffer.isBuffer(data)) {
- origBuffer = data;
- data = data.utf8Slice(0, data.length).trim();
- } else if (typeof data === 'string') {
- data = data.trim();
- } else {
- return new Error('Key data must be a Buffer or string');
- }
- // eslint-disable-next-line eqeqeq
- if (passphrase != undefined) {
- if (typeof passphrase === 'string')
- passphrase = Buffer.from(passphrase);
- else if (!Buffer.isBuffer(passphrase))
- return new Error('Passphrase must be a string or Buffer when supplied');
- }
- let ret;
- // First try as printable string format (e.g. PEM)
- // Private keys
- if ((ret = OpenSSH_Private.parse(data, passphrase)) !== null)
- return ret;
- if ((ret = OpenSSH_Old_Private.parse(data, passphrase)) !== null)
- return ret;
- if ((ret = PPK_Private.parse(data, passphrase)) !== null)
- return ret;
- // Public keys
- if ((ret = OpenSSH_Public.parse(data)) !== null)
- return ret;
- if ((ret = RFC4716_Public.parse(data)) !== null)
- return ret;
- // Finally try as a binary format if we were originally passed binary data
- if (origBuffer) {
- binaryKeyParser.init(origBuffer, 0);
- const type = binaryKeyParser.readString(true);
- if (type !== undefined) {
- data = binaryKeyParser.readRaw();
- if (data !== undefined) {
- ret = parseDER(data, type, '', type);
- // Ignore potentially useless errors in case the data was not actually
- // in the binary format
- if (ret instanceof Error)
- ret = null;
- }
- }
- binaryKeyParser.clear();
- }
- if (ret)
- return ret;
- return new Error('Unsupported key format');
- }
- module.exports = {
- isParsedKey,
- isSupportedKeyType,
- parseDERKey: (data, type) => parseDER(data, type, '', type),
- parseKey,
- };
|