wip: bring up to date with latest v3

This commit is contained in:
AJ ONeal 2019-04-08 00:14:28 -06:00
parent 317dc3853f
commit 879b278d5f
3 changed files with 235 additions and 175 deletions

View File

@ -1,6 +1,15 @@
# le-store-fs # [greenlock-store-fs](https://git.coolaj86.com/coolaj86/greenlock-store-fs.js)
A greenlock keypair and certificate storage strategy with wildcard support (simpler successor to le-store-certbot). | A [Root](https://rootprojects.org) project |
A keypair and certificate storage strategy for Greenlock v2.7+ (and v3).
The (much simpler) successor to le-store-certbot.
Works with all ACME (Let's Encrypt) SSL certificate sytles:
* [x] single domains
* [x] multiple domains (SANs, AltNames)
* [x] wildcards
* [x] private / localhost domains
# Usage # Usage
@ -8,7 +17,7 @@ A greenlock keypair and certificate storage strategy with wildcard support (simp
var greenlock = require('greenlock'); var greenlock = require('greenlock');
var gl = greenlock.create({ var gl = greenlock.create({
configDir: '~/.config/acme' configDir: '~/.config/acme'
, store: require('le-store-fs') , store: require('greenlock-store-fs')
, approveDomains: approveDomains , approveDomains: approveDomains
, ... , ...
}); });
@ -42,16 +51,17 @@ acme
# Wildcards & AltNames # Wildcards & AltNames
Working with wildcards and multiple altnames requires greenlock >= v2.7. Working with wildcards and multiple altnames requires greenlock >= v2.7 (or v3).
To do so you must set `opts.subject` and `opts.domains` within the `approvedomains()` callback. To do so you must return `{ subject: '...', altnames: ['...', ...] }` within the `approveDomains()` callback.
`subject` refers to "the subject of the ssl certificate" as opposed to `domain` which indicates "the domain servername `subject` refers to "the subject of the ssl certificate" as opposed to `domain` which indicates "the domain servername
used in the current request". For single-domain certificates they're always the same, but for multiple-domain used in the current request". For single-domain certificates they're always the same, but for multiple-domain
certificates `subject` must be the name no matter what `domain` is receiving a request. `subject` is used as certificates `subject` must be the name no matter what `domain` is receiving a request. `subject` is used as
part of the name of the file storage path where the certificate will be saved (or retrieved). part of the name of the file storage path where the certificate will be saved (or retrieved).
`domains` should be the list of "altnames" on the certificate, which should include the `subject`. `altnames` should be the list of SubjectAlternativeNames (SANs) on the certificate.
The subject and the first altname must be an exact match: `subject === altnames[0]`.
## Simple Example ## Simple Example
@ -61,14 +71,13 @@ function approveDomains(opts) {
// foo.example.com => *.example.com // foo.example.com => *.example.com
var wild = '*.' + opts.domain.split('.').slice(1).join('.'); var wild = '*.' + opts.domain.split('.').slice(1).join('.');
if ('example.com' !== opts.domain && '*.example.com' !== wild) { if ('example.com' !== opts.domain && '*.example.com' !== wild) {
cb(new Error(opts.domain + " is not allowed")); cb(new Error(opts.domain + " is not allowed"));
} }
opts.subject = 'example.com'; var result = { subject: 'example.com', altnames: [ 'example.com', '*.example.com' ] };
opts.domains = [ 'example.com', '*.example.com' ]; return Promise.resolve(result);
return Promise.resolve(opts);
} }
``` ```

377
index.js
View File

@ -1,250 +1,276 @@
'use strict'; 'use strict';
/*global Promise*/ var os = require("os");
var PromiseA;
var util = require('util');
if (!util.promisify) {
try {
PromiseA = require('bluebird');
util.promisify = PromiseA.promisify;
} catch(e) {
console.error("Your version of node is missing Promise. Please run `npm install --save bluebird` in your project to fix");
process.exit(10);
}
}
if ('undefined' !== typeof Promise) { PromiseA = Promise; }
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var readFileAsync = util.promisify(fs.readFile);
var writeFileAsync = util.promisify(fs.writeFile);
var sfs = require('safe-replace'); var sfs = require('safe-replace');
var mkdirpAsync = util.promisify(require('mkdirp')); var PromiseA = getPromise();
var os = require("os"); var readFileAsync = PromiseA.promisify(fs.readFile);
var writeFileAsync = PromiseA.promisify(fs.writeFile);
// TODO replace with zero-depenency version
var mkdirpAsync = PromiseA.promisify(require('mkdirp'));
// create():
// Your storage plugin may take special options, or it may not. // How Storage Works in Greenlock: High-Level Call Stack
// If it does, document to your users that they must call create() with those options. //
// If you user does not call create(), greenlock will call it for you with the options it has. // nested === skipped if parent succeeds (or has cached result)
// It's kind of stupid, but it's done this way so that it can be more convenient for users to not repeat shared options //
// (such as the config directory), but sometimes configs would clash. I hate having ambiguity, so I may change this in // tls.SNICallback() // TLS connection with SNI kicks of the request
// a future version, but it's very much an issue of "looks cleaner" vs "behaves cleaner". //
// greenlock.approveDomains(opts) // Greenlokc does some housekeeping, checks for a cert in
// // an internal cash, and only asks you to approve new
// // certificate // registration if it doesn't find anything.
// // In `opts` you'll receive `domain` and a few other things.
// // You should return { subject: '...', altnames: ['...'] }
// // Anything returned by approveDomains() will be received
// // by all plugins at all stages
//
// greenlock.store.certificates.check() // Certificate checking happens after approval for several
// // reasons, including preventing duplicate registrations
// // but most importantly because you can dynamically swap the
// // storage plugin right from approveDomains().
// greenlock.store.certificates.checkKeypair() // Check for a keypair associated with the domain
//
// greenlock.store.accounts.check() // Optional. If you need it, look at other Greenlock docs
//
// greenlock.store.accounts.checkKeypair() // Check storage for registered account key
// (opts.generateKeypair||RSA.generateKeypair)() // Generates a new keypair
// greenlock.core.accounts.register() // Registers the keypair as an ACME account
// greenlock.store.accounts.setKeypair() // Saves the keypair of the registered account
// greenlock.store.accounts.set() // Optional. Saves superfluous ACME account metadata
//
// greenlock.core.certificates.register() // Begin certificate registration process & housekeeping
// (opts.generateKeypair||RSA.generateKeypair)() // Generates a new certificate keypair
// greenlock.acme.certificates.register() // Performs the ACME challenge processes
// greenlock.store.certificates.setKeypair() // Saves the keypair for the valid certificate
// greenlock.store.certificates.set() // Saves the valid certificate
////////////////////////////////////////////
// Recap of the high-level overview above //
////////////////////////////////////////////
//
// None of this ever gets called except if there's not a cert already cached.
// That only happens on service boot, and about every 75 days for each cert's renewal.
//
// Therefore, none of this needs to be fast, fancy, or clever
//
// For any type of customization, whatever is set in `approveDomains()` is available everywhere else.
// Either your user calls create with specific options, or greenlock calls it for you with a big options blob
module.exports.create = function (config) { module.exports.create = function (config) {
// This file has been laid out in the order that options are used and calls are made
// SNICallback() // le-sni-auto has a cache
// greenlock.approveDomains()
// // you get opts.domain passed to you from SNI
// // you should set opts.subject as the cert "id" domain
// // you should set opts.domains as all domains on the cert
// // you should set opts.account.id, otherwise opts.email will be used
// greenlock.store.certificates.checkAsync() // on success -> SNI cache, on fail -> checkAccount
// greenlock.store.accounts.checkAsync() // optional (you can always return null)
// greenlock.store.accounts.checkKeypairAsync()
// greenlock.core.RSA.generateKeypair() // TODO double check name
// greenlock.core.accounts.register() // TODO double check name
// greenlock.store.accounts.setKeypairAsync() // TODO make sure this only happens on generate
// greenlock.store.accounts.setAsync() // optional
// greenlock.store.certificates.checkKeypairAsync()
// greenlock.core.RSA.generateKeypair() // TODO double check name
// greenlock.core.certificates.register() // TODO double check name
// greenlock.store.certificates.setKeypairAsync()
// greenlock.store.certificates.setAsync()
// store
// Bear in mind that the only time any of this gets called is on first access after startup, new registration, and // Bear in mind that the only time any of this gets called is on first access after startup, new registration, and
// renewal - so none of this needs to be particularly fast. It may need to be memory efficient, however - if you have // renewal - so none of this needs to be particularly fast. It may need to be memory efficient, however - if you have
// more than 10,000 domains, for example. // more than 10,000 domains, for example.
var store = {};
// options: // basic setup
// var store = { accounts: {}, certificates: {} };
// If your module requires options (i.e. file paths or database urls) you should check what you get from create()
// and copy over the things you'll use into this options object. You should also merge in any defaults for options // For you store.options should probably start empty and get a minimal set of options copied from `config` above.
// that have not been set. This object should not be circular, should not be changed after it is set, and should // Example:
// contain every property that you can use, using falsey JSON-able values like 0, null, false, or '' for "unset" //store.options = {};
// values. //store.options.databaseUrl = config.databaseUrl;
// See the note on create() above.
// In the case of greenlock-store-fs there's a bunch of legacy stuff that goes on, so we just clobber it all on.
// Don't be like greenlock-store-fs (see note above).
store.options = mergeOptions(config); store.options = mergeOptions(config);
// set and check account keypairs and account data
store.accounts = {};
// set and check domain keypairs and domain certificates
store.certificates = {};
// certificates.checkAsync({ subject, ... }):
// Certificates.check
// //
// The first check is that a certificate looked for by its subject (primary domain name). // Use certificate.id, or subject, if id hasn't been set, to find a certificate.
// If that lookup succeeds, then nothing else needs to happen. Otherwise accounts.checkAsync will happen next. // Return an object with string PEMs for cert and chain (or null, not undefined)
// The only input you need to be concerned with is opts.subject (which falls back to opts.domains[0] if not set). store.certificates.check = function (opts) {
// And since this is called after `approveDomains()`, any options that you set there will be available here too. // { certificate.id, subject, ... }
store.certificates.checkAsync = function (opts) {
// { certificate.id, subject, domains, ... }
var id = opts.certificate && opts.certificate.id || opts.subject; var id = opts.certificate && opts.certificate.id || opts.subject;
//console.log('certificates.checkAsync for', opts.domain, opts.subject, opts.domains); //console.log('certificates.check for', opts.certificate, opts.subject);
//console.log(opts); //console.log(opts);
// Just to show that any options set in approveDomains() will be available here // For advanced use cases:
// This just goes to show that any options set in approveDomains() will be available here
// (the same is true for all of the hooks in this file) // (the same is true for all of the hooks in this file)
if (opts.exampleThrowError) { return PromiseA.reject(new Error("You want an error? You got it!")); } if (opts.exampleThrowError) { return PromiseA.reject(new Error("You want an error? You got it!")); }
if (opts.exampleReturnNull) { return PromiseA.resolve(null); } if (opts.exampleReturnNull) { return PromiseA.resolve(null); }
if (opts.exampleReturnCerts) { return PromiseA.resolve(opts.exampleReturnCerts); } if (opts.exampleReturnCerts) { return PromiseA.resolve(opts.exampleReturnCerts); }
// Ignore this first bit, it's just file system template / compatibility stuff
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject); var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
// TODO this shouldn't be necessary here (we should get it from checkKeypairAsync)
var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem'); var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
var certPath = opts.certPath || path.join(liveDir, 'cert.pem'); var certPath = opts.certPath || path.join(liveDir, 'cert.pem');
var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem'); var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem');
return PromiseA.all([ return PromiseA.all([
// all other PEM files are arrangements of these three readFileAsync(tameWild(privkeyPath, id), 'ascii') // 0 // all other PEM types are just
readFileAsync(tameWild(privkeyPath, id), 'ascii') // 0 , readFileAsync(tameWild(certPath, id), 'ascii') // 1 // some arrangement of these 3
, readFileAsync(tameWild(certPath, id), 'ascii') // 1 , readFileAsync(tameWild(chainPath, id), 'ascii') // 2 // (bundle, combined, fullchain, etc)
, readFileAsync(tameWild(chainPath, id), 'ascii') // 2
]).then(function (all) { ]).then(function (all) {
// Success
////////////////////////
// PAY ATTENTION HERE //
////////////////////////
// This is all you have to return: cert, chain
return { return {
privkey: all[0] cert: all[1] // string PEM. the bare cert, half of the concatonated fullchain.pem you need
, cert: all[1] , chain: all[2] // string PEM. the bare chain, the second half of the fullchain.pem
, chain: all[2] , privkey: all[0] // string PEM. optional, allows checkKeypair to be skipped
// When using a database, these should be retrieved too
// These can be useful to store in your database,
// but otherwise they're easy to derive from the cert.
// (when not available they'll be generated from cert-info) // (when not available they'll be generated from cert-info)
//, subject: certinfo.subject //, subject: certinfo.subject // string domain name
//, altnames: certinfo.altnames //, altnames: certinfo.altnames // array of domain name strings
//, issuedAt: certinfo.issuedAt // a.k.a. NotBefore //, issuedAt: certinfo.issuedAt // number in ms (a.k.a. NotBefore)
//, expiresAt: certinfo.expiresAt // a.k.a. NotAfter //, expiresAt: certinfo.expiresAt // number in ms (a.k.a. NotAfter)
}; };
}).catch(function (err) { }).catch(function (err) {
// Non-success // Treat non-exceptional failures as null returns (not undefined)
if ('ENOENT' === err.code) { return null; } if ('ENOENT' === err.code) { return null; }
// Failure throw err; // True exceptions should be thrown
throw err;
}); });
}; };
// accounts.checkAsync({ accountId, email, [...] }): // Optional
//
// This is where you promise an account corresponding to the given the email and ID. All options set in
// approveDomains() are also available. You can ignore them unless your implementation is using them in some way.
//
// Since accounts are based on public key, the act of creating a new account or returning an existing account
// are the same in regards to the API and so we don't really need to store the account id or retrieve it.
// This method only needs to be implemented if you need it for your own purposes
//
// On Success: Promise.resolve({ id, keypair, ... }) - an id and, for backwards compatibility, the abstract keypair
// On Failure: Promise.resolve(null) - do not return undefined, do not throw, do not reject
// On Error: Promise.reject(new Error("something descriptive for the user"))
store.accounts.checkAsync = function (/*opts*/) {
//var id = opts.account.id || 'single-user';
//console.log('accounts.checkAsync for', id);
return PromiseA.resolve(null);
};
// accounts.checkKeypairAsync({ email, ... }):
// Implement if you need the ACME account metadata elsewhere in the chain of events
//store.accounts.check = function (opts) {
// console.log('accounts.check for', opts.account, opts.email);
// return PromiseA.resolve(null);
//};
// Accounts.checkKeypair
// //
// Same rules as above apply, except for the private key of the account, not the account object itself. // Use account.id, or email, if id hasn't been set, to find an account keypair.
// // Return an object with string privateKeyPem and/or object privateKeyJwk (or null, not undefined)
// On Success: Promise.resolve({ ... }) - the abstract object representing the keypair store.accounts.checkKeypair = function (opts) {
// On Failure: Promise.resolve(null) - do not return undefined, do not throw, do not reject var id = opts.account.id || opts.email || 'single-user';
// On Error: Promise.reject(new Error("something descriptive for the user")) //console.log('accounts.checkKeypair for', id);
store.accounts.checkKeypairAsync = function (opts) {
var id = opts.account.id || 'single-user';
//console.log('accounts.checkKeypairAsync for', id);
if (!opts.account.id) { return PromiseA.reject(new Error("'account.id' should have been set in approveDomains()")); }
var pathname = path.join(tameWild(opts.accountsDir, opts.subject), sanitizeFilename(id) + '.json'); var pathname = path.join(tameWild(opts.accountsDir, opts.subject), sanitizeFilename(id) + '.json');
return readFileAsync(tameWild(pathname, opts.subject), 'utf8').then(function (blob) { return readFileAsync(tameWild(pathname, opts.subject), 'utf8').then(function (blob) {
// keypair is an opaque object that should be treated as blob // keypair can treated as an opaque object and just passed along,
return JSON.parse(blob); // but just to show you what it is...
var keypair = JSON.parse(blob);
return {
privateKeyPem: keypair.privateKeyPem // string PEM private key
, privateKeyJwk: keypair.privateKeyJwk // object JWK private key
};
}).catch(function (err) { }).catch(function (err) {
if ('ENOENT' === err.code) { return null; } if ('ENOENT' === err.code) { return null; }
throw err; throw err;
}); });
}; };
// accounts.setKeypairAsync({ keypair, email, ... }):
// Accounts.setKeypair({ account, email, keypair, ... }):
// //
// The keypair details (RSA, ECDSA, etc) are chosen either by the greenlock defaults, global user defaults, // Use account.id (or email if no id is present) to save an account keypair
// or whatever you set in approveDomains(). This is called *after* the account is successfully created. // Return null (not undefined) on success, or throw on error
// store.accounts.setKeypair = function (opts) {
// On Success: Promise.resolve(null) - just knowing the operation is successful will do //console.log('accounts.setKeypair for', opts.account, opts.email, opts.keypair);
// On Error: Promise.reject(new Error("something descriptive for the user"))
store.accounts.setKeypairAsync = function (opts, keypair) {
var id = opts.account.id || 'single-user'; var id = opts.account.id || 'single-user';
//console.log('accounts.setKeypairAsync for', id);
keypair = opts.keypair || keypair; // you can just treat the keypair as opaque and save and retrieve it as JSON
if (!opts.account.id) { return PromiseA.reject(new Error("'account.id' should have been set in approveDomains()")); } var keyblob = JSON.stringify({
privateKeyPem: opts.keypair.privateKeyPem // string PEM
, privateKeyJwk: opts.keypair.privateKeyJwk // object JWK
});
// Ignore.
// Just implementation specific details here.
return mkdirpAsync(tameWild(opts.accountsDir, opts.subject)).then(function () { return mkdirpAsync(tameWild(opts.accountsDir, opts.subject)).then(function () {
// keypair is an opaque object that should be treated as blob
var pathname = tameWild(path.join(opts.accountsDir, sanitizeFilename(id) + '.json'), opts.subject); var pathname = tameWild(path.join(opts.accountsDir, sanitizeFilename(id) + '.json'), opts.subject);
return writeFileAsync(tameWild(pathname, opts.subject), JSON.stringify(keypair), 'utf8'); return writeFileAsync(tameWild(pathname, opts.subject), keyblob, 'utf8');
}).then(function () {
// This is your job: return null, not undefined
return null;
}); });
}; };
// accounts.setAsync({ account, keypair, email, ... }):
//
// The account details, from ACME, if everything is successful. Unless you need to do something with those account
// details, this implementation can remain empty.
//
// On Success: Promise.resolve(null||{ id }) - do not return undefined, do not throw, do not reject
// On Error: Promise.reject(new Error("something descriptive for the user"))
store.accounts.setAsync = function (/*opts*/) {
//receipt = opts.receipt || receipt;
//console.log('account.setAsync:', receipt);
return PromiseA.resolve(null);
};
// certificates.checkKeypairAsync({ subject, ... }):
// Implement if you need the ACME account metadata elsewhere in the chain of events
//store.accounts.set = function (opts) {
// console.log('account.set:', opts.account, opts.email, opts.receipt);
// return PromiseA.resolve(null);
//};
// Certificates.checkKeypair
// //
// Same rules as certificates.checkAsync apply, except for the private key of the certificate, not the public // Use certificate.kid, certificate.id, or subject to find a certificate keypair
// certificate itself (similar to accounts.checkKeyPairAsync, but for certs). // Return an object with string privateKeyPem and/or object privateKeyJwk (or null, not undefined)
store.certificates.checkKeypairAsync = function (opts) { store.certificates.checkKeypair = function (opts) {
//console.log('certificates.checkKeypairAsync:'); //console.log('certificates.checkKeypair:', opts.certificate, opts.subject);
// Ignore this. It's just special stuff for file system compat with the old le-store-certbot
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject); var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem'); var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
return readFileAsync(tameWild(privkeyPath, opts.subject), 'ascii').then(function (key) { return readFileAsync(tameWild(privkeyPath, opts.subject), 'ascii').then(function (key) {
// keypair is normally an opaque object, but here it's a pem for the filesystem ////////////////////////
return { privateKeyPem: key }; // PAY ATTENTION HERE //
////////////////////////
return {
privateKeyPem: key // In this case we only saved privateKeyPem, so we only return it
//privateKeyJwk: null // (but it's fine, just different encodings of the same thing)
};
}).catch(function (err) { }).catch(function (err) {
if ('ENOENT' === err.code) { return null; } if ('ENOENT' === err.code) { return null; }
throw err; throw err;
}); });
}; };
// certificates.setKeypairAsync({ domain, keypair, ... }):
// Certificates.setKeypair({ certificate, subject, keypair, ... }):
// //
// Same as accounts.setKeypairAsync, but by domains rather than email / accountId // Use certificate.kid (or certificate.id or subject if no kid is present) to find a certificate keypair
store.certificates.setKeypairAsync = function (opts, keypair) { // Return null (not undefined) on success, or throw on error
keypair = opts.keypair || keypair; store.certificates.setKeypair = function (opts) {
var keypair = opts.keypair || keypair;
// Ignore.
// Just specific implementation details.
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject); var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem'); var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
// keypair is normally an opaque object, but here it's a PEM for the FS
return mkdirpAsync(tameWild(path.dirname(privkeyPath), opts.subject)).then(function () { return mkdirpAsync(tameWild(path.dirname(privkeyPath), opts.subject)).then(function () {
// keypair is normally an opaque object, but here it's a PEM for the FS (for things like Apache and Nginx)
return writeFileAsync(tameWild(privkeyPath, opts.subject), keypair.privateKeyPem, 'ascii').then(function () { return writeFileAsync(tameWild(privkeyPath, opts.subject), keypair.privateKeyPem, 'ascii').then(function () {
return null; return null;
}); });
}); });
}; };
// certificates.setAsync({ domain, certs, ... }):
// Certificates.set({ subject, pems, ... }):
// //
// This is where certificates are set, as well as certinfo // Use certificate.id (or subject if no ki is present) to save a certificate
store.certificates.setAsync = function (opts) { // Return null (not undefined) on success, or throw on error
//console.log('certificates.setAsync:'); store.certificates.set = function (opts) {
//console.log(opts.domain, '<=', opts.subject); //console.log('certificates.set:', opts.subject, opts.pems);
var pems = { var pems = {
privkey: opts.pems.privkey cert: opts.pems.cert // string PEM the first half of the concatonated fullchain.pem cert
, cert: opts.pems.cert , chain: opts.pems.chain // string PEM the second half (yes, you need this too)
, chain: opts.pems.chain , privkey: opts.pems.privkey // Ignore. string PEM, useful if you have to create bundle.pem
}; };
// Ignore
// Just implementation specific details (writing lots of combinatons of files)
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject); var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
var certPath = opts.certPath || path.join(liveDir, 'cert.pem'); var certPath = opts.certPath || path.join(liveDir, 'cert.pem');
var fullchainPath = opts.fullchainPath || path.join(liveDir, 'fullchain.pem'); var fullchainPath = opts.fullchainPath || path.join(liveDir, 'fullchain.pem');
var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem'); var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem');
//var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
var bundlePath = opts.bundlePath || path.join(liveDir, 'bundle.pem'); var bundlePath = opts.bundlePath || path.join(liveDir, 'bundle.pem');
return mkdirpAsync(path.dirname(tameWild(certPath, opts.subject))).then(function () { return mkdirpAsync(path.dirname(tameWild(certPath, opts.subject))).then(function () {
return mkdirpAsync(path.dirname(tameWild(chainPath, opts.subject))).then(function () { return mkdirpAsync(path.dirname(tameWild(chainPath, opts.subject))).then(function () {
return mkdirpAsync(path.dirname(tameWild(fullchainPath, opts.subject))).then(function () { return mkdirpAsync(path.dirname(tameWild(fullchainPath, opts.subject))).then(function () {
@ -263,13 +289,21 @@ module.exports.create = function (config) {
}); });
}); });
}).then(function () { }).then(function () {
// That's your job: return null
return null; return null;
}); });
}; };
return store; return store;
}; };
///////////////////////////////////////////////////////////////////////////////
// Ignore //
///////////////////////////////////////////////////////////////////////////////
//
// Everything below this line is just implementation specific
var defaults = { var defaults = {
configDir: path.join(os.homedir(), 'acme', 'etc') configDir: path.join(os.homedir(), 'acme', 'etc')
@ -307,3 +341,20 @@ function tameWild(path, wild) {
var tame = wild.replace(/\*/g, '_'); var tame = wild.replace(/\*/g, '_');
return path.replace(wild, tame); return path.replace(wild, tame);
} }
function getPromise() {
var util = require('util');
var PromiseA;
if (util.promisify && global.Promise) {
PromiseA = global.Promise;
PromiseA.promisify = util.promisify;
} else {
try {
PromiseA = require('bluebird');
} catch(e) {
console.error("Your version of node is missing Promise. Please run `npm install --save bluebird` in your project to fix");
process.exit(10);
}
}
return PromiseA;
}

View File

@ -1,6 +1,6 @@
{ {
"name": "le-store-fs", "name": "greenlock-store-fs",
"version": "1.0.3", "version": "3.0.0",
"description": "A file-based certificate store for greenlock that supports wildcards.", "description": "A file-based certificate store for greenlock that supports wildcards.",
"homepage": "https://git.coolaj86.com/coolaj86/le-store-fs.js", "homepage": "https://git.coolaj86.com/coolaj86/le-store-fs.js",
"main": "index.js", "main": "index.js",