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) {
|
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
|
// It's just fine if there's no account, we'll go get the key id we need via the existing key
|
||||||
options._kid =
|
var kid =
|
||||||
options._kid ||
|
options.kid ||
|
||||||
options.accountKid ||
|
(options.account && (options.account.key && options.account.key.kid));
|
||||||
(options.account &&
|
|
||||||
(options.account.kid ||
|
|
||||||
(options.account.key && options.account.key.kid)));
|
|
||||||
|
|
||||||
if (options._kid) {
|
if (kid) {
|
||||||
return Promise.resolve(options._kid);
|
return Promise.resolve(kid);
|
||||||
}
|
}
|
||||||
|
|
||||||
//return Promise.reject(new Error("must include KeyID"));
|
//return Promise.reject(new Error("must include KeyID"));
|
||||||
// This is an idempotent request. It'll return the same account for the same public key.
|
// This is an idempotent request. It'll return the same account for the same public key.
|
||||||
return A._registerAccount(me, options).then(function(account) {
|
return A._registerAccount(me, options).then(function(account) {
|
||||||
options._kid = account.key.kid;
|
return account.key.kid;
|
||||||
// start back from the top
|
|
||||||
return options._kid;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,50 +49,33 @@ A._registerAccount = function(me, options) {
|
||||||
function agree(tosUrl) {
|
function agree(tosUrl) {
|
||||||
var err;
|
var err;
|
||||||
if (me._tos !== tosUrl) {
|
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';
|
err.code = 'E_AGREE_TOS';
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return U._importKeypair(
|
function getAccount() {
|
||||||
me,
|
return U._importKeypair(options.accountKey).then(function(pair) {
|
||||||
options.accountKey || options.accountKeypair
|
|
||||||
).then(function(pair) {
|
|
||||||
var contact;
|
var contact;
|
||||||
if (options.contact) {
|
if (options.contact) {
|
||||||
contact = options.contact.slice(0);
|
contact = options.contact.slice(0);
|
||||||
} else if (options.subscriberEmail || options.email) {
|
} else if (options.subscriberEmail) {
|
||||||
contact = [
|
contact = ['mailto:' + options.subscriberEmail];
|
||||||
'mailto:' + (options.subscriberEmail || options.email)
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var accountRequest = {
|
var accountRequest = {
|
||||||
termsOfServiceAgreed: tosUrl === me._tos,
|
termsOfServiceAgreed: true,
|
||||||
onlyReturnExisting: false,
|
onlyReturnExisting: false,
|
||||||
contact: contact
|
contact: contact
|
||||||
};
|
};
|
||||||
var pExt;
|
|
||||||
if (options.externalAccount) {
|
var pub = pair.public;
|
||||||
pExt = Keypairs.signJws({
|
return attachExtAcc(pub, accountRequest).then(function(accReq) {
|
||||||
// TODO is HMAC the standard, or is this arbitrary?
|
var payload = JSON.stringify(accReq);
|
||||||
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);
|
|
||||||
return U._jwsRequest(me, {
|
return U._jwsRequest(me, {
|
||||||
options: options,
|
accountKey: options.accountKey,
|
||||||
url: me._directoryUrls.newAccount,
|
url: me._directoryUrls.newAccount,
|
||||||
protected: { kid: false, jwk: pair.public },
|
protected: { kid: false, jwk: pair.public },
|
||||||
payload: Enc.strToBuf(payload)
|
payload: Enc.strToBuf(payload)
|
||||||
|
@ -118,34 +96,42 @@ A._registerAccount = function(me, options) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var location = resp.headers.location;
|
// the account id url is the "kid"
|
||||||
// the account id url
|
var kid = resp.headers.location;
|
||||||
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'
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
account = { _emptyResponse: true };
|
account = { _emptyResponse: true };
|
||||||
}
|
}
|
||||||
// https://git.rootprojects.org/root/acme.js/issues/8
|
|
||||||
if (!account.key) {
|
if (!account.key) {
|
||||||
account.key = {};
|
account.key = {};
|
||||||
}
|
}
|
||||||
account.key.kid = options._kid;
|
account.key.kid = kid;
|
||||||
return account;
|
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()
|
return Promise.resolve()
|
||||||
.then(function() {
|
.then(function() {
|
||||||
//#console.debug('[ACME.js] agreeToTerms');
|
//#console.debug('[ACME.js] agreeToTerms');
|
||||||
|
@ -157,5 +143,6 @@ A._registerAccount = function(me, options) {
|
||||||
}
|
}
|
||||||
return agreeToTerms(me._tos);
|
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;
|
var http = module.exports;
|
||||||
|
|
||||||
http.request = function(opts) {
|
http.request = function(opts) {
|
||||||
|
opts.cors = true;
|
||||||
return window.fetch(opts.url, opts).then(function(resp) {
|
return window.fetch(opts.url, opts).then(function(resp) {
|
||||||
var headers = {};
|
var headers = {};
|
||||||
var result = {
|
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 U = module.exports;
|
||||||
|
|
||||||
var Keypairs = require('@root/keypairs');
|
var Keypairs = require('@root/keypairs');
|
||||||
|
var UserAgent = require('./lib/node/client-user-agent.js');
|
||||||
|
|
||||||
// Handle nonce, signing, and request altogether
|
// Handle nonce, signing, and request altogether
|
||||||
U._jwsRequest = function(me, bigopts) {
|
U._jwsRequest = function(me, bigopts) {
|
||||||
|
@ -12,16 +13,14 @@ U._jwsRequest = function(me, bigopts) {
|
||||||
// protected.alg: added by Keypairs.signJws
|
// protected.alg: added by Keypairs.signJws
|
||||||
if (!bigopts.protected.jwk) {
|
if (!bigopts.protected.jwk) {
|
||||||
// protected.kid must be overwritten due to ACME's interpretation of the spec
|
// protected.kid must be overwritten due to ACME's interpretation of the spec
|
||||||
if (!bigopts.protected.kid) {
|
if (!('kid' in bigopts.protected)) {
|
||||||
bigopts.protected.kid = bigopts.options._kid;
|
bigopts.protected.kid = bigopts.kid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this will shasum the thumbprint the 2nd time
|
// this will shasum the thumbprint the 2nd time
|
||||||
return Keypairs.signJws({
|
return Keypairs.signJws({
|
||||||
jwk:
|
jwk: bigopts.accountKey,
|
||||||
bigopts.options.accountKey ||
|
|
||||||
bigopts.options.accountKeypair.privateKeyJwk,
|
|
||||||
protected: bigopts.protected,
|
protected: bigopts.protected,
|
||||||
payload: bigopts.payload
|
payload: bigopts.payload
|
||||||
})
|
})
|
||||||
|
@ -72,16 +71,36 @@ U._getNonce = function(me) {
|
||||||
|
|
||||||
// Handle some ACME-specific defaults
|
// Handle some ACME-specific defaults
|
||||||
U._request = function(me, opts) {
|
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) {
|
if (!opts.headers) {
|
||||||
opts.headers = {};
|
opts.headers = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ua && !opts.headers['User-Agent']) {
|
||||||
|
opts.headers['User-Agent'] = ua;
|
||||||
|
}
|
||||||
if (opts.json && true !== opts.json) {
|
if (opts.json && true !== opts.json) {
|
||||||
opts.headers['Content-Type'] = 'application/jose+json';
|
opts.headers['Content-Type'] = 'application/jose+json';
|
||||||
opts.body = JSON.stringify(opts.json);
|
opts.body = JSON.stringify(opts.json);
|
||||||
|
}
|
||||||
if (!opts.method) {
|
if (!opts.method) {
|
||||||
|
opts.method = 'GET';
|
||||||
|
if (opts.body) {
|
||||||
opts.method = 'POST';
|
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) {
|
return me.request(opts).then(function(resp) {
|
||||||
if (resp.toJSON) {
|
if (resp.toJSON) {
|
||||||
resp = resp.toJSON();
|
resp = resp.toJSON();
|
||||||
|
@ -89,6 +108,9 @@ U._request = function(me, opts) {
|
||||||
if (resp.headers['replay-nonce']) {
|
if (resp.headers['replay-nonce']) {
|
||||||
U._setNonce(me, 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 e;
|
||||||
var err;
|
var err;
|
||||||
|
@ -122,7 +144,7 @@ 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, key) {
|
U._importKeypair = function(key) {
|
||||||
var p;
|
var p;
|
||||||
var pub;
|
var pub;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue