The base set of tests for all certificate and keypair storage strategies. Any Greenlock `greenlock-store-` plugin should be able to pass these tests.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

202 lines
8.4 KiB

'use strict';
/*global Promise*/
module.exports.create = function () {
throw new Error("greenlock-store-test is a test fixture for greenlock-store-* plugins, not a plugin itself");
};
// ignore all of this, it's just to normalize Promise vs node-style callback thunk vs synchronous
function promiseCheckAndCatch(obj, name, mergables) {
var promisify = require('util').promisify;
// don't loose this-ness, just in case that's important
var fn = obj[name].bind(obj);
var promiser;
// function signature must match, or an error will be thrown
if (1 === fn.length) {
// wrap so that synchronous errors are caught (alsa handles synchronous results)
promiser = function (opts) {
return Promise.resolve().then(function () {
return fn(merge(mergables, opts));
});
};
} else if (2 === fn.length) {
// wrap as a promise
promiser = promisify(function (opts, cb) {
return fn(merge(mergables, opts), cb);
});
} else {
return Promise.reject(new Error("'store." + name + "' should accept either one argument, the options,"
+ " and return a Promise or accept two arguments, the options and a node-style callback thunk"));
}
function shouldntBeNull(result) {
if ('undefined' === typeof result) {
throw new Error("'store.'" + name + "' should never return `undefined`. Please explicitly return null"
+ " (or fix the place where a value should have been returned but wasn't).");
}
return result;
}
return function (opts) {
return promiser(opts).then(shouldntBeNull);
};
}
function merge(one, two) {
var three = {};
Object.keys(one||{}).forEach(function (k) {
if ('undefined' !== typeof one[k]) {
three[k] = one[k];
}
});
Object.keys(two||{}).forEach(function (k) {
if ('undefined' !== typeof two[k]) {
three[k] = two[k];
}
});
return three;
}
// Here's the meat, where the tests are happening:
module.exports.test = function (store) {
var subject = '*.example.com';
var email = 'jon.doe@sbemail.com';
// TODO go just barely beyond dumb storage and provide real keys using 'keypairs.js'
var accountKey = { privateKeyPem: 'AccountKey', privateKeyJwk: { account: true } };
var domainKey = { privateKeyPem: 'DomainKey', privateKeyJwk: { domain: true } };
var domainCert = {
cert: 'DomainCert', chain: 'DomainChain', privkey: 'DomainKey'
, subject: '*.example.com', altnames: ['*.example.com', 'example.com']
, issuedAt: new Date().valueOf(), expiresAt: new Date(Date.now() + 3600000).valueOf()
};
var checkCerts = promiseCheckAndCatch(store.certificates, 'check', store.options);
var checkCertKey = promiseCheckAndCatch(store.certificates, 'checkKeypair', store.options);
var checkAccountKey = promiseCheckAndCatch(store.accounts, 'checkKeypair', store.options);
var setAccountKey = promiseCheckAndCatch(store.accounts, 'setKeypair', store.options);
var setCertKey = promiseCheckAndCatch(store.certificates, 'setKeypair', store.options);
var setCert = promiseCheckAndCatch(store.certificates, 'set', store.options);
return checkCerts({ certificate: {}, subject: subject }).then(function (results) {
if (null !== results) {
throw new Error("should get null when checking certificates for " + subject);
}
}).then(function () {
// TODO check accounts.check
var opts = { account: {}, email: email, subject: subject, certificate: {} };
return checkAccountKey(opts).then(function (results) {
if (null !== results) {
throw new Error("should not have an account keypair for " + subject);
}
});
}).then(function () {
var opts = { account: {}, email: email, keypair: accountKey, subject: subject, certificate: {} };
return setAccountKey(opts).then(function (results) {
if ('undefined' === typeof results) {
throw new Error("should never get undefined as a return value, did you forget to return?");
}
});
}).then(function () {
var opts = { certificate: {}, subject: subject, keypair: domainKey, email: email, account: {} };
return setCertKey(opts).then(function (results) {
if ('undefined' === typeof results) {
throw new Error("should never get undefined as a return value, did you forget to return?");
}
});
}).then(function () {
var opts = { certificate: {}, subject: subject, pems: domainCert, email: email, account: {} };
return setCert(opts).then(function (results) {
if ('undefined' === typeof results) {
throw new Error("should never get undefined as a return value, did you forget to return?");
}
});
}).then(function () {
var opts = { certificate: {}, subject: subject, email: email, account: {} };
return checkCerts(opts).then(function (results) {
if (!results || !results.chain || !results.cert) {
throw new Error("expected to get certificate and chain for " + subject);
}
if (domainCert.cert.replace(/\r\n/g, '\n').trim() !== results.cert.replace(/\r\n/g, '\n').trim()) {
throw new Error("expected to get exactly the same certificate (aside from CRLF and ending newline) as was set");
}
if (domainCert.chain.replace(/\r\n/g, '\n').trim() !== results.chain.replace(/\r\n/g, '\n').trim()) {
throw new Error("expected to get exactly the same chain (aside from CRLF and ending newline) as was set");
}
if (results.privkey) {
if (domainCert.chain.replace(/\r\n/g, '\n').trim() !== results.chain.replace(/\r\n/g, '\n').trim()) {
throw new Error("privkey is optional, but when it's set it should be the same string PEM as was set");
}
}
if (results.altnames) {
if (domainCert.altnames.join(' ') !== results.altnames.join(' ')) {
throw new Error("altnames sholud be the same type and value as was set");
}
}
if (results.subject) {
if (domainCert.subject !== results.subject) {
throw new Error("subject sholud be the same type and value as was set");
}
}
if (results.issuedAt) {
if (domainCert.issuedAt !== results.issuedAt) {
throw new Error("issuedAt sholud be the same type and value as was set");
}
}
if (results.expiresAt) {
if (domainCert.expiresAt !== results.expiresAt) {
throw new Error("expiresAt sholud be the same type and value as was set");
}
}
});
}).then(function () {
var opts = { certificate: {}, subject: subject, email: email, account: {} };
return checkCertKey(opts).then(function (results) {
if (!results) {
throw new Error("Should have found a keypair for " + opts.subject);
}
if (!results.privateKeyPem && !results.privateKeyJwk) {
throw new Error("Should have received privateKeyPem and/or privateKeyJwk, but got neither: "
+ JSON.stringify(results));
}
if (results.privateKeyPem) {
if (domainCert.privkey.replace(/\r\n/g, '\n').trim() !== results.privateKeyPem.replace(/\r\n/g, '\n').trim()) {
throw new Error("privateKeyPem should be the same string PEM as was set");
}
}
if (results.privateKeyJwk) {
if (Object.keys(domainKey.privateKeyJwk).sort().join(' ')
!== Object.keys(results.privateKeyJwk).sort().join(' ')
) {
throw new Error("privateKeyJwk should be the same string Jwk as was set");
}
}
});
}).then(function () {
var opts = { account: {}, email: email, subject: subject, certificate: {} };
return checkAccountKey(opts).then(function (results) {
if (!results) {
throw new Error("Should have found a keypair for " + opts.email);
}
if (!results.privateKeyPem && !results.privateKeyJwk) {
throw new Error("Should have received privateKeyPem and/or privateKeyJwk, but got neither: "
+ JSON.stringify(results));
}
if (results.privateKeyPem) {
if (accountKey.privateKeyPem.replace(/\r\n/g, '\n').trim()
!== results.privateKeyPem.replace(/\r\n/g, '\n').trim()
) {
throw new Error("privateKeyPem should be the same string PEM as was set");
}
}
if (results.privateKeyJwk) {
var akeys = Object.keys(accountKey.privateKeyJwk).sort().join(' ');
var bkeys = Object.keys(results.privateKeyJwk).sort().join(' ');
if (akeys !== bkeys) {
throw new Error("privateKeyJwk should be the same string Jwk as was set."
+ "\nExpected: " + akeys + "\nActual: " + bkeys);
}
}
});
});
};