Skip to content
This repository was archived by the owner on Dec 19, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ var gulp = require('gulp'),

var TESTS = './test/*.js';
var SRC = './lib/**/*.js';
var MOCHA_OPTS = {
timeout: 20000
};

gulp.task('clean', function() {
var del = require('del');
Expand All @@ -29,7 +32,7 @@ gulp.task('lint', function() {

gulp.task('test', ['lint'], function() {
return gulp.src([TESTS])
.pipe(mocha());
.pipe(mocha(MOCHA_OPTS));
});

gulp.task('doc', function() {
Expand All @@ -56,7 +59,7 @@ gulp.task('pre-coverage', ['clean'], function() {

gulp.task('coverage', ['pre-coverage'], function() {
var t = gulp.src([TESTS])
.pipe(mocha().on('error', function(er) {
.pipe(mocha(MOCHA_OPTS).on('error', function(er) {
gutil.log(er);
t.end();
}))
Expand Down
48 changes: 28 additions & 20 deletions lib/crypto-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ var forge = require('node-forge');
var jose = require('node-jose');
var util = require('./utils');

const ONE_DAY_IN_MS = 24 * 3600 * 1000;

// TODO: Have node-jose expose this directly
function convertToForge(key, isPublic) {
var obj = key.toObject(true);
var parts = isPublic ?
['n', 'e'] :
['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'];
parts = parts.map(function(f) {
return new forge.jsbn.BigInteger(obj[f].toString('hex'), 16);
});

var fn = isPublic ?
forge.pki.rsa.setPublicKey :
forge.pki.rsa.setPrivateKey;
return fn.apply(forge.pki.rsa, parts);
}

// A note on formats:
// * Keys are always represented as JWKs
// * Signature objects are in ACME format
Expand Down Expand Up @@ -53,16 +71,6 @@ module.exports = {
return jose.JWK.asKey(pem, 'pem');
},

/**
* Convert a private key object to a PEM-encoded string.
*
* @param {Object} key kty, etc.
* @return {String} PEM-encoded key
*/
privateKeyToPem: function(key) {
return key.toPEM(true);
},

generateSignature: function(priv, nonce, payload) {
return jose.JWS.createSign({format: 'flattened'}, {
reference: 'jwk',
Expand Down Expand Up @@ -132,22 +140,22 @@ module.exports = {
/**
* Create a self-signed certificate with a given name and public key
*
* @param {String} name Domain name to included in the cert
* @param {Object} keyPair JWK key pair. Public key will be put in
* the cert; the private key will be used to
* sign it.
* @return {String} PEM-encoded certificate
* @param {String} name Domain name to included in the cert
* @param {Object} privateKey JWK private key. Public key will be put in the
* cert; the private key will be used to sign it.
* @return {String} PEM-encoded certificate
*/
selfSigned: function(name, keyPair) {
var privKey = this.importPrivateKey(keyPair.privateKey);
var pubKey = this.importPublicKey(keyPair.publicKey);
selfSigned: function(name, privateKey) {
var privKey = convertToForge(privateKey, false);
var pubKey = convertToForge(privateKey, true);

var cert = forge.pki.createCertificate();

// Constants that shouldn't matter
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
var now = (new Date()).getTime();
cert.validity.notBefore = new Date(now - ONE_DAY_IN_MS);
cert.validity.notAfter = new Date(now + 365 * ONE_DAY_IN_MS);

// Specifics of this certificate
cert.publicKey = pubKey;
Expand Down
2 changes: 1 addition & 1 deletion lib/validate-dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class DNS01Validator {
* @param {string} domain Domain name being validated
* @param {object} challenge ACME challenge object being used for validation
* @param {integer} port Port number on which to listen
* @return {object} A server object
* @return {DNS01Validator} The created server object
* @throws {TypeError} Invalid challenge
*/
constructor(domain, challenge, port) {
Expand Down
12 changes: 11 additions & 1 deletion lib/validate-http.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

'use strict';

var express = require('express');
var express = require('express');
var util = require('./utils');

// The fields that should be present in a completed challenge
const CHALLENGE_FIELDS = ['type', 'token', 'keyAuthorization'];

/**
* An HTTP01validator performs the client-side half of the ACME 'http-01'
Expand All @@ -20,8 +24,14 @@ class HTTP01validator {
* @param {object} challenge The challenge returned from an authz request
* @param {number=} port Port to listen on (default: 80)
* @return {HTTP01validator} Created object
* @throws {TypeError} Invalid challenge
*/
constructor(domain, challenge, port) {
if (!util.fieldsPresent(CHALLENGE_FIELDS, challenge) ||
(challenge.type !== 'http-01')) {
throw new TypeError('Mal-formed challenge');
}

this.port = port || 80;
this.app = express();
this.app.get('/.well-known/acme-challenge/:token', (req, res) => {
Expand Down
85 changes: 85 additions & 0 deletions lib/validate-tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,88 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

'use strict';

var tls = require('tls');
var util = require('./utils');
var crypto = require('./crypto-utils');

// The fields that should be present in a completed challenge
const CHALLENGE_FIELDS = ['type', 'token', 'keyAuthorization'];

const KEY_SIZE = 2048;

/**
* A TLSSNI01Validator performs the client-side half of the ACME 'tls-sni-01'
* validation. Based on a challenge, it creates a sequence of hashes values and
* responds in the correct way to TLS queries to server names based on those
* hash values.
*/
class TLSSNI01Validator {
/**
* Create a DNS validation server.
*
* @param {string} domain Domain name being validated
* @param {object} challenge ACME challenge object being used for validation
* @param {integer} port Port number on which to listen
* @return {TLSSNI01Validator} The created server object
* @throws {TypeError} Invalid challenge
*/
constructor(domain, challenge, port) {
if (!util.fieldsPresent(CHALLENGE_FIELDS, challenge) ||
(challenge.type !== 'tls-sni-01')) {
throw new TypeError('Mal-formed challenge');
}

this.port = port;
this.zNames = [];
this.ready = crypto.generateKey(KEY_SIZE)
.then((privateKey) => {
var privateKeyPEM = privateKey.toPEM(true);
var defaultCertPEM = crypto.selfSigned('acme.invalid', privateKey);

var opts = {
key: privateKeyPEM,
cert: defaultCertPEM
};
var closeImmediately = function(socket) { socket.end(); };
this.server = tls.createServer(opts, closeImmediately);

var n = challenge.n || 1;
var z = challenge.keyAuthorization;
for (var i=0; i<n; ++i) {
// Z(i) = lowercase_hexadecimal(SHA256(Z(i-1)))
z = crypto.sha256(new Buffer(z)).toString('hex').toLowerCase();
var zName = z.substr(0, 32) + '.' + z.substr(32) + '.acme.invalid';
this.zNames.push(zName);

var certPEM = crypto.selfSigned(zName, privateKey);
this.server.addContext(zName, {
key: privateKeyPEM,
cert: certPEM
});
}
});
}

/**
* Start the server
*
* @return {TLSSNI01Validator} Object instance
*/
start() {
this.server.listen(this.port);
return this;
}

/**
* Stop the server
*
* @return {TLSSNI01Validator} Object instance
*/
stop() {
this.server.close();
return this;
}
}

module.exports = TLSSNI01Validator;
115 changes: 0 additions & 115 deletions lib/validation-servers.js

This file was deleted.

2 changes: 1 addition & 1 deletion test/cryto-utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('crypto utilities', function() {
'kty', 'kid',
'e', 'n', 'd',
'p', 'q', 'dp', 'dq', 'qi'], priv.toJSON(true)));
var priv_pem = cutils.privateKeyToPem(priv);
var priv_pem = priv.toPEM(true);
assert.match(priv_pem, /^-----BEGIN RSA PRIVATE KEY-----$/m);
return cutils.importPemPrivateKey(priv_pem)
.then(function(ipriv) {
Expand Down
Loading