forked from root/acme.js
API and test cleanup
This commit is contained in:
parent
161e9183c6
commit
90c7154a24
105
account.js
105
account.js
|
@ -8,23 +8,18 @@ var Enc = require('@root/encoding/bytes');
|
|||
|
||||
A._getAccountKid = function(me, options) {
|
||||
// It's just fine if there's no account, we'll go get the key id we need via the existing key
|
||||
options._kid =
|
||||
options._kid ||
|
||||
options.accountKid ||
|
||||
(options.account &&
|
||||
(options.account.kid ||
|
||||
(options.account.key && options.account.key.kid)));
|
||||
var kid =
|
||||
options.kid ||
|
||||
(options.account && (options.account.key && options.account.key.kid));
|
||||
|
||||
if (options._kid) {
|
||||
return Promise.resolve(options._kid);
|
||||
if (kid) {
|
||||
return Promise.resolve(kid);
|
||||
}
|
||||
|
||||
//return Promise.reject(new Error("must include KeyID"));
|
||||
// This is an idempotent request. It'll return the same account for the same public key.
|
||||
return A._registerAccount(me, options).then(function(account) {
|
||||
options._kid = account.key.kid;
|
||||
// start back from the top
|
||||
return options._kid;
|
||||
return account.key.kid;
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -54,50 +49,33 @@ A._registerAccount = function(me, options) {
|
|||
function agree(tosUrl) {
|
||||
var err;
|
||||
if (me._tos !== tosUrl) {
|
||||
err = new Error("You must agree to the ToS at '" + me._tos + "'");
|
||||
err = new Error("must agree to '" + tosUrl + "'");
|
||||
err.code = 'E_AGREE_TOS';
|
||||
throw err;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return U._importKeypair(
|
||||
me,
|
||||
options.accountKey || options.accountKeypair
|
||||
).then(function(pair) {
|
||||
function getAccount() {
|
||||
return U._importKeypair(options.accountKey).then(function(pair) {
|
||||
var contact;
|
||||
if (options.contact) {
|
||||
contact = options.contact.slice(0);
|
||||
} else if (options.subscriberEmail || options.email) {
|
||||
contact = [
|
||||
'mailto:' + (options.subscriberEmail || options.email)
|
||||
];
|
||||
} else if (options.subscriberEmail) {
|
||||
contact = ['mailto:' + options.subscriberEmail];
|
||||
}
|
||||
|
||||
var accountRequest = {
|
||||
termsOfServiceAgreed: tosUrl === me._tos,
|
||||
termsOfServiceAgreed: true,
|
||||
onlyReturnExisting: false,
|
||||
contact: contact
|
||||
};
|
||||
var pExt;
|
||||
if (options.externalAccount) {
|
||||
pExt = Keypairs.signJws({
|
||||
// TODO is HMAC the standard, or is this arbitrary?
|
||||
secret: options.externalAccount.secret,
|
||||
protected: {
|
||||
alg: options.externalAccount.alg || 'HS256',
|
||||
kid: options.externalAccount.id,
|
||||
url: me._directoryUrls.newAccount
|
||||
},
|
||||
payload: Enc.strToBuf(JSON.stringify(pair.public))
|
||||
}).then(function(jws) {
|
||||
accountRequest.externalAccountBinding = jws;
|
||||
return accountRequest;
|
||||
});
|
||||
} else {
|
||||
pExt = Promise.resolve(accountRequest);
|
||||
}
|
||||
return pExt.then(function(accountRequest) {
|
||||
var payload = JSON.stringify(accountRequest);
|
||||
|
||||
var pub = pair.public;
|
||||
return attachExtAcc(pub, accountRequest).then(function(accReq) {
|
||||
var payload = JSON.stringify(accReq);
|
||||
return U._jwsRequest(me, {
|
||||
options: options,
|
||||
accountKey: options.accountKey,
|
||||
url: me._directoryUrls.newAccount,
|
||||
protected: { kid: false, jwk: pair.public },
|
||||
payload: Enc.strToBuf(payload)
|
||||
|
@ -118,34 +96,42 @@ A._registerAccount = function(me, options) {
|
|||
);
|
||||
}
|
||||
|
||||
var location = resp.headers.location;
|
||||
// the account id url
|
||||
options._kid = location;
|
||||
//#console.debug('[DEBUG] new account location:');
|
||||
//#console.debug(location);
|
||||
//#console.debug(resp);
|
||||
|
||||
/*
|
||||
{
|
||||
contact: ["mailto:jon@example.com"],
|
||||
orders: "https://some-url",
|
||||
status: 'valid'
|
||||
}
|
||||
*/
|
||||
// the account id url is the "kid"
|
||||
var kid = resp.headers.location;
|
||||
if (!account) {
|
||||
account = { _emptyResponse: true };
|
||||
}
|
||||
// https://git.rootprojects.org/root/acme.js/issues/8
|
||||
if (!account.key) {
|
||||
account.key = {};
|
||||
}
|
||||
account.key.kid = options._kid;
|
||||
account.key.kid = kid;
|
||||
return account;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// for external accounts (probably useless, but spec'd)
|
||||
function attachExtAcc(pubkey, accountRequest) {
|
||||
if (!options.externalAccount) {
|
||||
return Promise.resolve(accountRequest);
|
||||
}
|
||||
|
||||
return Keypairs.signJws({
|
||||
// TODO is HMAC the standard, or is this arbitrary?
|
||||
secret: options.externalAccount.secret,
|
||||
protected: {
|
||||
alg: options.externalAccount.alg || 'HS256',
|
||||
kid: options.externalAccount.id,
|
||||
url: me._directoryUrls.newAccount
|
||||
},
|
||||
payload: Enc.strToBuf(JSON.stringify(pubkey))
|
||||
}).then(function(jws) {
|
||||
accountRequest.externalAccountBinding = jws;
|
||||
return accountRequest;
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(function() {
|
||||
//#console.debug('[ACME.js] agreeToTerms');
|
||||
|
@ -157,5 +143,6 @@ A._registerAccount = function(me, options) {
|
|||
}
|
||||
return agreeToTerms(me._tos);
|
||||
})
|
||||
.then(agree);
|
||||
.then(agree)
|
||||
.then(getAccount);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
'use strict';
|
||||
|
||||
var E = module.exports;
|
||||
|
||||
E.NO_SUITABLE_CHALLENGE = function(domain, challenges, presenters) {
|
||||
// Bail with a descriptive message if no usable challenge could be selected
|
||||
// For example, wildcards require dns-01 and, if we don't have that, we have to bail
|
||||
var enabled = presenters.join(', ') || 'none';
|
||||
var suitable =
|
||||
challenges
|
||||
.map(function(r) {
|
||||
return r.type;
|
||||
})
|
||||
.join(', ') || 'none';
|
||||
return new Error(
|
||||
"None of the challenge types that you've enabled ( " +
|
||||
enabled +
|
||||
' )' +
|
||||
" are suitable for validating the domain you've selected (" +
|
||||
domain +
|
||||
').' +
|
||||
' You must enable one of ( ' +
|
||||
suitable +
|
||||
' ).'
|
||||
);
|
||||
};
|
||||
E.UNHANDLED_ORDER_STATUS = function(options, domains, resp) {
|
||||
return new Error(
|
||||
"Didn't finalize order: Unhandled status '" +
|
||||
resp.body.status +
|
||||
"'." +
|
||||
' This is not one of the known statuses...\n' +
|
||||
"Requested: '" +
|
||||
options.domains.join(', ') +
|
||||
"'\n" +
|
||||
"Validated: '" +
|
||||
domains.join(', ') +
|
||||
"'\n" +
|
||||
JSON.stringify(resp.body, null, 2) +
|
||||
'\n\n' +
|
||||
'Please open an issue at https://git.rootprojects.org/root/acme.js'
|
||||
);
|
||||
};
|
||||
E.DOUBLE_READY_ORDER = function(options, domains, resp) {
|
||||
return new Error(
|
||||
"Did not finalize order: status 'ready'." +
|
||||
" Hmmm... this state shouldn't be possible here. That was the last state." +
|
||||
" This one should at least be 'processing'.\n" +
|
||||
"Requested: '" +
|
||||
options.domains.join(', ') +
|
||||
"'\n" +
|
||||
"Validated: '" +
|
||||
domains.join(', ') +
|
||||
"'\n" +
|
||||
JSON.stringify(resp.body, null, 2) +
|
||||
'\n\n' +
|
||||
'Please open an issue at https://git.rootprojects.org/root/acme.js'
|
||||
);
|
||||
};
|
||||
E.ORDER_INVALID = function(options, domains, resp) {
|
||||
return new Error(
|
||||
"Did not finalize order: status 'invalid'." +
|
||||
' Best guess: One or more of the domain challenges could not be verified' +
|
||||
' (or the order was canceled).\n' +
|
||||
"Requested: '" +
|
||||
options.domains.join(', ') +
|
||||
"'\n" +
|
||||
"Validated: '" +
|
||||
domains.join(', ') +
|
||||
"'\n" +
|
||||
JSON.stringify(resp.body, null, 2)
|
||||
);
|
||||
};
|
||||
E.NO_AUTHORIZATIONS = function(options, resp) {
|
||||
return new Error(
|
||||
"[acme-v2.js] authorizations were not fetched for '" +
|
||||
options.domains.join() +
|
||||
"':\n" +
|
||||
JSON.stringify(resp.body)
|
||||
);
|
||||
};
|
|
@ -3,6 +3,7 @@
|
|||
var http = module.exports;
|
||||
|
||||
http.request = function(opts) {
|
||||
opts.cors = true;
|
||||
return window.fetch(opts.url, opts).then(function(resp) {
|
||||
var headers = {};
|
||||
var result = {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
async function main() {
|
||||
await require('./generate-cert-key.js')();
|
||||
await require('./format-pem-chains.js')();
|
||||
await require('./compute-authorization-response.js')();
|
||||
await require('./issue-certificates.js')();
|
||||
}
|
||||
|
||||
main();
|
34
utils.js
34
utils.js
|
@ -3,6 +3,7 @@
|
|||
var U = module.exports;
|
||||
|
||||
var Keypairs = require('@root/keypairs');
|
||||
var UserAgent = require('./lib/node/client-user-agent.js');
|
||||
|
||||
// Handle nonce, signing, and request altogether
|
||||
U._jwsRequest = function(me, bigopts) {
|
||||
|
@ -12,16 +13,14 @@ U._jwsRequest = function(me, bigopts) {
|
|||
// protected.alg: added by Keypairs.signJws
|
||||
if (!bigopts.protected.jwk) {
|
||||
// protected.kid must be overwritten due to ACME's interpretation of the spec
|
||||
if (!bigopts.protected.kid) {
|
||||
bigopts.protected.kid = bigopts.options._kid;
|
||||
if (!('kid' in bigopts.protected)) {
|
||||
bigopts.protected.kid = bigopts.kid;
|
||||
}
|
||||
}
|
||||
|
||||
// this will shasum the thumbprint the 2nd time
|
||||
return Keypairs.signJws({
|
||||
jwk:
|
||||
bigopts.options.accountKey ||
|
||||
bigopts.options.accountKeypair.privateKeyJwk,
|
||||
jwk: bigopts.accountKey,
|
||||
protected: bigopts.protected,
|
||||
payload: bigopts.payload
|
||||
})
|
||||
|
@ -72,16 +71,36 @@ U._getNonce = function(me) {
|
|||
|
||||
// Handle some ACME-specific defaults
|
||||
U._request = function(me, opts) {
|
||||
// no-op on browser
|
||||
var ua = UserAgent.get(me, opts);
|
||||
|
||||
// Note: the required User-Agent string will be set in node, but not browsers
|
||||
if (!opts.headers) {
|
||||
opts.headers = {};
|
||||
}
|
||||
|
||||
if (ua && !opts.headers['User-Agent']) {
|
||||
opts.headers['User-Agent'] = ua;
|
||||
}
|
||||
if (opts.json && true !== opts.json) {
|
||||
opts.headers['Content-Type'] = 'application/jose+json';
|
||||
opts.body = JSON.stringify(opts.json);
|
||||
}
|
||||
if (!opts.method) {
|
||||
opts.method = 'GET';
|
||||
if (opts.body) {
|
||||
opts.method = 'POST';
|
||||
}
|
||||
}
|
||||
if (opts.json) {
|
||||
opts.headers.Accept = 'application/json';
|
||||
if (true !== opts.json) {
|
||||
opts.body = JSON.stringify(opts.json);
|
||||
}
|
||||
}
|
||||
|
||||
//console.log('\n[debug] REQUEST');
|
||||
//console.log(opts);
|
||||
return me.request(opts).then(function(resp) {
|
||||
if (resp.toJSON) {
|
||||
resp = resp.toJSON();
|
||||
|
@ -89,6 +108,9 @@ U._request = function(me, opts) {
|
|||
if (resp.headers['replay-nonce']) {
|
||||
U._setNonce(me, resp.headers['replay-nonce']);
|
||||
}
|
||||
//console.log('[debug] RESPONSE:');
|
||||
//console.log(resp.headers);
|
||||
//console.log(resp.body);
|
||||
|
||||
var e;
|
||||
var err;
|
||||
|
@ -122,7 +144,7 @@ U._setNonce = function(me, nonce) {
|
|||
me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
|
||||
};
|
||||
|
||||
U._importKeypair = function(me, key) {
|
||||
U._importKeypair = function(key) {
|
||||
var p;
|
||||
var pub;
|
||||
|
||||
|
|
Loading…
Reference in New Issue