diff --git a/README.md b/README.md index 0ae2cdf..2bb9c51 100644 --- a/README.md +++ b/README.md @@ -12,125 +12,180 @@ Works with all ACME (Let's Encrypt) SSL certificate sytles: # Usage +**Global** config: + ```js -var greenlock = require('greenlock'); -var gl = greenlock.create({ - configDir: '~/.config/acme' -, store: require('greenlock-store-fs') -, approveDomains: approveDomains -, ... +greenlock.manager.defaults({ + store: { + module: "greenlock-store-fs", + basePath: "~/.config/greenlock" + } +}); +``` + +**Per-site** config: + +```js +greenlock.add({ + subject: "example.com", + altnames: ["example.com", "www.example.com"], + store: { + module: "greenlock-store-fs", + basePath: "~/.config/greenlock" + } }); ``` # File System -The default file system layout mirrors that of le-store-certbot in order to make transitioning effortless, -in most situations: +The default file system layout mirrors that of certbot (python Let's Encrypt implementation) and +the prior le-store-certbot in order to make transitioning effortless. -``` -acme -├── accounts -│   └── acme-staging-v02.api.letsencrypt.org -│   └── directory -│   └── sites@example.com.json -└── live - ├── example.com - │   ├── bundle.pem - │   ├── cert.pem - │   ├── chain.pem - │   ├── fullchain.pem - │   └── privkey.pem - └── www.example.com - ├── bundle.pem - ├── cert.pem - ├── chain.pem - ├── fullchain.pem - └── privkey.pem +The default structure looks like this: + +```txt +.config +└── greenlock + ├── accounts + │   └── acme-staging-v02.api.letsencrypt.org + │   └── directory + │   └── sites@example.com.json + ├── staging + │ └── (same as live) + └── live + ├── example.com + │   ├── bundle.pem + │   ├── cert.pem + │   ├── chain.pem + │   ├── fullchain.pem + │   └── privkey.pem + └── www.example.com + ├── bundle.pem + ├── cert.pem + ├── chain.pem + ├── fullchain.pem + └── privkey.pem ``` -# Wildcards & AltNames +# Internal Implementation Details -Working with wildcards and multiple altnames requires greenlock >= v2.7 (or v3). +You **DO NOT NEED TO KNOW** these details. -To do so you must return `{ subject: '...', altnames: ['...', ...] }` within the `approveDomains()` callback. +They're provided for the sake of understanding what happens "under the hood" +to help you make better choices "in the seat". -`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 -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). +# Parameters -`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]`. +| parameters | example | notes | +| ----------------- | -------------------------------------------------------- | ---------------- | +| `env` | `staging` or `live` | - | +| `directoryUrl` | `https://acme-staging-v02.api.letsencrypt.org/directory` | - | +| `keypair` | `{ privateKeyPem, privateKeyJwk }` | | +| `account` | `{ id: "an-arbitrary-id" }` | account only | +| `subscriberEmail` | `webhost@example.com` | account only | +| `certificate` | `{ id: "an-arbitrary-id" }` | certificate only | +| `subject` | `example.com` | certificate only | +| `pems` | `{ privkey, cert, chain, issuedAt, expiresAt }` | certificate only | -## Simple Example +### Account Keypair ```js -function approveDomains(opts) { - // Allow only example.com and *.example.com (such as foo.example.com) - - // foo.example.com => *.example.com - var wild = - "*." + - opts.domain - .split(".") - .slice(1) - .join("."); - - if ("example.com" !== opts.domain && "*.example.com" !== wild) { - cb(new Error(opts.domain + " is not allowed")); - } - - var result = { - subject: "example.com", - altnames: ["example.com", "*.example.com"] - }; - return Promise.resolve(result); -} -``` - -## Realistic Example - -```js -function approveDomains(opts, certs, cb) { - var related = getRelated(opts.domain); - if (!related) { - cb(new Error(opts.domain + " is not allowed")); - } - - opts.subject = related.subject; - opts.domains = related.domains; - - cb({ options: opts, certs: certs }); -} -``` - -```js -function getRelated(domain) { - var related; - var wild = - "*." + - domain - .split(".") - .slice(1) - .join("."); - if ( - Object.keys(allAllowedDomains).some(function(k) { - return allAllowedDomains[k].some(function(name) { - if (domain === name || wild === name) { - related = { subject: k, altnames: allAllowedDomains[k] }; - return true; - } - }); - }) - ) { - return related; - } -} -``` - -```js -var allAllowedDomains = { - "example.com": ["example.com", "*.example.com"], - "example.net": ["example.net", "*.example.net"] +accounts.setKeypair = async function({ + env, + basePath, + directoryUrl, + email, + account +}) { + var id = account.id || email; + var serverDir = directoryUrl.replace("https://", ""); +}; +``` + +```js +accounts.checkKeypair = async function({ + env, + basePath, + directoryUrl, + email, + account +}) { + var id = account.id || email; + var serverDir = directoryUrl.replace("https://", ""); + + return { + privateKeyPem, + privateKeyJwk + }; +}; +``` + +### Certificate Keypair + +```js +certificate.setKeypair = async function({ + env, + basePath, + directoryUrl, + subject, + certificate +}) { + var id = account.id || email; + env = env || directoryUrl.replace("https://", ""); +}; +``` + +```js +certificate.checkKeypair = async function({ + env, + basePath, + directoryUrl, + subject, + certificate +}) { + var id = account.id || email; + env = env || directoryUrl.replace("https://", ""); + + return { + privateKeyPem, + privateKeyJwk + }; +}; +``` + +### Certificate PEMs + +```js +certificate.set = async function({ + env, + basePath, + directoryUrl, + subject, + certificate, + pems +}) { + var id = account.id || email; + env = env || directoryUrl.replace("https://", ""); +}; +``` + +```js +certificate.check = async function({ + env, + basePath, + directoryUrl, + subject, + certificate +}) { + var id = account.id || email; + env = env || directoryUrl.replace("https://", ""); + + return { + privkey, + cert, + chain, + issuedAt, + expiresAt + }; }; ``` diff --git a/certificates.js b/certificates.js index a4a5bbf..cfc405a 100644 --- a/certificates.js +++ b/certificates.js @@ -17,7 +17,7 @@ var mkdirpAsync = PromiseA.promisify(require("@root/mkdirp")); // Use certificate.id, or subject, if id hasn't been set, to find a certificate. // Return an object with string PEMs for cert and chain (or null, not undefined) certificates.check = function(opts) { - // { certificate.id, subject, ... } + // { directoryUrl, subject, certificate.id, ... } var id = (opts.certificate && opts.certificate.id) || opts.subject; //console.log('certificates.check for', opts); diff --git a/package.json b/package.json index 52fd16e..ae95389 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "greenlock-store-fs", - "version": "3.2.1", + "version": "3.2.2", "description": "A file-based certificate store for greenlock that supports wildcards.", "homepage": "https://git.rootprojects.org/root/greenlock-store-fs.js", "main": "index.js",