Compare commits
	
		
			No commits in common. "master" and "v3.2.0" have entirely different histories.
		
	
	
		
	
		
| @ -1,7 +0,0 @@ | ||||
| { | ||||
|   "bracketSpacing": true, | ||||
|   "printWidth": 80, | ||||
|   "tabWidth": 4, | ||||
|   "trailingComma": "none", | ||||
|   "useTabs": false | ||||
| } | ||||
							
								
								
									
										249
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										249
									
								
								README.md
									
									
									
									
									
								
							| @ -4,192 +4,115 @@ 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 | ||||
| * [x] single domains | ||||
| * [x] multiple domains (SANs, AltNames) | ||||
| * [x] wildcards | ||||
| * [x] private / localhost domains | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| **Global** config: | ||||
| 
 | ||||
| ```js | ||||
| 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" | ||||
|     } | ||||
| var greenlock = require('greenlock'); | ||||
| var gl = greenlock.create({ | ||||
|   configDir: '~/.config/acme' | ||||
| , store: require('greenlock-store-fs') | ||||
| , approveDomains: approveDomains | ||||
| , ... | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| # File System | ||||
| 
 | ||||
| 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. | ||||
| The default file system layout mirrors that of le-store-certbot in order to make transitioning effortless, | ||||
| in most situations: | ||||
| 
 | ||||
| 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 | ||||
| ``` | ||||
| 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 | ||||
| ``` | ||||
| 
 | ||||
| # Internal Implementation Details | ||||
| # Wildcards & AltNames | ||||
| 
 | ||||
| You **DO NOT NEED TO KNOW** these details. | ||||
| Working with wildcards and multiple altnames requires greenlock >= v2.7 (or v3). | ||||
| 
 | ||||
| They're provided for the sake of understanding what happens "under the hood" | ||||
| to help you make better choices "in the seat". | ||||
| To do so you must return `{ subject: '...', altnames: ['...', ...] }` within the `approveDomains()` callback. | ||||
| 
 | ||||
| **Note**: The actual code could stand to be tidied up. | ||||
| It does need to continue to support Greenlock v2 for a few more months, | ||||
| so I didn't rip out the old v1 -> v2 -> v3 cruft yet. | ||||
| `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 | | ||||
| 
 | ||||
| ### Account Keypair | ||||
| ## Simple Example | ||||
| 
 | ||||
| ```js | ||||
| accounts.setKeypair = async function({ | ||||
|     env, | ||||
|     basePath, | ||||
|     directoryUrl, | ||||
|     email, | ||||
|     account | ||||
| }) { | ||||
|     var id = account.id || email; | ||||
|     var serverDir = directoryUrl.replace("https://", ""); | ||||
| }; | ||||
| 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 | ||||
| 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://", ""); | ||||
| }; | ||||
| 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 | ||||
| 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 | ||||
|     }; | ||||
| }; | ||||
| var allAllowedDomains = { | ||||
|   'example.com': ['example.com', '*.example.com'] | ||||
| , 'example.net': ['example.net', '*.example.net'] | ||||
| } | ||||
| ``` | ||||
|  | ||||
							
								
								
									
										118
									
								
								accounts.js
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								accounts.js
									
									
									
									
									
								
							| @ -1,15 +1,15 @@ | ||||
| "use strict"; | ||||
| 'use strict'; | ||||
| 
 | ||||
| var accounts = module.exports; | ||||
| var store = accounts; | ||||
| var U = require("./utils.js"); | ||||
| var U = require('./utils.js'); | ||||
| 
 | ||||
| var fs = require("fs"); | ||||
| var path = require("path"); | ||||
| var PromiseA = require("./promise.js"); | ||||
| var fs = require('fs'); | ||||
| var path = require('path'); | ||||
| var PromiseA = require('./promise.js'); | ||||
| var readFileAsync = PromiseA.promisify(fs.readFile); | ||||
| var writeFileAsync = PromiseA.promisify(fs.writeFile); | ||||
| var mkdirpAsync = PromiseA.promisify(require("@root/mkdirp")); | ||||
| var mkdirpAsync = PromiseA.promisify(require('@root/mkdirp')); | ||||
| 
 | ||||
| // Implement if you need the ACME account metadata elsewhere in the chain of events
 | ||||
| //store.accounts.check = function (opts) {
 | ||||
| @ -22,23 +22,23 @@ var mkdirpAsync = PromiseA.promisify(require("@root/mkdirp")); | ||||
| // 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)
 | ||||
| accounts.checkKeypair = function(opts) { | ||||
|     var id = | ||||
|         (opts.account && opts.account.id) || | ||||
|         (opts.subscriberEmail || opts.email) || | ||||
|         "single-user"; | ||||
|     //console.log('accounts.checkKeypair for', id);
 | ||||
| 	var id = | ||||
| 		(opts.account && opts.account.id) || | ||||
| 		(opts.subscriberEmail || opts.email) || | ||||
| 		'single-user'; | ||||
| 	//console.log('accounts.checkKeypair for', id);
 | ||||
| 
 | ||||
|     var pathname = path.join( | ||||
|         accountsDir(store, opts), | ||||
|         sanitizeFilename(id) + ".json" | ||||
|     ); | ||||
|     return readFileAsync(U._tameWild(pathname, opts.subject), "utf8") | ||||
|         .then(function(blob) { | ||||
|             // keypair can treated as an opaque object and just passed along,
 | ||||
|             // but just to show you what it is...
 | ||||
|             var keypair = JSON.parse(blob); | ||||
|             return keypair; | ||||
|             /* | ||||
| 	var pathname = path.join( | ||||
| 		accountsDir(store, opts), | ||||
| 		sanitizeFilename(id) + '.json' | ||||
| 	); | ||||
| 	return readFileAsync(U._tameWild(pathname, opts.subject), 'utf8') | ||||
| 		.then(function(blob) { | ||||
| 			// keypair can treated as an opaque object and just passed along,
 | ||||
| 			// but just to show you what it is...
 | ||||
| 			var keypair = JSON.parse(blob); | ||||
| 			return keypair; | ||||
| 			/* | ||||
|       { | ||||
| 				privateKeyPem: keypair.privateKeyPem, // string PEM private key
 | ||||
| 				privateKeyJwk: keypair.privateKeyJwk, // object JWK private key
 | ||||
| @ -46,13 +46,13 @@ accounts.checkKeypair = function(opts) { | ||||
| 				public: keypair.public | ||||
| 			}; | ||||
|       */ | ||||
|         }) | ||||
|         .catch(function(err) { | ||||
|             if ("ENOENT" === err.code) { | ||||
|                 return null; | ||||
|             } | ||||
|             throw err; | ||||
|         }); | ||||
| 		}) | ||||
| 		.catch(function(err) { | ||||
| 			if ('ENOENT' === err.code) { | ||||
| 				return null; | ||||
| 			} | ||||
| 			throw err; | ||||
| 		}); | ||||
| }; | ||||
| 
 | ||||
| // Accounts.setKeypair({ account, email, keypair, ... }):
 | ||||
| @ -60,12 +60,12 @@ accounts.checkKeypair = function(opts) { | ||||
| // Use account.id (or email if no id is present) to save an account keypair
 | ||||
| // Return null (not undefined) on success, or throw on error
 | ||||
| accounts.setKeypair = function(opts) { | ||||
|     //console.log('accounts.setKeypair for', opts.account, opts.email, opts.keypair);
 | ||||
|     var id = opts.account.id || opts.email || "single-user"; | ||||
| 	//console.log('accounts.setKeypair for', opts.account, opts.email, opts.keypair);
 | ||||
| 	var id = opts.account.id || opts.email || 'single-user'; | ||||
| 
 | ||||
|     // you can just treat the keypair as opaque and save and retrieve it as JSON
 | ||||
|     var keyblob = JSON.stringify(opts.keypair); | ||||
|     /* | ||||
| 	// you can just treat the keypair as opaque and save and retrieve it as JSON
 | ||||
| 	var keyblob = JSON.stringify(opts.keypair); | ||||
| 	/* | ||||
| 	var keyblob = JSON.stringify({ | ||||
| 		privateKeyPem: opts.keypair.privateKeyPem, // string PEM
 | ||||
| 		privateKeyJwk: opts.keypair.privateKeyJwk, // object JWK
 | ||||
| @ -73,24 +73,24 @@ accounts.setKeypair = function(opts) { | ||||
| 	}); | ||||
|   */ | ||||
| 
 | ||||
|     // Ignore.
 | ||||
|     // Just implementation specific details here.
 | ||||
|     return mkdirpAsync(accountsDir(store, opts)) | ||||
|         .then(function() { | ||||
|             var pathname = path.join( | ||||
|                 accountsDir(store, opts), | ||||
|                 sanitizeFilename(id) + ".json" | ||||
|             ); | ||||
|             return writeFileAsync( | ||||
|                 U._tameWild(pathname, opts.subject), | ||||
|                 keyblob, | ||||
|                 "utf8" | ||||
|             ); | ||||
|         }) | ||||
|         .then(function() { | ||||
|             // This is your job: return null, not undefined
 | ||||
|             return null; | ||||
|         }); | ||||
| 	// Ignore.
 | ||||
| 	// Just implementation specific details here.
 | ||||
| 	return mkdirpAsync(accountsDir(store, opts)) | ||||
| 		.then(function() { | ||||
| 			var pathname = path.join( | ||||
| 				accountsDir(store, opts), | ||||
| 				sanitizeFilename(id) + '.json' | ||||
| 			); | ||||
| 			return writeFileAsync( | ||||
| 				U._tameWild(pathname, opts.subject), | ||||
| 				keyblob, | ||||
| 				'utf8' | ||||
| 			); | ||||
| 		}) | ||||
| 		.then(function() { | ||||
| 			// This is your job: return null, not undefined
 | ||||
| 			return null; | ||||
| 		}); | ||||
| }; | ||||
| 
 | ||||
| // Implement if you need the ACME account metadata elsewhere in the chain of events
 | ||||
| @ -100,14 +100,14 @@ accounts.setKeypair = function(opts) { | ||||
| //};
 | ||||
| 
 | ||||
| function sanitizeFilename(id) { | ||||
|     return id.replace(/(\.\.)|\\|\//g, "_").replace(/[^!-~]/g, "_"); | ||||
| 	return id.replace(/(\.\.)|\\|\//g, '_').replace(/[^!-~]/g, '_'); | ||||
| } | ||||
| 
 | ||||
| function accountsDir(store, opts) { | ||||
|     var dir = U._tpl( | ||||
|         store, | ||||
|         opts, | ||||
|         opts.accountsDir || store.options.accountsDir | ||||
|     ); | ||||
|     return U._tameWild(dir, opts.subject || ""); | ||||
| 	var dir = U._tpl( | ||||
| 		store, | ||||
| 		opts, | ||||
| 		opts.accountsDir || store.options.accountsDir | ||||
| 	); | ||||
| 	return U._tameWild(dir, opts.subject || ''); | ||||
| } | ||||
|  | ||||
							
								
								
									
										412
									
								
								certificates.js
									
									
									
									
									
								
							
							
						
						
									
										412
									
								
								certificates.js
									
									
									
									
									
								
							| @ -1,70 +1,70 @@ | ||||
| "use strict"; | ||||
| 'use strict'; | ||||
| 
 | ||||
| var certificates = module.exports; | ||||
| var store = certificates; | ||||
| var U = require("./utils.js"); | ||||
| var U = require('./utils.js'); | ||||
| 
 | ||||
| var fs = require("fs"); | ||||
| var path = require("path"); | ||||
| var PromiseA = require("./promise.js"); | ||||
| var sfs = require("safe-replace"); | ||||
| var fs = require('fs'); | ||||
| var path = require('path'); | ||||
| var PromiseA = require('./promise.js'); | ||||
| var sfs = require('safe-replace'); | ||||
| var readFileAsync = PromiseA.promisify(fs.readFile); | ||||
| var writeFileAsync = PromiseA.promisify(fs.writeFile); | ||||
| var mkdirpAsync = PromiseA.promisify(require("@root/mkdirp")); | ||||
| var mkdirpAsync = PromiseA.promisify(require('@root/mkdirp')); | ||||
| 
 | ||||
| // Certificates.check
 | ||||
| //
 | ||||
| // 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) { | ||||
|     // { directoryUrl, subject, certificate.id, ... }
 | ||||
|     var id = (opts.certificate && opts.certificate.id) || opts.subject; | ||||
|     //console.log('certificates.check for', opts);
 | ||||
| 	// { certificate.id, subject, ... }
 | ||||
| 	var id = (opts.certificate && opts.certificate.id) || opts.subject; | ||||
| 	//console.log('certificates.check for', opts);
 | ||||
| 
 | ||||
|     // 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)
 | ||||
|     if (opts.exampleThrowError) { | ||||
|         return Promise.reject(new Error("You want an error? You got it!")); | ||||
|     } | ||||
|     if (opts.exampleReturnNull) { | ||||
|         return Promise.resolve(null); | ||||
|     } | ||||
|     if (opts.exampleReturnCerts) { | ||||
|         return Promise.resolve(opts.exampleReturnCerts); | ||||
|     } | ||||
| 	// 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)
 | ||||
| 	if (opts.exampleThrowError) { | ||||
| 		return Promise.reject(new Error('You want an error? You got it!')); | ||||
| 	} | ||||
| 	if (opts.exampleReturnNull) { | ||||
| 		return Promise.resolve(null); | ||||
| 	} | ||||
| 	if (opts.exampleReturnCerts) { | ||||
| 		return Promise.resolve(opts.exampleReturnCerts); | ||||
| 	} | ||||
| 
 | ||||
|     return Promise.all([ | ||||
|         readFileAsync(U._tameWild(privkeyPath(store, opts), id), "ascii"), // 0 // all other PEM types are just
 | ||||
|         readFileAsync(U._tameWild(certPath(store, opts), id), "ascii"), // 1 // some arrangement of these 3
 | ||||
|         readFileAsync(U._tameWild(chainPath(store, opts), id), "ascii") // 2 // (bundle, combined, fullchain, etc)
 | ||||
|     ]) | ||||
|         .then(function(all) { | ||||
|             ////////////////////////
 | ||||
|             // PAY ATTENTION HERE //
 | ||||
|             ////////////////////////
 | ||||
|             // This is all you have to return: cert, chain
 | ||||
|             return { | ||||
|                 cert: all[1], // string PEM. the bare cert, half of the concatonated fullchain.pem you need
 | ||||
|                 chain: all[2], // string PEM. the bare chain, the second half of the fullchain.pem
 | ||||
|                 privkey: all[0] // string PEM. optional, allows checkKeypair to be skipped
 | ||||
| 	return Promise.all([ | ||||
| 		readFileAsync(U._tameWild(privkeyPath(store, opts), id), 'ascii'), // 0 // all other PEM types are just
 | ||||
| 		readFileAsync(U._tameWild(certPath(store, opts), id), 'ascii'), // 1 // some arrangement of these 3
 | ||||
| 		readFileAsync(U._tameWild(chainPath(store, opts), id), 'ascii') // 2 // (bundle, combined, fullchain, etc)
 | ||||
| 	]) | ||||
| 		.then(function(all) { | ||||
| 			////////////////////////
 | ||||
| 			// PAY ATTENTION HERE //
 | ||||
| 			////////////////////////
 | ||||
| 			// This is all you have to return: cert, chain
 | ||||
| 			return { | ||||
| 				cert: all[1], // string PEM. the bare cert, half of the concatonated fullchain.pem you need
 | ||||
| 				chain: all[2], // string PEM. the bare chain, the second half of the fullchain.pem
 | ||||
| 				privkey: all[0] // string PEM. optional, allows checkKeypair to be skipped
 | ||||
| 
 | ||||
|                 // 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)
 | ||||
|                 //, subject: certinfo.subject     // string domain name
 | ||||
|                 //, altnames: certinfo.altnames   // array of domain name strings
 | ||||
|                 //, issuedAt: certinfo.issuedAt   // number in ms (a.k.a. NotBefore)
 | ||||
|                 //, expiresAt: certinfo.expiresAt // number in ms (a.k.a. NotAfter)
 | ||||
|             }; | ||||
|         }) | ||||
|         .catch(function(err) { | ||||
|             // Treat non-exceptional failures as null returns (not undefined)
 | ||||
|             if ("ENOENT" === err.code) { | ||||
|                 return null; | ||||
|             } | ||||
|             throw err; // True exceptions should be thrown
 | ||||
|         }); | ||||
| 				// 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)
 | ||||
| 				//, subject: certinfo.subject     // string domain name
 | ||||
| 				//, altnames: certinfo.altnames   // array of domain name strings
 | ||||
| 				//, issuedAt: certinfo.issuedAt   // number in ms (a.k.a. NotBefore)
 | ||||
| 				//, expiresAt: certinfo.expiresAt // number in ms (a.k.a. NotAfter)
 | ||||
| 			}; | ||||
| 		}) | ||||
| 		.catch(function(err) { | ||||
| 			// Treat non-exceptional failures as null returns (not undefined)
 | ||||
| 			if ('ENOENT' === err.code) { | ||||
| 				return null; | ||||
| 			} | ||||
| 			throw err; // True exceptions should be thrown
 | ||||
| 		}); | ||||
| }; | ||||
| 
 | ||||
| // Certificates.checkKeypair
 | ||||
| @ -72,27 +72,27 @@ certificates.check = function(opts) { | ||||
| // Use certificate.kid, certificate.id, or subject to find a certificate keypair
 | ||||
| // Return an object with string privateKeyPem and/or object privateKeyJwk (or null, not undefined)
 | ||||
| certificates.checkKeypair = function(opts) { | ||||
|     //console.log('certificates.checkKeypair:', opts);
 | ||||
| 	//console.log('certificates.checkKeypair:', opts);
 | ||||
| 
 | ||||
|     return readFileAsync( | ||||
|         U._tameWild(privkeyPath(store, opts), opts.subject), | ||||
|         "ascii" | ||||
|     ) | ||||
|         .then(function(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) { | ||||
|             if ("ENOENT" === err.code) { | ||||
|                 return null; | ||||
|             } | ||||
|             throw err; | ||||
|         }); | ||||
| 	return readFileAsync( | ||||
| 		U._tameWild(privkeyPath(store, opts), opts.subject), | ||||
| 		'ascii' | ||||
| 	) | ||||
| 		.then(function(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) { | ||||
| 			if ('ENOENT' === err.code) { | ||||
| 				return null; | ||||
| 			} | ||||
| 			throw err; | ||||
| 		}); | ||||
| }; | ||||
| 
 | ||||
| // Certificates.setKeypair({ certificate, subject, keypair, ... }):
 | ||||
| @ -100,22 +100,22 @@ certificates.checkKeypair = function(opts) { | ||||
| // Use certificate.kid (or certificate.id or subject if no kid is present) to find a certificate keypair
 | ||||
| // Return null (not undefined) on success, or throw on error
 | ||||
| certificates.setKeypair = function(opts) { | ||||
|     var keypair = opts.keypair || keypair; | ||||
| 	var keypair = opts.keypair || keypair; | ||||
| 
 | ||||
|     // Ignore.
 | ||||
|     // Just specific implementation details.
 | ||||
|     return mkdirpAsync( | ||||
|         U._tameWild(path.dirname(privkeyPath(store, opts)), 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( | ||||
|             U._tameWild(privkeyPath(store, opts), opts.subject), | ||||
|             keypair.privateKeyPem, | ||||
|             "ascii" | ||||
|         ).then(function() { | ||||
|             return null; | ||||
|         }); | ||||
|     }); | ||||
| 	// Ignore.
 | ||||
| 	// Just specific implementation details.
 | ||||
| 	return mkdirpAsync( | ||||
| 		U._tameWild(path.dirname(privkeyPath(store, opts)), 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( | ||||
| 			U._tameWild(privkeyPath(store, opts), opts.subject), | ||||
| 			keypair.privateKeyPem, | ||||
| 			'ascii' | ||||
| 		).then(function() { | ||||
| 			return null; | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| // Certificates.set({ subject, pems, ... }):
 | ||||
| @ -123,143 +123,143 @@ certificates.setKeypair = function(opts) { | ||||
| // Use certificate.id (or subject if no ki is present) to save a certificate
 | ||||
| // Return null (not undefined) on success, or throw on error
 | ||||
| certificates.set = function(opts) { | ||||
|     //console.log('certificates.set:', opts);
 | ||||
|     var pems = { | ||||
|         cert: opts.pems.cert, // string PEM the first half of the concatonated fullchain.pem cert
 | ||||
|         chain: opts.pems.chain, // string PEM the second half (yes, you need this too)
 | ||||
|         privkey: opts.pems.privkey // Ignore. string PEM, useful if you have to create bundle.pem
 | ||||
|     }; | ||||
| 	//console.log('certificates.set:', opts);
 | ||||
| 	var pems = { | ||||
| 		cert: opts.pems.cert, // string PEM the first half of the concatonated fullchain.pem cert
 | ||||
| 		chain: opts.pems.chain, // string PEM the second half (yes, you need this too)
 | ||||
| 		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)
 | ||||
|     return mkdirpAsync(path.dirname(certPath(store, opts))) | ||||
|         .then(function() { | ||||
|             return mkdirpAsync( | ||||
|                 path.dirname(U._tameWild(chainPath(store, opts), opts.subject)) | ||||
|             ).then(function() { | ||||
|                 return mkdirpAsync( | ||||
|                     path.dirname( | ||||
|                         U._tameWild(fullchainPath(store, opts), opts.subject) | ||||
|                     ) | ||||
|                 ).then(function() { | ||||
|                     return mkdirpAsync( | ||||
|                         path.dirname( | ||||
|                             U._tameWild(bundlePath(store, opts), opts.subject) | ||||
|                         ) | ||||
|                     ).then(function() { | ||||
|                         var fullchainPem = [ | ||||
|                             pems.cert.trim() + "\n", | ||||
|                             pems.chain.trim() + "\n" | ||||
|                         ].join("\n"); // for Apache, Nginx, etc
 | ||||
|                         var bundlePem = [ | ||||
|                             pems.privkey, | ||||
|                             pems.cert, | ||||
|                             pems.chain | ||||
|                         ].join("\n"); // for HAProxy
 | ||||
|                         return PromiseA.all([ | ||||
|                             sfs.writeFileAsync( | ||||
|                                 U._tameWild( | ||||
|                                     certPath(store, opts), | ||||
|                                     opts.subject | ||||
|                                 ), | ||||
|                                 pems.cert, | ||||
|                                 "ascii" | ||||
|                             ), | ||||
|                             sfs.writeFileAsync( | ||||
|                                 U._tameWild( | ||||
|                                     chainPath(store, opts), | ||||
|                                     opts.subject | ||||
|                                 ), | ||||
|                                 pems.chain, | ||||
|                                 "ascii" | ||||
|                             ), | ||||
|                             // Most web servers need these two
 | ||||
|                             sfs.writeFileAsync( | ||||
|                                 U._tameWild( | ||||
|                                     fullchainPath(store, opts), | ||||
|                                     opts.subject | ||||
|                                 ), | ||||
|                                 fullchainPem, | ||||
|                                 "ascii" | ||||
|                             ), | ||||
|                             // HAProxy needs "bundle.pem" aka "combined.pem"
 | ||||
|                             sfs.writeFileAsync( | ||||
|                                 U._tameWild( | ||||
|                                     bundlePath(store, opts), | ||||
|                                     opts.subject | ||||
|                                 ), | ||||
|                                 bundlePem, | ||||
|                                 "ascii" | ||||
|                             ) | ||||
|                         ]); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|         }) | ||||
|         .then(function() { | ||||
|             // That's your job: return null
 | ||||
|             return null; | ||||
|         }); | ||||
| 	// Ignore
 | ||||
| 	// Just implementation specific details (writing lots of combinatons of files)
 | ||||
| 	return mkdirpAsync(path.dirname(certPath(store, opts))) | ||||
| 		.then(function() { | ||||
| 			return mkdirpAsync( | ||||
| 				path.dirname(U._tameWild(chainPath(store, opts), opts.subject)) | ||||
| 			).then(function() { | ||||
| 				return mkdirpAsync( | ||||
| 					path.dirname( | ||||
| 						U._tameWild(fullchainPath(store, opts), opts.subject) | ||||
| 					) | ||||
| 				).then(function() { | ||||
| 					return mkdirpAsync( | ||||
| 						path.dirname( | ||||
| 							U._tameWild(bundlePath(store, opts), opts.subject) | ||||
| 						) | ||||
| 					).then(function() { | ||||
| 						var fullchainPem = [ | ||||
| 							pems.cert.trim() + '\n', | ||||
| 							pems.chain.trim() + '\n' | ||||
| 						].join('\n'); // for Apache, Nginx, etc
 | ||||
| 						var bundlePem = [ | ||||
| 							pems.privkey, | ||||
| 							pems.cert, | ||||
| 							pems.chain | ||||
| 						].join('\n'); // for HAProxy
 | ||||
| 						return PromiseA.all([ | ||||
| 							sfs.writeFileAsync( | ||||
| 								U._tameWild( | ||||
| 									certPath(store, opts), | ||||
| 									opts.subject | ||||
| 								), | ||||
| 								pems.cert, | ||||
| 								'ascii' | ||||
| 							), | ||||
| 							sfs.writeFileAsync( | ||||
| 								U._tameWild( | ||||
| 									chainPath(store, opts), | ||||
| 									opts.subject | ||||
| 								), | ||||
| 								pems.chain, | ||||
| 								'ascii' | ||||
| 							), | ||||
| 							// Most web servers need these two
 | ||||
| 							sfs.writeFileAsync( | ||||
| 								U._tameWild( | ||||
| 									fullchainPath(store, opts), | ||||
| 									opts.subject | ||||
| 								), | ||||
| 								fullchainPem, | ||||
| 								'ascii' | ||||
| 							), | ||||
| 							// HAProxy needs "bundle.pem" aka "combined.pem"
 | ||||
| 							sfs.writeFileAsync( | ||||
| 								U._tameWild( | ||||
| 									bundlePath(store, opts), | ||||
| 									opts.subject | ||||
| 								), | ||||
| 								bundlePem, | ||||
| 								'ascii' | ||||
| 							) | ||||
| 						]); | ||||
| 					}); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}) | ||||
| 		.then(function() { | ||||
| 			// That's your job: return null
 | ||||
| 			return null; | ||||
| 		}); | ||||
| }; | ||||
| 
 | ||||
| function liveDir(store, opts) { | ||||
|     return opts.liveDir || path.join(opts.configDir, "live", opts.subject); | ||||
| 	return opts.liveDir || path.join(opts.configDir, 'live', opts.subject); | ||||
| } | ||||
| 
 | ||||
| function privkeyPath(store, opts) { | ||||
|     var dir = U._tpl( | ||||
|         store, | ||||
|         opts, | ||||
|         opts.serverKeyPath || | ||||
|             opts.privkeyPath || | ||||
|             opts.domainKeyPath || | ||||
|             store.options.serverKeyPath || | ||||
|             store.options.privkeyPath || | ||||
|             store.options.domainKeyPath || | ||||
|             path.join(liveDir(), "privkey.pem") | ||||
|     ); | ||||
|     return U._tameWild(dir, opts.subject || ""); | ||||
| 	var dir = U._tpl( | ||||
| 		store, | ||||
| 		opts, | ||||
| 		opts.serverKeyPath || | ||||
| 			opts.privkeyPath || | ||||
| 			opts.domainKeyPath || | ||||
| 			store.options.serverKeyPath || | ||||
| 			store.options.privkeyPath || | ||||
| 			store.options.domainKeyPath || | ||||
| 			path.join(liveDir(), 'privkey.pem') | ||||
| 	); | ||||
| 	return U._tameWild(dir, opts.subject || ''); | ||||
| } | ||||
| 
 | ||||
| function certPath(store, opts) { | ||||
|     var pathname = | ||||
|         opts.certPath || | ||||
|         store.options.certPath || | ||||
|         path.join(liveDir(), "cert.pem"); | ||||
| 	var pathname = | ||||
| 		opts.certPath || | ||||
| 		store.options.certPath || | ||||
| 		path.join(liveDir(), 'cert.pem'); | ||||
| 
 | ||||
|     var dir = U._tpl(store, opts, pathname); | ||||
|     return U._tameWild(dir, opts.subject || ""); | ||||
| 	var dir = U._tpl(store, opts, pathname); | ||||
| 	return U._tameWild(dir, opts.subject || ''); | ||||
| } | ||||
| 
 | ||||
| function fullchainPath(store, opts) { | ||||
|     var dir = U._tpl( | ||||
|         store, | ||||
|         opts, | ||||
|         opts.fullchainPath || | ||||
|             store.options.fullchainPath || | ||||
|             path.join(liveDir(), "fullchain.pem") | ||||
|     ); | ||||
|     return U._tameWild(dir, opts.subject || ""); | ||||
| 	var dir = U._tpl( | ||||
| 		store, | ||||
| 		opts, | ||||
| 		opts.fullchainPath || | ||||
| 			store.options.fullchainPath || | ||||
| 			path.join(liveDir(), 'fullchain.pem') | ||||
| 	); | ||||
| 	return U._tameWild(dir, opts.subject || ''); | ||||
| } | ||||
| 
 | ||||
| function chainPath(store, opts) { | ||||
|     var dir = U._tpl( | ||||
|         store, | ||||
|         opts, | ||||
|         opts.chainPath || | ||||
|             store.options.chainPath || | ||||
|             path.join(liveDir(), "chain.pem") | ||||
|     ); | ||||
|     return U._tameWild(dir, opts.subject || ""); | ||||
| 	var dir = U._tpl( | ||||
| 		store, | ||||
| 		opts, | ||||
| 		opts.chainPath || | ||||
| 			store.options.chainPath || | ||||
| 			path.join(liveDir(), 'chain.pem') | ||||
| 	); | ||||
| 	return U._tameWild(dir, opts.subject || ''); | ||||
| } | ||||
| 
 | ||||
| function bundlePath(store, opts) { | ||||
|     var dir = U._tpl( | ||||
|         store, | ||||
|         opts, | ||||
|         opts.bundlePath || | ||||
|             store.options.bundlePath || | ||||
|             path.join(liveDir(), "bundle.pem") | ||||
|     ); | ||||
|     return U._tameWild(dir, opts.subject || ""); | ||||
| 	var dir = U._tpl( | ||||
| 		store, | ||||
| 		opts, | ||||
| 		opts.bundlePath || | ||||
| 			store.options.bundlePath || | ||||
| 			path.join(liveDir(), 'bundle.pem') | ||||
| 	); | ||||
| 	return U._tameWild(dir, opts.subject || ''); | ||||
| } | ||||
|  | ||||
							
								
								
									
										98
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										98
									
								
								index.js
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| "use strict"; | ||||
| 'use strict'; | ||||
| 
 | ||||
| var os = require("os"); | ||||
| var path = require("path"); | ||||
| var os = require('os'); | ||||
| var path = require('path'); | ||||
| 
 | ||||
| // How Storage Works in Greenlock: High-Level Call Stack
 | ||||
| //
 | ||||
| @ -50,32 +50,32 @@ var path = require("path"); | ||||
| 
 | ||||
| // Either your user calls create with specific options, or greenlock calls it for you with a big options blob
 | ||||
| module.exports.create = function(config) { | ||||
|     // 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
 | ||||
|     // more than 10,000 domains, for example.
 | ||||
| 	// 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
 | ||||
| 	// more than 10,000 domains, for example.
 | ||||
| 
 | ||||
|     // basic setup
 | ||||
|     var store = { | ||||
|         accounts: require("./accounts.js"), | ||||
|         certificates: require("./certificates.js") | ||||
|     }; | ||||
| 	// basic setup
 | ||||
| 	var store = { | ||||
| 		accounts: require('./accounts.js'), | ||||
| 		certificates: require('./certificates.js') | ||||
| 	}; | ||||
| 
 | ||||
|     // For you store.options should probably start empty and get a minimal set of options copied from `config` above.
 | ||||
|     // Example:
 | ||||
|     //store.options = {};
 | ||||
|     //store.options.databaseUrl = config.databaseUrl;
 | ||||
| 	// For you store.options should probably start empty and get a minimal set of options copied from `config` above.
 | ||||
| 	// Example:
 | ||||
| 	//store.options = {};
 | ||||
| 	//store.options.databaseUrl = config.databaseUrl;
 | ||||
| 
 | ||||
|     // 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.accounts.options = store.options; | ||||
|     store.certificates.options = store.options; | ||||
| 	// 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.accounts.options = store.options; | ||||
| 	store.certificates.options = store.options; | ||||
| 
 | ||||
|     if (!config.basePath && !config.configDir) { | ||||
|         console.info("Greenlock Store FS Path:", store.options.configDir); | ||||
|     } | ||||
| 	if (!config.basePath && !config.configDir) { | ||||
| 		console.info('Greenlock Store FS Path:', store.options.configDir); | ||||
| 	} | ||||
| 
 | ||||
|     return store; | ||||
| 	return store; | ||||
| }; | ||||
| 
 | ||||
| ///////////////////////////////////////////////////////////////////////////////
 | ||||
| @ -84,36 +84,36 @@ module.exports.create = function(config) { | ||||
| //
 | ||||
| // Everything below this line is just implementation specific
 | ||||
| var defaults = { | ||||
|     basePath: path.join(os.homedir(), ".config", "greenlock"), | ||||
| 	basePath: path.join(os.homedir(), '.config', 'greenlock'), | ||||
| 
 | ||||
|     accountsDir: path.join(":basePath", "accounts", ":directoryUrl"), | ||||
|     serverDirGet: function(copy) { | ||||
|         return (copy.directoryUrl || copy.server || "") | ||||
|             .replace("https://", "") | ||||
|             .replace(/(\/)$/, "") | ||||
|             .replace(/\//g, path.sep); | ||||
|     }, | ||||
|     privkeyPath: path.join(":basePath", ":env", ":subject", "privkey.pem"), | ||||
|     fullchainPath: path.join(":basePath", ":env", ":subject", "fullchain.pem"), | ||||
|     certPath: path.join(":basePath", ":env", ":subject", "cert.pem"), | ||||
|     chainPath: path.join(":basePath", ":env", ":subject", "chain.pem"), | ||||
|     bundlePath: path.join(":basePath", ":env", ":subject", "bundle.pem") | ||||
| 	accountsDir: path.join(':basePath', 'accounts', ':directoryUrl'), | ||||
| 	serverDirGet: function(copy) { | ||||
| 		return (copy.directoryUrl || copy.server || '') | ||||
| 			.replace('https://', '') | ||||
| 			.replace(/(\/)$/, '') | ||||
| 			.replace(/\//g, path.sep); | ||||
| 	}, | ||||
|   privkeyPath: path.join(':basePath', ':env', ':subject', 'privkey.pem'), | ||||
| 	fullchainPath: path.join(':basePath', ':env', ':subject', 'fullchain.pem'), | ||||
| 	certPath: path.join(':basePath', ':env', ':subject', 'cert.pem'), | ||||
| 	chainPath: path.join(':basePath', ':env', ':subject', 'chain.pem'), | ||||
| 	bundlePath: path.join(':basePath', ':env', ':subject', 'bundle.pem') | ||||
| }; | ||||
| defaults.configDir = defaults.basePath; | ||||
| 
 | ||||
| function mergeOptions(configs) { | ||||
|     if (!configs.serverKeyPath) { | ||||
|         configs.serverKeyPath = | ||||
|             configs.domainKeyPath || | ||||
|             configs.privkeyPath || | ||||
|             defaults.privkeyPath; | ||||
|     } | ||||
| 	if (!configs.serverKeyPath) { | ||||
| 		configs.serverKeyPath = | ||||
| 			configs.domainKeyPath || | ||||
| 			configs.privkeyPath || | ||||
| 			defaults.privkeyPath; | ||||
| 	} | ||||
| 
 | ||||
|     Object.keys(defaults).forEach(function(key) { | ||||
|         if (!configs[key]) { | ||||
|             configs[key] = defaults[key]; | ||||
|         } | ||||
|     }); | ||||
| 	Object.keys(defaults).forEach(function(key) { | ||||
| 		if (!configs[key]) { | ||||
| 			configs[key] = defaults[key]; | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
|     return configs; | ||||
| 	return configs; | ||||
| } | ||||
|  | ||||
							
								
								
									
										30
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,18 +1,18 @@ | ||||
| { | ||||
|     "name": "greenlock-store-fs", | ||||
|     "version": "3.2.0", | ||||
|     "lockfileVersion": 1, | ||||
|     "requires": true, | ||||
|     "dependencies": { | ||||
|         "@root/mkdirp": { | ||||
|             "version": "1.0.0", | ||||
|             "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | ||||
|             "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" | ||||
|         }, | ||||
|         "safe-replace": { | ||||
|             "version": "1.1.0", | ||||
|             "resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", | ||||
|             "integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" | ||||
|         } | ||||
|   "name": "greenlock-store-fs", | ||||
|   "version": "3.0.1", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|     "@root/mkdirp": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | ||||
|       "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" | ||||
|     }, | ||||
|     "safe-replace": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", | ||||
|       "integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										58
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								package.json
									
									
									
									
									
								
							| @ -1,31 +1,31 @@ | ||||
| { | ||||
|     "name": "greenlock-store-fs", | ||||
|     "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", | ||||
|     "directories": { | ||||
|         "test": "tests" | ||||
|     }, | ||||
|     "scripts": { | ||||
|         "test": "node tests" | ||||
|     }, | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|         "url": "https://git.rootprojects.org/root/greenlock-store-fs.js.git" | ||||
|     }, | ||||
|     "keywords": [ | ||||
|         "greenlock", | ||||
|         "json", | ||||
|         "keypairs", | ||||
|         "certificates", | ||||
|         "store", | ||||
|         "database" | ||||
|     ], | ||||
|     "author": "AJ ONeal <solderjs@gmail.com> (https://solderjs.com/)", | ||||
|     "license": "MPL-2.0", | ||||
|     "dependencies": { | ||||
|         "@root/mkdirp": "^1.0.0", | ||||
|         "safe-replace": "^1.1.0" | ||||
|     } | ||||
| 	"name": "greenlock-store-fs", | ||||
| 	"version": "3.2.0", | ||||
| 	"description": "A file-based certificate store for greenlock that supports wildcards.", | ||||
| 	"homepage": "https://git.rootprojects.org/root/greenlock-store-fs.js", | ||||
| 	"main": "index.js", | ||||
| 	"directories": { | ||||
| 		"test": "tests" | ||||
| 	}, | ||||
| 	"scripts": { | ||||
| 		"test": "node tests" | ||||
| 	}, | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| 		"url": "https://git.rootprojects.org/root/greenlock-store-fs.js.git" | ||||
| 	}, | ||||
| 	"keywords": [ | ||||
| 		"greenlock", | ||||
| 		"json", | ||||
| 		"keypairs", | ||||
| 		"certificates", | ||||
| 		"store", | ||||
| 		"database" | ||||
| 	], | ||||
| 	"author": "AJ ONeal <solderjs@gmail.com> (https://solderjs.com/)", | ||||
| 	"license": "MPL-2.0", | ||||
| 	"dependencies": { | ||||
| 		"@root/mkdirp": "^1.0.0", | ||||
| 		"safe-replace": "^1.1.0" | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										34
									
								
								promise.js
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								promise.js
									
									
									
									
									
								
							| @ -1,22 +1,22 @@ | ||||
| "use strict"; | ||||
| 'use strict'; | ||||
| 
 | ||||
| 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; | ||||
| 	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; | ||||
| } | ||||
| 
 | ||||
| module.exports = getPromise(); | ||||
|  | ||||
							
								
								
									
										52
									
								
								test.js
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								test.js
									
									
									
									
									
								
							| @ -1,33 +1,27 @@ | ||||
| "use strict"; | ||||
| 'use strict'; | ||||
| 
 | ||||
| var tester = require("greenlock-store-test"); | ||||
| var tester = require('greenlock-store-test'); | ||||
| 
 | ||||
| var crypto = require("crypto"); | ||||
| var os = require("os"); | ||||
| var path = require("path"); | ||||
| var basedir = path.join( | ||||
|     os.tmpdir(), | ||||
|     "greenlock-store-fs-test-" + crypto.randomBytes(4).toString("hex") | ||||
| ); | ||||
| var domain = "*.example.com"; | ||||
| var store = require("./").create({ | ||||
|     configDir: basedir, | ||||
|     accountsDir: path.join(basedir, "accounts"), | ||||
|     privkeyPath: path.join(basedir, "live", domain, "privkey.pem"), | ||||
|     fullchainPath: path.join(basedir, "live", domain, "fullchain.pem"), | ||||
|     certPath: path.join(basedir, "live", domain, "cert.pem"), | ||||
|     chainPath: path.join(basedir, "live", domain, "chain.pem"), | ||||
|     bundlePath: path.join(basedir, "live", domain, "bundle.pem") | ||||
| var crypto = require('crypto'); | ||||
| var os = require('os'); | ||||
| var path = require('path'); | ||||
| var basedir = path.join(os.tmpdir(), 'greenlock-store-fs-test-' + crypto.randomBytes(4).toString('hex')); | ||||
| var domain = '*.example.com'; | ||||
| var store = require('./').create({ | ||||
|   configDir: basedir | ||||
| , accountsDir: path.join(basedir, 'accounts') | ||||
| , privkeyPath: path.join(basedir, 'live', domain, 'privkey.pem') | ||||
| , fullchainPath: path.join(basedir, 'live', domain, 'fullchain.pem') | ||||
| , certPath: path.join(basedir, 'live', domain, 'cert.pem') | ||||
| , chainPath: path.join(basedir, 'live', domain, 'chain.pem') | ||||
| , bundlePath: path.join(basedir, 'live', domain, 'bundle.pem') | ||||
| }); | ||||
| console.info("Test Dir:", basedir); | ||||
| console.info('Test Dir:', basedir); | ||||
| 
 | ||||
| tester | ||||
|     .test(store) | ||||
|     .then(function() { | ||||
|         console.info("PASS"); | ||||
|     }) | ||||
|     .catch(function(err) { | ||||
|         console.error("FAIL"); | ||||
|         console.error(err); | ||||
|         process.exit(20); | ||||
|     }); | ||||
| tester.test(store).then(function () { | ||||
|   console.info("PASS"); | ||||
| }).catch(function (err) { | ||||
|   console.error("FAIL"); | ||||
|   console.error(err); | ||||
|   process.exit(20); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										80
									
								
								utils.js
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								utils.js
									
									
									
									
									
								
							| @ -1,51 +1,51 @@ | ||||
| "use strict"; | ||||
| 'use strict'; | ||||
| 
 | ||||
| var U = module.exports; | ||||
| 
 | ||||
| // because not all file systems like '*' in a name (and they're scary)
 | ||||
| U._tameWild = function tameWild(pathname, wild) { | ||||
|     if (!wild) { | ||||
|         return pathname; | ||||
|     } | ||||
|     var tame = wild.replace(/\*/g, "_"); | ||||
|     return pathname.replace(wild, tame); | ||||
| 	if (!wild) { | ||||
| 		return pathname; | ||||
| 	} | ||||
| 	var tame = wild.replace(/\*/g, '_'); | ||||
| 	return pathname.replace(wild, tame); | ||||
| }; | ||||
| 
 | ||||
| U._tpl = function tpl(store, opts, str) { | ||||
|     var server = ["directoryUrl", "serverDir", "server"]; | ||||
|     var env = ["env", "directoryUrl"]; | ||||
|     [ | ||||
|         ["basePath", "configDir"], | ||||
|         server, | ||||
|         ["subject", "hostname", "domain"], | ||||
|         env | ||||
|     ].forEach(function(group) { | ||||
|         group.forEach(function(tmpl) { | ||||
|             group.forEach(function(key) { | ||||
|                 var item = opts[key] || store.options[key]; | ||||
|                 if ("string" !== typeof item) { | ||||
|                     return; | ||||
|                 } | ||||
| 	var server = ['directoryUrl', 'serverDir', 'server']; | ||||
| 	var env = ['env', 'directoryUrl']; | ||||
| 	[ | ||||
| 		['basePath', 'configDir'], | ||||
| 		server, | ||||
| 		['subject', 'hostname', 'domain'], | ||||
| 		env | ||||
| 	].forEach(function(group) { | ||||
| 		group.forEach(function(tmpl) { | ||||
| 			group.forEach(function(key) { | ||||
| 				var item = opts[key] || store.options[key]; | ||||
| 				if ('string' !== typeof item) { | ||||
| 					return; | ||||
| 				} | ||||
| 
 | ||||
|                 if ("directoryUrl" === key) { | ||||
|                     item = item.replace(/^https?:\/\//i, ""); | ||||
|                 } | ||||
|                 if ("env" === tmpl) { | ||||
|                     if (/staging/.test(item)) { | ||||
|                         item = "staging"; | ||||
|                     } else if (/acme-v02/.test(item)) { | ||||
|                         item = "live"; | ||||
|                     } else { | ||||
|                         // item = item;
 | ||||
|                     } | ||||
|                 } | ||||
| 				if ('directoryUrl' === key) { | ||||
| 					item = item.replace(/^https?:\/\//i, ''); | ||||
| 				} | ||||
| 				if ('env' === tmpl) { | ||||
| 					if (/staging/.test(item)) { | ||||
| 						item = 'staging'; | ||||
| 					} else if (/acme-v02/.test(item)) { | ||||
| 						item = 'live'; | ||||
| 					} else { | ||||
| 						// item = item;
 | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
|                 if (-1 === str.indexOf(":" + tmpl)) { | ||||
|                     return; | ||||
|                 } | ||||
|                 str = str.replace(":" + tmpl, item); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|     return str; | ||||
| 				if (-1 === str.indexOf(':' + tmpl)) { | ||||
| 					return; | ||||
| 				} | ||||
| 				str = str.replace(':' + tmpl, item); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| 	return str; | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user