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