dnssec.js 8.9 KB

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