From 971a47eb76aa1446a78e9c8790af2a5a78a35cf7 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 9 Aug 2016 22:39:39 -0400 Subject: [PATCH] use letsencrypt v2 --- README.md | 13 +++--- bin/letsencrypt.js | 64 +++++------------------------ example-standalone.bash | 4 +- index.js | 88 ++++++++++++++++++++++++++++++++++++++++ lib/servers.js | 89 +++++++++++++++++++++++++++++++++++++++++ lib/standalone.js | 69 +++++++------------------------- lib/webroot.js | 2 +- package.json | 6 ++- 8 files changed, 215 insertions(+), 120 deletions(-) mode change 100644 => 100755 example-standalone.bash create mode 100644 index.js create mode 100644 lib/servers.js diff --git a/README.md b/README.md index be43dd6..6acc9d4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Join the chat at https://gitter.im/Daplie/letsencrypt-express](https://badges.gitter.im/Daplie/letsencrypt-express.svg)](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | [letsencrypt (library)](https://github.com/Daplie/node-letsencrypt) -| **letsencrypt-cli** +| **letsencrypt-cli** | [letsencrypt-express](https://github.com/Daplie/letsencrypt-express) | [letsencrypt-koa](https://github.com/Daplie/letsencrypt-koa) | [letsencrypt-hapi](https://github.com/Daplie/letsencrypt-hapi) @@ -138,7 +138,7 @@ you could change the permissions on them. **Probably a BAD IDEA**. Probabry a se ``` # PROBABLY A BAD IDEA -sudo chown -R $(whoami) /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt +sudo chown -R $(whoami) /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt ``` ## Command line Options @@ -159,10 +159,13 @@ Options: --debug BOOLEAN show traces and logs - --tls-sni-01-port NUMBER Port number to perform tls-sni-01 challenge. - Boulder in testing mode defaults to 5001. (default: 443 and 5001) + --tls-sni-01-port NUMBER Use TLS-SNI-01 challenge type with this port. (Default is 443) + (must be 443 with most production servers) (Boulder allows 5001 in testing mode) - --http-01-port [NUMBER] Port used in the SimpleHttp challenge. (Default is 80) + --http-01-port [NUMBER] Use HTTP-01 challenge type with this port, used for SimpleHttp challenge. (Default is 80) + (must be 80 with most production servers) + + --dns-01 Use DNS-01 challenge type. --rsa-key-size [NUMBER] Size (in bits) of the RSA key. (Default is 2048) diff --git a/bin/letsencrypt.js b/bin/letsencrypt.js index 4f78695..252cc3d 100755 --- a/bin/letsencrypt.js +++ b/bin/letsencrypt.js @@ -10,13 +10,15 @@ cli.parse({ , duplicate: [ false, " Allow getting a certificate that duplicates an existing one", 'boolean', false ] , 'agree-tos': [ false, " Agree to the Let's Encrypt Subscriber Agreement", 'boolean', false ] , debug: [ false, " show traces and logs", 'boolean', false ] -, 'tls-sni-01-port': [ false, " Port number to perform tls-sni-01 challenge. Boulder in testing mode defaults to 5001. (default: 443,5001)", 'string' ] -, 'http-01-port': [ false, " Port used in the SimpleHttp challenge. (default: 80)", 'string' ] +, 'tls-sni-01-port': [ false, " Use TLS-SNI-01 challenge type with this port (only port 443 is valid with most production servers) (default: 443,5001)", 'string' ] +, 'http-01-port': [ false, " Use HTTP-01 challenge type with this port (only port 80 is valid with most production servers) (default: 80)", 'string' ] +, 'dns-01': [ false, " Use DNS-01 challange type", 'boolean', false ] , 'rsa-key-size': [ false, " Size (in bits) of the RSA key.", 'int', 2048 ] -, 'cert-path': [ false, " Path to where new cert.pem is saved", 'string',':config/live/:hostname/cert.pem' ] -, 'fullchain-path': [ false, " Path to where new fullchain.pem (cert + chain) is saved", 'string', ':config/live/:hostname/fullchain.pem' ] -, 'chain-path': [ false, " Path to where new chain.pem is saved", 'string', ':config/live/:hostname/chain.pem' ] +, 'cert-path': [ false, " Path to where new cert.pem is saved", 'string',':configDir/live/:hostname/cert.pem' ] +, 'fullchain-path': [ false, " Path to where new fullchain.pem (cert + chain) is saved", 'string', ':configDir/live/:hostname/fullchain.pem' ] +, 'chain-path': [ false, " Path to where new chain.pem is saved", 'string', ':configDir/live/:hostname/chain.pem' ] , 'domain-key-path': [ false, " Path to privkey.pem to use for domain (default: generate new)", 'string' ] +, 'account-key-path': [ false, " Path to privkey.pem to use for account (default: generate new)", 'string' ] , 'config-dir': [ false, " Configuration directory.", 'string', '~/letsencrypt/etc/' ] , server: [ false, " ACME Directory Resource URI.", 'string', 'https://acme-v01.api.letsencrypt.org/directory)' ] , standalone: [ false, " Obtain certs using a \"standalone\" webserver.", 'boolean', false ] @@ -49,7 +51,7 @@ cli.main(function(_, options) { var val = args[key]; if ('string' === typeof val) { - val = val.replace(/\:config/, args.configDir); + val = val.replace(/(\:configDir)|(\:config)/, args.configDir); } args[key] = val; @@ -88,54 +90,6 @@ cli.main(function(_, options) { return; } - var LE = require('letsencrypt'); - var handlers; - - if (args.webrootPath) { - handlers = require('../lib/webroot').create(args); - } - else /*if (args.standalone)*/ { - handlers = require('../lib/standalone').create(); - handlers.startServers(args.http01Port || [80], args.tlsSni01Port || [443, 5001]); - } - - // let LE know that we're handling standalone / webroot here - LE.create({ - manual: true - , debug: args.debug - , configDir: args.configDir - , privkeyPath: ':config/live/:hostname/privkey.pem' //args.privkeyPath - , fullchainPath: args.fullchainPath - , certPath: args.certPath - , chainPath: args.chainPath - }, handlers).register(args, function (err, results) { - if (err) { - console.error('[Error]: letsencrypt-cli'); - console.error(err.stack || err); - return; - } - - if (!results || ('object' !== typeof results)) { - console.error("Error: An unknown error occurred. My best guess is that we got an error that we're not used to from the ACME server and accidentally interpretted it as a success... or forgot to expose the error."); - console.error(results); - err = new Error("Here's a stack trace, in case it helps:"); - console.error(err.stack); - return; - } - - if (handlers.closeServers) { - handlers.closeServers(); - } - - // should get back account, path to certs, pems, etc? - console.log('\nCertificates installed at:'); - console.log(Object.keys(results).filter(function (key) { - return /Path/.test(key); - }).map(function (key) { - return results[key]; - }).join('\n')); - - process.exit(0); - }); + require('../').run(args); }); }); diff --git a/example-standalone.bash b/example-standalone.bash old mode 100644 new mode 100755 index 52ed2a1..740ced6 --- a/example-standalone.bash +++ b/example-standalone.bash @@ -1,7 +1,7 @@ #!/bin/bash -letsencrypt certonly \ - --agree-tos --email coolaj86+le.1010@gmail.com \ +node bin/letsencrypt certonly \ + --agree-tos --email 'coolaj86+le.1010@gmail.com' \ --standalone \ --domains pokemap.hellabit.com,www.pokemap.hellabit.com \ --server https://acme-staging.api.letsencrypt.org/directory \ diff --git a/index.js b/index.js new file mode 100644 index 0000000..bbbf671 --- /dev/null +++ b/index.js @@ -0,0 +1,88 @@ +'use strict'; + +var LE = require('letsencrypt'); + +module.exports.run = function (args) { + var leChallenge; + var leStore; + var servers; + var USE_DNS = {}; + + var challengeType; + if (args.dns01) { + challengeType = 'dns-01'; + args.webrootPath = ''; + args.standalone = USE_DNS; + } else if (args.tlsSni01Port) { + challengeType = 'tls-sni-01'; + } else /*if (args.http01Port)*/ { + challengeType = 'http-01'; + } + + if (args.webrootPath) { + // webrootPath is all that really matters here + leChallenge = require('./lib/webroot').create({ webrootPath: args.webrootPath }); + } + else if (USE_DNS !== args.standalone) { + leChallenge = require('./lib/standalone').create({}); + servers = require('./lib/servers').create(leChallenge).startServers( + args.http01Port || [80], args.tlsSni01Port || [443, 5001] + , { debug: args.debug } + ); + } + + leStore = require('le-store-certbot').create({ + configDir: args.configDir + , privkeyPath: args.domainKeyPath || ':configDir/live/:hostname/privkey.pem' //args.privkeyPath + , fullchainPath: args.fullchainPath + , certPath: args.certPath + , chainPath: args.chainPath + , webrootPath: args.webrootPath + , domainKeyPath: args.domainKeyPath + , accountKeyPath: args.accountKeyPath + }); + + // let LE know that we're handling standalone / webroot here + var le = LE.create({ + debug: args.debug + , server: args.server + , store: leStore + , challenge: leChallenge + , duplicate: args.duplicate + }); + + // Note: can't use args directly as null values will overwrite template values + le.register({ + domains: args.domains + , email: args.email + , agreeTos: args.agreeTos + , challengeType: challengeType + , rsaKeySize: args.rsaKeySize + }).then(function (certs) { + if (servers) { + servers.closeServers(); + } + + // should get back account, path to certs, pems, etc? + console.log('\nCertificates installed at:'); + console.log(Object.keys(args).filter(function (key) { + return /Path/.test(key); + }).map(function (key) { + return args[key]; + }).join('\n').replace(/:hostname/, args.domains[0])); + + console.log(""); + console.log("Got certificate(s) for " + certs.altnames.join(', ')); + console.log("\tIssued at " + new Date(certs.issuedAt).toISOString() + ""); + console.log("\tValid until " + new Date(certs.expiresAt).toISOString() + ""); + console.log(""); + + process.exit(0); + }, function (err) { + console.error('[Error]: letsencrypt-cli'); + console.error(err.stack || new Error('get stack').stack); + + process.exit(1); + }); + +}; diff --git a/lib/servers.js b/lib/servers.js new file mode 100644 index 0000000..5c351ee --- /dev/null +++ b/lib/servers.js @@ -0,0 +1,89 @@ +'use strict'; + +var NOBJ = {}; + +module.exports.create = function (challenge) { + var servers = { + _servers: [] + + , httpResponder: function (req, res) { + console.log('[LE-CLI] httpResponder'); + var acmeChallengePrefix = '/.well-known/acme-challenge/'; + + if (0 !== req.url.indexOf(acmeChallengePrefix)) { + res.end("Let's Encrypt! Command line tool"); + return; + } + + var token = req.url.slice(acmeChallengePrefix.length); + + challenge.get(NOBJ, req.headers.host.replace(/:.*/, ''), token, function (err, val) { + res.end(val || '_ ERROR challenge not found _'); + }); + } + + , startServers: function (plainPorts, tlsPorts, opts) { + opts = opts || {}; + + var httpsOptions = require('localhost.daplie.com-certificates'); + var https = require('https'); + var http = require('http'); + + if (servers._servers.length) { + return; + } + + // http-01-port + plainPorts.forEach(function (port) { + var server = http.createServer(servers.httpResponder); + + servers._servers.push(server); + server.listen(port, function () { + if (opts.debug) { + console.info('Listening http on', this.address()); + } + }); + server.on('error', function (err) { + if ('EADDRINUSE' === err.code) { + console.error(""); + console.error("You already have a different server running on port '" + port + "'."); + console.error("You should probably use the --webroot-path option instead of --standalone."); + return; + } + throw err; + }); + }); + + // tls-sni-01-port + tlsPorts.forEach(function (port) { + var server = https.createServer(httpsOptions, servers.httpResponder); + + servers._servers.push(server); + servers.listen(port, function () { + if (opts.debug) { + console.info('Listening https on', this.address()); + } + }); + servers.on('error', function (err) { + if ('EADDRINUSE' === err.code) { + console.error(""); + console.error("You already have a different server running on port '" + port + "'."); + console.error("You should probably use the --webroot-path option instead of --standalone."); + return; + } + throw err; + }); + }); + + } + + , closeServers: function () { + servers._servers.forEach(function (server) { + server.close(); + }); + servers._servers = []; + } + }; + + return servers; +}; diff --git a/lib/standalone.js b/lib/standalone.js index 8bbbf68..f21b907 100644 --- a/lib/standalone.js +++ b/lib/standalone.js @@ -1,75 +1,34 @@ 'use strict'; -module.exports.create = function () { +module.exports.create = function (defaults) { var handlers = { + getOptions: function () { + return defaults; + } // // set,get,remove challenges // // Note: this is fine for a one-off CLI tool // but a webserver using node-cluster or multiple // servers should use a database of some sort - _challenges: {} - , setChallenge: function (args, key, value, cb) { - handlers._challenges[key] = value; + , _challenges: {} + , set: function (args, domain, token, secret, cb) { + console.log('bloh 1'); + handlers._challenges[token] = secret; cb(null); } - , getChallenge: function (args, key, cb) { + , get: function (args, domain, token, cb) { + console.log('bloh 2'); // TODO keep in mind that, generally get args are just args.domains // and it is disconnected from the flow of setChallenge and removeChallenge - cb(null, handlers._challenges[key]); + cb(null, handlers._challenges[token]); } - , removeChallenge: function (args, key, cb) { - delete handlers._challenges[key]; + , remove: function (args, domain, token, cb) { + console.log('balh 3'); + delete handlers._challenges[token]; cb(null); } - , _servers: [] - , httpResponder: function (req, res) { - var acmeChallengePrefix = '/.well-known/acme-challenge/'; - - if (0 !== req.url.indexOf(acmeChallengePrefix)) { - res.end('Hello World!'); - return; - } - - var key = req.url.slice(acmeChallengePrefix.length); - - handlers.getChallenge(req.headers.host, key, function (err, val) { - res.end(val || '_'); - }); - } - , startServers: function (plainPorts, tlsPorts, opts) { - opts = opts || {}; - var httpsOptions = require('localhost.daplie.com-certificates'); - var https = require('https'); - var http = require('http'); - - // tls-sni-01-port - if (handlers._servers.length) { - return; - } - - plainPorts.forEach(function (port) { - http.createServer(handlers.httpResponder).listen(port, function () { - if (opts.debug) { - console.info('Listening http on', this.address()); - } - }); - }); - tlsPorts.forEach(function (port) { - https.createServer(httpsOptions, handlers.httpResponder).listen(port, function () { - if (opts.debug) { - console.info('Listening https on', this.address()); - } - }); - }); - } - , closeServers: function () { - handlers._servers.forEach(function (server) { - server.close(); - }); - handlers._servers = []; - } }; return handlers; diff --git a/lib/webroot.js b/lib/webroot.js index e900f92..11cf574 100644 --- a/lib/webroot.js +++ b/lib/webroot.js @@ -10,7 +10,7 @@ module.exports.create = function (defaults) { // set,get,remove challenges // getOptions: function () { - return { webrootPath: defaults.webrootPath }; + return defaults; } , set: function (args, domain, token, secret, cb) { diff --git a/package.json b/package.json index 32ec81d..cc94003 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,10 @@ "dependencies": { "cli": "^0.11.1", "homedir": "^0.6.0", - "letsencrypt": "^1.4.3", - "localhost.daplie.com-certificates": "^1.1.2", + "le-acme-core": "^2.0.5", + "le-store-certbot": "^2.0.2", + "letsencrypt": "^2.0.3", + "localhost.daplie.com-certificates": "^1.2.0", "mkdirp": "^0.5.1" } }