forked from root/acme.js
update API and tests
This commit is contained in:
parent
f05e9db38e
commit
0efa94eeb0
199
acme.js
199
acme.js
|
@ -179,17 +179,17 @@ ACME._testChallengeOptions = function() {
|
||||||
|
|
||||||
ACME._thumber = function(me, options, thumb) {
|
ACME._thumber = function(me, options, thumb) {
|
||||||
var thumbPromise;
|
var thumbPromise;
|
||||||
return function() {
|
return function(key) {
|
||||||
if (thumb) {
|
if (thumb) {
|
||||||
return Promise.resolve(thumb);
|
return Promise.resolve(thumb);
|
||||||
}
|
}
|
||||||
if (thumbPromise) {
|
if (thumbPromise) {
|
||||||
return thumbPromise;
|
return thumbPromise;
|
||||||
}
|
}
|
||||||
thumbPromise = U._importKeypair(
|
if (!key) {
|
||||||
me,
|
key = options.accountKey || options.accountKeypair;
|
||||||
options.accountKey || options.accountKeypair
|
}
|
||||||
).then(function(pair) {
|
thumbPromise = U._importKeypair(null, key).then(function(pair) {
|
||||||
return Keypairs.thumbprint({
|
return Keypairs.thumbprint({
|
||||||
jwk: pair.public
|
jwk: pair.public
|
||||||
});
|
});
|
||||||
|
@ -266,7 +266,14 @@ ACME._dryRun = function(me, realOptions) {
|
||||||
type: ch.type
|
type: ch.type
|
||||||
//challenge: ch
|
//challenge: ch
|
||||||
});
|
});
|
||||||
noopts.challenges[ch.type].remove({ challenge: ch });
|
noopts.challenges[ch.type]
|
||||||
|
.remove({ challenge: ch })
|
||||||
|
.catch(function(err) {
|
||||||
|
err.action = 'challenge_remove';
|
||||||
|
err.altname = ch.altname;
|
||||||
|
err.type = ch.type;
|
||||||
|
ACME._notify(me, noopts, 'error', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,9 +317,8 @@ ACME._computeAuths = function(me, options, thumb, request, dryrun) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var getThumbprint = ACME._thumber(me, options, thumb);
|
var getThumbprint = ACME._thumber(null, options, thumb);
|
||||||
|
|
||||||
return getThumbprint().then(function(thumb) {
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
request.challenges.map(function(challenge) {
|
request.challenges.map(function(challenge) {
|
||||||
// Don't do extra work for challenges that we can't satisfy
|
// Don't do extra work for challenges that we can't satisfy
|
||||||
|
@ -340,65 +346,88 @@ ACME._computeAuths = function(me, options, thumb, request, dryrun) {
|
||||||
auth.hostname = auth.identifier.value;
|
auth.hostname = auth.identifier.value;
|
||||||
// because I'm not 100% clear if the wildcard identifier does or doesn't
|
// because I'm not 100% clear if the wildcard identifier does or doesn't
|
||||||
// have the leading *. in all cases
|
// have the leading *. in all cases
|
||||||
auth.altname = ACME._untame(
|
auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
|
||||||
auth.identifier.value,
|
|
||||||
auth.wildcard
|
|
||||||
);
|
|
||||||
|
|
||||||
auth.thumbprint = thumb;
|
|
||||||
// keyAuthorization = token + '.' + base64url(JWK_Thumbprint(accountKey))
|
|
||||||
auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
|
|
||||||
|
|
||||||
if ('http-01' === auth.type) {
|
|
||||||
// conflicts with ACME challenge id url is already in use,
|
|
||||||
// so we call this challengeUrl instead
|
|
||||||
// TODO auth.http01Url ?
|
|
||||||
auth.challengeUrl =
|
|
||||||
'http://' +
|
|
||||||
auth.identifier.value +
|
|
||||||
ACME.challengePrefixes['http-01'] +
|
|
||||||
'/' +
|
|
||||||
auth.token;
|
|
||||||
return auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('dns-01' !== auth.type) {
|
|
||||||
return auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
var zone = pluckZone(
|
var zone = pluckZone(
|
||||||
options.zonenames || [],
|
options.zonenames || [],
|
||||||
auth.identifier.value
|
auth.identifier.value
|
||||||
);
|
);
|
||||||
|
|
||||||
// Always calculate dnsAuthorization because we
|
return ACME.computeChallenge({
|
||||||
// may need to present to the user for confirmation / instruction
|
accountKey: options.accountKey,
|
||||||
// _as part of_ the decision making process
|
_getThumbprint: getThumbprint,
|
||||||
return sha2
|
challenge: auth,
|
||||||
.sum(256, auth.keyAuthorization)
|
zone: zone,
|
||||||
.then(function(hash) {
|
dnsPrefix: dnsPrefix
|
||||||
return Enc.bufToUrlBase64(new Uint8Array(hash));
|
}).then(function(resp) {
|
||||||
})
|
Object.keys(resp).forEach(function(k) {
|
||||||
.then(function(hash64) {
|
auth[k] = resp[k];
|
||||||
auth.dnsHost =
|
});
|
||||||
dnsPrefix + '.' + auth.hostname.replace('*.', '');
|
|
||||||
|
|
||||||
auth.dnsAuthorization = hash64;
|
|
||||||
auth.keyAuthorizationDigest = hash64;
|
|
||||||
|
|
||||||
if (zone) {
|
|
||||||
auth.dnsZone = zone;
|
|
||||||
auth.dnsPrefix = auth.dnsHost
|
|
||||||
.replace(newZoneRegExp(zone), '')
|
|
||||||
.replace(/\.$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return auth;
|
return auth;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
).then(function(auths) {
|
).then(function(auths) {
|
||||||
return auths.filter(Boolean);
|
return auths.filter(Boolean);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ACME.computeChallenge = function(opts) {
|
||||||
|
var auth = opts.challenge;
|
||||||
|
var hostname = auth.hostname || opts.hostname;
|
||||||
|
var zone = opts.zone;
|
||||||
|
var thumb = opts.thumbprint || '';
|
||||||
|
var accountKey = opts.accountKey;
|
||||||
|
var getThumbprint = opts._getThumbprint || ACME._thumber(null, opts, thumb);
|
||||||
|
var dnsPrefix = opts.dnsPrefix || ACME.challengePrefixes['dns-01'];
|
||||||
|
|
||||||
|
return getThumbprint(accountKey).then(function(thumb) {
|
||||||
|
var resp = {};
|
||||||
|
resp.thumbprint = thumb;
|
||||||
|
// keyAuthorization = token + '.' + base64url(JWK_Thumbprint(accountKey))
|
||||||
|
resp.keyAuthorization = auth.token + '.' + thumb;
|
||||||
|
|
||||||
|
if ('http-01' === auth.type) {
|
||||||
|
// conflicts with ACME challenge id url is already in use,
|
||||||
|
// so we call this challengeUrl instead
|
||||||
|
// TODO auth.http01Url ?
|
||||||
|
resp.challengeUrl =
|
||||||
|
'http://' +
|
||||||
|
// `hostname` is an alias of `auth.indentifier.value`
|
||||||
|
hostname +
|
||||||
|
ACME.challengePrefixes['http-01'] +
|
||||||
|
'/' +
|
||||||
|
auth.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('dns-01' !== auth.type) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always calculate dnsAuthorization because we
|
||||||
|
// may need to present to the user for confirmation / instruction
|
||||||
|
// _as part of_ the decision making process
|
||||||
|
return sha2
|
||||||
|
.sum(256, resp.keyAuthorization)
|
||||||
|
.then(function(hash) {
|
||||||
|
return Enc.bufToUrlBase64(Uint8Array.from(hash));
|
||||||
|
})
|
||||||
|
.then(function(hash64) {
|
||||||
|
resp.dnsHost = dnsPrefix + '.' + hostname; // .replace('*.', '');
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
resp.dnsAuthorization = hash64;
|
||||||
|
// should use this instead
|
||||||
|
resp.keyAuthorizationDigest = hash64;
|
||||||
|
|
||||||
|
if (zone) {
|
||||||
|
resp.dnsZone = zone;
|
||||||
|
resp.dnsPrefix = resp.dnsHost
|
||||||
|
.replace(newZoneRegExp(zone), '')
|
||||||
|
.replace(/\.$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -583,6 +612,7 @@ ACME._setChallenges = function(me, options, order) {
|
||||||
var claims = order._claims.slice(0);
|
var claims = order._claims.slice(0);
|
||||||
var valids = [];
|
var valids = [];
|
||||||
var auths = [];
|
var auths = [];
|
||||||
|
var placed = [];
|
||||||
var USE_DNS = false;
|
var USE_DNS = false;
|
||||||
var DNS_DELAY = 0;
|
var DNS_DELAY = 0;
|
||||||
|
|
||||||
|
@ -618,6 +648,7 @@ ACME._setChallenges = function(me, options, order) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
auths.push(selected);
|
auths.push(selected);
|
||||||
|
placed.push(selected);
|
||||||
ACME._notify(me, options, 'challenge_select', {
|
ACME._notify(me, options, 'challenge_select', {
|
||||||
// API-locked
|
// API-locked
|
||||||
altname: ACME._untame(
|
altname: ACME._untame(
|
||||||
|
@ -651,10 +682,13 @@ ACME._setChallenges = function(me, options, order) {
|
||||||
function waitAll() {
|
function waitAll() {
|
||||||
//#console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
|
//#console.debug('\n[DEBUG] waitChallengeDelay %s\n', DELAY);
|
||||||
if (!DNS_DELAY || DNS_DELAY <= 0) {
|
if (!DNS_DELAY || DNS_DELAY <= 0) {
|
||||||
|
if (!ACME._propagationDelayWarning) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'the given dns-01 challenge did not specify `propagationDelay`'
|
'warn: the given dns-01 challenge did not specify `propagationDelay`'
|
||||||
);
|
);
|
||||||
console.warn('the default of 5000ms will be used');
|
console.warn('warn: the default of 5000ms will be used');
|
||||||
|
ACME._propagationDelayWarning = true;
|
||||||
|
}
|
||||||
DNS_DELAY = 5000;
|
DNS_DELAY = 5000;
|
||||||
}
|
}
|
||||||
return ACME._wait(DNS_DELAY);
|
return ACME._wait(DNS_DELAY);
|
||||||
|
@ -683,7 +717,22 @@ ACME._setChallenges = function(me, options, order) {
|
||||||
// is so that we don't poison our own DNS cache with misses.
|
// is so that we don't poison our own DNS cache with misses.
|
||||||
return setNext()
|
return setNext()
|
||||||
.then(waitAll)
|
.then(waitAll)
|
||||||
.then(checkNext);
|
.then(checkNext)
|
||||||
|
.catch(function(err) {
|
||||||
|
if (!options.debug) {
|
||||||
|
placed.forEach(function(ch) {
|
||||||
|
options.challenges[ch.type]
|
||||||
|
.remove({ challenge: ch })
|
||||||
|
.catch(function(err) {
|
||||||
|
err.action = 'challenge_remove';
|
||||||
|
err.altname = ch.altname;
|
||||||
|
err.type = ch.type;
|
||||||
|
ACME._notify(me, options, 'error', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ACME._normalizePresenters = function(me, options, presenters) {
|
ACME._normalizePresenters = function(me, options, presenters) {
|
||||||
|
@ -1283,40 +1332,6 @@ ACME._prnd = function(n) {
|
||||||
ACME._toHex = function(pair) {
|
ACME._toHex = function(pair) {
|
||||||
return parseInt(pair, 10).toString(16);
|
return parseInt(pair, 10).toString(16);
|
||||||
};
|
};
|
||||||
ACME._removeChallenge = function(me, options, auth) {
|
|
||||||
var challengers = options.challenges || {};
|
|
||||||
var ch = auth.challenge;
|
|
||||||
var removeChallenge = challengers[ch.type] && challengers[ch.type].remove;
|
|
||||||
if (!removeChallenge) {
|
|
||||||
throw new Error('challenge plugin is missing remove()');
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO normalize, warn, and just use promises
|
|
||||||
if (1 === removeChallenge.length) {
|
|
||||||
return Promise.resolve(removeChallenge(auth)).then(
|
|
||||||
function() {},
|
|
||||||
function(e) {
|
|
||||||
console.error('Error during remove challenge:');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (2 === removeChallenge.length) {
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
removeChallenge(auth, function(err) {
|
|
||||||
resolve();
|
|
||||||
if (err) {
|
|
||||||
console.error('Error during remove challenge:');
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
"Bad function signature for '" + auth.type + "' challenge.remove()"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ACME._depInit = function(me, presenter) {
|
ACME._depInit = function(me, presenter) {
|
||||||
if ('function' !== typeof presenter.init) {
|
if ('function' !== typeof presenter.init) {
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var ACME = require('../');
|
||||||
|
var accountKey = require('../fixtures/account.jwk.json').private;
|
||||||
|
|
||||||
|
var authorization = {
|
||||||
|
identifier: {
|
||||||
|
type: 'dns',
|
||||||
|
value: 'example.com'
|
||||||
|
},
|
||||||
|
status: 'pending',
|
||||||
|
expires: '2018-04-25T00:23:57Z',
|
||||||
|
challenges: [
|
||||||
|
{
|
||||||
|
type: 'dns-01',
|
||||||
|
status: 'pending',
|
||||||
|
url:
|
||||||
|
'https://acme-staging-v02.api.letsencrypt.org/acme/challenge/cMkwXI8pIeKN04Ynfem8ErHK3GeqAPdSt2x6q7PvVGU/118755342',
|
||||||
|
token: 'LZdlUiZ-kWPs6q5WTmQFYQHZKpz9szn2vxEUu0XhyyM'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'http-01',
|
||||||
|
status: 'pending',
|
||||||
|
url:
|
||||||
|
'https://acme-staging-v02.api.letsencrypt.org/acme/challenge/cMkwXI8pIeKN04Ynfem8ErHK3GeqAPdSt2x6q7PvVGU/118755343',
|
||||||
|
token: '1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
var expectedChallengeUrl =
|
||||||
|
'http://example.com/.well-known/acme-challenge/1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU';
|
||||||
|
var expectedKeyAuth =
|
||||||
|
'1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU.UuuZa_56jCM2douUq1riGyRphPtRvCPkxtkg0bP-pNs';
|
||||||
|
var expectedKeyAuthDigest = 'iQiMcQUDiAeD0TJV1RHJuGnI5D2-PuSpxKz9JqUaZ2M';
|
||||||
|
var expectedDnsHost = '_test-challenge.example.com';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.info('\n[Test] computing challenge authorizatin responses');
|
||||||
|
var challenges = authorization.challenges.slice(0);
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
var ch = challenges.shift();
|
||||||
|
if (!ch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostname = authorization.identifier.value;
|
||||||
|
return ACME.computeChallenge({
|
||||||
|
accountKey: accountKey,
|
||||||
|
hostname: hostname,
|
||||||
|
challenge: ch,
|
||||||
|
dnsPrefix: '_test-challenge'
|
||||||
|
})
|
||||||
|
.then(function(auth) {
|
||||||
|
if ('dns-01' === ch.type) {
|
||||||
|
if (auth.keyAuthorizationDigest !== expectedKeyAuthDigest) {
|
||||||
|
console.error('[keyAuthorizationDigest]');
|
||||||
|
console.error(auth.keyAuthorizationDigest);
|
||||||
|
console.error(expectedKeyAuthDigest);
|
||||||
|
throw new Error('bad keyAuthDigest');
|
||||||
|
}
|
||||||
|
if (auth.dnsHost !== expectedDnsHost) {
|
||||||
|
console.error('[dnsHost]');
|
||||||
|
console.error(auth.dnsHost);
|
||||||
|
console.error(expectedDnsHost);
|
||||||
|
throw new Error('bad dnsHost');
|
||||||
|
}
|
||||||
|
} else if ('http-01' === ch.type) {
|
||||||
|
if (auth.challengeUrl !== expectedChallengeUrl) {
|
||||||
|
console.error('[challengeUrl]');
|
||||||
|
console.error(auth.challengeUrl);
|
||||||
|
console.error(expectedChallengeUrl);
|
||||||
|
throw new Error('bad challengeUrl');
|
||||||
|
}
|
||||||
|
if (auth.challengeUrl !== expectedChallengeUrl) {
|
||||||
|
console.error('[keyAuthorization]');
|
||||||
|
console.error(auth.keyAuthorization);
|
||||||
|
console.error(expectedKeyAuth);
|
||||||
|
throw new Error('bad keyAuth');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('bad authorization inputs');
|
||||||
|
}
|
||||||
|
console.info('PASS', hostname, ch.type);
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
err.message =
|
||||||
|
'Error computing ' +
|
||||||
|
ch.type +
|
||||||
|
' for ' +
|
||||||
|
hostname +
|
||||||
|
':' +
|
||||||
|
err.message;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
return main(authorization)
|
||||||
|
.then(function() {
|
||||||
|
console.info('PASS');
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.error(err.stack);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
};
|
|
@ -35,55 +35,46 @@ var tests = [
|
||||||
'----\nxxxx\nyyyy\n----\r\n----\nxxxx\ryyyy\n----\n'
|
'----\nxxxx\nyyyy\n----\r\n----\nxxxx\ryyyy\n----\n'
|
||||||
];
|
];
|
||||||
|
|
||||||
function formatPemChain(str) {
|
var ACME = require('../');
|
||||||
return (
|
|
||||||
str
|
|
||||||
.trim()
|
|
||||||
.replace(/[\r\n]+/g, '\n')
|
|
||||||
.replace(/\-\n\-/g, '-\n\n-') + '\n'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function splitPemChain(str) {
|
|
||||||
return str
|
|
||||||
.trim()
|
|
||||||
.split(/[\r\n]{2,}/g)
|
|
||||||
.map(function(str) {
|
|
||||||
return str + '\n';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tests.forEach(function(str) {
|
module.exports = function() {
|
||||||
var actual = formatPemChain(str);
|
console.info('\n[Test] can split and format PEM chain properly');
|
||||||
|
|
||||||
|
tests.forEach(function(str) {
|
||||||
|
var actual = ACME.formatPemChain(str);
|
||||||
if (expected !== actual) {
|
if (expected !== actual) {
|
||||||
console.error('input: ', JSON.stringify(str));
|
console.error('input: ', JSON.stringify(str));
|
||||||
console.error('expected:', JSON.stringify(expected));
|
console.error('expected:', JSON.stringify(expected));
|
||||||
console.error('actual: ', JSON.stringify(actual));
|
console.error('actual: ', JSON.stringify(actual));
|
||||||
throw new Error('did not pass');
|
throw new Error('did not pass');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
'----\nxxxx\nyyyy\n----\n' !==
|
'----\nxxxx\nyyyy\n----\n' !==
|
||||||
formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n')
|
ACME.formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n')
|
||||||
) {
|
) {
|
||||||
throw new Error('Not proper for single cert in chain');
|
throw new Error('Not proper for single cert in chain');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !==
|
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !==
|
||||||
formatPemChain(
|
ACME.formatPemChain(
|
||||||
'\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n'
|
'\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n'
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new Error('Not proper for three certs in chain');
|
throw new Error('Not proper for three certs in chain');
|
||||||
}
|
}
|
||||||
|
|
||||||
splitPemChain(
|
ACME.splitPemChain(
|
||||||
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n'
|
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n'
|
||||||
).forEach(function(str) {
|
).forEach(function(str) {
|
||||||
if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) {
|
if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) {
|
||||||
throw new Error('bad thingy');
|
throw new Error('bad thingy');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.info('PASS');
|
console.info('PASS');
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
|
@ -1,15 +1,27 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
async function run() {
|
module.exports = async function() {
|
||||||
|
console.log('[Test] can generate, export, and import key');
|
||||||
var Keypairs = require('@root/keypairs');
|
var Keypairs = require('@root/keypairs');
|
||||||
|
|
||||||
var certKeypair = await Keypairs.generate({ kty: 'RSA' });
|
var certKeypair = await Keypairs.generate({ kty: 'RSA' });
|
||||||
console.log(certKeypair);
|
//console.log(certKeypair);
|
||||||
var pem = await Keypairs.export({
|
var pem = await Keypairs.export({
|
||||||
jwk: certKeypair.private,
|
jwk: certKeypair.private,
|
||||||
encoding: 'pem'
|
encoding: 'pem'
|
||||||
});
|
});
|
||||||
console.log(pem);
|
var jwk = await Keypairs.import({
|
||||||
}
|
pem: pem
|
||||||
|
});
|
||||||
|
['kty', 'd', 'n', 'e'].forEach(function(k) {
|
||||||
|
if (!jwk[k] || jwk[k] !== certKeypair.private[k]) {
|
||||||
|
throw new Error('bad export/import');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//console.log(pem);
|
||||||
|
console.log('PASS');
|
||||||
|
};
|
||||||
|
|
||||||
run();
|
if (require.main === module) {
|
||||||
|
module.exports();
|
||||||
|
}
|
||||||
|
|
245
tests/index.js
245
tests/index.js
|
@ -1,245 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
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');
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
var acme = ACME.create({
|
|
||||||
// debug: true
|
|
||||||
maintainerEmail: config.email,
|
|
||||||
notify: function(ev, params) {
|
|
||||||
console.info(
|
|
||||||
ev,
|
|
||||||
params.subject || params.altname || params.domain,
|
|
||||||
params.status
|
|
||||||
);
|
|
||||||
if ('error' === ev) {
|
|
||||||
console.error(params);
|
|
||||||
console.error(params.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 });
|
|
||||||
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(tos);
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
// Now try RSA + EC
|
|
||||||
rnd = random();
|
|
||||||
return happyPath('RSA', 'EC', rnd).then(function() {
|
|
||||||
console.info('success');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.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) + '例'
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,253 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
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 });
|
||||||
|
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(tos);
|
||||||
|
}
|
||||||
|
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) + '例'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
27
utils.js
27
utils.js
|
@ -122,29 +122,26 @@ U._setNonce = function(me, nonce) {
|
||||||
me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
|
me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
|
||||||
};
|
};
|
||||||
|
|
||||||
U._importKeypair = function(me, kp) {
|
U._importKeypair = function(me, key) {
|
||||||
var jwk = kp.privateKeyJwk;
|
|
||||||
if (kp.kty) {
|
|
||||||
jwk = kp;
|
|
||||||
kp = {};
|
|
||||||
}
|
|
||||||
var pub;
|
|
||||||
var p;
|
var p;
|
||||||
if (jwk) {
|
var pub;
|
||||||
|
|
||||||
|
if (key && key.kty) {
|
||||||
// nix the browser jwk extras
|
// nix the browser jwk extras
|
||||||
jwk.key_ops = undefined;
|
key.key_ops = undefined;
|
||||||
jwk.ext = undefined;
|
key.ext = undefined;
|
||||||
pub = Keypairs.neuter({ jwk: jwk });
|
pub = Keypairs.neuter({ jwk: key });
|
||||||
p = Promise.resolve({
|
p = Promise.resolve({
|
||||||
private: jwk,
|
private: key,
|
||||||
public: pub
|
public: pub
|
||||||
});
|
});
|
||||||
|
} else if ('string' === typeof key) {
|
||||||
|
p = Keypairs.import({ pem: key });
|
||||||
} else {
|
} else {
|
||||||
p = Keypairs.import({ pem: kp.privateKeyPem });
|
throw new Error('no private key given');
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.then(function(pair) {
|
return p.then(function(pair) {
|
||||||
kp.privateKeyJwk = pair.private;
|
|
||||||
kp.publicKeyJwk = pair.public;
|
|
||||||
if (pair.public.kid) {
|
if (pair.public.kid) {
|
||||||
pair = JSON.parse(JSON.stringify(pair));
|
pair = JSON.parse(JSON.stringify(pair));
|
||||||
delete pair.public.kid;
|
delete pair.public.kid;
|
||||||
|
|
Loading…
Reference in New Issue