mirror of
				https://github.com/therootcompany/acme.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	moved common code to own modules
This commit is contained in:
		
							parent
							
								
									5623ed1914
								
							
						
					
					
						commit
						21f7e87606
					
				
							
								
								
									
										4
									
								
								acme.js
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								acme.js
									
									
									
									
									
								
							| @ -10,7 +10,7 @@ var Enc = require('@root/encoding/base64'); | ||||
| var ACME = module.exports; | ||||
| //var Keypairs = exports.Keypairs || {};
 | ||||
| //var CSR = exports.CSR;
 | ||||
| var sha2 = require('./lib/node/sha2.js'); | ||||
| var sha2 = require('@root/keypairs/lib/node/sha2.js'); | ||||
| var http = require('./lib/node/http.js'); | ||||
| 
 | ||||
| ACME.formatPemChain = function formatPemChain(str) { | ||||
| @ -1318,7 +1318,7 @@ ACME.create = function create(me) { | ||||
| 	// me.debug = true;
 | ||||
| 	me.challengePrefixes = ACME.challengePrefixes; | ||||
| 	me.Keypairs = me.Keypairs || require('@root/keypairs'); | ||||
| 	me.CSR = me.CSR || require('./csr.js'); | ||||
| 	me.CSR = me.CSR || require('@root/csr'); | ||||
| 	me._nonces = []; | ||||
| 	me._canUse = {}; | ||||
| 	if (!me._baseUrl) { | ||||
|  | ||||
							
								
								
									
										322
									
								
								csr.js
									
									
									
									
									
								
							
							
						
						
									
										322
									
								
								csr.js
									
									
									
									
									
								
							| @ -1,322 +0,0 @@ | ||||
| // Copyright 2018-present 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'; | ||||
| /*global Promise*/ | ||||
| 
 | ||||
| var Enc = require('@root/encoding'); | ||||
| 
 | ||||
| var ASN1 = require('./asn1/packer.js'); // DER, actually
 | ||||
| var Asn1 = ASN1.Any; | ||||
| var BitStr = ASN1.BitStr; | ||||
| var UInt = ASN1.UInt; | ||||
| var Asn1Parser = require('./asn1/parser.js'); | ||||
| var PEM = require('./pem.js'); | ||||
| var X509 = require('./x509.js'); | ||||
| var Keypairs = require('@root/keypairs'); | ||||
| 
 | ||||
| // TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
 | ||||
| var CSR = module.exports; | ||||
| CSR.csr = function(opts) { | ||||
| 	// We're using a Promise here to be compatible with the browser version
 | ||||
| 	// which will probably use the webcrypto API for some of the conversions
 | ||||
| 	return CSR._prepare(opts).then(function(opts) { | ||||
| 		return CSR.create(opts).then(function(bytes) { | ||||
| 			return CSR._encode(opts, bytes); | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| CSR._prepare = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		opts = JSON.parse(JSON.stringify(opts)); | ||||
| 
 | ||||
| 		// We do a bit of extra error checking for user convenience
 | ||||
| 		if (!opts) { | ||||
| 			throw new Error( | ||||
| 				'You must pass options with key and domains to rsacsr' | ||||
| 			); | ||||
| 		} | ||||
| 		if (!Array.isArray(opts.domains) || 0 === opts.domains.length) { | ||||
| 			new Error('You must pass options.domains as a non-empty array'); | ||||
| 		} | ||||
| 
 | ||||
| 		// I need to check that 例.中国 is a valid domain name
 | ||||
| 		if ( | ||||
| 			!opts.domains.every(function(d) { | ||||
| 				// allow punycode? xn--
 | ||||
| 				if ( | ||||
| 					'string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/ | ||||
| 				) { | ||||
| 					return true; | ||||
| 				} | ||||
| 			}) | ||||
| 		) { | ||||
| 			throw new Error('You must pass options.domains as strings'); | ||||
| 		} | ||||
| 
 | ||||
| 		if (opts.jwk) { | ||||
| 			return opts; | ||||
| 		} | ||||
| 		if (opts.key && opts.key.kty) { | ||||
| 			opts.jwk = opts.key; | ||||
| 			return opts; | ||||
| 		} | ||||
| 		if (!opts.pem && !opts.key) { | ||||
| 			throw new Error('You must pass options.key as a JSON web key'); | ||||
| 		} | ||||
| 
 | ||||
| 		return Keypairs.import({ pem: opts.pem || opts.key }).then(function( | ||||
| 			pair | ||||
| 		) { | ||||
| 			opts.jwk = pair.private; | ||||
| 			return opts; | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| CSR._encode = function(opts, bytes) { | ||||
| 	if ('der' === (opts.encoding || '').toLowerCase()) { | ||||
| 		return bytes; | ||||
| 	} | ||||
| 	return PEM.packBlock({ | ||||
| 		type: 'CERTIFICATE REQUEST', | ||||
| 		bytes: bytes /* { jwk: jwk, domains: opts.domains } */ | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| CSR.create = function createCsr(opts) { | ||||
| 	var hex = CSR.request(opts.jwk, opts.domains); | ||||
| 	return CSR._sign(opts.jwk, hex).then(function(csr) { | ||||
| 		return Enc.hexToBuf(csr); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| //
 | ||||
| // EC / RSA
 | ||||
| //
 | ||||
| CSR.request = function createCsrBodyEc(jwk, domains) { | ||||
| 	var asn1pub; | ||||
| 	if (/^EC/i.test(jwk.kty)) { | ||||
| 		asn1pub = X509.packCsrEcPublicKey(jwk); | ||||
| 	} else { | ||||
| 		asn1pub = X509.packCsrRsaPublicKey(jwk); | ||||
| 	} | ||||
| 	return X509.packCsr(asn1pub, domains); | ||||
| }; | ||||
| 
 | ||||
| CSR._sign = function csrEcSig(jwk, request) { | ||||
| 	// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
 | ||||
| 	// TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
 | ||||
| 	// TODO have a consistent non-private way to sign
 | ||||
| 	return Keypairs.sign( | ||||
| 		{ jwk: jwk, format: 'x509' }, | ||||
| 		Enc.hexToBuf(request) | ||||
| 	).then(function(sig) { | ||||
| 		return CSR._toDer({ | ||||
| 			request: request, | ||||
| 			signature: sig, | ||||
| 			kty: jwk.kty | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| CSR._toDer = function encode(opts) { | ||||
| 	var sty; | ||||
| 	if (/^EC/i.test(opts.kty)) { | ||||
| 		// 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA algorithm with SHA256)
 | ||||
| 		sty = Asn1('30', Asn1('06', '2a8648ce3d040302')); | ||||
| 	} else { | ||||
| 		// 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
 | ||||
| 		sty = Asn1('30', Asn1('06', '2a864886f70d01010b'), Asn1('05')); | ||||
| 	} | ||||
| 	return Asn1( | ||||
| 		'30', | ||||
| 		// The Full CSR Request Body
 | ||||
| 		opts.request, | ||||
| 		// The Signature Type
 | ||||
| 		sty, | ||||
| 		// The Signature
 | ||||
| 		BitStr(Enc.bufToHex(opts.signature)) | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| X509.packCsr = function(asn1pubkey, domains) { | ||||
| 	return Asn1( | ||||
| 		'30', | ||||
| 		// Version (0)
 | ||||
| 		UInt('00'), | ||||
| 
 | ||||
| 		// 2.5.4.3 commonName (X.520 DN component)
 | ||||
| 		Asn1( | ||||
| 			'30', | ||||
| 			Asn1( | ||||
| 				'31', | ||||
| 				Asn1( | ||||
| 					'30', | ||||
| 					Asn1('06', '550403'), | ||||
| 					// TODO utf8 => punycode
 | ||||
| 					Asn1('0c', Enc.strToHex(domains[0])) | ||||
| 				) | ||||
| 			) | ||||
| 		), | ||||
| 
 | ||||
| 		// Public Key (RSA or EC)
 | ||||
| 		asn1pubkey, | ||||
| 
 | ||||
| 		// Request Body
 | ||||
| 		Asn1( | ||||
| 			'a0', | ||||
| 			Asn1( | ||||
| 				'30', | ||||
| 				// 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
| 				Asn1('06', '2a864886f70d01090e'), | ||||
| 				Asn1( | ||||
| 					'31', | ||||
| 					Asn1( | ||||
| 						'30', | ||||
| 						Asn1( | ||||
| 							'30', | ||||
| 							// 2.5.29.17 subjectAltName (X.509 extension)
 | ||||
| 							Asn1('06', '551d11'), | ||||
| 							Asn1( | ||||
| 								'04', | ||||
| 								Asn1( | ||||
| 									'30', | ||||
| 									domains | ||||
| 										.map(function(d) { | ||||
| 											// TODO utf8 => punycode
 | ||||
| 											return Asn1('82', Enc.strToHex(d)); | ||||
| 										}) | ||||
| 										.join('') | ||||
| 								) | ||||
| 							) | ||||
| 						) | ||||
| 					) | ||||
| 				) | ||||
| 			) | ||||
| 		) | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| // TODO finish this later
 | ||||
| // we want to parse the domains, the public key, and verify the signature
 | ||||
| CSR._info = function(der) { | ||||
| 	// standard base64 PEM
 | ||||
| 	if ('string' === typeof der && '-' === der[0]) { | ||||
| 		der = PEM.parseBlock(der).bytes; | ||||
| 	} | ||||
| 	// jose urlBase64 not-PEM
 | ||||
| 	if ('string' === typeof der) { | ||||
| 		der = Enc.base64ToBuf(der); | ||||
| 	} | ||||
| 	// not supporting binary-encoded bas64
 | ||||
| 	var c = Asn1Parser.parse(der); | ||||
| 	var kty; | ||||
| 	// A cert has 3 parts: cert, signature meta, signature
 | ||||
| 	if (c.children.length !== 3) { | ||||
| 		throw new Error( | ||||
| 			"doesn't look like a certificate request: expected 3 parts of header" | ||||
| 		); | ||||
| 	} | ||||
| 	var sig = c.children[2]; | ||||
| 	if (sig.children.length) { | ||||
| 		// ASN1/X509 EC
 | ||||
| 		sig = sig.children[0]; | ||||
| 		sig = Asn1( | ||||
| 			'30', | ||||
| 			UInt(Enc.bufToHex(sig.children[0].value)), | ||||
| 			UInt(Enc.bufToHex(sig.children[1].value)) | ||||
| 		); | ||||
| 		sig = Enc.hexToBuf(sig); | ||||
| 		kty = 'EC'; | ||||
| 	} else { | ||||
| 		// Raw RSA Sig
 | ||||
| 		sig = sig.value; | ||||
| 		kty = 'RSA'; | ||||
| 	} | ||||
| 	//c.children[1]; // signature type
 | ||||
| 	var req = c.children[0]; | ||||
| 	if (4 !== req.children.length) { | ||||
| 		throw new Error( | ||||
| 			"doesn't look like a certificate request: expected 4 parts to request" | ||||
| 		); | ||||
| 	} | ||||
| 	// 0 null
 | ||||
| 	// 1 commonName / subject
 | ||||
| 	var sub = Enc.bufToStr( | ||||
| 		req.children[1].children[0].children[0].children[1].value | ||||
| 	); | ||||
| 	// 3 public key (type, key)
 | ||||
| 	//console.log('oid', Enc.bufToHex(req.children[2].children[0].children[0].value));
 | ||||
| 	var pub; | ||||
| 	// TODO reuse ASN1 parser for these?
 | ||||
| 	if ('EC' === kty) { | ||||
| 		// throw away compression byte
 | ||||
| 		pub = req.children[2].children[1].value.slice(1); | ||||
| 		pub = { kty: kty, x: pub.slice(0, 32), y: pub.slice(32) }; | ||||
| 		while (0 === pub.x[0]) { | ||||
| 			pub.x = pub.x.slice(1); | ||||
| 		} | ||||
| 		while (0 === pub.y[0]) { | ||||
| 			pub.y = pub.y.slice(1); | ||||
| 		} | ||||
| 		if ((pub.x.length || pub.x.byteLength) > 48) { | ||||
| 			pub.crv = 'P-521'; | ||||
| 		} else if ((pub.x.length || pub.x.byteLength) > 32) { | ||||
| 			pub.crv = 'P-384'; | ||||
| 		} else { | ||||
| 			pub.crv = 'P-256'; | ||||
| 		} | ||||
| 		pub.x = Enc.bufToUrlBase64(pub.x); | ||||
| 		pub.y = Enc.bufToUrlBase64(pub.y); | ||||
| 	} else { | ||||
| 		pub = req.children[2].children[1].children[0]; | ||||
| 		pub = { | ||||
| 			kty: kty, | ||||
| 			n: pub.children[0].value, | ||||
| 			e: pub.children[1].value | ||||
| 		}; | ||||
| 		while (0 === pub.n[0]) { | ||||
| 			pub.n = pub.n.slice(1); | ||||
| 		} | ||||
| 		while (0 === pub.e[0]) { | ||||
| 			pub.e = pub.e.slice(1); | ||||
| 		} | ||||
| 		pub.n = Enc.bufToUrlBase64(pub.n); | ||||
| 		pub.e = Enc.bufToUrlBase64(pub.e); | ||||
| 	} | ||||
| 	// 4 extensions
 | ||||
| 	var domains = req.children[3].children | ||||
| 		.filter(function(seq) { | ||||
| 			//  1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
 | ||||
| 			if ('2a864886f70d01090e' === Enc.bufToHex(seq.children[0].value)) { | ||||
| 				return true; | ||||
| 			} | ||||
| 		}) | ||||
| 		.map(function(seq) { | ||||
| 			return seq.children[1].children[0].children | ||||
| 				.filter(function(seq2) { | ||||
| 					// subjectAltName (X.509 extension)
 | ||||
| 					if ('551d11' === Enc.bufToHex(seq2.children[0].value)) { | ||||
| 						return true; | ||||
| 					} | ||||
| 				}) | ||||
| 				.map(function(seq2) { | ||||
| 					return seq2.children[1].children[0].children.map(function( | ||||
| 						name | ||||
| 					) { | ||||
| 						// TODO utf8 => punycode
 | ||||
| 						return Enc.bufToStr(name.value); | ||||
| 					}); | ||||
| 				})[0]; | ||||
| 		})[0]; | ||||
| 
 | ||||
| 	return { | ||||
| 		subject: sub, | ||||
| 		altnames: domains, | ||||
| 		jwk: pub, | ||||
| 		signature: sig | ||||
| 	}; | ||||
| }; | ||||
							
								
								
									
										239
									
								
								ecdsa.js
									
									
									
									
									
								
							
							
						
						
									
										239
									
								
								ecdsa.js
									
									
									
									
									
								
							| @ -1,239 +0,0 @@ | ||||
| /*global Promise*/ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var Enc = require('@root/encoding'); | ||||
| 
 | ||||
| var EC = module.exports; | ||||
| var native = require('./lib/node/ecdsa.js'); | ||||
| 
 | ||||
| // TODO SSH
 | ||||
| var SSH; | ||||
| 
 | ||||
| var x509 = require('./x509.js'); | ||||
| var PEM = require('./pem.js'); | ||||
| //var SSH = require('./ssh-keys.js');
 | ||||
| var sha2 = require('./lib/node/sha2.js'); | ||||
| 
 | ||||
| // 1.2.840.10045.3.1.7
 | ||||
| // prime256v1 (ANSI X9.62 named elliptic curve)
 | ||||
| var OBJ_ID_EC = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase(); | ||||
| // 1.3.132.0.34
 | ||||
| // secp384r1 (SECG (Certicom) named elliptic curve)
 | ||||
| var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase(); | ||||
| 
 | ||||
| EC._stance = | ||||
| 	"We take the stance that if you're knowledgeable enough to" + | ||||
| 	" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| native._stance = EC._stance; | ||||
| EC._universal = | ||||
| 	'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; | ||||
| EC.generate = native.generate; | ||||
| 
 | ||||
| EC.export = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { | ||||
| 			throw new Error('must pass { jwk: jwk } as a JSON object'); | ||||
| 		} | ||||
| 		var jwk = JSON.parse(JSON.stringify(opts.jwk)); | ||||
| 		var format = opts.format; | ||||
| 		if ( | ||||
| 			opts.public || | ||||
| 			-1 !== ['spki', 'pkix', 'ssh', 'rfc4716'].indexOf(format) | ||||
| 		) { | ||||
| 			jwk.d = null; | ||||
| 		} | ||||
| 		if ('EC' !== jwk.kty) { | ||||
| 			throw new Error("options.jwk.kty must be 'EC' for EC keys"); | ||||
| 		} | ||||
| 		if (!jwk.d) { | ||||
| 			if (!format || -1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 				format = 'spki'; | ||||
| 			} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 				format = 'ssh'; | ||||
| 			} else { | ||||
| 				throw new Error( | ||||
| 					"options.format must be 'spki' or 'ssh' for public EC keys, not (" + | ||||
| 						typeof format + | ||||
| 						') ' + | ||||
| 						format | ||||
| 				); | ||||
| 			} | ||||
| 		} else { | ||||
| 			if (!format || 'sec1' === format) { | ||||
| 				format = 'sec1'; | ||||
| 			} else if ('pkcs8' !== format) { | ||||
| 				throw new Error( | ||||
| 					"options.format must be 'sec1' or 'pkcs8' for private EC keys, not '" + | ||||
| 						format + | ||||
| 						"'" | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 		if (-1 === ['P-256', 'P-384'].indexOf(jwk.crv)) { | ||||
| 			throw new Error( | ||||
| 				"options.jwk.crv must be either P-256 or P-384 for EC keys, not '" + | ||||
| 					jwk.crv + | ||||
| 					"'" | ||||
| 			); | ||||
| 		} | ||||
| 		if (!jwk.y) { | ||||
| 			throw new Error( | ||||
| 				'options.jwk.y must be a urlsafe base64-encoded either P-256 or P-384' | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		if ('sec1' === format) { | ||||
| 			return PEM.packBlock({ | ||||
| 				type: 'EC PRIVATE KEY', | ||||
| 				bytes: x509.packSec1(jwk) | ||||
| 			}); | ||||
| 		} else if ('pkcs8' === format) { | ||||
| 			return PEM.packBlock({ | ||||
| 				type: 'PRIVATE KEY', | ||||
| 				bytes: x509.packPkcs8(jwk) | ||||
| 			}); | ||||
| 		} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 			return PEM.packBlock({ | ||||
| 				type: 'PUBLIC KEY', | ||||
| 				bytes: x509.packSpki(jwk) | ||||
| 			}); | ||||
| 		} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 			return SSH.packSsh(jwk); | ||||
| 		} else { | ||||
| 			throw new Error( | ||||
| 				'Sanity Error: reached unreachable code block with format: ' + | ||||
| 					format | ||||
| 			); | ||||
| 		} | ||||
| 	}); | ||||
| }; | ||||
| native.export = EC.export; | ||||
| 
 | ||||
| EC.import = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		if (!opts || !opts.pem || 'string' !== typeof opts.pem) { | ||||
| 			throw new Error('must pass { pem: pem } as a string'); | ||||
| 		} | ||||
| 		if (0 === opts.pem.indexOf('ecdsa-sha2-')) { | ||||
| 			return SSH.parseSsh(opts.pem); | ||||
| 		} | ||||
| 		var pem = opts.pem; | ||||
| 		var u8 = PEM.parseBlock(pem).bytes; | ||||
| 		var hex = Enc.bufToHex(u8); | ||||
| 		var jwk = { kty: 'EC', crv: null, x: null, y: null }; | ||||
| 
 | ||||
| 		//console.log();
 | ||||
| 		if ( | ||||
| 			-1 !== hex.indexOf(OBJ_ID_EC) || | ||||
| 			-1 !== hex.indexOf(OBJ_ID_EC_384) | ||||
| 		) { | ||||
| 			if (-1 !== hex.indexOf(OBJ_ID_EC_384)) { | ||||
| 				jwk.crv = 'P-384'; | ||||
| 			} else { | ||||
| 				jwk.crv = 'P-256'; | ||||
| 			} | ||||
| 
 | ||||
| 			// PKCS8
 | ||||
| 			if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) { | ||||
| 				//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 | ||||
| 				jwk = x509.parsePkcs8(u8, jwk); | ||||
| 				// EC-only
 | ||||
| 			} else if (0x02 === u8[2] && 0x04 === u8[5] && 0xa0 === u8[39]) { | ||||
| 				//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
 | ||||
| 				jwk = x509.parseSec1(u8, jwk); | ||||
| 				// EC-only
 | ||||
| 			} else if (0x02 === u8[3] && 0x04 === u8[6] && 0xa0 === u8[56]) { | ||||
| 				//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
 | ||||
| 				jwk = x509.parseSec1(u8, jwk); | ||||
| 				// SPKI/PKIK (Public)
 | ||||
| 			} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) { | ||||
| 				//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
 | ||||
| 				jwk = x509.parseSpki(u8, jwk); | ||||
| 				// Error
 | ||||
| 			} else { | ||||
| 				//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
 | ||||
| 				//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
 | ||||
| 				//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
 | ||||
| 				//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
 | ||||
| 				throw new Error('unrecognized key format'); | ||||
| 			} | ||||
| 		} else { | ||||
| 			throw new Error('Supported key types are P-256 and P-384'); | ||||
| 		} | ||||
| 		if (opts.public) { | ||||
| 			if (true !== opts.public) { | ||||
| 				throw new Error( | ||||
| 					'options.public must be either `true` or `false` not (' + | ||||
| 						typeof opts.public + | ||||
| 						") '" + | ||||
| 						opts.public + | ||||
| 						"'" | ||||
| 				); | ||||
| 			} | ||||
| 			delete jwk.d; | ||||
| 		} | ||||
| 		return jwk; | ||||
| 	}); | ||||
| }; | ||||
| native.import = EC.import; | ||||
| 
 | ||||
| EC.pack = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		return EC.export(opts); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| // Chopping off the private parts is now part of the public API.
 | ||||
| // I thought it sounded a little too crude at first, but it really is the best name in every possible way.
 | ||||
| EC.neuter = function(opts) { | ||||
| 	// trying to find the best balance of an immutable copy with custom attributes
 | ||||
| 	var jwk = {}; | ||||
| 	Object.keys(opts.jwk).forEach(function(k) { | ||||
| 		if ('undefined' === typeof opts.jwk[k]) { | ||||
| 			return; | ||||
| 		} | ||||
| 		// ignore EC private parts
 | ||||
| 		if ('d' === k) { | ||||
| 			return; | ||||
| 		} | ||||
| 		jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
| 	}); | ||||
| 	return jwk; | ||||
| }; | ||||
| native.neuter = EC.neuter; | ||||
| 
 | ||||
| // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||
| EC.__thumbprint = function(jwk) { | ||||
| 	// Use the same entropy for SHA as for key
 | ||||
| 	var alg = 'SHA-256'; | ||||
| 	if (/384/.test(jwk.crv)) { | ||||
| 		alg = 'SHA-384'; | ||||
| 	} | ||||
| 	var payload = | ||||
| 		'{"crv":"' + | ||||
| 		jwk.crv + | ||||
| 		'","kty":"EC","x":"' + | ||||
| 		jwk.x + | ||||
| 		'","y":"' + | ||||
| 		jwk.y + | ||||
| 		'"}'; | ||||
| 	return sha2.sum(alg, payload).then(function(hash) { | ||||
| 		return Enc.bufToUrlBase64(Uint8Array.from(hash)); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| EC.thumbprint = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		var jwk; | ||||
| 		if ('EC' === opts.kty) { | ||||
| 			jwk = opts; | ||||
| 		} else if (opts.jwk) { | ||||
| 			jwk = opts.jwk; | ||||
| 		} else { | ||||
| 			return native.import(opts).then(function(jwk) { | ||||
| 				return EC.__thumbprint(jwk); | ||||
| 			}); | ||||
| 		} | ||||
| 		return EC.__thumbprint(jwk); | ||||
| 	}); | ||||
| }; | ||||
							
								
								
									
										315
									
								
								keypairs.js
									
									
									
									
									
								
							
							
						
						
									
										315
									
								
								keypairs.js
									
									
									
									
									
								
							| @ -1,315 +0,0 @@ | ||||
| /*global Promise*/ | ||||
| 'use strict'; | ||||
| 
 | ||||
| require('@root/encoding/bytes'); | ||||
| var Enc = require('@root/encoding/base64'); | ||||
| 
 | ||||
| var Keypairs = module.exports; | ||||
| var Rasha = require('./rsa.js'); | ||||
| var Eckles = require('./ecdsa.js'); | ||||
| var native = require('./lib/node/keypairs.js'); | ||||
| 
 | ||||
| Keypairs._stance = | ||||
| 	"We take the stance that if you're knowledgeable enough to" + | ||||
| 	" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| Keypairs._universal = | ||||
| 	'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; | ||||
| Keypairs.generate = function(opts) { | ||||
| 	opts = opts || {}; | ||||
| 	var p; | ||||
| 	if (!opts.kty) { | ||||
| 		opts.kty = opts.type; | ||||
| 	} | ||||
| 	if (!opts.kty) { | ||||
| 		opts.kty = 'EC'; | ||||
| 	} | ||||
| 	if (/^EC/i.test(opts.kty)) { | ||||
| 		p = Eckles.generate(opts); | ||||
| 	} else if (/^RSA$/i.test(opts.kty)) { | ||||
| 		p = Rasha.generate(opts); | ||||
| 	} else { | ||||
| 		return Promise.Reject( | ||||
| 			new Error( | ||||
| 				"'" + | ||||
| 					opts.kty + | ||||
| 					"' is not a well-supported key type." + | ||||
| 					Keypairs._universal + | ||||
| 					" Please choose 'EC', or 'RSA' if you have good reason to." | ||||
| 			) | ||||
| 		); | ||||
| 	} | ||||
| 	return p.then(function(pair) { | ||||
| 		return Keypairs.thumbprint({ jwk: pair.public }).then(function(thumb) { | ||||
| 			pair.private.kid = thumb; // maybe not the same id on the private key?
 | ||||
| 			pair.public.kid = thumb; | ||||
| 			return pair; | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| Keypairs.export = function(opts) { | ||||
| 	return Eckles.export(opts).catch(function(err) { | ||||
| 		return Rasha.export(opts).catch(function() { | ||||
| 			return Promise.reject(err); | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| // XXX
 | ||||
| native.export = Keypairs.export; | ||||
| 
 | ||||
| /** | ||||
|  * Chopping off the private parts is now part of the public API. | ||||
|  * I thought it sounded a little too crude at first, but it really is the best name in every possible way. | ||||
|  */ | ||||
| Keypairs.neuter = function(opts) { | ||||
| 	/** trying to find the best balance of an immutable copy with custom attributes */ | ||||
| 	var jwk = {}; | ||||
| 	Object.keys(opts.jwk).forEach(function(k) { | ||||
| 		if ('undefined' === typeof opts.jwk[k]) { | ||||
| 			return; | ||||
| 		} | ||||
| 		// ignore RSA and EC private parts
 | ||||
| 		if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { | ||||
| 			return; | ||||
| 		} | ||||
| 		jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
| 	}); | ||||
| 	return jwk; | ||||
| }; | ||||
| 
 | ||||
| Keypairs.thumbprint = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		if (/EC/i.test(opts.jwk.kty)) { | ||||
| 			return Eckles.thumbprint(opts); | ||||
| 		} else { | ||||
| 			return Rasha.thumbprint(opts); | ||||
| 		} | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| Keypairs.publish = function(opts) { | ||||
| 	if ('object' !== typeof opts.jwk || !opts.jwk.kty) { | ||||
| 		throw new Error('invalid jwk: ' + JSON.stringify(opts.jwk)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** returns a copy */ | ||||
| 	var jwk = Keypairs.neuter(opts); | ||||
| 
 | ||||
| 	if (jwk.exp) { | ||||
| 		jwk.exp = setTime(jwk.exp); | ||||
| 	} else { | ||||
| 		if (opts.exp) { | ||||
| 			jwk.exp = setTime(opts.exp); | ||||
| 		} else if (opts.expiresIn) { | ||||
| 			jwk.exp = Math.round(Date.now() / 1000) + opts.expiresIn; | ||||
| 		} else if (opts.expiresAt) { | ||||
| 			jwk.exp = opts.expiresAt; | ||||
| 		} | ||||
| 	} | ||||
| 	if (!jwk.use && false !== jwk.use) { | ||||
| 		jwk.use = 'sig'; | ||||
| 	} | ||||
| 
 | ||||
| 	if (jwk.kid) { | ||||
| 		return Promise.resolve(jwk); | ||||
| 	} | ||||
| 	return Keypairs.thumbprint({ jwk: jwk }).then(function(thumb) { | ||||
| 		jwk.kid = thumb; | ||||
| 		return jwk; | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| // JWT a.k.a. JWS with Claims using Compact Serialization
 | ||||
| Keypairs.signJwt = function(opts) { | ||||
| 	return Keypairs.thumbprint({ jwk: opts.jwk }).then(function(thumb) { | ||||
| 		var header = opts.header || {}; | ||||
| 		var claims = JSON.parse(JSON.stringify(opts.claims || {})); | ||||
| 		header.typ = 'JWT'; | ||||
| 
 | ||||
| 		if (!header.kid && false !== header.kid) { | ||||
| 			header.kid = thumb; | ||||
| 		} | ||||
| 		if (!header.alg && opts.alg) { | ||||
| 			header.alg = opts.alg; | ||||
| 		} | ||||
| 		if (!claims.iat && (false === claims.iat || false === opts.iat)) { | ||||
| 			claims.iat = undefined; | ||||
| 		} else if (!claims.iat) { | ||||
| 			claims.iat = Math.round(Date.now() / 1000); | ||||
| 		} | ||||
| 
 | ||||
| 		if (opts.exp) { | ||||
| 			claims.exp = setTime(opts.exp); | ||||
| 		} else if ( | ||||
| 			!claims.exp && | ||||
| 			(false === claims.exp || false === opts.exp) | ||||
| 		) { | ||||
| 			claims.exp = undefined; | ||||
| 		} else if (!claims.exp) { | ||||
| 			throw new Error( | ||||
| 				"opts.claims.exp should be the expiration date as seconds, human form (i.e. '1h' or '15m') or false" | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		if (opts.iss) { | ||||
| 			claims.iss = opts.iss; | ||||
| 		} | ||||
| 		if (!claims.iss && (false === claims.iss || false === opts.iss)) { | ||||
| 			claims.iss = undefined; | ||||
| 		} else if (!claims.iss) { | ||||
| 			throw new Error( | ||||
| 				'opts.claims.iss should be in the form of https://example.com/, a secure OIDC base url' | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		return Keypairs.signJws({ | ||||
| 			jwk: opts.jwk, | ||||
| 			pem: opts.pem, | ||||
| 			protected: header, | ||||
| 			header: undefined, | ||||
| 			payload: claims | ||||
| 		}).then(function(jws) { | ||||
| 			return [jws.protected, jws.payload, jws.signature].join('.'); | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| Keypairs.signJws = function(opts) { | ||||
| 	return Keypairs.thumbprint(opts).then(function(thumb) { | ||||
| 		function alg() { | ||||
| 			if (!opts.jwk) { | ||||
| 				throw new Error("opts.jwk must exist and must declare 'typ'"); | ||||
| 			} | ||||
| 			if (opts.jwk.alg) { | ||||
| 				return opts.jwk.alg; | ||||
| 			} | ||||
| 			var typ = 'RSA' === opts.jwk.kty ? 'RS' : 'ES'; | ||||
| 			return typ + Keypairs._getBits(opts); | ||||
| 		} | ||||
| 
 | ||||
| 		function sign() { | ||||
| 			var protect = opts.protected; | ||||
| 			var payload = opts.payload; | ||||
| 
 | ||||
| 			// Compute JWS signature
 | ||||
| 			var protectedHeader = ''; | ||||
| 			// Because unprotected headers are allowed, regrettably...
 | ||||
| 			// https://stackoverflow.com/a/46288694
 | ||||
| 			if (false !== protect) { | ||||
| 				if (!protect) { | ||||
| 					protect = {}; | ||||
| 				} | ||||
| 				if (!protect.alg) { | ||||
| 					protect.alg = alg(); | ||||
| 				} | ||||
| 				// There's a particular request where ACME / Let's Encrypt explicitly doesn't use a kid
 | ||||
| 				if (false === protect.kid) { | ||||
| 					protect.kid = undefined; | ||||
| 				} else if (!protect.kid) { | ||||
| 					protect.kid = thumb; | ||||
| 				} | ||||
| 				protectedHeader = JSON.stringify(protect); | ||||
| 			} | ||||
| 
 | ||||
| 			// Not sure how to handle the empty case since ACME POST-as-GET must be empty
 | ||||
| 			//if (!payload) {
 | ||||
| 			//  throw new Error("opts.payload should be JSON, string, or ArrayBuffer (it may be empty, but that must be explicit)");
 | ||||
| 			//}
 | ||||
| 			// Trying to detect if it's a plain object (not Buffer, ArrayBuffer, Array, Uint8Array, etc)
 | ||||
| 			if ( | ||||
| 				payload && | ||||
| 				'string' !== typeof payload && | ||||
| 				'undefined' === typeof payload.byteLength && | ||||
| 				'undefined' === typeof payload.buffer | ||||
| 			) { | ||||
| 				payload = JSON.stringify(payload); | ||||
| 			} | ||||
| 			// Converting to a buffer, even if it was just converted to a string
 | ||||
| 			if ('string' === typeof payload) { | ||||
| 				payload = Enc.strToBuf(payload); | ||||
| 			} | ||||
| 
 | ||||
| 			var protected64 = Enc.strToUrlBase64(protectedHeader); | ||||
| 			var payload64 = Enc.bufToUrlBase64(payload); | ||||
| 			var msg = protected64 + '.' + payload64; | ||||
| 
 | ||||
| 			return native._sign(opts, msg).then(function(buf) { | ||||
| 				var signedMsg = { | ||||
| 					protected: protected64, | ||||
| 					payload: payload64, | ||||
| 					signature: Enc.bufToUrlBase64(buf) | ||||
| 				}; | ||||
| 
 | ||||
| 				return signedMsg; | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		if (opts.jwk) { | ||||
| 			return sign(); | ||||
| 		} else { | ||||
| 			return Keypairs.import({ pem: opts.pem }).then(function(pair) { | ||||
| 				opts.jwk = pair.private; | ||||
| 				return sign(); | ||||
| 			}); | ||||
| 		} | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| // TODO expose consistently
 | ||||
| Keypairs.sign = native._sign; | ||||
| 
 | ||||
| Keypairs._getBits = function(opts) { | ||||
| 	if (opts.alg) { | ||||
| 		return opts.alg.replace(/[a-z\-]/gi, ''); | ||||
| 	} | ||||
| 	// base64 len to byte len
 | ||||
| 	var len = Math.floor((opts.jwk.n || '').length * 0.75); | ||||
| 
 | ||||
| 	// TODO this may be a bug
 | ||||
| 	// need to confirm that the padding is no more or less than 1 byte
 | ||||
| 	if (/521/.test(opts.jwk.crv) || len >= 511) { | ||||
| 		return '512'; | ||||
| 	} else if (/384/.test(opts.jwk.crv) || len >= 383) { | ||||
| 		return '384'; | ||||
| 	} | ||||
| 
 | ||||
| 	return '256'; | ||||
| }; | ||||
| // XXX
 | ||||
| native._getBits = Keypairs._getBits; | ||||
| 
 | ||||
| function setTime(time) { | ||||
| 	if ('number' === typeof time) { | ||||
| 		return time; | ||||
| 	} | ||||
| 
 | ||||
| 	var t = time.match(/^(\-?\d+)([dhms])$/i); | ||||
| 	if (!t || !t[0]) { | ||||
| 		throw new Error( | ||||
| 			"'" + | ||||
| 				time + | ||||
| 				"' should be datetime in seconds or human-readable format (i.e. 3d, 1h, 15m, 30s" | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	var now = Math.round(Date.now() / 1000); | ||||
| 	var num = parseInt(t[1], 10); | ||||
| 	var unit = t[2]; | ||||
| 	var mult = 1; | ||||
| 	switch (unit) { | ||||
| 		// fancy fallthrough, what fun!
 | ||||
| 		case 'd': | ||||
| 			mult *= 24; | ||||
| 		/*falls through*/ | ||||
| 		case 'h': | ||||
| 			mult *= 60; | ||||
| 		/*falls through*/ | ||||
| 		case 'm': | ||||
| 			mult *= 60; | ||||
| 		/*falls through*/ | ||||
| 		case 's': | ||||
| 			mult *= 1; | ||||
| 	} | ||||
| 
 | ||||
| 	return now + mult * num; | ||||
| } | ||||
| @ -1,57 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var native = module.exports; | ||||
| // XXX received from caller
 | ||||
| var EC = native; | ||||
| 
 | ||||
| native.generate = function(opts) { | ||||
| 	var wcOpts = {}; | ||||
| 	if (!opts) { | ||||
| 		opts = {}; | ||||
| 	} | ||||
| 	if (!opts.kty) { | ||||
| 		opts.kty = 'EC'; | ||||
| 	} | ||||
| 
 | ||||
| 	// ECDSA has only the P curves and an associated bitlength
 | ||||
| 	wcOpts.name = 'ECDSA'; | ||||
| 	if (!opts.namedCurve) { | ||||
| 		opts.namedCurve = 'P-256'; | ||||
| 	} | ||||
| 	wcOpts.namedCurve = opts.namedCurve; // true for supported curves
 | ||||
| 	if (/256/.test(wcOpts.namedCurve)) { | ||||
| 		wcOpts.namedCurve = 'P-256'; | ||||
| 		wcOpts.hash = { name: 'SHA-256' }; | ||||
| 	} else if (/384/.test(wcOpts.namedCurve)) { | ||||
| 		wcOpts.namedCurve = 'P-384'; | ||||
| 		wcOpts.hash = { name: 'SHA-384' }; | ||||
| 	} else { | ||||
| 		return Promise.Reject( | ||||
| 			new Error( | ||||
| 				"'" + | ||||
| 					wcOpts.namedCurve + | ||||
| 					"' is not an NIST approved ECDSA namedCurve. " + | ||||
| 					" Please choose either 'P-256' or 'P-384'. " + | ||||
| 					// XXX received from caller
 | ||||
| 					EC._stance | ||||
| 			) | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	var extractable = true; | ||||
| 	return window.crypto.subtle | ||||
| 		.generateKey(wcOpts, extractable, ['sign', 'verify']) | ||||
| 		.then(function(result) { | ||||
| 			return window.crypto.subtle | ||||
| 				.exportKey('jwk', result.privateKey) | ||||
| 				.then(function(privJwk) { | ||||
| 					privJwk.key_ops = undefined; | ||||
| 					privJwk.ext = undefined; | ||||
| 					return { | ||||
| 						private: privJwk, | ||||
| 						// XXX received from caller
 | ||||
| 						public: EC.neuter({ jwk: privJwk }) | ||||
| 					}; | ||||
| 				}); | ||||
| 		}); | ||||
| }; | ||||
| @ -1,108 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var Keypairs = module.exports; | ||||
| 
 | ||||
| Keypairs._sign = function(opts, payload) { | ||||
| 	return Keypairs._import(opts).then(function(privkey) { | ||||
| 		if ('string' === typeof payload) { | ||||
| 			payload = new TextEncoder().encode(payload); | ||||
| 		} | ||||
| 
 | ||||
| 		return window.crypto.subtle | ||||
| 			.sign( | ||||
| 				{ | ||||
| 					name: Keypairs._getName(opts), | ||||
| 					hash: { name: 'SHA-' + Keypairs._getBits(opts) } | ||||
| 				}, | ||||
| 				privkey, | ||||
| 				payload | ||||
| 			) | ||||
| 			.then(function(signature) { | ||||
| 				signature = new Uint8Array(signature); // ArrayBuffer -> u8
 | ||||
| 				// This will come back into play for CSRs, but not for JOSE
 | ||||
| 				if ('EC' === opts.jwk.kty && /x509|asn1/i.test(opts.format)) { | ||||
| 					return Keypairs._ecdsaJoseSigToAsn1Sig(signature); | ||||
| 				} else { | ||||
| 					// jose/jws/jwt
 | ||||
| 					return signature; | ||||
| 				} | ||||
| 			}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| Keypairs._import = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		var ops; | ||||
| 		// all private keys just happen to have a 'd'
 | ||||
| 		if (opts.jwk.d) { | ||||
| 			ops = ['sign']; | ||||
| 		} else { | ||||
| 			ops = ['verify']; | ||||
| 		} | ||||
| 		// gotta mark it as extractable, as if it matters
 | ||||
| 		opts.jwk.ext = true; | ||||
| 		opts.jwk.key_ops = ops; | ||||
| 
 | ||||
| 		return window.crypto.subtle | ||||
| 			.importKey( | ||||
| 				'jwk', | ||||
| 				opts.jwk, | ||||
| 				{ | ||||
| 					name: Keypairs._getName(opts), | ||||
| 					namedCurve: opts.jwk.crv, | ||||
| 					hash: { name: 'SHA-' + Keypairs._getBits(opts) } | ||||
| 				}, | ||||
| 				true, | ||||
| 				ops | ||||
| 			) | ||||
| 			.then(function(privkey) { | ||||
| 				delete opts.jwk.ext; | ||||
| 				return privkey; | ||||
| 			}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| // ECDSA JOSE / JWS / JWT signatures differ from "normal" ASN1/X509 ECDSA signatures
 | ||||
| // https://tools.ietf.org/html/rfc7518#section-3.4
 | ||||
| Keypairs._ecdsaJoseSigToAsn1Sig = function(bufsig) { | ||||
| 	// it's easier to do the manipulation in the browser with an array
 | ||||
| 	bufsig = Array.from(bufsig); | ||||
| 	var hlen = bufsig.length / 2; // should be even
 | ||||
| 	var r = bufsig.slice(0, hlen); | ||||
| 	var s = bufsig.slice(hlen); | ||||
| 	// unpad positive ints less than 32 bytes wide
 | ||||
| 	while (!r[0]) { | ||||
| 		r = r.slice(1); | ||||
| 	} | ||||
| 	while (!s[0]) { | ||||
| 		s = s.slice(1); | ||||
| 	} | ||||
| 	// pad (or re-pad) ambiguously non-negative BigInts, up to 33 bytes wide
 | ||||
| 	if (0x80 & r[0]) { | ||||
| 		r.unshift(0); | ||||
| 	} | ||||
| 	if (0x80 & s[0]) { | ||||
| 		s.unshift(0); | ||||
| 	} | ||||
| 
 | ||||
| 	var len = 2 + r.length + 2 + s.length; | ||||
| 	var head = [0x30]; | ||||
| 	// hard code 0x80 + 1 because it won't be longer than
 | ||||
| 	// two SHA512 plus two pad bytes (130 bytes <= 256)
 | ||||
| 	if (len >= 0x80) { | ||||
| 		head.push(0x81); | ||||
| 	} | ||||
| 	head.push(len); | ||||
| 
 | ||||
| 	return Uint8Array.from( | ||||
| 		head.concat([0x02, r.length], r, [0x02, s.length], s) | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| Keypairs._getName = function(opts) { | ||||
| 	if (/EC/i.test(opts.jwk.kty)) { | ||||
| 		return 'ECDSA'; | ||||
| 	} else { | ||||
| 		return 'RSASSA-PKCS1-v1_5'; | ||||
| 	} | ||||
| }; | ||||
| @ -1,59 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var native = module.exports; | ||||
| // XXX added by caller: _stance, neuter
 | ||||
| var RSA = native; | ||||
| 
 | ||||
| native.generate = function(opts) { | ||||
| 	var wcOpts = {}; | ||||
| 	if (!opts) { | ||||
| 		opts = {}; | ||||
| 	} | ||||
| 	if (!opts.kty) { | ||||
| 		opts.kty = 'RSA'; | ||||
| 	} | ||||
| 
 | ||||
| 	// Support PSS? I don't think it's used for Let's Encrypt
 | ||||
| 	wcOpts.name = 'RSASSA-PKCS1-v1_5'; | ||||
| 	if (!opts.modulusLength) { | ||||
| 		opts.modulusLength = 2048; | ||||
| 	} | ||||
| 	wcOpts.modulusLength = opts.modulusLength; | ||||
| 	if (wcOpts.modulusLength >= 2048 && wcOpts.modulusLength < 3072) { | ||||
| 		// erring on the small side... for no good reason
 | ||||
| 		wcOpts.hash = { name: 'SHA-256' }; | ||||
| 	} else if (wcOpts.modulusLength >= 3072 && wcOpts.modulusLength < 4096) { | ||||
| 		wcOpts.hash = { name: 'SHA-384' }; | ||||
| 	} else if (wcOpts.modulusLength < 4097) { | ||||
| 		wcOpts.hash = { name: 'SHA-512' }; | ||||
| 	} else { | ||||
| 		// Public key thumbprints should be paired with a hash of similar length,
 | ||||
| 		// so anything above SHA-512's keyspace would be left under-represented anyway.
 | ||||
| 		return Promise.Reject( | ||||
| 			new Error( | ||||
| 				"'" + | ||||
| 					wcOpts.modulusLength + | ||||
| 					"' is not within the safe and universally" + | ||||
| 					' acceptable range of 2048-4096. Typically you should pick 2048, 3072, or 4096, though other values' + | ||||
| 					' divisible by 8 are allowed. ' + | ||||
| 					RSA._stance | ||||
| 			) | ||||
| 		); | ||||
| 	} | ||||
| 	// TODO maybe allow this to be set to any of the standard values?
 | ||||
| 	wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); | ||||
| 
 | ||||
| 	var extractable = true; | ||||
| 	return window.crypto.subtle | ||||
| 		.generateKey(wcOpts, extractable, ['sign', 'verify']) | ||||
| 		.then(function(result) { | ||||
| 			return window.crypto.subtle | ||||
| 				.exportKey('jwk', result.privateKey) | ||||
| 				.then(function(privJwk) { | ||||
| 					return { | ||||
| 						private: privJwk, | ||||
| 						public: RSA.neuter({ jwk: privJwk }) | ||||
| 					}; | ||||
| 				}); | ||||
| 		}); | ||||
| }; | ||||
| @ -1,113 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var native = module.exports; | ||||
| // XXX provided by caller: import, export
 | ||||
| var EC = native; | ||||
| // TODO SSH
 | ||||
| 
 | ||||
| native.generate = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		var typ = 'ec'; | ||||
| 		var format = opts.format; | ||||
| 		var encoding = opts.encoding; | ||||
| 		var priv; | ||||
| 		var pub = 'spki'; | ||||
| 
 | ||||
| 		if (!format) { | ||||
| 			format = 'jwk'; | ||||
| 		} | ||||
| 		if (-1 !== ['spki', 'pkcs8', 'ssh'].indexOf(format)) { | ||||
| 			format = 'pkcs8'; | ||||
| 		} | ||||
| 
 | ||||
| 		if ('pem' === format) { | ||||
| 			format = 'sec1'; | ||||
| 			encoding = 'pem'; | ||||
| 		} else if ('der' === format) { | ||||
| 			format = 'sec1'; | ||||
| 			encoding = 'der'; | ||||
| 		} | ||||
| 
 | ||||
| 		if ('jwk' === format || 'json' === format) { | ||||
| 			format = 'jwk'; | ||||
| 			encoding = 'json'; | ||||
| 		} else { | ||||
| 			priv = format; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!encoding) { | ||||
| 			encoding = 'pem'; | ||||
| 		} | ||||
| 
 | ||||
| 		if (priv) { | ||||
| 			priv = { type: priv, format: encoding }; | ||||
| 			pub = { type: pub, format: encoding }; | ||||
| 		} else { | ||||
| 			// jwk
 | ||||
| 			priv = { type: 'sec1', format: 'pem' }; | ||||
| 			pub = { type: 'spki', format: 'pem' }; | ||||
| 		} | ||||
| 
 | ||||
| 		return new Promise(function(resolve, reject) { | ||||
| 			return require('crypto').generateKeyPair( | ||||
| 				typ, | ||||
| 				{ | ||||
| 					namedCurve: opts.crv || opts.namedCurve || 'P-256', | ||||
| 					privateKeyEncoding: priv, | ||||
| 					publicKeyEncoding: pub | ||||
| 				}, | ||||
| 				function(err, pubkey, privkey) { | ||||
| 					if (err) { | ||||
| 						reject(err); | ||||
| 					} | ||||
| 					resolve({ | ||||
| 						private: privkey, | ||||
| 						public: pubkey | ||||
| 					}); | ||||
| 				} | ||||
| 			); | ||||
| 		}).then(function(keypair) { | ||||
| 			if ('jwk' === format) { | ||||
| 				return Promise.all([ | ||||
| 					native.import({ | ||||
| 						pem: keypair.private, | ||||
| 						format: priv.type | ||||
| 					}), | ||||
| 					native.import({ | ||||
| 						pem: keypair.public, | ||||
| 						format: pub.type, | ||||
| 						public: true | ||||
| 					}) | ||||
| 				]).then(function(pair) { | ||||
| 					return { | ||||
| 						private: pair[0], | ||||
| 						public: pair[1] | ||||
| 					}; | ||||
| 				}); | ||||
| 			} | ||||
| 
 | ||||
| 			if ('ssh' !== opts.format) { | ||||
| 				return keypair; | ||||
| 			} | ||||
| 
 | ||||
| 			return native | ||||
| 				.import({ | ||||
| 					pem: keypair.public, | ||||
| 					format: format, | ||||
| 					public: true | ||||
| 				}) | ||||
| 				.then(function(jwk) { | ||||
| 					return EC.export({ | ||||
| 						jwk: jwk, | ||||
| 						format: opts.format, | ||||
| 						public: true | ||||
| 					}).then(function(pub) { | ||||
| 						return { | ||||
| 							private: keypair.private, | ||||
| 							public: pub | ||||
| 						}; | ||||
| 					}); | ||||
| 				}); | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| @ -1,55 +0,0 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| module.exports = function(bitlen, exp) { | ||||
| 	var k = require('node-forge').pki.rsa.generateKeyPair({ | ||||
| 		bits: bitlen || 2048, | ||||
| 		e: exp || 0x10001 | ||||
| 	}).privateKey; | ||||
| 	var jwk = { | ||||
| 		kty: 'RSA', | ||||
| 		n: _toUrlBase64(k.n), | ||||
| 		e: _toUrlBase64(k.e), | ||||
| 		d: _toUrlBase64(k.d), | ||||
| 		p: _toUrlBase64(k.p), | ||||
| 		q: _toUrlBase64(k.q), | ||||
| 		dp: _toUrlBase64(k.dP), | ||||
| 		dq: _toUrlBase64(k.dQ), | ||||
| 		qi: _toUrlBase64(k.qInv) | ||||
| 	}; | ||||
| 	return { | ||||
| 		private: jwk, | ||||
| 		public: { | ||||
| 			kty: jwk.kty, | ||||
| 			n: jwk.n, | ||||
| 			e: jwk.e | ||||
| 		} | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| function _toUrlBase64(fbn) { | ||||
| 	var hex = fbn.toRadix(16); | ||||
| 	if (hex.length % 2) { | ||||
| 		// Invalid hex string
 | ||||
| 		hex = '0' + hex; | ||||
| 	} | ||||
| 	while ('00' === hex.slice(0, 2)) { | ||||
| 		hex = hex.slice(2); | ||||
| 	} | ||||
| 	return Buffer.from(hex, 'hex') | ||||
| 		.toString('base64') | ||||
| 		.replace(/\+/g, '-') | ||||
| 		.replace(/\//g, '_') | ||||
| 		.replace(/=/g, ''); | ||||
| } | ||||
| 
 | ||||
| if (require.main === module) { | ||||
| 	var keypair = module.exports(2048, 0x10001); | ||||
| 	console.info(keypair.private); | ||||
| 	console.warn(keypair.public); | ||||
| 	//console.info(keypair.privateKeyJwk);
 | ||||
| 	//console.warn(keypair.publicKeyJwk);
 | ||||
| } | ||||
| @ -1,21 +0,0 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| module.exports = function(bitlen, exp) { | ||||
| 	var keypair = require('crypto').generateKeyPairSync('rsa', { | ||||
| 		modulusLength: bitlen, | ||||
| 		publicExponent: exp, | ||||
| 		privateKeyEncoding: { type: 'pkcs1', format: 'pem' }, | ||||
| 		publicKeyEncoding: { type: 'pkcs1', format: 'pem' } | ||||
| 	}); | ||||
| 	var result = { privateKeyPem: keypair.privateKey.trim() }; | ||||
| 	return result; | ||||
| }; | ||||
| 
 | ||||
| if (require.main === module) { | ||||
| 	var keypair = module.exports(2048, 0x10001); | ||||
| 	console.info(keypair.privateKeyPem); | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| module.exports = function(bitlen, exp) { | ||||
| 	var ursa; | ||||
| 	try { | ||||
| 		ursa = require('ursa'); | ||||
| 	} catch (e) { | ||||
| 		ursa = require('ursa-optional'); | ||||
| 	} | ||||
| 	var keypair = ursa.generatePrivateKey(bitlen, exp); | ||||
| 	var result = { | ||||
| 		privateKeyPem: keypair | ||||
| 			.toPrivatePem() | ||||
| 			.toString('ascii') | ||||
| 			.trim() | ||||
| 	}; | ||||
| 	return result; | ||||
| }; | ||||
| 
 | ||||
| if (require.main === module) { | ||||
| 	var keypair = module.exports(2048, 0x10001); | ||||
| 	console.info(keypair.privateKeyPem); | ||||
| } | ||||
| @ -1,90 +0,0 @@ | ||||
| // 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 oldver = false; | ||||
| 
 | ||||
| module.exports = function(bitlen, exp) { | ||||
| 	bitlen = parseInt(bitlen, 10) || 2048; | ||||
| 	exp = parseInt(exp, 10) || 65537; | ||||
| 
 | ||||
| 	try { | ||||
| 		return require('./generate-privkey-node.js')(bitlen, exp); | ||||
| 	} catch (e) { | ||||
| 		if (!/generateKeyPairSync is not a function/.test(e.message)) { | ||||
| 			throw e; | ||||
| 		} | ||||
| 		try { | ||||
| 			return require('./generate-privkey-ursa.js')(bitlen, exp); | ||||
| 		} catch (e) { | ||||
| 			if (e.code !== 'MODULE_NOT_FOUND') { | ||||
| 				console.error( | ||||
| 					"[rsa-compat] Unexpected error when using 'ursa':" | ||||
| 				); | ||||
| 				console.error(e); | ||||
| 			} | ||||
| 			if (!oldver) { | ||||
| 				oldver = true; | ||||
| 				console.warn( | ||||
| 					'[WARN] rsa-compat: Your version of node does not have crypto.generateKeyPair()' | ||||
| 				); | ||||
| 				console.warn( | ||||
| 					"[WARN] rsa-compat: Please update to node >= v10.12 or 'npm install --save ursa node-forge'" | ||||
| 				); | ||||
| 				console.warn( | ||||
| 					'[WARN] rsa-compat: Using node-forge as a fallback may be unacceptably slow.' | ||||
| 				); | ||||
| 				if (/arm|mips/i.test(require('os').arch)) { | ||||
| 					console.warn( | ||||
| 						'================================================================' | ||||
| 					); | ||||
| 					console.warn('                         WARNING'); | ||||
| 					console.warn( | ||||
| 						'================================================================' | ||||
| 					); | ||||
| 					console.warn(''); | ||||
| 					console.warn( | ||||
| 						'WARNING: You are generating an RSA key using pure JavaScript on' | ||||
| 					); | ||||
| 					console.warn( | ||||
| 						'         a VERY SLOW cpu. This could take DOZENS of minutes!' | ||||
| 					); | ||||
| 					console.warn(''); | ||||
| 					console.warn( | ||||
| 						"         We recommend installing node >= v10.12, or 'gcc' and 'ursa'" | ||||
| 					); | ||||
| 					console.warn(''); | ||||
| 					console.warn('EXAMPLE:'); | ||||
| 					console.warn(''); | ||||
| 					console.warn( | ||||
| 						'        sudo apt-get install build-essential && npm install ursa' | ||||
| 					); | ||||
| 					console.warn(''); | ||||
| 					console.warn( | ||||
| 						'================================================================' | ||||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 			try { | ||||
| 				return require('./generate-privkey-forge.js')(bitlen, exp); | ||||
| 			} catch (e) { | ||||
| 				if (e.code !== 'MODULE_NOT_FOUND') { | ||||
| 					throw e; | ||||
| 				} | ||||
| 				console.error( | ||||
| 					'[ERROR] rsa-compat: could not generate a private key.' | ||||
| 				); | ||||
| 				console.error( | ||||
| 					'None of crypto.generateKeyPair, ursa, nor node-forge are present' | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| if (require.main === module) { | ||||
| 	var keypair = module.exports(2048, 0x10001); | ||||
| 	console.info(keypair.privateKeyPem); | ||||
| } | ||||
| @ -1,84 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var Keypairs = module.exports; | ||||
| var crypto = require('crypto'); | ||||
| 
 | ||||
| Keypairs._sign = function(opts, payload) { | ||||
| 	return Keypairs._import(opts).then(function(pem) { | ||||
| 		payload = Buffer.from(payload); | ||||
| 
 | ||||
| 		// node specifies RSA-SHAxxx even when it's actually ecdsa (it's all encoded x509 shasums anyway)
 | ||||
| 		// TODO opts.alg = (protect||header).alg
 | ||||
| 		var nodeAlg = 'SHA' + Keypairs._getBits(opts); | ||||
| 		var binsig = crypto | ||||
| 			.createSign(nodeAlg) | ||||
| 			.update(payload) | ||||
| 			.sign(pem); | ||||
| 
 | ||||
| 		if ('EC' === opts.jwk.kty && !/x509|asn1/i.test(opts.format)) { | ||||
| 			// ECDSA JWT signatures differ from "normal" ECDSA signatures
 | ||||
| 			// https://tools.ietf.org/html/rfc7518#section-3.4
 | ||||
| 			binsig = Keypairs._ecdsaAsn1SigToJoseSig(binsig); | ||||
| 		} | ||||
| 
 | ||||
| 		return binsig; | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| Keypairs._import = function(opts) { | ||||
| 	if (opts.pem && opts.jwk) { | ||||
| 		return Promise.resolve(opts.pem); | ||||
| 	} else { | ||||
| 		// XXX added by caller
 | ||||
| 		return Keypairs.export({ jwk: opts.jwk }); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| Keypairs._ecdsaAsn1SigToJoseSig = function(binsig) { | ||||
| 	// should have asn1 sequence header of 0x30
 | ||||
| 	if (0x30 !== binsig[0]) { | ||||
| 		throw new Error('Impossible EC SHA head marker'); | ||||
| 	} | ||||
| 	var index = 2; // first ecdsa "R" header byte
 | ||||
| 	var len = binsig[1]; | ||||
| 	var lenlen = 0; | ||||
| 	// Seek length of length if length is greater than 127 (i.e. two 512-bit / 64-byte R and S values)
 | ||||
| 	if (0x80 & len) { | ||||
| 		lenlen = len - 0x80; // should be exactly 1
 | ||||
| 		len = binsig[2]; // should be <= 130 (two 64-bit SHA-512s, plus padding)
 | ||||
| 		index += lenlen; | ||||
| 	} | ||||
| 	// should be of BigInt type
 | ||||
| 	if (0x02 !== binsig[index]) { | ||||
| 		throw new Error('Impossible EC SHA R marker'); | ||||
| 	} | ||||
| 	index += 1; | ||||
| 
 | ||||
| 	var rlen = binsig[index]; | ||||
| 	var bits = 32; | ||||
| 	if (rlen > 49) { | ||||
| 		bits = 64; | ||||
| 	} else if (rlen > 33) { | ||||
| 		bits = 48; | ||||
| 	} | ||||
| 	var r = binsig.slice(index + 1, index + 1 + rlen).toString('hex'); | ||||
| 	var slen = binsig[index + 1 + rlen + 1]; // skip header and read length
 | ||||
| 	var s = binsig.slice(index + 1 + rlen + 1 + 1).toString('hex'); | ||||
| 	if (2 * slen !== s.length) { | ||||
| 		throw new Error('Impossible EC SHA S length'); | ||||
| 	} | ||||
| 	// There may be one byte of padding on either
 | ||||
| 	while (r.length < 2 * bits) { | ||||
| 		r = '00' + r; | ||||
| 	} | ||||
| 	while (s.length < 2 * bits) { | ||||
| 		s = '00' + s; | ||||
| 	} | ||||
| 	if (2 * (bits + 1) === r.length) { | ||||
| 		r = r.slice(2); | ||||
| 	} | ||||
| 	if (2 * (bits + 1) === s.length) { | ||||
| 		s = s.slice(2); | ||||
| 	} | ||||
| 	return Buffer.concat([Buffer.from(r, 'hex'), Buffer.from(s, 'hex')]); | ||||
| }; | ||||
							
								
								
									
										154
									
								
								lib/node/rsa.js
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								lib/node/rsa.js
									
									
									
									
									
								
							| @ -1,154 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var native = module.exports; | ||||
| 
 | ||||
| // XXX provided by caller: export
 | ||||
| var RSA = native; | ||||
| var PEM = require('../../pem.js'); | ||||
| var x509 = require('../../x509.js'); | ||||
| var ASN1 = require('../../asn1/parser.js'); | ||||
| 
 | ||||
| native.generate = function(opts) { | ||||
| 	opts.kty = 'RSA'; | ||||
| 	return native._generate(opts).then(function(pair) { | ||||
| 		var format = opts.format; | ||||
| 		var encoding = opts.encoding; | ||||
| 
 | ||||
| 		// The easy way
 | ||||
| 		if ('json' === format && !encoding) { | ||||
| 			format = 'jwk'; | ||||
| 			encoding = 'json'; | ||||
| 		} | ||||
| 		if ( | ||||
| 			('jwk' === format || !format) && | ||||
| 			('json' === encoding || !encoding) | ||||
| 		) { | ||||
| 			return pair; | ||||
| 		} | ||||
| 		if ('jwk' === format || 'json' === encoding) { | ||||
| 			throw new Error( | ||||
| 				"format '" + | ||||
| 					format + | ||||
| 					"' is incompatible with encoding '" + | ||||
| 					encoding + | ||||
| 					"'" | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		// The... less easy way
 | ||||
| 		/* | ||||
|     var priv; | ||||
|     var pub; | ||||
| 
 | ||||
|     if ('spki' === format || 'pkcs8' === format) { | ||||
|       format = 'pkcs8'; | ||||
|       pub = 'spki'; | ||||
|     } | ||||
| 
 | ||||
|     if ('pem' === format) { | ||||
|       format = 'pkcs1'; | ||||
|       encoding = 'pem'; | ||||
|     } else if ('der' === format) { | ||||
|       format = 'pkcs1'; | ||||
|       encoding = 'der'; | ||||
|     } | ||||
| 
 | ||||
|     priv = format; | ||||
|     pub = pub || format; | ||||
| 
 | ||||
|     if (!encoding) { | ||||
|       encoding = 'pem'; | ||||
|     } | ||||
| 
 | ||||
|     if (priv) { | ||||
|       priv = { type: priv, format: encoding }; | ||||
|       pub = { type: pub, format: encoding }; | ||||
|     } else { | ||||
|       // jwk
 | ||||
|       priv = { type: 'pkcs1', format: 'pem' }; | ||||
|       pub = { type: 'pkcs1', format: 'pem' }; | ||||
|     } | ||||
|     */ | ||||
| 		if (('pem' === format || 'der' === format) && !encoding) { | ||||
| 			encoding = format; | ||||
| 			format = 'pkcs1'; | ||||
| 		} | ||||
| 
 | ||||
| 		var exOpts = { jwk: pair.private, format: format, encoding: encoding }; | ||||
| 		return RSA.export(exOpts).then(function(priv) { | ||||
| 			exOpts.public = true; | ||||
| 			if ('pkcs8' === exOpts.format) { | ||||
| 				exOpts.format = 'spki'; | ||||
| 			} | ||||
| 			return RSA.export(exOpts).then(function(pub) { | ||||
| 				return { private: priv, public: pub }; | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| native._generate = function(opts) { | ||||
| 	if (!opts) { | ||||
| 		opts = {}; | ||||
| 	} | ||||
| 	return new Promise(function(resolve, reject) { | ||||
| 		try { | ||||
| 			var modlen = opts.modulusLength || 2048; | ||||
| 			var exp = opts.publicExponent || 0x10001; | ||||
| 			var pair = require('./generate-privkey.js')(modlen, exp); | ||||
| 			if (pair.private) { | ||||
| 				resolve(pair); | ||||
| 				return; | ||||
| 			} | ||||
| 			pair = toJwks(pair); | ||||
| 			resolve({ private: pair.private, public: pair.public }); | ||||
| 		} catch (e) { | ||||
| 			reject(e); | ||||
| 		} | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| // PKCS1 to JWK only
 | ||||
| function toJwks(oldpair) { | ||||
| 	var block = PEM.parseBlock(oldpair.privateKeyPem); | ||||
| 	var asn1 = ASN1.parse(block.bytes); | ||||
| 	var jwk = { kty: 'RSA', n: null, e: null }; | ||||
| 	jwk = x509.parsePkcs1(block.bytes, asn1, jwk); | ||||
| 	return { private: jwk, public: RSA.neuter({ jwk: jwk }) }; | ||||
| } | ||||
| 
 | ||||
| // TODO
 | ||||
| var Enc = require('@root/encoding/base64'); | ||||
| x509.parsePkcs1 = function parseRsaPkcs1(buf, asn1, jwk) { | ||||
| 	if ( | ||||
| 		!asn1.children.every(function(el) { | ||||
| 			return 0x02 === el.type; | ||||
| 		}) | ||||
| 	) { | ||||
| 		throw new Error( | ||||
| 			'not an RSA PKCS#1 public or private key (not all ints)' | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	if (2 === asn1.children.length) { | ||||
| 		jwk.n = Enc.bufToUrlBase64(asn1.children[0].value); | ||||
| 		jwk.e = Enc.bufToUrlBase64(asn1.children[1].value); | ||||
| 		return jwk; | ||||
| 	} else if (asn1.children.length >= 9) { | ||||
| 		// the standard allows for "otherPrimeInfos", hence at least 9
 | ||||
| 
 | ||||
| 		jwk.n = Enc.bufToUrlBase64(asn1.children[1].value); | ||||
| 		jwk.e = Enc.bufToUrlBase64(asn1.children[2].value); | ||||
| 		jwk.d = Enc.bufToUrlBase64(asn1.children[3].value); | ||||
| 		jwk.p = Enc.bufToUrlBase64(asn1.children[4].value); | ||||
| 		jwk.q = Enc.bufToUrlBase64(asn1.children[5].value); | ||||
| 		jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value); | ||||
| 		jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value); | ||||
| 		jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value); | ||||
| 		return jwk; | ||||
| 	} else { | ||||
| 		throw new Error( | ||||
| 			'not an RSA PKCS#1 public or private key (wrong number of ints)' | ||||
| 		); | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										4844
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4844
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							| @ -40,15 +40,20 @@ | ||||
| 	"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
| 	"license": "MPL-2.0", | ||||
| 	"dependencies": { | ||||
| 		"@root/csr": "^1.0.0-wip.0", | ||||
| 		"@root/csr": "^0.8.0", | ||||
| 		"@root/encoding": "^1.0.1", | ||||
| 		"@root/keypairs": "^1.0.0-wip.0" | ||||
| 		"@root/keypairs": "^0.9.0", | ||||
| 		"@root/pem": "^1.0.4", | ||||
| 		"@root/x509": "^0.7.2" | ||||
| 	}, | ||||
| 	"devDependencies": { | ||||
| 		"@root/request": "^1.3.10", | ||||
| 		"dig.js": "^1.3.9", | ||||
| 		"dns-suite": "^1.2.12", | ||||
| 		"dotenv": "^8.1.0", | ||||
| 		"punycode": "^1.4.1" | ||||
| 		"eslint": "^6.5.1", | ||||
| 		"punycode": "^1.4.1", | ||||
| 		"webpack": "^4.41.0", | ||||
| 		"webpack-cli": "^3.3.9" | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										158
									
								
								rsa.js
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								rsa.js
									
									
									
									
									
								
							| @ -1,158 +0,0 @@ | ||||
| /*global Promise*/ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var RSA = module.exports; | ||||
| var native = require('./lib/node/rsa.js'); | ||||
| var x509 = require('./x509.js'); | ||||
| var PEM = require('./pem.js'); | ||||
| //var SSH = require('./ssh-keys.js');
 | ||||
| var sha2 = require('./lib/node/sha2.js'); | ||||
| var Enc = require('@root/encoding/base64'); | ||||
| 
 | ||||
| RSA._universal = | ||||
| 	'Bluecrypt only supports crypto with standard cross-browser and cross-platform support.'; | ||||
| RSA._stance = | ||||
| 	"We take the stance that if you're knowledgeable enough to" + | ||||
| 	" properly and securely use non-standard crypto then you shouldn't need Bluecrypt anyway."; | ||||
| native._stance = RSA._stance; | ||||
| 
 | ||||
| RSA.generate = native.generate; | ||||
| 
 | ||||
| // Chopping off the private parts is now part of the public API.
 | ||||
| // I thought it sounded a little too crude at first, but it really is the best name in every possible way.
 | ||||
| RSA.neuter = function(opts) { | ||||
| 	// trying to find the best balance of an immutable copy with custom attributes
 | ||||
| 	var jwk = {}; | ||||
| 	Object.keys(opts.jwk).forEach(function(k) { | ||||
| 		if ('undefined' === typeof opts.jwk[k]) { | ||||
| 			return; | ||||
| 		} | ||||
| 		// ignore RSA private parts
 | ||||
| 		if (-1 !== ['d', 'p', 'q', 'dp', 'dq', 'qi'].indexOf(k)) { | ||||
| 			return; | ||||
| 		} | ||||
| 		jwk[k] = JSON.parse(JSON.stringify(opts.jwk[k])); | ||||
| 	}); | ||||
| 	return jwk; | ||||
| }; | ||||
| native.neuter = RSA.neuter; | ||||
| 
 | ||||
| // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
 | ||||
| RSA.__thumbprint = function(jwk) { | ||||
| 	// Use the same entropy for SHA as for key
 | ||||
| 	var len = Math.floor(jwk.n.length * 0.75); | ||||
| 	var alg = 'SHA-256'; | ||||
| 	// TODO this may be a bug
 | ||||
| 	// need to confirm that the padding is no more or less than 1 byte
 | ||||
| 	if (len >= 511) { | ||||
| 		alg = 'SHA-512'; | ||||
| 	} else if (len >= 383) { | ||||
| 		alg = 'SHA-384'; | ||||
| 	} | ||||
| 	return sha2 | ||||
| 		.sum(alg, '{"e":"' + jwk.e + '","kty":"RSA","n":"' + jwk.n + '"}') | ||||
| 		.then(function(hash) { | ||||
| 			return Enc.bufToUrlBase64(Uint8Array.from(hash)); | ||||
| 		}); | ||||
| }; | ||||
| 
 | ||||
| RSA.thumbprint = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		var jwk; | ||||
| 		if ('EC' === opts.kty) { | ||||
| 			jwk = opts; | ||||
| 		} else if (opts.jwk) { | ||||
| 			jwk = opts.jwk; | ||||
| 		} else { | ||||
| 			return RSA.import(opts).then(function(jwk) { | ||||
| 				return RSA.__thumbprint(jwk); | ||||
| 			}); | ||||
| 		} | ||||
| 		return RSA.__thumbprint(jwk); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| RSA.export = function(opts) { | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { | ||||
| 			throw new Error('must pass { jwk: jwk }'); | ||||
| 		} | ||||
| 		var jwk = JSON.parse(JSON.stringify(opts.jwk)); | ||||
| 		var format = opts.format; | ||||
| 		var pub = opts.public; | ||||
| 		if (pub || -1 !== ['spki', 'pkix', 'ssh', 'rfc4716'].indexOf(format)) { | ||||
| 			jwk = RSA.neuter({ jwk: jwk }); | ||||
| 		} | ||||
| 		if ('RSA' !== jwk.kty) { | ||||
| 			throw new Error("options.jwk.kty must be 'RSA' for RSA keys"); | ||||
| 		} | ||||
| 		if (!jwk.p) { | ||||
| 			// TODO test for n and e
 | ||||
| 			pub = true; | ||||
| 			if (!format || 'pkcs1' === format) { | ||||
| 				format = 'pkcs1'; | ||||
| 			} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 				format = 'spki'; | ||||
| 			} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 				format = 'ssh'; | ||||
| 			} else { | ||||
| 				throw new Error( | ||||
| 					"options.format must be 'spki', 'pkcs1', or 'ssh' for public RSA keys, not (" + | ||||
| 						typeof format + | ||||
| 						') ' + | ||||
| 						format | ||||
| 				); | ||||
| 			} | ||||
| 		} else { | ||||
| 			// TODO test for all necessary keys (d, p, q ...)
 | ||||
| 			if (!format || 'pkcs1' === format) { | ||||
| 				format = 'pkcs1'; | ||||
| 			} else if ('pkcs8' !== format) { | ||||
| 				throw new Error( | ||||
| 					"options.format must be 'pkcs1' or 'pkcs8' for private RSA keys" | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if ('pkcs1' === format) { | ||||
| 			if (jwk.d) { | ||||
| 				return PEM.packBlock({ | ||||
| 					type: 'RSA PRIVATE KEY', | ||||
| 					bytes: x509.packPkcs1(jwk) | ||||
| 				}); | ||||
| 			} else { | ||||
| 				return PEM.packBlock({ | ||||
| 					type: 'RSA PUBLIC KEY', | ||||
| 					bytes: x509.packPkcs1(jwk) | ||||
| 				}); | ||||
| 			} | ||||
| 		} else if ('pkcs8' === format) { | ||||
| 			return PEM.packBlock({ | ||||
| 				type: 'PRIVATE KEY', | ||||
| 				bytes: x509.packPkcs8(jwk) | ||||
| 			}); | ||||
| 		} else if (-1 !== ['spki', 'pkix'].indexOf(format)) { | ||||
| 			return PEM.packBlock({ | ||||
| 				type: 'PUBLIC KEY', | ||||
| 				bytes: x509.packSpki(jwk) | ||||
| 			}); | ||||
| 		} else if (-1 !== ['ssh', 'rfc4716'].indexOf(format)) { | ||||
| 			return SSH.pack({ jwk: jwk, comment: opts.comment }); | ||||
| 		} else { | ||||
| 			throw new Error( | ||||
| 				'Sanity Error: reached unreachable code block with format: ' + | ||||
| 					format | ||||
| 			); | ||||
| 		} | ||||
| 	}); | ||||
| }; | ||||
| native.export = RSA.export; | ||||
| 
 | ||||
| RSA.pack = function(opts) { | ||||
| 	// wrapped in a promise for API compatibility
 | ||||
| 	// with the forthcoming browser version
 | ||||
| 	// (and potential future native node capability)
 | ||||
| 	return Promise.resolve().then(function() { | ||||
| 		return RSA.export(opts); | ||||
| 	}); | ||||
| }; | ||||
| @ -4,10 +4,10 @@ require('dotenv').config(); | ||||
| 
 | ||||
| var CSR = require('@root/csr'); | ||||
| var Enc = require('@root/encoding/base64'); | ||||
| var PEM = require('../pem.js'); | ||||
| var PEM = require('@root/pem'); | ||||
| var punycode = require('punycode'); | ||||
| var ACME = require('../acme.js'); | ||||
| var Keypairs = require('../keypairs.js'); | ||||
| var Keypairs = require('@root/keypairs'); | ||||
| var acme = ACME.create({ | ||||
| 	// debug: true
 | ||||
| }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user