v1.0.2
This commit is contained in:
parent
52406344ed
commit
28f5baf3d3
58
README.md
58
README.md
|
@ -1,11 +1,59 @@
|
|||
acme-v2.js (draft 11)
|
||||
==========
|
||||
|
||||
| [acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js)
|
||||
| [acme-v2-cli.js](https://git.coolaj86.com/coolaj86/acme-v2-cli.js)
|
||||
| [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
|
||||
| [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js)
|
||||
|
||||
| Sponsored by [ppl](https://ppl.family)
|
||||
|
||||
A framework for building letsencrypt v2 (IETF ACME draft 11) clients, successor to `le-acme-core.js`.
|
||||
A framework for building Let's Encrypt v2 (ACME draft 11) clients, successor to `le-acme-core.js`.
|
||||
Built [by request](https://git.coolaj86.com/coolaj86/greenlock.js/issues/5#issuecomment-8).
|
||||
|
||||
Summary of spec that I'm working off of here: https://git.coolaj86.com/coolaj86/greenlock.js/issues/5#issuecomment-8
|
||||
## Looking for Quick 'n' Easy™?
|
||||
|
||||
If you're looking for an *ACME-enabled webserver*, try [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js).
|
||||
If you're looking to *build a webserver*, try [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js).
|
||||
|
||||
* [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
|
||||
* [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js)
|
||||
|
||||
## How to build ACME clients
|
||||
|
||||
As this is intended to build ACME clients, there is not a simple 2-line example.
|
||||
|
||||
I'd recommend first running the example CLI client with a test domain and then investigating the files used for that example:
|
||||
|
||||
```bash
|
||||
node examples/cli.js
|
||||
```
|
||||
|
||||
The example cli has the following prompts:
|
||||
|
||||
```
|
||||
What web address(es) would you like to get certificates for? (ex: example.com,*.example.com)
|
||||
What challenge will you be testing today? http-01 or dns-01? [http-01]
|
||||
What email should we use? (optional)
|
||||
What API style would you like to test? v1-compat or promise? [v1-compat]
|
||||
|
||||
Put the string 'mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM.VNAzCR4THe4czVzo9piNn73B1ZXRLaB2CESwJfKkvRM' into a file at 'example.com/.well-known/acme-challenge/mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM'
|
||||
|
||||
echo 'mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM.VNAzCR4THe4czVzo9piNn73B1ZXRLaB2CESwJfKkvRM' > 'example.com/.well-known/acme-challenge/mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM'
|
||||
|
||||
Then hit the 'any' key to continue...
|
||||
```
|
||||
|
||||
When you've completed the challenge you can hit a key to continue the process.
|
||||
|
||||
If you place the certificate you receive back in `tests/fullchain.pem`
|
||||
you can then test it with `examples/https-server.js`.
|
||||
|
||||
```
|
||||
examples/cli.js
|
||||
examples/genkeypair.js
|
||||
tests/compat.js
|
||||
```
|
||||
|
||||
## Let's Encrypt Directory URLs
|
||||
|
||||
|
@ -136,7 +184,11 @@ Todo
|
|||
Changelog
|
||||
---------
|
||||
|
||||
* v1.0.0
|
||||
* v1.0.2
|
||||
* use `options.contact` to provide raw contact array
|
||||
* made `options.email` optional
|
||||
* file cleanup
|
||||
* v1.0.1
|
||||
* Compat API is ready for use
|
||||
* Eliminate debug logging
|
||||
* Apr 10, 2018 - tested backwards-compatibility using greenlock.js
|
||||
|
|
|
@ -7,6 +7,8 @@ var rl = readline.createInterface({
|
|||
output: process.stdout
|
||||
});
|
||||
|
||||
require('./genkeypair.js');
|
||||
|
||||
function getWeb() {
|
||||
rl.question('What web address(es) would you like to get certificates for? (ex: example.com,*.example.com) ', function (web) {
|
||||
web = (web||'').trim().split(/,/g);
|
||||
|
@ -35,12 +37,31 @@ function getEmail(web, chType) {
|
|||
email = (email||'').trim();
|
||||
if (!email) { email = null; }
|
||||
|
||||
getApiStyle(web, chType, email);
|
||||
});
|
||||
}
|
||||
|
||||
function getApiStyle(web, chType, email) {
|
||||
var defaultStyle = 'compat';
|
||||
rl.question('What API style would you like to test? v1-compat or promise? [v1-compat] ', function (apiStyle) {
|
||||
apiStyle = (apiStyle||'').trim();
|
||||
if (!apiStyle) { apiStyle = 'v1-compat'; }
|
||||
|
||||
rl.close();
|
||||
var accountKeypair = RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/account.privkey.pem') });
|
||||
var domainKeypair = RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/privkey.pem') });
|
||||
//require('./test.compat.js').run(web, chType, email, accountKeypair, domainKeypair);
|
||||
//require('./test.cb.js').run(web, chType, email, accountKeypair, domainKeypair);
|
||||
require('./test.promise.js').run(web, chType, email, accountKeypair, domainKeypair);
|
||||
|
||||
var RSA = require('rsa-compat').RSA;
|
||||
var accountKeypair = RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/../tests/account.privkey.pem') });
|
||||
var domainKeypair = RSA.import({ privateKeyPem: require('fs').readFileSync(__dirname + '/../tests/privkey.pem') });
|
||||
var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
||||
|
||||
if ('promise' === apiStyle) {
|
||||
require('../tests/promise.js').run(directoryUrl, RSA, web, chType, email, accountKeypair, domainKeypair);
|
||||
} else if ('cb' === apiStyle) {
|
||||
require('../tests/cb.js').run(directoryUrl, RSA, web, chType, email, accountKeypair, domainKeypair);
|
||||
} else {
|
||||
if ('v1-compat' !== apiStyle) { console.warn("Didn't understand '" + apiStyle + "', using 'v1-compat' instead..."); }
|
||||
require('../tests/compat.js').run(directoryUrl, RSA, web, chType, email, accountKeypair, domainKeypair);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
var RSA = require('rsa-compat').RSA;
|
||||
var fs = require('fs');
|
||||
|
||||
if (!fs.existsSync(__dirname + '/../tests/account.privkey.pem')) {
|
||||
RSA.generateKeypair(2048, 65537, {}, function (err, keypair) {
|
||||
console.log(keypair);
|
||||
var privkeyPem = RSA.exportPrivatePem(keypair)
|
||||
console.log(privkeyPem);
|
||||
|
||||
fs.writeFileSync(__dirname + '/../tests/account.privkey.pem', privkeyPem);
|
||||
});
|
||||
}
|
||||
|
||||
if (!fs.existsSync(__dirname + '/../tests/privkey.pem')) {
|
||||
RSA.generateKeypair(2048, 65537, {}, function (err, keypair) {
|
||||
console.log(keypair);
|
||||
var privkeyPem = RSA.exportPrivatePem(keypair)
|
||||
console.log(privkeyPem);
|
||||
|
||||
fs.writeFileSync(__dirname + '/../tests/privkey.pem', privkeyPem);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var http = require('http');
|
||||
var express = require('express');
|
||||
var server = http.createServer(express.static('../tests')).listen(80, function () {
|
||||
console.log('Listening on', this.address());
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
var https = require('https');
|
||||
var server = https.createServer({
|
||||
key: require('fs').readFileSync('../tests/privkey.pem')
|
||||
, cert: require('fs').readFileSync('../tests/fullchain.pem')
|
||||
}, function (req, res) {
|
||||
res.end("Hello, World!");
|
||||
}).listen(443, function () {
|
||||
console.log('Listening on', this.address());
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
var RSA = require('rsa-compat').RSA;
|
||||
var fs = require('fs');
|
||||
|
||||
RSA.generateKeypair(2048, 65537, {}, function (err, keypair) {
|
||||
console.log(keypair);
|
||||
var privkeyPem = RSA.exportPrivatePem(keypair)
|
||||
console.log(privkeyPem);
|
||||
|
||||
fs.writeFileSync(__dirname + '/account.privkey.pem', privkeyPem);
|
||||
});
|
||||
|
||||
RSA.generateKeypair(2048, 65537, {}, function (err, keypair) {
|
||||
console.log(keypair);
|
||||
var privkeyPem = RSA.exportPrivatePem(keypair)
|
||||
console.log(privkeyPem);
|
||||
|
||||
fs.writeFileSync(__dirname + '/privkey.pem', privkeyPem);
|
||||
});
|
30
node.js
30
node.js
|
@ -112,10 +112,16 @@ ACME._registerAccount = function (me, options) {
|
|||
}
|
||||
|
||||
var jwk = me.RSA.exportPublicJwk(options.accountKeypair);
|
||||
var contact;
|
||||
if (options.contact) {
|
||||
contact = options.contact.slice(0);
|
||||
} else if (options.email) {
|
||||
contact = [ 'mailto:' + options.email ]
|
||||
}
|
||||
var body = {
|
||||
termsOfServiceAgreed: tosUrl === me._tos
|
||||
, onlyReturnExisting: false
|
||||
, contact: [ 'mailto:' + options.email ]
|
||||
, contact: contact
|
||||
};
|
||||
if (options.externalAccount) {
|
||||
body.externalAccountBinding = me.RSA.signJws(
|
||||
|
@ -150,6 +156,8 @@ ACME._registerAccount = function (me, options) {
|
|||
, headers: { 'Content-Type': 'application/jose+json' }
|
||||
, json: jws
|
||||
}).then(function (resp) {
|
||||
var account = resp.body;
|
||||
|
||||
me._nonce = resp.toJSON().headers['replay-nonce'];
|
||||
var location = resp.toJSON().headers.location;
|
||||
// the account id url
|
||||
|
@ -157,15 +165,33 @@ ACME._registerAccount = function (me, options) {
|
|||
if (me.debug) console.debug('[DEBUG] new account location:');
|
||||
if (me.debug) console.debug(location);
|
||||
if (me.debug) console.debug(resp.toJSON());
|
||||
return resp.body;
|
||||
|
||||
/*
|
||||
{
|
||||
id: 5925245,
|
||||
key:
|
||||
{ kty: 'RSA',
|
||||
n: 'tBr7m1hVaUNQjUeakznGidnrYyegVUQrsQjNrcipljI9Vxvxd0baHc3vvRZWFyFO5BlS7UDl-KHQdbdqb-MQzfP6T2sNXsOHARQ41pCGY5BYzIPRJF0nD48-CY717is-7BKISv8rf9yx5iSjvK1wZ3Ke3YIpxzK2fWRqccVxXQ92VYioxOfGObACgEUSvdoEttWV2B0Uv4Sdi6zZbk5eo2zALvyGb1P4fKVfQycGLXC41AyhHOAuTqzNCyIkiWEkbfh2lZNcYClP2epS0pHRFXYyjJN6-c8InfM3PISo4k6Qew65HZ-oqUow0tTIgNwuen9q5O6Hc73GvU-2npGJVQ',
|
||||
e: 'AQAB' },
|
||||
contact: [],
|
||||
initialIp: '198.199.82.211',
|
||||
createdAt: '2018-04-16T00:41:00.720584972Z',
|
||||
status: 'valid'
|
||||
}
|
||||
*/
|
||||
if (!account) { account = { _emptyResponse: true, key: {} }; }
|
||||
account.key.kid = me._kid;
|
||||
return account;
|
||||
}).then(resolve, reject);
|
||||
}
|
||||
|
||||
if (me.debug) console.debug('[acme-v2] agreeToTerms');
|
||||
if (1 === options.agreeToTerms.length) {
|
||||
// newer promise API
|
||||
return options.agreeToTerms(me._tos).then(agree, reject);
|
||||
}
|
||||
else if (2 === options.agreeToTerms.length) {
|
||||
// backwards compat cb API
|
||||
return options.agreeToTerms(me._tos, function (err, tosUrl) {
|
||||
if (!err) { agree(tosUrl); return; }
|
||||
reject(err);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "acme-v2",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "Free SSL. A framework for building Let's Encrypt v2 clients, and other ACME v2 (draft 11) clients. Successor to le-acme-core.js",
|
||||
"homepage": "https://git.coolaj86.com/coolaj86/acme-v2.js",
|
||||
"main": "node.js",
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
module.exports.run = function run(web, chType, email, accountKeypair, domainKeypair) {
|
||||
var RSA = require('rsa-compat').RSA;
|
||||
var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
||||
var acme2 = require('./').ACME.create({ RSA: RSA });
|
||||
module.exports.run = function run(directoryUrl, RSA, web, chType, email, accountKeypair, domainKeypair) {
|
||||
// [ 'test.ppl.family' ] 'coolaj86@gmail.com''http-01'
|
||||
var acme2 = require('../').ACME.create({ RSA: RSA });
|
||||
acme2.init(directoryUrl).then(function () {
|
||||
var options = {
|
||||
agreeToTerms: function (tosUrl, agree) {
|
|
@ -1,11 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
var RSA = require('rsa-compat').RSA;
|
||||
|
||||
module.exports.run = function (web, chType, email, accountKeypair, domainKeypair) {
|
||||
module.exports.run = function (directoryUrl, RSA, web, chType, email, accountKeypair, domainKeypair) {
|
||||
console.log('[DEBUG] run', web, chType, email);
|
||||
|
||||
var acme2 = require('./compat.js').ACME.create({ RSA: RSA });
|
||||
var acme2 = require('../compat.js').ACME.create({ RSA: RSA });
|
||||
acme2.getAcmeUrls(acme2.stagingServerUrl, function (err/*, directoryUrls*/) {
|
||||
if (err) { console.log('err 1'); throw err; }
|
||||
|
||||
|
@ -44,8 +42,8 @@ module.exports.run = function (web, chType, email, accountKeypair, domainKeypair
|
|||
|
||||
acme2.registerNewAccount(options, function (err, account) {
|
||||
if (err) { console.log('err 2'); throw err; }
|
||||
console.log('account:');
|
||||
console.log(account);
|
||||
if (options.debug) console.debug('account:');
|
||||
if (options.debug) console.log(account);
|
||||
|
||||
acme2.getCertificate(options, function (err, fullchainPem) {
|
||||
if (err) { console.log('err 3'); throw err; }
|
|
@ -1,10 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
/* global Promise */
|
||||
module.exports.run = function run(web, chType, email, accountKeypair, domainKeypair) {
|
||||
var RSA = require('rsa-compat').RSA;
|
||||
var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
||||
var acme2 = require('./').ACME.create({ RSA: RSA });
|
||||
module.exports.run = function run(directoryUrl, RSA, web, chType, email, accountKeypair, domainKeypair) {
|
||||
var acme2 = require('../').ACME.create({ RSA: RSA });
|
||||
// [ 'test.ppl.family' ] 'coolaj86@gmail.com''http-01'
|
||||
acme2.init(directoryUrl).then(function () {
|
||||
var options = {
|
Loading…
Reference in New Issue