dnssec.js (9193B)
1 // Copyright 2017 Joyent, Inc. 2 3 module.exports = { 4 read: read, 5 write: write 6 }; 7 8 var assert = require('assert-plus'); 9 var Buffer = require('safer-buffer').Buffer; 10 var Key = require('../key'); 11 var PrivateKey = require('../private-key'); 12 var utils = require('../utils'); 13 var SSHBuffer = require('../ssh-buffer'); 14 var Dhe = require('../dhe'); 15 16 var supportedAlgos = { 17 'rsa-sha1' : 5, 18 'rsa-sha256' : 8, 19 'rsa-sha512' : 10, 20 'ecdsa-p256-sha256' : 13, 21 'ecdsa-p384-sha384' : 14 22 /* 23 * ed25519 is hypothetically supported with id 15 24 * but the common tools available don't appear to be 25 * capable of generating/using ed25519 keys 26 */ 27 }; 28 29 var supportedAlgosById = {}; 30 Object.keys(supportedAlgos).forEach(function (k) { 31 supportedAlgosById[supportedAlgos[k]] = k.toUpperCase(); 32 }); 33 34 function read(buf, options) { 35 if (typeof (buf) !== 'string') { 36 assert.buffer(buf, 'buf'); 37 buf = buf.toString('ascii'); 38 } 39 var lines = buf.split('\n'); 40 if (lines[0].match(/^Private-key-format\: v1/)) { 41 var algElems = lines[1].split(' '); 42 var algoNum = parseInt(algElems[1], 10); 43 var algoName = algElems[2]; 44 if (!supportedAlgosById[algoNum]) 45 throw (new Error('Unsupported algorithm: ' + algoName)); 46 return (readDNSSECPrivateKey(algoNum, lines.slice(2))); 47 } 48 49 // skip any comment-lines 50 var line = 0; 51 /* JSSTYLED */ 52 while (lines[line].match(/^\;/)) 53 line++; 54 // we should now have *one single* line left with our KEY on it. 55 if ((lines[line].match(/\. IN KEY /) || 56 lines[line].match(/\. IN DNSKEY /)) && lines[line+1].length === 0) { 57 return (readRFC3110(lines[line])); 58 } 59 throw (new Error('Cannot parse dnssec key')); 60 } 61 62 function readRFC3110(keyString) { 63 var elems = keyString.split(' '); 64 //unused var flags = parseInt(elems[3], 10); 65 //unused var protocol = parseInt(elems[4], 10); 66 var algorithm = parseInt(elems[5], 10); 67 if (!supportedAlgosById[algorithm]) 68 throw (new Error('Unsupported algorithm: ' + algorithm)); 69 var base64key = elems.slice(6, elems.length).join(); 70 var keyBuffer = Buffer.from(base64key, 'base64'); 71 if (supportedAlgosById[algorithm].match(/^RSA-/)) { 72 // join the rest of the body into a single base64-blob 73 var publicExponentLen = keyBuffer.readUInt8(0); 74 if (publicExponentLen != 3 && publicExponentLen != 1) 75 throw (new Error('Cannot parse dnssec key: ' + 76 'unsupported exponent length')); 77 78 var publicExponent = keyBuffer.slice(1, publicExponentLen+1); 79 publicExponent = utils.mpNormalize(publicExponent); 80 var modulus = keyBuffer.slice(1+publicExponentLen); 81 modulus = utils.mpNormalize(modulus); 82 // now, make the key 83 var rsaKey = { 84 type: 'rsa', 85 parts: [] 86 }; 87 rsaKey.parts.push({ name: 'e', data: publicExponent}); 88 rsaKey.parts.push({ name: 'n', data: modulus}); 89 return (new Key(rsaKey)); 90 } 91 if (supportedAlgosById[algorithm] === 'ECDSA-P384-SHA384' || 92 supportedAlgosById[algorithm] === 'ECDSA-P256-SHA256') { 93 var curve = 'nistp384'; 94 var size = 384; 95 if (supportedAlgosById[algorithm].match(/^ECDSA-P256-SHA256/)) { 96 curve = 'nistp256'; 97 size = 256; 98 } 99 100 var ecdsaKey = { 101 type: 'ecdsa', 102 curve: curve, 103 size: size, 104 parts: [ 105 {name: 'curve', data: Buffer.from(curve) }, 106 {name: 'Q', data: utils.ecNormalize(keyBuffer) } 107 ] 108 }; 109 return (new Key(ecdsaKey)); 110 } 111 throw (new Error('Unsupported algorithm: ' + 112 supportedAlgosById[algorithm])); 113 } 114 115 function elementToBuf(e) { 116 return (Buffer.from(e.split(' ')[1], 'base64')); 117 } 118 119 function readDNSSECRSAPrivateKey(elements) { 120 var rsaParams = {}; 121 elements.forEach(function (element) { 122 if (element.split(' ')[0] === 'Modulus:') 123 rsaParams['n'] = elementToBuf(element); 124 else if (element.split(' ')[0] === 'PublicExponent:') 125 rsaParams['e'] = elementToBuf(element); 126 else if (element.split(' ')[0] === 'PrivateExponent:') 127 rsaParams['d'] = elementToBuf(element); 128 else if (element.split(' ')[0] === 'Prime1:') 129 rsaParams['p'] = elementToBuf(element); 130 else if (element.split(' ')[0] === 'Prime2:') 131 rsaParams['q'] = elementToBuf(element); 132 else if (element.split(' ')[0] === 'Exponent1:') 133 rsaParams['dmodp'] = elementToBuf(element); 134 else if (element.split(' ')[0] === 'Exponent2:') 135 rsaParams['dmodq'] = elementToBuf(element); 136 else if (element.split(' ')[0] === 'Coefficient:') 137 rsaParams['iqmp'] = elementToBuf(element); 138 }); 139 // now, make the key 140 var key = { 141 type: 'rsa', 142 parts: [ 143 { name: 'e', data: utils.mpNormalize(rsaParams['e'])}, 144 { name: 'n', data: utils.mpNormalize(rsaParams['n'])}, 145 { name: 'd', data: utils.mpNormalize(rsaParams['d'])}, 146 { name: 'p', data: utils.mpNormalize(rsaParams['p'])}, 147 { name: 'q', data: utils.mpNormalize(rsaParams['q'])}, 148 { name: 'dmodp', 149 data: utils.mpNormalize(rsaParams['dmodp'])}, 150 { name: 'dmodq', 151 data: utils.mpNormalize(rsaParams['dmodq'])}, 152 { name: 'iqmp', 153 data: utils.mpNormalize(rsaParams['iqmp'])} 154 ] 155 }; 156 return (new PrivateKey(key)); 157 } 158 159 function readDNSSECPrivateKey(alg, elements) { 160 if (supportedAlgosById[alg].match(/^RSA-/)) { 161 return (readDNSSECRSAPrivateKey(elements)); 162 } 163 if (supportedAlgosById[alg] === 'ECDSA-P384-SHA384' || 164 supportedAlgosById[alg] === 'ECDSA-P256-SHA256') { 165 var d = Buffer.from(elements[0].split(' ')[1], 'base64'); 166 var curve = 'nistp384'; 167 var size = 384; 168 if (supportedAlgosById[alg] === 'ECDSA-P256-SHA256') { 169 curve = 'nistp256'; 170 size = 256; 171 } 172 // DNSSEC generates the public-key on the fly (go calculate it) 173 var publicKey = utils.publicFromPrivateECDSA(curve, d); 174 var Q = publicKey.part['Q'].data; 175 var ecdsaKey = { 176 type: 'ecdsa', 177 curve: curve, 178 size: size, 179 parts: [ 180 {name: 'curve', data: Buffer.from(curve) }, 181 {name: 'd', data: d }, 182 {name: 'Q', data: Q } 183 ] 184 }; 185 return (new PrivateKey(ecdsaKey)); 186 } 187 throw (new Error('Unsupported algorithm: ' + supportedAlgosById[alg])); 188 } 189 190 function dnssecTimestamp(date) { 191 var year = date.getFullYear() + ''; //stringify 192 var month = (date.getMonth() + 1); 193 var timestampStr = year + month + date.getUTCDate(); 194 timestampStr += '' + date.getUTCHours() + date.getUTCMinutes(); 195 timestampStr += date.getUTCSeconds(); 196 return (timestampStr); 197 } 198 199 function rsaAlgFromOptions(opts) { 200 if (!opts || !opts.hashAlgo || opts.hashAlgo === 'sha1') 201 return ('5 (RSASHA1)'); 202 else if (opts.hashAlgo === 'sha256') 203 return ('8 (RSASHA256)'); 204 else if (opts.hashAlgo === 'sha512') 205 return ('10 (RSASHA512)'); 206 else 207 throw (new Error('Unknown or unsupported hash: ' + 208 opts.hashAlgo)); 209 } 210 211 function writeRSA(key, options) { 212 // if we're missing parts, add them. 213 if (!key.part.dmodp || !key.part.dmodq) { 214 utils.addRSAMissing(key); 215 } 216 217 var out = ''; 218 out += 'Private-key-format: v1.3\n'; 219 out += 'Algorithm: ' + rsaAlgFromOptions(options) + '\n'; 220 var n = utils.mpDenormalize(key.part['n'].data); 221 out += 'Modulus: ' + n.toString('base64') + '\n'; 222 var e = utils.mpDenormalize(key.part['e'].data); 223 out += 'PublicExponent: ' + e.toString('base64') + '\n'; 224 var d = utils.mpDenormalize(key.part['d'].data); 225 out += 'PrivateExponent: ' + d.toString('base64') + '\n'; 226 var p = utils.mpDenormalize(key.part['p'].data); 227 out += 'Prime1: ' + p.toString('base64') + '\n'; 228 var q = utils.mpDenormalize(key.part['q'].data); 229 out += 'Prime2: ' + q.toString('base64') + '\n'; 230 var dmodp = utils.mpDenormalize(key.part['dmodp'].data); 231 out += 'Exponent1: ' + dmodp.toString('base64') + '\n'; 232 var dmodq = utils.mpDenormalize(key.part['dmodq'].data); 233 out += 'Exponent2: ' + dmodq.toString('base64') + '\n'; 234 var iqmp = utils.mpDenormalize(key.part['iqmp'].data); 235 out += 'Coefficient: ' + iqmp.toString('base64') + '\n'; 236 // Assume that we're valid as-of now 237 var timestamp = new Date(); 238 out += 'Created: ' + dnssecTimestamp(timestamp) + '\n'; 239 out += 'Publish: ' + dnssecTimestamp(timestamp) + '\n'; 240 out += 'Activate: ' + dnssecTimestamp(timestamp) + '\n'; 241 return (Buffer.from(out, 'ascii')); 242 } 243 244 function writeECDSA(key, options) { 245 var out = ''; 246 out += 'Private-key-format: v1.3\n'; 247 248 if (key.curve === 'nistp256') { 249 out += 'Algorithm: 13 (ECDSAP256SHA256)\n'; 250 } else if (key.curve === 'nistp384') { 251 out += 'Algorithm: 14 (ECDSAP384SHA384)\n'; 252 } else { 253 throw (new Error('Unsupported curve')); 254 } 255 var base64Key = key.part['d'].data.toString('base64'); 256 out += 'PrivateKey: ' + base64Key + '\n'; 257 258 // Assume that we're valid as-of now 259 var timestamp = new Date(); 260 out += 'Created: ' + dnssecTimestamp(timestamp) + '\n'; 261 out += 'Publish: ' + dnssecTimestamp(timestamp) + '\n'; 262 out += 'Activate: ' + dnssecTimestamp(timestamp) + '\n'; 263 264 return (Buffer.from(out, 'ascii')); 265 } 266 267 function write(key, options) { 268 if (PrivateKey.isPrivateKey(key)) { 269 if (key.type === 'rsa') { 270 return (writeRSA(key, options)); 271 } else if (key.type === 'ecdsa') { 272 return (writeECDSA(key, options)); 273 } else { 274 throw (new Error('Unsupported algorithm: ' + key.type)); 275 } 276 } else if (Key.isKey(key)) { 277 /* 278 * RFC3110 requires a keyname, and a keytype, which we 279 * don't really have a mechanism for specifying such 280 * additional metadata. 281 */ 282 throw (new Error('Format "dnssec" only supports ' + 283 'writing private keys')); 284 } else { 285 throw (new Error('key is not a Key or PrivateKey')); 286 } 287 }