1// Copyright 2017 Joyent, Inc. 2 3module.exports = PrivateKey; 4 5var assert = require('assert-plus'); 6var Buffer = require('safer-buffer').Buffer; 7var algs = require('./algs'); 8var crypto = require('crypto'); 9var Fingerprint = require('./fingerprint'); 10var Signature = require('./signature'); 11var errs = require('./errors'); 12var util = require('util'); 13var utils = require('./utils'); 14var dhe = require('./dhe'); 15var generateECDSA = dhe.generateECDSA; 16var generateED25519 = dhe.generateED25519; 17var edCompat; 18var nacl; 19 20try { 21 edCompat = require('./ed-compat'); 22} catch (e) { 23 /* Just continue through, and bail out if we try to use it. */ 24} 25 26var Key = require('./key'); 27 28var InvalidAlgorithmError = errs.InvalidAlgorithmError; 29var KeyParseError = errs.KeyParseError; 30var KeyEncryptedError = errs.KeyEncryptedError; 31 32var formats = {}; 33formats['auto'] = require('./formats/auto'); 34formats['pem'] = require('./formats/pem'); 35formats['pkcs1'] = require('./formats/pkcs1'); 36formats['pkcs8'] = require('./formats/pkcs8'); 37formats['rfc4253'] = require('./formats/rfc4253'); 38formats['ssh-private'] = require('./formats/ssh-private'); 39formats['openssh'] = formats['ssh-private']; 40formats['ssh'] = formats['ssh-private']; 41formats['dnssec'] = require('./formats/dnssec'); 42 43function PrivateKey(opts) { 44 assert.object(opts, 'options'); 45 Key.call(this, opts); 46 47 this._pubCache = undefined; 48} 49util.inherits(PrivateKey, Key); 50 51PrivateKey.formats = formats; 52 53PrivateKey.prototype.toBuffer = function (format, options) { 54 if (format === undefined) 55 format = 'pkcs1'; 56 assert.string(format, 'format'); 57 assert.object(formats[format], 'formats[format]'); 58 assert.optionalObject(options, 'options'); 59 60 return (formats[format].write(this, options)); 61}; 62 63PrivateKey.prototype.hash = function (algo) { 64 return (this.toPublic().hash(algo)); 65}; 66 67PrivateKey.prototype.toPublic = function () { 68 if (this._pubCache) 69 return (this._pubCache); 70 71 var algInfo = algs.info[this.type]; 72 var pubParts = []; 73 for (var i = 0; i < algInfo.parts.length; ++i) { 74 var p = algInfo.parts[i]; 75 pubParts.push(this.part[p]); 76 } 77 78 this._pubCache = new Key({ 79 type: this.type, 80 source: this, 81 parts: pubParts 82 }); 83 if (this.comment) 84 this._pubCache.comment = this.comment; 85 return (this._pubCache); 86}; 87 88PrivateKey.prototype.derive = function (newType) { 89 assert.string(newType, 'type'); 90 var priv, pub, pair; 91 92 if (this.type === 'ed25519' && newType === 'curve25519') { 93 if (nacl === undefined) 94 nacl = require('tweetnacl'); 95 96 priv = this.part.k.data; 97 if (priv[0] === 0x00) 98 priv = priv.slice(1); 99 100 pair = nacl.box.keyPair.fromSecretKey(new Uint8Array(priv)); 101 pub = Buffer.from(pair.publicKey); 102 103 return (new PrivateKey({ 104 type: 'curve25519', 105 parts: [ 106 { name: 'A', data: utils.mpNormalize(pub) }, 107 { name: 'k', data: utils.mpNormalize(priv) } 108 ] 109 })); 110 } else if (this.type === 'curve25519' && newType === 'ed25519') { 111 if (nacl === undefined) 112 nacl = require('tweetnacl'); 113 114 priv = this.part.k.data; 115 if (priv[0] === 0x00) 116 priv = priv.slice(1); 117 118 pair = nacl.sign.keyPair.fromSeed(new Uint8Array(priv)); 119 pub = Buffer.from(pair.publicKey); 120 121 return (new PrivateKey({ 122 type: 'ed25519', 123 parts: [ 124 { name: 'A', data: utils.mpNormalize(pub) }, 125 { name: 'k', data: utils.mpNormalize(priv) } 126 ] 127 })); 128 } 129 throw (new Error('Key derivation not supported from ' + this.type + 130 ' to ' + newType)); 131}; 132 133PrivateKey.prototype.createVerify = function (hashAlgo) { 134 return (this.toPublic().createVerify(hashAlgo)); 135}; 136 137PrivateKey.prototype.createSign = function (hashAlgo) { 138 if (hashAlgo === undefined) 139 hashAlgo = this.defaultHashAlgorithm(); 140 assert.string(hashAlgo, 'hash algorithm'); 141 142 /* ED25519 is not supported by OpenSSL, use a javascript impl. */ 143 if (this.type === 'ed25519' && edCompat !== undefined) 144 return (new edCompat.Signer(this, hashAlgo)); 145 if (this.type === 'curve25519') 146 throw (new Error('Curve25519 keys are not suitable for ' + 147 'signing or verification')); 148 149 var v, nm, err; 150 try { 151 nm = hashAlgo.toUpperCase(); 152 v = crypto.createSign(nm); 153 } catch (e) { 154 err = e; 155 } 156 if (v === undefined || (err instanceof Error && 157 err.message.match(/Unknown message digest/))) { 158 nm = 'RSA-'; 159 nm += hashAlgo.toUpperCase(); 160 v = crypto.createSign(nm); 161 } 162 assert.ok(v, 'failed to create verifier'); 163 var oldSign = v.sign.bind(v); 164 var key = this.toBuffer('pkcs1'); 165 var type = this.type; 166 var curve = this.curve; 167 v.sign = function () { 168 var sig = oldSign(key); 169 if (typeof (sig) === 'string') 170 sig = Buffer.from(sig, 'binary'); 171 sig = Signature.parse(sig, type, 'asn1'); 172 sig.hashAlgorithm = hashAlgo; 173 sig.curve = curve; 174 return (sig); 175 }; 176 return (v); 177}; 178 179PrivateKey.parse = function (data, format, options) { 180 if (typeof (data) !== 'string') 181 assert.buffer(data, 'data'); 182 if (format === undefined) 183 format = 'auto'; 184 assert.string(format, 'format'); 185 if (typeof (options) === 'string') 186 options = { filename: options }; 187 assert.optionalObject(options, 'options'); 188 if (options === undefined) 189 options = {}; 190 assert.optionalString(options.filename, 'options.filename'); 191 if (options.filename === undefined) 192 options.filename = '(unnamed)'; 193 194 assert.object(formats[format], 'formats[format]'); 195 196 try { 197 var k = formats[format].read(data, options); 198 assert.ok(k instanceof PrivateKey, 'key is not a private key'); 199 if (!k.comment) 200 k.comment = options.filename; 201 return (k); 202 } catch (e) { 203 if (e.name === 'KeyEncryptedError') 204 throw (e); 205 throw (new KeyParseError(options.filename, format, e)); 206 } 207}; 208 209PrivateKey.isPrivateKey = function (obj, ver) { 210 return (utils.isCompatible(obj, PrivateKey, ver)); 211}; 212 213PrivateKey.generate = function (type, options) { 214 if (options === undefined) 215 options = {}; 216 assert.object(options, 'options'); 217 218 switch (type) { 219 case 'ecdsa': 220 if (options.curve === undefined) 221 options.curve = 'nistp256'; 222 assert.string(options.curve, 'options.curve'); 223 return (generateECDSA(options.curve)); 224 case 'ed25519': 225 return (generateED25519()); 226 default: 227 throw (new Error('Key generation not supported with key ' + 228 'type "' + type + '"')); 229 } 230}; 231 232/* 233 * API versions for PrivateKey: 234 * [1,0] -- initial ver 235 * [1,1] -- added auto, pkcs[18], openssh/ssh-private formats 236 * [1,2] -- added defaultHashAlgorithm 237 * [1,3] -- added derive, ed, createDH 238 * [1,4] -- first tagged version 239 * [1,5] -- changed ed25519 part names and format 240 */ 241PrivateKey.prototype._sshpkApiVersion = [1, 5]; 242 243PrivateKey._oldVersionDetect = function (obj) { 244 assert.func(obj.toPublic); 245 assert.func(obj.createSign); 246 if (obj.derive) 247 return ([1, 3]); 248 if (obj.defaultHashAlgorithm) 249 return ([1, 2]); 250 if (obj.formats['auto']) 251 return ([1, 1]); 252 return ([1, 0]); 253}; 254