Compare commits

..

No commits in common. "master" and "v3.0.7" have entirely different histories.

27 changed files with 378 additions and 383 deletions

View File

@ -2,16 +2,10 @@
| Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub)
## Automated Certificate Management Environment
ACME ([RFC 8555](https://tools.ietf.org/html/rfc8555)) is the protocol that powers **Let's Encrypt**.
ACME.js is a _low-level_ client that speaks RFC 8555 to get Free SSL certificates through Let's Encrypt.
ACME.js is a _low-level_ client for Let's Encrypt.
Looking for an **easy**, _high-level_ client? Check out [Greenlock.js](https://git.rootprojects.org/root/greenlock.js).
# Quick Start
```js
var acme = ACME.create({ maintainerEmail, packageAgent, notify });
await acme.init(directoryUrl);
@ -103,7 +97,7 @@ The public API encapsulates the three high-level steps of the ACME protocol:
- Challenge Presentation
- Certificate Redemption
## API Overview
## Overview
The core API can be show in just four functions:
@ -183,7 +177,7 @@ These `notify` events are intended for _logging_ and debugging, NOT as a data AP
Note: DO NOT rely on **undocumented properties**. They are experimental and **will break**.
If you have a use case for a particular property **open an issue** - we can lock it down and document it.
# Example (Full Walkthrough)
# Example
### See [examples/README.md](https://git.rootprojects.org/root/acme.js/src/branch/master/examples/README.md)

View File

@ -11,7 +11,7 @@ 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
var kid =
options.kid ||
(options.account && options.account.key && options.account.key.kid);
(options.account && (options.account.key && options.account.key.kid));
if (kid) {
return Promise.resolve(kid);

77
acme.js
View File

@ -486,7 +486,6 @@ ACME._dryRun = function (me, realOptions, zonenames) {
type: ch.type
//challenge: ch
});
// ignore promise return
noopts.challenges[ch.type]
.remove({ challenge: ch })
.catch(function(err) {
@ -756,8 +755,12 @@ ACME._postChallenge = function (me, options, kid, auth) {
altname: altname
});
// State can be pending while waiting ACME server to transition to
// processing
if ('processing' === resp.body.status) {
//#console.debug('poll: again', auth.url);
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
}
// This state should never occur
if ('pending' === resp.body.status) {
if (count >= MAX_PEND) {
return ACME._wait(RETRY_INTERVAL)
@ -765,25 +768,13 @@ ACME._postChallenge = function (me, options, kid, auth) {
.then(respondToChallenge);
}
//#console.debug('poll: again', auth.url);
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
}
if ('processing' === resp.body.status) {
//#console.debug('poll: again', auth.url);
return ACME._wait(RETRY_INTERVAL).then(pollStatus);
return ACME._wait(RETRY_INTERVAL).then(respondToChallenge);
}
// REMOVE DNS records as soon as the state is non-processing
// (valid or invalid or other)
try {
options.challenges[auth.type]
.remove({ challenge: auth })
.catch(function (err) {
err.action = 'challenge_remove';
err.altname = auth.altname;
err.type = auth.type;
ACME._notify(me, options, 'error', err);
});
options.challenges[auth.type].remove({ challenge: auth });
} catch (e) {}
if ('valid' === resp.body.status) {
@ -1013,7 +1004,14 @@ ACME._pollOrderStatus = function (me, options, kid, order, verifieds) {
var body = { csr: csr64 };
var payload = JSON.stringify(body);
function processResponse(resp) {
function pollCert() {
//#console.debug('[ACME.js] pollCert:', order._finalizeUrl);
return U._jwsRequest(me, {
accountKey: options.accountKey,
url: order._finalizeUrl,
protected: { kid: kid },
payload: Enc.strToBuf(payload)
}).then(function(resp) {
ACME._notify(me, options, 'certificate_status', {
subject: options.domains[0],
status: resp.body.status
@ -1029,7 +1027,7 @@ ACME._pollOrderStatus = function (me, options, kid, order, verifieds) {
}
if ('processing' === resp.body.status) {
return ACME._wait().then(pollStatus);
return ACME._wait().then(pollCert);
}
if (me.debug) {
@ -1069,28 +1067,10 @@ ACME._pollOrderStatus = function (me, options, kid, order, verifieds) {
return Promise.reject(
E.UNHANDLED_ORDER_STATUS(options, verifieds, resp)
);
});
}
function pollStatus() {
return U._jwsRequest(me, {
accountKey: options.accountKey,
url: order._orderUrl,
protected: { kid: kid },
payload: Enc.binToBuf('')
}).then(processResponse);
}
function finalizeOrder() {
//#console.debug('[ACME.js] pollCert:', order._finalizeUrl);
return U._jwsRequest(me, {
accountKey: options.accountKey,
url: order._finalizeUrl,
protected: { kid: kid },
payload: Enc.strToBuf(payload)
}).then(processResponse);
}
return finalizeOrder();
return pollCert();
};
ACME._redeemCert = function(me, options, kid, voucher) {
@ -1248,8 +1228,14 @@ ACME._prepRequest = function (me, options) {
options.domains = options.domains || _csr.altnames;
_csr.altnames = _csr.altnames || [];
if (
options.domains.slice(0).sort().join(' ') !==
_csr.altnames.slice(0).sort().join(' ')
options.domains
.slice(0)
.sort()
.join(' ') !==
_csr.altnames
.slice(0)
.sort()
.join(' ')
) {
return Promise.reject(
new Error('certificate altnames do not match requested domains')
@ -1353,7 +1339,10 @@ ACME._csrToUrlBase64 = function (csr) {
// TODO use PEM.parseBlock()
// nix PEM headers, if any
if ('-' === csr[0]) {
csr = csr.split(/\n+/).slice(1, -1).join('');
csr = csr
.split(/\n+/)
.slice(1, -1)
.join('');
}
return Enc.base64ToUrlBase64(csr.trim().replace(/\s+/g, ''));
};
@ -1362,7 +1351,9 @@ ACME._csrToUrlBase64 = function (csr) {
ACME._prnd = function(n) {
var rnd = '';
while (rnd.length / 2 < n) {
var i = Math.random().toString().substr(2);
var i = Math.random()
.toString()
.substr(2);
var h = parseInt(i, 10).toString(16);
if (h.length % 2) {
h = '0' + h;

View File

@ -9,6 +9,9 @@ sha2.sum = function (alg, str) {
var sha = 'sha' + String(alg).replace(/^sha-?/i, '');
// utf8 is the default for strings
var buf = Buffer.from(str);
return crypto.createHash(sha).update(buf).digest();
return crypto
.createHash(sha)
.update(buf)
.digest();
});
};

View File

@ -33,10 +33,9 @@ M.init = function (me) {
};
M._init = function(me, tz, locale) {
setTimeout(function () {
// prevent a stampede from misconfigured clients in an eternal loop
setTimeout(function() {
me.request({
timeout: 3000,
method: 'GET',
url: 'https://api.rootprojects.org/api/nonce',
json: true
@ -48,7 +47,6 @@ M._init = function (me, tz, locale) {
})
.then(function(hashcash) {
var req = {
timeout: 3000,
headers: {
'x-root-nonce-v1': hashcash
},
@ -62,8 +60,8 @@ M._init = function (me, tz, locale) {
locale: locale
}
};
return me.request(req);
})
return me
.request(req)
.catch(function(err) {
if (me.debug) {
console.error(
@ -76,6 +74,7 @@ M._init = function (me, tz, locale) {
oldCollegeTries[me.maintainerEmail] = true;
//console.log(resp);
});
});
}, me.__timeout || 3000);
};

21
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "@root/acme",
"version": "3.1.1",
"version": "3.0.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -16,6 +16,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz",
"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==",
"dev": true,
"requires": {
"@root/asn1": "^1.0.0",
"@root/pem": "^1.0.4",
@ -28,9 +29,9 @@
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
},
"@root/keypairs": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.0.tgz",
"integrity": "sha512-t8VocY46Mtb0NTsxzyLLf5tsgfw0BXLYVADAyiRdEdqHcvPFGJdjkXNtHVQuSV/FMaC65iTOHVP4E6X8iT3Ikg==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz",
"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==",
"requires": {
"@root/encoding": "^1.0.1",
"@root/pem": "^1.0.4",
@ -43,9 +44,9 @@
"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA=="
},
"@root/request": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.6.1.tgz",
"integrity": "sha512-8wrWyeBLRp7T8J36GkT3RODJ6zYmL0/maWlAUD5LOXT28D3TDquUepyYDKYANNA3Gc8R5ZCgf+AXvSTYpJEWwQ=="
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz",
"integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw=="
},
"@root/x509": {
"version": "0.7.2",
@ -152,9 +153,9 @@
"dev": true
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
"integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@root/acme",
"version": "3.1.1",
"version": "3.0.7",
"description": "Free SSL certificates for Node.js and Browsers. Issued via Let's Encrypt",
"homepage": "https://rootprojects.org/acme/",
"main": "acme.js",
@ -42,14 +42,14 @@
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0",
"dependencies": {
"@root/csr": "^0.8.1",
"@root/encoding": "^1.0.1",
"@root/keypairs": "^0.10.0",
"@root/keypairs": "^0.9.0",
"@root/pem": "^1.0.4",
"@root/request": "^1.6.1",
"@root/request": "^1.3.11",
"@root/x509": "^0.7.2"
},
"devDependencies": {
"@root/csr": "^0.8.1",
"dig.js": "^1.3.9",
"dns-suite": "^1.2.13",
"dotenv": "^8.1.0",

View File

@ -247,7 +247,12 @@ module.exports = function () {
function random() {
return (
parseInt(Math.random().toString().slice(2, 99), 10)
parseInt(
Math.random()
.toString()
.slice(2, 99),
10
)
.toString(16)
.slice(0, 4) + '例'
);

View File

@ -33,7 +33,10 @@ native
var now = Date.now();
var nonce = '20';
var needle = crypto.randomBytes(3).toString('hex').slice(0, 5);
var needle = crypto
.randomBytes(3)
.toString('hex')
.slice(0, 5);
native
._hashcash({
alg: 'SHA-256',

View File

@ -11,13 +11,12 @@ U._jwsRequest = function (me, bigopts) {
bigopts.protected.nonce = nonce;
bigopts.protected.url = bigopts.url;
// protected.alg: added by Keypairs.signJws
if (bigopts.protected.jwk) {
bigopts.protected.kid = false;
} else if (!('kid' in bigopts.protected)) {
// protected.kid must be provided according to ACME's interpretation of the spec
// (using the provided URL rather than the Key's Thumbprint as Key ID)
if (!bigopts.protected.jwk) {
// protected.kid must be overwritten due to ACME's interpretation of the spec
if (!('kid' in bigopts.protected)) {
bigopts.protected.kid = bigopts.kid;
}
}
// this will shasum the thumbprint the 2nd time
return Keypairs.signJws({