214 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Copyright 2016-2018 AJ ONeal. All rights reserved
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 'use strict';
 | |
| 
 | |
| var RSA = module.exports;
 | |
| RSA.utils = {};
 | |
| 
 | |
| try {
 | |
|   require('buffer-v6-polyfill');
 | |
| } catch(e) {
 | |
|   /* ignore */
 | |
| }
 | |
| 
 | |
| var Rasha = require('./rasha');
 | |
| var RSACSR = require('./rsa-csr');
 | |
| var NOBJ = {};
 | |
| var DEFAULT_BITLEN = 2048;
 | |
| var DEFAULT_EXPONENT = 0x10001;
 | |
| 
 | |
| RSA.generateKeypair = function (options, cb, extra1, extra2) {
 | |
|   var length;
 | |
|   var exponent;
 | |
|   if ('function' === typeof extra2) {
 | |
|     length = options || DEFAULT_BITLEN;
 | |
|     exponent = cb || DEFAULT_EXPONENT;
 | |
|     options = extra1 || NOBJ;
 | |
|     cb = extra2;
 | |
|   } else {
 | |
|     if (!options) { options = NOBJ; }
 | |
|     length = options.bitlen || DEFAULT_BITLEN;
 | |
|     exponent = options.exp || DEFAULT_EXPONENT;
 | |
|   }
 | |
| 
 | |
|   try {
 | |
|     var keypair = require('./generate-privkey.js')(length, exponent);
 | |
|     keypair.thumbprint = RSA.thumbprint(keypair);
 | |
|     cb(null, keypair);
 | |
|   } catch(e) {
 | |
|     cb(e);
 | |
|   }
 | |
| };
 | |
| 
 | |
| RSA.import = function (options) {
 | |
|   options = JSON.parse(JSON.stringify(options));
 | |
| 
 | |
|   // Private Keys
 | |
|   if (options.privateKeyPem) {
 | |
|     if (!options.privateKeyJwk) {
 | |
|       options.privateKeyJwk = Rasha.importSync({ pem: options.privateKeyPem });
 | |
|     }
 | |
|   }
 | |
|   if (options.privateKeyJwk) {
 | |
|     if (!options.privateKeyPem) {
 | |
|       options.privateKeyPem = Rasha.exportSync({
 | |
|         jwk: options.privateKeyJwk
 | |
|       , format: options.format || 'pkcs1'
 | |
|       , encoding: options.encoding || 'pem'
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Public Keys
 | |
|   if (options.publicKeyPem || options.privateKeyPem) {
 | |
|     if (!options.publicKeyJwk) {
 | |
|       options.publicKeyJwk = Rasha.importSync({
 | |
|         pem: options.publicKeyPem || options.privateKeyPem
 | |
|       , public: true
 | |
|       });
 | |
|     }
 | |
|   }
 | |
|   if (options.publicKeyJwk || options.privateKeyJwk) {
 | |
|     if (!options.publicKeyPem) {
 | |
|       options.publicKeyPem = Rasha.exportSync({
 | |
|         jwk: options.publicKeyJwk || options.privateKeyJwk
 | |
|       , format: options.format || 'pkcs1'
 | |
|       , encoding: options.encoding || 'pem'
 | |
|       , public: true
 | |
|       });
 | |
|     }
 | |
|   }
 | |
|   if (!options.publicKeyPem) {
 | |
|     throw new Error("Error: no keys were present to import");
 | |
|   }
 | |
| 
 | |
|   // Consistent CRLF
 | |
|   if (options.privateKeyPem) {
 | |
|     options.privateKeyPem = options.privateKeyPem
 | |
|       .trim().replace(/[\r\n]+/g, '\r\n') + '\r\n';
 | |
|   }
 | |
|   options.publicKeyPem = options.publicKeyPem
 | |
|     .trim().replace(/[\r\n]+/g, '\r\n') + '\r\n';
 | |
| 
 | |
|   // Thumbprint
 | |
|   if (!options.thumbprint) {
 | |
|     options.thumbprint = RSA._thumbprint(options);
 | |
|   }
 | |
| 
 | |
|   return options;
 | |
| };
 | |
| 
 | |
| RSA.exportPrivatePem = function (keypair) {
 | |
|   keypair = RSA.import(keypair);
 | |
|   return keypair.privateKeyPem;
 | |
| };
 | |
| RSA.exportPublicPem = function (keypair) {
 | |
|   keypair = RSA.import(keypair);
 | |
|   return keypair.publicKeyPem;
 | |
| };
 | |
| 
 | |
| RSA.exportPrivateJwk = function (keypair) {
 | |
|   keypair = RSA.import(keypair);
 | |
|   return keypair.privateKeyJwk;
 | |
| };
 | |
| RSA.exportPublicJwk = function (keypair) {
 | |
|   if (!keypair.publicKeyJwk) {
 | |
|     keypair = RSA.import(keypair);
 | |
|   }
 | |
|   return keypair.publicKeyJwk;
 | |
| };
 | |
| 
 | |
| RSA.signJws = RSA.generateJws = RSA.generateSignatureJws = RSA.generateSignatureJwk =
 | |
| function (keypair, header, protect, payload) {
 | |
| // old   (keypair, payload, nonce)
 | |
|   var nonce;
 | |
| 
 | |
|   keypair = RSA.import(keypair);
 | |
|   keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
 | |
| 
 | |
|   if ('string' === typeof protect || ('undefined' === typeof protect && 'undefined' === typeof payload)) {
 | |
|     console.warn("deprecation notice: new signature for signJws(keypair, header, protect, payload)");
 | |
|     // old API
 | |
|     payload = header;
 | |
|     nonce = protect;
 | |
|     protect = undefined;
 | |
|     header = {
 | |
|       alg: "RS256"
 | |
|     , jwk: keypair.publicKeyJwk
 | |
|     };
 | |
|     protect = { nonce: nonce };
 | |
|   }
 | |
| 
 | |
|   // Compute JWS signature
 | |
|   var protectedHeader = "";
 | |
|   if (protect) {
 | |
|     protectedHeader = JSON.stringify(protect); // { alg: prot.alg, nonce: prot.nonce, url: prot.url });
 | |
|   }
 | |
|   var protected64 = RSA.utils.toWebsafeBase64(Buffer.from(protectedHeader).toString('base64'));
 | |
|   var payload64 = RSA.utils.toWebsafeBase64(payload.toString('base64'));
 | |
|   var raw = protected64 + "." + payload64;
 | |
|   var pem = RSA.exportPrivatePem(keypair);
 | |
|   var signer = require('crypto').createSign("RSA-SHA256");
 | |
|   signer.update(raw);
 | |
| 
 | |
|   return {
 | |
|     header: header
 | |
|   , protected: protected64
 | |
|   , payload: payload64
 | |
|   , signature: signer.sign(pem, 'base64')
 | |
|     .replace(/\+/g, '-')
 | |
|     .replace(/\//g, '_')
 | |
|     .replace(/=/g, '')
 | |
|   };
 | |
| };
 | |
| 
 | |
| RSA.generateCsrPem = function (keypair, domains) {
 | |
|   keypair = RSA.import(keypair);
 | |
|   return RSACSR.sync({ jwk: keypair.privateKeyJwk, domains: domains });
 | |
| };
 | |
| RSA.generateCsrDer = function (keypair, domains) {
 | |
|   keypair = RSA.import(keypair);
 | |
|   return RSACSR.sync({
 | |
|     jwk: keypair.privateKeyJwk
 | |
|   , domains: domains
 | |
|   , encoding: 'der'
 | |
|   });
 | |
| };
 | |
| RSA.generateCsrDerWeb64 =RSA.generateCsrWeb64 = function (keypair, names) {
 | |
|   var buf = RSA.generateCsrDer(keypair, names);
 | |
|   var b64 = buf.toString('base64');
 | |
|   return RSA.utils.toWebsafeBase64(b64);
 | |
| };
 | |
| 
 | |
| RSA._thumbprintInput = function (n, e) {
 | |
|   // #L147 const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
 | |
|   return Buffer.from('{"e":"'+ e + '","kty":"RSA","n":"'+ n +'"}', 'ascii');
 | |
| };
 | |
| RSA._thumbprint = function (keypair) {
 | |
|   var publicKeyJwk = keypair.publicKeyJwk;
 | |
| 
 | |
|   if (!publicKeyJwk.e || !publicKeyJwk.n) {
 | |
|     throw new Error("You must provide an RSA jwk with 'e' and 'n' (the public components)");
 | |
|   }
 | |
| 
 | |
|   var input = RSA._thumbprintInput(publicKeyJwk.n, publicKeyJwk.e);
 | |
|   var base64Digest = require('crypto').createHash('sha256').update(input).digest('base64');
 | |
| 
 | |
|   return RSA.utils.toWebsafeBase64(base64Digest);
 | |
| };
 | |
| RSA.thumbprint = function (keypair) {
 | |
|   if (!keypair.publicKeyJwk) {
 | |
|     keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
 | |
|   }
 | |
|   return RSA._thumbprint(keypair);
 | |
| };
 | |
| 
 | |
| RSA.utils.toWebsafeBase64 = function (b64) {
 | |
|   return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g,"");
 | |
| };
 | |
| 
 | |
| RSA.exportPrivateKey = RSA.exportPrivatePem;
 | |
| RSA.exportPublicKey = RSA.exportPublicPem;
 |