Compare commits

..

No commits in common. "master" and "v1.x" have entirely different histories.
master ... v1.x

18 changed files with 165 additions and 318 deletions

2
.gitignore vendored
View File

@ -29,5 +29,3 @@ build/Release
# Dependency directory # Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules node_modules
.idea
.DS_Store

View File

@ -1,3 +1,3 @@
ISRG ISRG
Anatol Sommer <anatol@anatol.at> Anatol Sommer <anatol@anatol.at>
AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/) AJ ONeal <aj@daplie.com> (https://daplie.com/)

102
README.md
View File

@ -1,8 +1,4 @@
# le-acme-core # letiny-core
Looking for **letiny-core**? Check the [v1.x branch](https://git.coolaj86.com/coolaj86/le-acme-core.js/tree/v1.x).
<!-- rename to le-acme-core -->
A framework for building letsencrypt clients, forked from `letiny`. A framework for building letsencrypt clients, forked from `letiny`.
@ -13,44 +9,19 @@ Supports all of:
* browser WebCrypto (not implemented, but... Let's Encrypt over WebRTC anyone?) * browser WebCrypto (not implemented, but... Let's Encrypt over WebRTC anyone?)
* any javascript implementation * any javascript implementation
# NEW: Let's Encrypt v2 Support
Let's Encrypt v2 (aka ACME v2 or ACME draft 11) is available in [acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js)
### These aren't the droids you're looking for ### These aren't the droids you're looking for
This is a library / framework for building letsencrypt clients. This is a library / framework for building letsencrypt clients.
You probably want one of these pre-built clients instead: You probably want one of these pre-built clients instead:
* [`letsencrypt`](https://git.coolaj86.com/coolaj86/greenlock.js) (compatible with the official client) * [`letsencrypt`](https://github.com/Daplie/node-letsencrypt) (compatible with the official client)
* `letiny` (lightweight client cli) * `letiny` (lightweight client cli)
* [`letsencrypt-express`](https://git.coolaj86.com/coolaj86/greenlock-express.js) (automatic https for express) * [`letsencrypt-express`](https://github.com/Daplie/letsencrypt-express) (automatic https for express)
## Install & Usage: ## Install & Usage:
```bash ```bash
npm install --save le-acme-core npm install --save letiny-core
```
To use the default dependencies:
```javascript
'use strict';
var ACME = require('le-acme-core').ACME.create();
```
For **testing** and **development**, you can also inject the dependencies you want to use:
```javascript
'use strict';
var ACME = require('le-acme-core').ACME.create({
, RSA: require('rsa-compat').RSA
});
ACME.getAcmeUrls(discoveryUrl, function (err, urls) {
console.log(urls);
});
``` ```
You will follow these steps to obtain certificates: You will follow these steps to obtain certificates:
@ -78,12 +49,12 @@ Note: use **YOUR EMAIL** and accept the terms of service (run `ddns --help` to s
<!-- TODO tutorial on ddns --> <!-- TODO tutorial on ddns -->
Install le-acme-core and its dependencies. **Note**: it's okay if you're on windows Install letiny-core and its dependencies. **Note**: it's okay if you're on windows
and `ursa` fails to compile. It'll still work. and `ursa` fails to compile. It'll still work.
```bash ```bash
git clone https://git.coolaj86.com/coolaj86/le-acme-core.js.git ~/le-acme-core git clone https://github.com/Daplie/letiny-core.git ~/letiny-core
pushd ~/le-acme-core pushd ~/letiny-core
npm install npm install
``` ```
@ -102,7 +73,7 @@ The Goodies
```javascript ```javascript
// Accounts // Accounts
ACME.registerNewAccount(options, cb) // returns "regr" registration data LeCore.registerNewAccount(options, cb) // returns "regr" registration data
{ newRegUrl: '<url>' // no defaults, specify acmeUrls.newAuthz { newRegUrl: '<url>' // no defaults, specify acmeUrls.newAuthz
, email: '<email>' // valid email (server checks MX records) , email: '<email>' // valid email (server checks MX records)
@ -113,7 +84,7 @@ ACME.registerNewAccount(options, cb) // returns "regr" registration data
} }
// Registration // Registration
ACME.getCertificate(options, cb) // returns (err, pems={ privkey (key), cert, chain (ca) }) LeCore.getCertificate(options, cb) // returns (err, pems={ privkey (key), cert, chain (ca) })
{ newAuthzUrl: '<url>' // specify acmeUrls.newAuthz { newAuthzUrl: '<url>' // specify acmeUrls.newAuthz
, newCertUrl: '<url>' // specify acmeUrls.newCert , newCertUrl: '<url>' // specify acmeUrls.newCert
@ -131,32 +102,49 @@ ACME.getCertificate(options, cb) // returns (err, pems={ privkey (key
} }
// Discovery URLs // Discovery URLs
ACME.getAcmeUrls(acmeDiscoveryUrl, cb) // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert}) LeCore.getAcmeUrls(acmeDiscoveryUrl, cb) // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert})
``` ```
Helpers & Stuff Helpers & Stuff
```javascript ```javascript
// Constants // Constants
ACME.productionServerUrl // https://acme-v01.api.letsencrypt.org/directory LeCore.productionServerUrl // https://acme-v01.api.letsencrypt.org/directory
ACME.stagingServerUrl // https://acme-staging.api.letsencrypt.org/directory LeCore.stagingServerUrl // https://acme-staging.api.letsencrypt.org/directory
ACME.acmeChallengePrefix // /.well-known/acme-challenge/ LeCore.acmeChallengePrefix // /.well-known/acme-challenge/
ACME.knownEndpoints // new-authz, new-cert, new-reg, revoke-cert LeCore.configDir // /etc/letsencrypt/
LeCore.logsDir // /var/log/letsencrypt/
LeCore.workDir // /var/lib/letsencrypt/
LeCore.knownEndpoints // new-authz, new-cert, new-reg, revoke-cert
// HTTP Client Helpers // HTTP Client Helpers
ACME.Acme // Signs requests with JWK LeCore.Acme // Signs requests with JWK
acme = new Acme(keypair) // 'keypair' is an object with `privateKeyPem` and/or `privateKeyJwk` acme = new Acme(keypair) // 'keypair' is an object with `privateKeyPem` and/or `privateKeyJwk`
acme.post(url, body, cb) // POST with signature acme.post(url, body, cb) // POST with signature
acme.parseLinks(link) // (internal) parses 'link' header acme.parseLinks(link) // (internal) parses 'link' header
acme.getNonce(url, cb) // (internal) HEAD request to get 'replay-nonce' strings acme.getNonce(url, cb) // (internal) HEAD request to get 'replay-nonce' strings
``` ```
For testing and development, you can also inject the dependencies you want to use:
```javascript
LeCore = LeCore.create({
request: require('request')
, RSA: rquire('rsa-compat').RSA
});
// now uses node `request` (could also use jQuery or Angular in the browser)
LeCore.getAcmeUrls(discoveryUrl, function (err, urls) {
console.log(urls);
});
```
## Example ## Example
Below you'll find a stripped-down example. You can see the full example in the example folder. Below you'll find a stripped-down example. You can see the full example in the example folder.
* [example/](https://git.coolaj86.com/coolaj86/le-acme-core.js/blob/master/example/) * [example/](https://github.com/Daplie/letiny-core/blob/master/example/)
#### Register Account & Domain #### Register Account & Domain
@ -165,12 +153,12 @@ This is how you **register an ACME account** and **get an HTTPS certificate**
```javascript ```javascript
'use strict'; 'use strict';
var ACME = require('le-acme-core').ACME.create(); var LeCore = require('letiny-core');
var RSA = require('rsa-compat').RSA; var RSA = require('rsa-compat').RSA;
var email = 'user@example.com'; // CHANGE TO YOUR EMAIL var email = 'user@example.com'; // CHANGE TO YOUR EMAIL
var domains = 'example.com'; // CHANGE TO YOUR DOMAIN var domains = 'example.com'; // CHANGE TO YOUR DOMAIN
var acmeDiscoveryUrl = ACME.stagingServerUrl; // CHANGE to production, when ready var acmeDiscoveryUrl = LeCore.stagingServerUrl; // CHANGE to production, when ready
var accountKeypair = null; // { privateKeyPem: null, privateKeyJwk: null }; var accountKeypair = null; // { privateKeyPem: null, privateKeyJwk: null };
var domainKeypair = null; // same as above var domainKeypair = null; // same as above
@ -179,14 +167,14 @@ var acmeUrls = null;
RSA.generateKeypair(2048, 65537, function (err, keypair) { RSA.generateKeypair(2048, 65537, function (err, keypair) {
accountKeypair = keypair; accountKeypair = keypair;
// ... // ...
ACME.getAcmeUrls(acmeDiscoveryUrl, function (err, urls) { LeCore.getAcmeUrls(acmeDiscoveryUrl, function (err, urls) {
// ... // ...
runDemo(); runDemo();
}); });
}); });
function runDemo() { function runDemo() {
ACME.registerNewAccount( LeCore.registerNewAccount(
{ newRegUrl: acmeUrls.newReg { newRegUrl: acmeUrls.newReg
, email: email , email: email
, accountKeypair: accountKeypair , accountKeypair: accountKeypair
@ -198,7 +186,7 @@ function runDemo() {
} }
, function (err, regr) { , function (err, regr) {
ACME.getCertificate( LeCore.getCertificate(
{ newAuthzUrl: acmeUrls.newAuthz { newAuthzUrl: acmeUrls.newAuthz
, newCertUrl: acmeUrls.newCert , newCertUrl: acmeUrls.newCert
@ -226,7 +214,7 @@ function runDemo() {
``` ```
**But wait**, there's more! **But wait**, there's more!
See [example/letsencrypt.js](https://git.coolaj86.com/coolaj86/le-acme-core.js/blob/master/example/letsencrypt.js) See [example/letsencrypt.js](https://github.com/Daplie/letiny-core/blob/master/example/letsencrypt.js)
#### Run a Server on 80, 443, and 5001 (https/tls) #### Run a Server on 80, 443, and 5001 (https/tls)
@ -239,7 +227,7 @@ var http = require('http');
var LeCore = deps.LeCore; var LeCore = deps.LeCore;
var tlsOptions = deps.tlsOptions; var httpsOptions = deps.httpsOptions;
var challengeStore = deps.challengeStore; var challengeStore = deps.challengeStore;
var certStore = deps.certStore; var certStore = deps.certStore;
@ -264,7 +252,7 @@ function acmeResponder(req, res) {
// //
// Server // Server
// //
https.createServer(tlsOptions, acmeResponder).listen(5001, function () { https.createServer(httpsOptions, acmeResponder).listen(5001, function () {
console.log('Listening https on', this.address()); console.log('Listening https on', this.address());
}); });
http.createServer(acmeResponder).listen(80, function () { http.createServer(acmeResponder).listen(80, function () {
@ -273,7 +261,7 @@ http.createServer(acmeResponder).listen(80, function () {
``` ```
**But wait**, there's more! **But wait**, there's more!
See [example/serve.js](https://git.coolaj86.com/coolaj86/le-acme-core.js/blob/master/example/serve.js) See [example/serve.js](https://github.com/Daplie/letiny-core/blob/master/example/serve.js)
#### Put some storage in place #### Put some storage in place
@ -314,14 +302,14 @@ var certStore = {
**But wait**, there's more! **But wait**, there's more!
See See
* [example/challenge-store.js](https://git.coolaj86.com/coolaj86/le-acme-core.js/blob/master/challenge-store.js) * [example/challenge-store.js](https://github.com/Daplie/letiny-core/blob/master/challenge-store.js)
* [example/cert-store.js](https://git.coolaj86.com/coolaj86/le-acme-core.js/blob/master/cert-store.js) * [example/cert-store.js](https://github.com/Daplie/letiny-core/blob/master/cert-store.js)
## Authors ## Authors
* ISRG * ISRG
* Anatol Sommer (https://github.com/anatolsommer) * Anatol Sommer (https://github.com/anatolsommer)
* AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com) * AJ ONeal <aj@daplie.com> (https://daplie.com)
## Licence ## Licence

View File

@ -1,6 +1,6 @@
/*! /*!
* letiny-core * letiny-core
* Copyright(c) 2015 AJ ONeal <coolaj86@gmail.com> https://coolaj86.com * Copyright(c) 2015 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0) * Apache-2.0 OR MIT (and hence also MPL 2.0)
*/ */
'use strict'; 'use strict';

View File

@ -1,6 +1,6 @@
/*! /*!
* letiny-core * letiny-core
* Copyright(c) 2015 AJ ONeal <coolaj86@gmail.com> https://coolaj86.com * Copyright(c) 2015 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0) * Apache-2.0 OR MIT (and hence also MPL 2.0)
*/ */
'use strict'; 'use strict';

View File

@ -1,12 +1,12 @@
/*! /*!
* letiny-core * letiny-core
* Copyright(c) 2015 AJ ONeal <coolaj86@gmail.com> https://coolaj86.com * Copyright(c) 2015 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0) * Apache-2.0 OR MIT (and hence also MPL 2.0)
*/ */
'use strict'; 'use strict';
//var LeCore = require('letiny-core'); //var LeCore = require('letiny-core');
var LeCore = require('../').ACME.create(); var LeCore = require('../');
var email = process.argv[2] || 'user@example.com'; // CHANGE TO YOUR EMAIL var email = process.argv[2] || 'user@example.com'; // CHANGE TO YOUR EMAIL
var domains = [process.argv[3] || 'example.com']; // CHANGE TO YOUR DOMAIN var domains = [process.argv[3] || 'example.com']; // CHANGE TO YOUR DOMAIN
@ -17,8 +17,8 @@ var certStore = require('./cert-store');
var serve = require('./serve'); var serve = require('./serve');
var closer; var closer;
var accountKeypair = null; var accountPrivateKeyPem = null;
var domainKeypair = null; var domainPrivateKeyPem = null;
var acmeUrls = null; var acmeUrls = null;
@ -44,14 +44,14 @@ function init() {
function getPrivateKeys(cb) { function getPrivateKeys(cb) {
console.log('Generating Account Keypair'); console.log('Generating Account Keypair');
const RSA = require('rsa-compat').RSA; console.log("(Note: if you're using forge and not ursa, this will take a long time");
RSA.generateKeypair(2048, 65537, {}, function (err, pems) { LeCore.leCrypto.generateRsaKeypair(2048, 65537, function (err, pems) {
accountKeypair = pems; accountPrivateKeyPem = pems.privateKeyPem;
console.log('Generating Domain Keypair'); console.log('Generating Domain Keypair');
RSA.generateKeypair(2048, 65537, {}, function (err, pems2) { LeCore.leCrypto.generateRsaKeypair(2048, 65537, function (err, pems) {
domainKeypair = pems2; domainPrivateKeyPem = pems.privateKeyPem;
cb(); cb();
}); });
}); });
@ -62,7 +62,7 @@ function runDemo() {
LeCore.registerNewAccount( LeCore.registerNewAccount(
{ newRegUrl: acmeUrls.newReg { newRegUrl: acmeUrls.newReg
, email: email , email: email
, accountKeypair: accountKeypair , accountPrivateKeyPem: accountPrivateKeyPem
, agreeToTerms: function (tosUrl, done) { , agreeToTerms: function (tosUrl, done) {
// agree to the exact version of these terms // agree to the exact version of these terms
@ -82,8 +82,8 @@ function runDemo() {
{ newAuthzUrl: acmeUrls.newAuthz { newAuthzUrl: acmeUrls.newAuthz
, newCertUrl: acmeUrls.newCert , newCertUrl: acmeUrls.newCert
, domainKeypair: domainKeypair , domainPrivateKeyPem: domainPrivateKeyPem
, accountKeypair: accountKeypair , accountPrivateKeyPem: accountPrivateKeyPem
, domains: domains , domains: domains
, setChallenge: challengeStore.set , setChallenge: challengeStore.set
@ -111,7 +111,8 @@ function runDemo() {
// //
closer = serve.init({ closer = serve.init({
LeCore: LeCore LeCore: LeCore
, tlsOptions: {} // needs a default key and cert chain, anything will do
, httpsOptions: require('localhost.daplie.com-certificates')
, challengeStore: challengeStore , challengeStore: challengeStore
, certStore: certStore , certStore: certStore
}); });

View File

@ -1,6 +1,6 @@
/*! /*!
* letiny-core * letiny-core
* Copyright(c) 2015 AJ ONeal <coolaj86@gmail.com> https://coolaj86.com * Copyright(c) 2015 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0) * Apache-2.0 OR MIT (and hence also MPL 2.0)
*/ */
'use strict'; 'use strict';
@ -15,7 +15,7 @@ module.exports.init = function (deps) {
var LeCore = deps.LeCore; var LeCore = deps.LeCore;
var tlsOptions = deps.tlsOptions || deps.httpsOptions; var httpsOptions = deps.httpsOptions;
var challengeStore = deps.challengeStore; var challengeStore = deps.challengeStore;
var certStore = deps.certStore; var certStore = deps.certStore;
@ -63,11 +63,11 @@ module.exports.init = function (deps) {
// //
// Server // Server
// //
tlsOptions.SNICallback = certGetter; httpsOptions.SNICallback = certGetter;
https.createServer(tlsOptions, acmeResponder).listen(443, function () { https.createServer(httpsOptions, acmeResponder).listen(443, function () {
console.log('Listening https on', this.address()); console.log('Listening https on', this.address());
}); });
https.createServer(tlsOptions, acmeResponder).listen(5001, function () { https.createServer(httpsOptions, acmeResponder).listen(5001, function () {
console.log('Listening https on', this.address()); console.log('Listening https on', this.address());
}); });
http.createServer(acmeResponder).listen(80, function () { http.createServer(acmeResponder).listen(80, function () {

View File

@ -8,10 +8,10 @@
module.exports.create = function (deps) { module.exports.create = function (deps) {
var NOOP = function () { var NOOP=function () {
}; };
var log = NOOP; var log=NOOP;
var acmeRequest = deps.acmeRequest; var request=require('request');
var RSA = deps.RSA; var RSA = deps.RSA;
var generateSignature = RSA.signJws; var generateSignature = RSA.signJws;
@ -30,7 +30,7 @@ module.exports.create = function (deps) {
Acme.prototype.getNonce=function(url, cb) { Acme.prototype.getNonce=function(url, cb) {
var self=this; var self=this;
acmeRequest.create().head({ request.head({
url:url, url:url,
}, function(err, res/*, body*/) { }, function(err, res/*, body*/) {
if (err) { if (err) {
@ -73,10 +73,10 @@ module.exports.create = function (deps) {
//process.exit(1); //process.exit(1);
//return; //return;
return acmeRequest.create().post({ return request.post({
url: url url:url,
, body: signed body:signed,
, encoding: null encoding:null
}, function(err, res, body) { }, function(err, res, body) {
var parsed; var parsed;

17
lib/client.js Normal file
View File

@ -0,0 +1,17 @@
/*!
* letiny
* Copyright(c) 2015 Anatol Sommer <anatol@anatol.at>
* Some code used from https://github.com/letsencrypt/boulder/tree/master/test/js
* MPL 2.0
*/
'use strict';
exports.Acme = require('./acme-client');
exports.registerNewAccount = require('./register-new-account');
exports.getCertificate = require('./get-certificate');
exports.getCert=function (options, cb) {
exports.registerNewAccount(options, function () {
exports.getCertificate(options, cb);
});
};

View File

@ -1,12 +1,12 @@
/*! /*!
* letiny-core * letiny-core
* Copyright(c) 2015 AJ ONeal <coolaj86@gmail.com> https://coolaj86.com * Copyright(c) 2015 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0) * Apache-2.0 OR MIT (and hence also MPL 2.0)
*/ */
'use strict'; 'use strict';
module.exports.create = function (deps) { module.exports.create = function (deps) {
var acmeRequest = deps.acmeRequest; var request = deps.request;
var knownUrls = deps.LeCore.knownEndpoints; var knownUrls = deps.LeCore.knownEndpoints;
function getAcmeUrls(acmeDiscoveryUrl, cb) { function getAcmeUrls(acmeDiscoveryUrl, cb) {
@ -15,7 +15,7 @@ module.exports.create = function (deps) {
} }
// TODO check response header on request for cache time // TODO check response header on request for cache time
return acmeRequest.create()({ return request({
url: acmeDiscoveryUrl url: acmeDiscoveryUrl
, encoding: 'utf8' , encoding: 'utf8'
}, function (err, resp) { }, function (err, resp) {
@ -30,15 +30,18 @@ module.exports.create = function (deps) {
try { try {
data = JSON.parse(data); data = JSON.parse(data);
} catch(e) { } catch(e) {
e.raw = data; err.raw = data;
e.url = acmeDiscoveryUrl; err.stack += '\n' + data;
e.stack += '\n\nresponse data:\n'
+ data + '\n\nacmeDiscoveryUrl:' + acmeDiscoveryUrl;
cb(e); cb(e);
return; return;
} }
} }
if (4 !== Object.keys(data).length) {
console.warn("This Let's Encrypt / ACME server has been updated with urls that this client doesn't understand");
console.warn(data);
}
if (!knownUrls.every(function (url) { if (!knownUrls.every(function (url) {
return data[url]; return data[url];
})) { })) {
@ -51,7 +54,6 @@ module.exports.create = function (deps) {
, newCert: data['new-cert'] , newCert: data['new-cert']
, newReg: data['new-reg'] , newReg: data['new-reg']
, revokeCert: data['revoke-cert'] , revokeCert: data['revoke-cert']
, keyChange: data['key-change']
}); });
}); });
} }

View File

@ -17,14 +17,8 @@ function _toStandardBase64(str) {
return b64; return b64;
} }
function certBufferToPem(cert) {
cert = _toStandardBase64(cert.toString('base64'));
cert = cert.match(/.{1,64}/g).join('\r\n');
return '-----BEGIN CERTIFICATE-----\r\n'+cert+'\r\n-----END CERTIFICATE-----\r\n';
}
module.exports.create = function (deps) { module.exports.create = function (deps) {
var acmeRequest = deps.acmeRequest; var request=deps.request;
var Acme = deps.Acme; var Acme = deps.Acme;
var RSA = deps.RSA; var RSA = deps.RSA;
@ -193,7 +187,7 @@ module.exports.create = function (deps) {
if (authz.status==='pending') { if (authz.status==='pending') {
setTimeout(function() { setTimeout(function() {
acmeRequest.create()({ request({
method: 'GET' method: 'GET'
, url: state.authorizationUrl , url: state.authorizationUrl
}, function(err, res, body) { }, function(err, res, body) {
@ -278,7 +272,7 @@ module.exports.create = function (deps) {
state.certificate=body; state.certificate=body;
certUrl=res.headers.location; certUrl=res.headers.location;
acmeRequest.create()({ request({
method: 'GET' method: 'GET'
, url: certUrl , url: certUrl
, encoding: null , encoding: null
@ -310,7 +304,7 @@ module.exports.create = function (deps) {
function downloadIssuerCert(links) { function downloadIssuerCert(links) {
log('Requesting issuer certificate...'); log('Requesting issuer certificate...');
acmeRequest.create()({ request({
method: 'GET' method: 'GET'
, url: links.up , url: links.up
, encoding: null , encoding: null
@ -327,7 +321,7 @@ module.exports.create = function (deps) {
return handleErr(err, 'Failed to fetch issuer certificate'); return handleErr(err, 'Failed to fetch issuer certificate');
} }
state.chainPem = certBufferToPem(body); state.chainPem=certBufferToPem(body);
log('Requesting issuer certificate: done'); log('Requesting issuer certificate: done');
done(); done();
}); });
@ -408,5 +402,11 @@ module.exports.create = function (deps) {
nextDomain(); nextDomain();
} }
function certBufferToPem(cert) {
cert=_toStandardBase64(cert.toString('base64'));
cert=cert.match(/.{1,64}/g).join('\r\n');
return '-----BEGIN CERTIFICATE-----\r\n'+cert+'\r\n-----END CERTIFICATE-----\r\n';
}
return getCert; return getCert;
}; };

View File

@ -1,72 +0,0 @@
/*!
* le-acme-core
* Author: Kelly Johnson
* Copyright 2017
* Apache-2.0 OR MIT (and hence also MPL 2.0)
*/
'use strict';
const request = require('request');
const pkgJSON = require('../package.json');
const version = pkgJSON.version;
const os = require('os');
const uaDefaults = {
pkg: `Greenlock/${version}`
, os: ` (${os.type()}; ${process.arch} ${os.platform()} ${os.release()})`
, node: ` Node.js/${process.version}`
, user: ''
}
let currentUAProps;
function getUaString() {
let userAgent = '';
for (let key in currentUAProps) {
userAgent += currentUAProps[key];
}
return userAgent.trim();
}
function getRequest() {
return request.defaults({
headers: {
'User-Agent': getUaString()
}
});
}
function resetUa() {
currentUAProps = {};
for (let key in uaDefaults) {
currentUAProps[key] = uaDefaults[key];
}
}
function addUaString(string) {
currentUAProps.user += ` ${string}`;
}
function omitUaProperties(opts) {
if (opts.all) {
currentUAProps = {};
} else {
for (let key in opts) {
currentUAProps[key] = '';
}
}
}
// Set our UA to begin with
resetUa();
module.exports = {
create: function create() {
// get deps and modify here if need be
return getRequest();
}
, addUaString: addUaString
, omitUaProperties: omitUaProperties
, resetUa: resetUa
, getUaString: getUaString
};

12
lib/node.js Normal file
View File

@ -0,0 +1,12 @@
/*!
* letiny-core
* Copyright(c) 2015 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0)
*/
'use strict';
var request = require('request');
var RSA = require('rsa-compat').RSA;
module.exports.request = request;
module.exports.RSA = RSA;

View File

@ -8,7 +8,7 @@
'use strict'; 'use strict';
module.exports.create = function (deps) { module.exports.create = function (deps) {
var NOOP=function () {}, log=NOOP; var NOOP=function () {}, log=NOOP;
var acmeRequest = deps.acmeRequest; var request=deps.request;
var RSA = deps.RSA; var RSA = deps.RSA;
var Acme = deps.Acme; var Acme = deps.Acme;
@ -24,11 +24,7 @@ module.exports.create = function (deps) {
function getTerms(err, res) { function getTerms(err, res) {
var links; var links;
if (err) { if (err || Math.floor(res.statusCode/100)!==2) {
return handleErr(err, 'Registration request failed: ' + err.toString());
}
if (Math.floor(res.statusCode/100)!==2) {
return handleErr(err, 'Registration request failed: ' + res.body.toString('utf8')); return handleErr(err, 'Registration request failed: ' + res.body.toString('utf8'));
} }
@ -55,7 +51,7 @@ module.exports.create = function (deps) {
state.agreeTerms = agree; state.agreeTerms = agree;
state.termsUrl=links['terms-of-service']; state.termsUrl=links['terms-of-service'];
log(state.termsUrl); log(state.termsUrl);
acmeRequest.create().get(state.termsUrl, getAgreement); request.get(state.termsUrl, getAgreement);
}); });
} else { } else {
cb(null, null); cb(null, null);

57
node.js
View File

@ -1,53 +1,30 @@
/*! /*!
* letiny-core * letiny-core
* Copyright(c) 2015 AJ ONeal <coolaj86@gmail.com> https://coolaj86.com * Copyright(c) 2015 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0) * Apache-2.0 OR MIT (and hence also MPL 2.0)
*/ */
'use strict'; 'use strict';
var defaults = {
productionServerUrl: 'https://acme-v01.api.letsencrypt.org/directory'
, stagingServerUrl: 'https://acme-staging.api.letsencrypt.org/directory'
, acmeChallengePrefix: '/.well-known/acme-challenge/'
, knownEndpoints: [ 'new-authz', 'new-cert', 'new-reg', 'revoke-cert', 'key-change' ]
, challengeType: 'http-01'
, rsaKeySize: 2048
};
function create(deps) { function create(deps) {
deps = deps || {}; var LeCore = {};
deps.LeCore = {};
Object.keys(defaults).forEach(function (key) { // Note: these are NOT DEFAULTS
deps[key] = defaults[key]; // They are de facto standards that you may
deps.LeCore[key] = defaults[key]; // or may not use in your implementation
}); LeCore.productionServerUrl = "https://acme-v01.api.letsencrypt.org/directory";
LeCore.stagingServerUrl = "https://acme-staging.api.letsencrypt.org/directory";
LeCore.acmeChallengePrefix = "/.well-known/acme-challenge/";
LeCore.knownEndpoints = [ 'new-authz', 'new-cert', 'new-reg', 'revoke-cert' ];
deps.RSA = deps.RSA || require('rsa-compat').RSA; deps.LeCore = LeCore;
deps.acmeRequest = require('./lib/le-acme-request'); deps.Acme = LeCore.Acme = require('./lib/acme-client').create(deps);
deps.Acme = require('./lib/acme-client').create(deps);
deps.LeCore.Acme = deps.Acme; LeCore.getAcmeUrls = require('./lib/get-acme-urls').create(deps);
deps.LeCore.acmeRequest = deps.acmeRequest; LeCore.registerNewAccount = require('./lib/register-new-account').create(deps);
deps.LeCore.getAcmeUrls = require('./lib/get-acme-urls').create(deps); LeCore.getCertificate = require('./lib/get-certificate').create(deps);
deps.LeCore.registerNewAccount = require('./lib/register-new-account').create(deps);
deps.LeCore.getCertificate = require('./lib/get-certificate').create(deps);
deps.LeCore.getOptions = function () {
var defs = {};
Object.keys(defaults).forEach(function (key) { return LeCore;
defs[key] = defs[deps] || defaults[key];
});
return defs;
};
return deps.LeCore;
} }
// TODO make this the official usage module.exports = create(require('./lib/node'));
module.exports.ACME = { create: create }; module.exports.create = create;
Object.keys(defaults).forEach(function (key) {
module.exports.ACME[key] = defaults[key];
});

View File

@ -1,6 +1,6 @@
{ {
"name": "le-acme-core", "name": "letiny-core",
"version": "2.1.4", "version": "2.0.3",
"description": "A framework for building letsencrypt clients, forked from letiny", "description": "A framework for building letsencrypt clients, forked from letiny",
"main": "node.js", "main": "node.js",
"browser": "browser.js", "browser": "browser.js",
@ -8,34 +8,36 @@
"example": "example", "example": "example",
"test": "test" "test": "test"
}, },
"scripts": {
"test": "node example/letsencrypt.js"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://git.coolaj86.com/coolaj86/le-acme-core.js.git" "url": "git+https://github.com/Daplie/letiny-core.git"
}, },
"license": "MPL-2.0", "license": "MPL-2.0",
"bugs": { "bugs": {
"url": "https://git.coolaj86.com/coolaj86/le-acme-core.js/issues" "url": "https://github.com/Daplie/letiny-core/issues"
}, },
"homepage": "https://git.coolaj86.com/coolaj86/le-acme-core.js#readme", "homepage": "https://github.com/Daplie/letiny-core#readme",
"keywords": [ "keywords": [
"le-acme",
"le-acme-",
"tiny", "tiny",
"acme", "acme",
"letsencrypt", "letsencrypt",
"client", "client",
"pem", "pem",
"jwk",
"pfx" "pfx"
], ],
"dependencies": { "dependencies": {
"request": "^2.74.0", "node-forge": "^0.6.38",
"rsa-compat": "^1.3.2" "request": "^2.55.0",
"rsa-compat": "^1.2.3"
},
"optionalDependencies": {
"ursa": "^0.9.1"
}, },
"devDependencies": { "devDependencies": {
"better-assert": "^1.0.2", "mocha": "^2.3.3",
"chai": "^3.5.0", "better-assert": "^1.0.2"
"chai-string": "^1.3.0",
"request-debug": "^0.2.0"
} }
} }

View File

@ -1,74 +0,0 @@
/*!
* le-acme-core
* Author: Kelly Johnson
* Copyright 2017
* Apache-2.0 OR MIT (and hence also MPL 2.0)
*/
'use strict';
const acmeRequest = require('../lib/le-acme-request');
const debugRequest = require('request-debug');
const chai = require('chai');
chai.use(require('chai-string'));
const expect = chai.expect;
const productId = 'Greenlock';
const UA = 'User-Agent';
function checkRequest(req, done, tester) {
debugRequest(req, function dbg(type, data, r) {
if (type !== 'request') return; // Only interested in the request
expect(data.headers).to.have.property(UA);
let uaString = data.headers[UA];
tester(uaString);
req.stopDebugging();
done();
});
req('http://www.google.com', function (error, response, body) {
});
}
describe('le-acme-request', function () {
beforeEach(function () {
acmeRequest.resetUa();
});
it('should build User-Agent string', function () {
let uaString = acmeRequest.getUaString();
expect(uaString).to.startsWith(productId);
});
it('should have proper User-Agent in request', function (done) {
let request = acmeRequest.create();
checkRequest(request, done, function (uaString) {
expect(uaString).to.startsWith(productId);
});
});
it('should add custom string to User Agent', function (done) {
let testStr = 'check it';
acmeRequest.addUaString(testStr);
let request = acmeRequest.create();
checkRequest(request, done, function (uaString) {
// Added space to ensure str was properly appended
expect(uaString).to.endsWith(` ${testStr}`);
});
});
it('should remove all items from User Agent', function (done) {
acmeRequest.omitUaProperties({all: true});
let request = acmeRequest.create();
checkRequest(request, done, function (uaString) {
expect(uaString).to.be.empty;
});
});
it('should remove one item from User Agent', function (done) {
acmeRequest.omitUaProperties({pkg: true});
const request = acmeRequest.create();
checkRequest(request, done, function (uaString) {
expect(uaString).to.not.have.string(productId);
});
});
});

View File

@ -1,5 +1,5 @@
var forge=require('node-forge'), assert=require('better-assert'), fs=require('fs'), var forge=require('node-forge'), assert=require('better-assert'), fs=require('fs'),
letiny=require('../'), config=require('./config.json'), letiny=require('../lib/client'), config=require('./config.json'),
res, newReg='https://acme-staging.api.letsencrypt.org/acme/new-reg'; res, newReg='https://acme-staging.api.letsencrypt.org/acme/new-reg';
config.newReg=config.newReg || newReg; config.newReg=config.newReg || newReg;