261 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| require('dotenv').config();
 | |
| 
 | |
| var pkg = require('../package.json');
 | |
| var CSR = require('@root/csr');
 | |
| var Enc = require('@root/encoding/base64');
 | |
| var PEM = require('@root/pem');
 | |
| var punycode = require('punycode');
 | |
| var ACME = require('../acme.js');
 | |
| var Keypairs = require('@root/keypairs');
 | |
| var ecJwk = require('../fixtures/account.jwk.json');
 | |
| 
 | |
| // TODO exec npm install --save-dev CHALLENGE_MODULE
 | |
| if (!process.env.CHALLENGE_OPTIONS) {
 | |
| 	console.error(
 | |
| 		'Please create a .env in the format of examples/example.env to run the tests'
 | |
| 	);
 | |
| 	process.exit(1);
 | |
| }
 | |
| 
 | |
| var config = {
 | |
| 	env: process.env.ENV,
 | |
| 	email: process.env.SUBSCRIBER_EMAIL,
 | |
| 	domain: process.env.BASE_DOMAIN,
 | |
| 	challengeType: process.env.CHALLENGE_TYPE,
 | |
| 	challengeModule: process.env.CHALLENGE_PLUGIN,
 | |
| 	challengeOptions: JSON.parse(process.env.CHALLENGE_OPTIONS)
 | |
| };
 | |
| //config.debug = !/^PROD/i.test(config.env);
 | |
| var pluginPrefix = 'acme-' + config.challengeType + '-';
 | |
| var pluginName = config.challengeModule;
 | |
| var plugin;
 | |
| 
 | |
| module.exports = function() {
 | |
| 	console.info('\n[Test] end-to-end issue certificates');
 | |
| 
 | |
| 	var acme = ACME.create({
 | |
| 		// debug: true
 | |
| 		maintainerEmail: config.email,
 | |
| 		packageAgent: 'test-' + pkg.name + '/' + pkg.version,
 | |
| 		notify: function(ev, params) {
 | |
| 			console.info(
 | |
| 				'\t' + ev,
 | |
| 				params.subject || params.altname || params.domain || '',
 | |
| 				params.status || ''
 | |
| 			);
 | |
| 			if ('error' === ev) {
 | |
| 				console.error(params.action || params.type || '');
 | |
| 				console.error(params);
 | |
| 			}
 | |
| 		}
 | |
| 	});
 | |
| 
 | |
| 	function badPlugin(err) {
 | |
| 		if ('MODULE_NOT_FOUND' !== err.code) {
 | |
| 			console.error(err);
 | |
| 			return;
 | |
| 		}
 | |
| 		console.error("Couldn't find '" + pluginName + "'. Is it installed?");
 | |
| 		console.error("\tnpm install --save-dev '" + pluginName + "'");
 | |
| 	}
 | |
| 	try {
 | |
| 		plugin = require(pluginName);
 | |
| 	} catch (err) {
 | |
| 		if (
 | |
| 			'MODULE_NOT_FOUND' !== err.code ||
 | |
| 			0 === pluginName.indexOf(pluginPrefix)
 | |
| 		) {
 | |
| 			badPlugin(err);
 | |
| 			process.exit(1);
 | |
| 		}
 | |
| 		try {
 | |
| 			pluginName = pluginPrefix + pluginName;
 | |
| 			plugin = require(pluginName);
 | |
| 		} catch (e) {
 | |
| 			badPlugin(e);
 | |
| 			process.exit(1);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	config.challenger = plugin.create(config.challengeOptions);
 | |
| 	if (!config.challengeType || !config.domain) {
 | |
| 		console.error(
 | |
| 			new Error('Missing config variables. Check you .env and the docs')
 | |
| 				.message
 | |
| 		);
 | |
| 		console.error(config);
 | |
| 		process.exit(1);
 | |
| 	}
 | |
| 
 | |
| 	var challenges = {};
 | |
| 	challenges[config.challengeType] = config.challenger;
 | |
| 
 | |
| 	async function happyPath(accKty, srvKty, rnd) {
 | |
| 		var agreed = false;
 | |
| 		var metadata = await acme.init(
 | |
| 			'https://acme-staging-v02.api.letsencrypt.org/directory'
 | |
| 		);
 | |
| 
 | |
| 		// Ready to use, show page
 | |
| 		if (config.debug) {
 | |
| 			console.info('ACME.js initialized');
 | |
| 			console.info(metadata);
 | |
| 			console.info();
 | |
| 			console.info();
 | |
| 		}
 | |
| 
 | |
| 		var accountKeypair = await Keypairs.generate({ kty: accKty });
 | |
| 		if (/EC/i.test(accKty)) {
 | |
| 			// to test that an existing account gets back data
 | |
| 			accountKeypair = ecJwk;
 | |
| 		}
 | |
| 		var accountKey = accountKeypair.private;
 | |
| 		if (config.debug) {
 | |
| 			console.info('Account Key Created');
 | |
| 			console.info(JSON.stringify(accountKey, null, 2));
 | |
| 			console.info();
 | |
| 			console.info();
 | |
| 		}
 | |
| 
 | |
| 		var account = await acme.accounts.create({
 | |
| 			agreeToTerms: agree,
 | |
| 			// TODO detect jwk/pem/der?
 | |
| 			accountKey: accountKey,
 | |
| 			subscriberEmail: config.email
 | |
| 		});
 | |
| 
 | |
| 		// TODO top-level agree
 | |
| 		function agree(tos) {
 | |
| 			if (config.debug) {
 | |
| 				console.info('Agreeing to Terms of Service:');
 | |
| 				console.info(tos);
 | |
| 				console.info();
 | |
| 				console.info();
 | |
| 			}
 | |
| 			agreed = true;
 | |
| 			return Promise.resolve(agreed);
 | |
| 		}
 | |
| 		if (config.debug) {
 | |
| 			console.info('New Subscriber Account');
 | |
| 			console.info(JSON.stringify(account, null, 2));
 | |
| 			console.info();
 | |
| 			console.info();
 | |
| 		}
 | |
| 		if (!agreed) {
 | |
| 			throw new Error('Failed to ask the user to agree to terms');
 | |
| 		}
 | |
| 
 | |
| 		var certKeypair = await Keypairs.generate({ kty: srvKty });
 | |
| 		var pem = await Keypairs.export({
 | |
| 			jwk: certKeypair.private,
 | |
| 			encoding: 'pem'
 | |
| 		});
 | |
| 		if (config.debug) {
 | |
| 			console.info('Server Key Created');
 | |
| 			console.info('privkey.jwk.json');
 | |
| 			console.info(JSON.stringify(certKeypair, null, 2));
 | |
| 			// This should be saved as `privkey.pem`
 | |
| 			console.info();
 | |
| 			console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
 | |
| 			console.info(pem);
 | |
| 			console.info();
 | |
| 		}
 | |
| 
 | |
| 		// 'subject' should be first in list
 | |
| 		var domains = randomDomains(rnd);
 | |
| 		if (config.debug) {
 | |
| 			console.info('Get certificates for random domains:');
 | |
| 			console.info(
 | |
| 				domains
 | |
| 					.map(function(puny) {
 | |
| 						var uni = punycode.toUnicode(puny);
 | |
| 						if (puny !== uni) {
 | |
| 							return puny + ' (' + uni + ')';
 | |
| 						}
 | |
| 						return puny;
 | |
| 					})
 | |
| 					.join('\n')
 | |
| 			);
 | |
| 			console.info();
 | |
| 		}
 | |
| 
 | |
| 		// Create CSR
 | |
| 		var csrDer = await CSR.csr({
 | |
| 			jwk: certKeypair.private,
 | |
| 			domains: domains,
 | |
| 			encoding: 'der'
 | |
| 		});
 | |
| 		var csr = Enc.bufToUrlBase64(csrDer);
 | |
| 		var csrPem = PEM.packBlock({
 | |
| 			type: 'CERTIFICATE REQUEST',
 | |
| 			bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
 | |
| 		});
 | |
| 		if (config.debug) {
 | |
| 			console.info('Certificate Signing Request');
 | |
| 			console.info(csrPem);
 | |
| 			console.info();
 | |
| 		}
 | |
| 
 | |
| 		var results = await acme.certificates.create({
 | |
| 			account: account,
 | |
| 			accountKey: accountKey,
 | |
| 			csr: csr,
 | |
| 			domains: domains,
 | |
| 			challenges: challenges, // must be implemented
 | |
| 			customerEmail: null
 | |
| 		});
 | |
| 
 | |
| 		if (config.debug) {
 | |
| 			console.info('Got SSL Certificate:');
 | |
| 			console.info(Object.keys(results));
 | |
| 			console.info(results.expires);
 | |
| 			console.info(results.cert);
 | |
| 			console.info(results.chain);
 | |
| 			console.info();
 | |
| 			console.info();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Try EC + RSA
 | |
| 	var rnd = random();
 | |
| 	happyPath('EC', 'RSA', rnd)
 | |
| 		.then(function() {
 | |
| 			console.info('PASS: ECDSA account key with RSA server key');
 | |
| 			// Now try RSA + EC
 | |
| 			rnd = random();
 | |
| 			return happyPath('RSA', 'EC', rnd).then(function() {
 | |
| 				console.info('PASS: RSA account key with ECDSA server key');
 | |
| 			});
 | |
| 		})
 | |
| 		.then(function() {
 | |
| 			console.info('PASS');
 | |
| 		})
 | |
| 		.catch(function(err) {
 | |
| 			console.error('Error:');
 | |
| 			console.error(err.stack);
 | |
| 		});
 | |
| 
 | |
| 	function randomDomains(rnd) {
 | |
| 		return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map(
 | |
| 			function(pre) {
 | |
| 				return punycode.toASCII(pre + '-' + rnd + '.' + config.domain);
 | |
| 			}
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	function random() {
 | |
| 		return (
 | |
| 			parseInt(
 | |
| 				Math.random()
 | |
| 					.toString()
 | |
| 					.slice(2, 99),
 | |
| 				10
 | |
| 			)
 | |
| 				.toString(16)
 | |
| 				.slice(0, 4) + '例'
 | |
| 		);
 | |
| 	}
 | |
| };
 |