initial commit
This commit is contained in:
		
							parent
							
								
									6fe884ae8a
								
							
						
					
					
						commit
						0404a2743c
					
				
							
								
								
									
										69
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								README.md
									
									
									
									
									
								
							| @ -1,2 +1,69 @@ | ||||
| # le-store-certbot | ||||
| le-store-certbot | ||||
| ================ | ||||
| 
 | ||||
| The "certbot" storage strategy for node-letsencrypt. | ||||
| 
 | ||||
| This le storage strategy aims to maintain compatibility with the | ||||
| configuration files and file structure of the official certbot client. | ||||
| 
 | ||||
| Note: You cannot use this strategy on ephemeral instances (heroku, aws elastic). | ||||
| 
 | ||||
| Usage | ||||
| ----- | ||||
| 
 | ||||
| ```bash | ||||
| npm install --save le-store-certbot | ||||
| ``` | ||||
| 
 | ||||
| ```bash | ||||
| var leStore = require('le-store-certbot').create({ | ||||
|   configDir: require('homedir')() + '/letsencrypt/etc'      // or /etc/letsencrypt or wherever | ||||
| , privkeyPath: ':config/live/:hostname/privkey.pem'         // | ||||
| , fullchainPath: ':config/live/:hostname/fullchain.pem'     // Note: both that :config and :hostname | ||||
| , certPath: ':config/live/:hostname/cert.pem'               //       will be templated as expected by | ||||
| , chainPath: ':config/live/:hostname/chain.pem'             //       node-letsencrypt | ||||
| 
 | ||||
| , workDir: require('homedir')() + '/letsencrypt/var/lib' | ||||
| , logsDir: require('homedir')() + '/letsencrypt/var/log' | ||||
| 
 | ||||
| , debug: false | ||||
| }); | ||||
| 
 | ||||
| var LE = require('letsencrypt'); | ||||
| 
 | ||||
| LE.create({ | ||||
|   server: LE.stagingServerUrl                               // Change to LE.productionServerUrl in production | ||||
| , store: leStore | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| Example File Structure | ||||
| ---------------------- | ||||
| 
 | ||||
| ``` | ||||
| ~/letsencrypt/ | ||||
| └── etc | ||||
|     ├── accounts | ||||
|     │   └── acme-staging.api.letsencrypt.org | ||||
|     │       └── directory | ||||
|     │           └── cd96ac4889ddfa47bfc66300ab223342 | ||||
|     │               ├── meta.json | ||||
|     │               ├── private_key.json | ||||
|     │               └── regr.json | ||||
|     ├── archive | ||||
|     │   └── example.daplie.me | ||||
|     │       ├── cert0.pem | ||||
|     │       ├── chain0.pem | ||||
|     │       ├── fullchain0.pem | ||||
|     │       └── privkey0.pem | ||||
|     ├── live | ||||
|     │   └── example.daplie.me | ||||
|     │       ├── cert.pem | ||||
|     │       ├── chain.pem | ||||
|     │       ├── fullchain.pem | ||||
|     │       ├── privkey.pem | ||||
|     │       └── privkey.pem.bak | ||||
|     └── renewal | ||||
|         ├── example.daplie.me.conf | ||||
|         └── example.daplie.me.conf.bak | ||||
| ``` | ||||
|  | ||||
							
								
								
									
										536
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										536
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,536 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var PromiseA = require('bluebird'); | ||||
| var mkdirpAsync = PromiseA.promisify(require('mkdirp')); | ||||
| var path = require('path'); | ||||
| var fs = PromiseA.promisifyAll(require('fs')); | ||||
| var sfs = require('safe-replace'); | ||||
| var homeRe = new RegExp("^~(\\/|\\\|\\" + path.sep + ")"); | ||||
| 
 | ||||
| var fetchFromConfigLiveDir = function (args) { | ||||
|   // TODO NO HARD-CODED DEFAULTS
 | ||||
|   if (!args.fullchainPath || !args.privkeyPath || !args.certPath || !args.chainPath) { | ||||
|     console.warn("missing one or more of args.privkeyPath, args.fullchainPath, args.certPath, args.chainPath"); | ||||
|     console.warn("hard-coded conventional pathnames were for debugging and are not a stable part of the API"); | ||||
|   } | ||||
| 
 | ||||
|   //, fs.readFileAsync(fullchainPath, 'ascii')
 | ||||
|   // note: if this ^^ gets added back in, the arrays below must change
 | ||||
|   return PromiseA.all([ | ||||
|     fs.readFileAsync(args.privkeyPath, 'ascii')   // 0
 | ||||
|   , fs.readFileAsync(args.certPath, 'ascii')      // 1
 | ||||
|   , fs.readFileAsync(args.chainPath, 'ascii')     // 2
 | ||||
| 
 | ||||
|     // stat the file, not the link
 | ||||
|   , fs.statAsync(args.certPath)                   // 3
 | ||||
|   ]).then(function (arr) { | ||||
|     return { | ||||
|       key: arr[0]                           // privkey.pem
 | ||||
|     , privkey: arr[0]                       // privkey.pem
 | ||||
| 
 | ||||
|     , fullchain: arr[1] + '\n' + arr[2]     // fullchain.pem
 | ||||
|     , cert: arr[1]                          // cert.pem
 | ||||
|       /* | ||||
|     , issuedAt: arr[3].mtime.valueOf() | ||||
|     , expiresAt: arr[3].mtime.valueOf() + (90 * 24 * 60 * 60 * 100) | ||||
|       */ | ||||
| 
 | ||||
|     , chain: arr[2]                         // chain.pem
 | ||||
|     , ca: arr[2]                            // chain.pem
 | ||||
| 
 | ||||
|     , privkeyPath: args.privkeyPath | ||||
|     , fullchainPath: args.fullchainPath | ||||
|     , certPath: args.certPath | ||||
|     , chainPath: args.chainPath | ||||
|     }; | ||||
|   }, function (err) { | ||||
|     if (args.debug) { | ||||
|       console.error("[letsencrypt/lib/common.js] fetchFromDisk"); | ||||
|       console.error(err.stack); | ||||
|     } | ||||
|     return null; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| function getAccount(args) { | ||||
|   var accountId = args.accountId; | ||||
|   var accountDir = path.join(args.accountsDir, accountId); | ||||
|   var files = {}; | ||||
|   var configs = [ 'meta.json', 'private_key.json', 'regr.json' ]; | ||||
| 
 | ||||
|   return PromiseA.all(configs.map(function (filename) { | ||||
|     var keyname = filename.slice(0, -5); | ||||
| 
 | ||||
|     return fs.readFileAsync(path.join(accountDir, filename), 'utf8').then(function (text) { | ||||
|       var data; | ||||
| 
 | ||||
|       try { | ||||
|         data = JSON.parse(text); | ||||
|       } catch(e) { | ||||
|         files[keyname] = { error: e }; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       files[keyname] = data; | ||||
|     }, function (err) { | ||||
|       files[keyname] = { error: err }; | ||||
|     }); | ||||
|   })).then(function () { | ||||
|     var err; | ||||
| 
 | ||||
|     if (!Object.keys(files).every(function (key) { | ||||
|       return !files[key].error; | ||||
|     }) || !files.private_key || !files.private_key.n) { | ||||
|       err = new Error("Account '" + accountId + "' was corrupt. No big deal (I think?). Creating a new one..."); | ||||
|       err.code = 'E_ACCOUNT_CORRUPT'; | ||||
|       err.data = files; | ||||
|       return PromiseA.reject(err); | ||||
|     } | ||||
| 
 | ||||
|     //files.private_key;
 | ||||
|     //files.regr;
 | ||||
|     //files.meta;
 | ||||
|     files.accountId = accountId;                  // preserve current account id
 | ||||
|     files.id = accountId; | ||||
|     files.keypair = { privateKeyJwk: files.private_key }; | ||||
| 
 | ||||
|     return files; | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function getAccountIdByEmail(args) { | ||||
|   // If we read 10,000 account directories looking for
 | ||||
|   // just one email address, that could get crazy.
 | ||||
|   // We should have a folder per email and list
 | ||||
|   // each account as a file in the folder
 | ||||
|   // TODO
 | ||||
|   var email = args.email; | ||||
|   if ('string' !== typeof email) { | ||||
|     if (args.debug) { | ||||
|       console.log("[LE] No email given"); | ||||
|     } | ||||
|     return PromiseA.resolve(null); | ||||
|   } | ||||
|   return fs.readdirAsync(args.accountsDir).then(function (nodes) { | ||||
|     if (args.debug) { | ||||
|       console.log("[LE] arg.accountsDir success"); | ||||
|     } | ||||
| 
 | ||||
|     return PromiseA.all(nodes.map(function (node) { | ||||
|       return fs.readFileAsync(path.join(args.accountsDir, node, 'regr.json'), 'utf8').then(function (text) { | ||||
|         var regr = JSON.parse(text); | ||||
|         regr.__accountId = node; | ||||
| 
 | ||||
|         return regr; | ||||
|       }); | ||||
|     })).then(function (regrs) { | ||||
|       var accountId; | ||||
| 
 | ||||
|       /* | ||||
|       if (args.debug) { | ||||
|         console.log('read many regrs'); | ||||
|         console.log('regrs', regrs); | ||||
|       } | ||||
|       */ | ||||
| 
 | ||||
|       regrs.some(function (regr) { | ||||
|         return regr.body.contact.some(function (contact) { | ||||
|           var match = contact.toLowerCase() === 'mailto:' + email.toLowerCase(); | ||||
|           if (match) { | ||||
|             accountId = regr.__accountId; | ||||
|             return true; | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       if (!accountId) { | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       return accountId; | ||||
|     }); | ||||
|   }).then(function (accountId) { | ||||
|     return accountId; | ||||
|   }, function (err) { | ||||
|     if ('ENOENT' === err.code) { | ||||
|       // ignore error
 | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return PromiseA.reject(err); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function readRenewalConfig(args) { | ||||
|   var pyconf = PromiseA.promisifyAll(require('pyconf')); | ||||
| 
 | ||||
|   return pyconf.readFileAsync(args.renewalPath).then(function (pyobj) { | ||||
|     return pyobj; | ||||
|   }, function () { | ||||
|     return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (pyobj) { | ||||
|       return pyobj; | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function writeRenewalConfig(args) { | ||||
|   function log() { | ||||
|     if (args.debug) { | ||||
|       console.log.apply(console, arguments); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   var pyobj = args.pyobj; | ||||
|   pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0; | ||||
| 
 | ||||
|   var pyconf = PromiseA.promisifyAll(require('pyconf')); | ||||
| 
 | ||||
|   var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]); | ||||
| 
 | ||||
|   var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem'); | ||||
|   var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem'); | ||||
|   var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem'); | ||||
|   var privkeyPath = args.privkeyPath || pyobj.privkey | ||||
|     //|| args.domainPrivateKeyPath || args.domainKeyPath || pyobj.keyPath
 | ||||
|     || path.join(liveDir, 'privkey.pem'); | ||||
| 
 | ||||
|   log('[le/core.js] privkeyPath', privkeyPath); | ||||
| 
 | ||||
|   var updates = { | ||||
|     account: args.account.id | ||||
|   , configDir: args.configDir | ||||
|   , domains: args.domains | ||||
| 
 | ||||
|   , email: args.email | ||||
|   , tos: args.agreeTos && true | ||||
|     // yes, it's an array. weird, right?
 | ||||
|   , webrootPath: args.webrootPath && [args.webrootPath] || [] | ||||
|   , server: args.server || args.acmeDiscoveryUrl | ||||
| 
 | ||||
|   , privkey: privkeyPath | ||||
|   , fullchain: fullchainPath | ||||
|   , cert: certPath | ||||
|   , chain: chainPath | ||||
| 
 | ||||
|   , http01Port: args.http01Port | ||||
|   , keyPath: args.domainPrivateKeyPath || args.privkeyPath | ||||
|   , rsaKeySize: args.rsaKeySize | ||||
|   , checkpoints: pyobj.checkpoints | ||||
|     /* // TODO XXX what's the deal with these? they don't make sense | ||||
|     // are they just old junk? or do they have a meaning that I don't know about?
 | ||||
|   , fullchainPath: path.join(args.configDir, 'chain.pem') | ||||
|   , certPath: path.join(args.configDir, 'cert.pem') | ||||
|   , chainPath: path.join(args.configDir, 'chain.pem') | ||||
|     */ // TODO XXX end | ||||
|   , workDir: args.workDir | ||||
|   , logsDir: args.logsDir | ||||
|   }; | ||||
| 
 | ||||
|   // final section is completely dynamic
 | ||||
|   // :hostname = :webroot_path
 | ||||
|   args.domains.forEach(function (hostname) { | ||||
|     updates[hostname] = args.webrootPath; | ||||
|   }); | ||||
| 
 | ||||
|   // must write back to the original pyobject or
 | ||||
|   // annotations will be lost
 | ||||
|   Object.keys(updates).forEach(function (key) { | ||||
|     pyobj[key] = updates[key]; | ||||
|   }); | ||||
| 
 | ||||
|   return mkdirpAsync(path.dirname(args.renewalPath)).then(function () { | ||||
|     return pyconf.writeFileAsync(args.renewalPath, pyobj); | ||||
|   }).then(function () { | ||||
|     // NOTE
 | ||||
|     // writing twice seems to causes a bug,
 | ||||
|     // so instead we re-read the file from the disk
 | ||||
|     return pyconf.readFileAsync(args.renewalPath); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function getOrCreateRenewal(args) { | ||||
|   return readRenewalConfig(args).then(function (pyobj) { | ||||
|     var minver = pyobj.checkpoints >= 0; | ||||
| 
 | ||||
|     args.pyobj = pyobj; | ||||
| 
 | ||||
|     if (!minver) { | ||||
|       args.checkpoints = 0; | ||||
|       pyobj.checkpoints = 0; | ||||
|       return writeRenewalConfig(args); | ||||
|     } | ||||
| 
 | ||||
|     // args.account.id = pyobj.account
 | ||||
|     // args.configDir = args.configDir || pyobj.configDir;
 | ||||
| 
 | ||||
|     args.checkpoints = pyobj.checkpoints; | ||||
| 
 | ||||
|     args.agreeTos = (args.agreeTos || pyobj.tos) && true; | ||||
|     args.email = args.email || pyobj.email; | ||||
|     args.domains = args.domains || pyobj.domains; | ||||
| 
 | ||||
|     // yes, it's an array. weird, right?
 | ||||
|     args.webrootPath = args.webrootPath || pyobj.webrootPath[0]; | ||||
|     args.server = args.server || args.acmeDiscoveryUrl || pyobj.server; | ||||
| 
 | ||||
|     args.certPath = args.certPath || pyobj.cert; | ||||
|     args.privkeyPath = args.privkeyPath || pyobj.privkey; | ||||
|     args.chainPath = args.chainPath || pyobj.chain; | ||||
|     args.fullchainPath = args.fullchainPath || pyobj.fullchain; | ||||
| 
 | ||||
|   //, workDir: args.workDir
 | ||||
|   //, logsDir: args.logsDir
 | ||||
|     args.rsaKeySize = args.rsaKeySize || pyobj.rsaKeySize; | ||||
|     args.http01Port = args.http01Port || pyobj.http01Port; | ||||
|     args.domainKeyPath = args.domainPrivateKeyPath || args.domainKeyPath || args.keyPath || pyobj.keyPath; | ||||
| 
 | ||||
|     return writeRenewalConfig(args); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function writeCertificateAsync(args) { | ||||
|   function log() { | ||||
|     if (args.debug) { | ||||
|       console.log.apply(console, arguments); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   log("[le/core.js] got certificate!"); | ||||
| 
 | ||||
|   var obj = args.pyobj; | ||||
|   var pems = args.pems; | ||||
| 
 | ||||
|   pems.fullchain = pems.cert + '\n' + (pems.chain || pems.ca); | ||||
|   obj.checkpoints = parseInt(obj.checkpoints, 10) || 0; | ||||
| 
 | ||||
|   var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]); | ||||
| 
 | ||||
|   var certPath = args.certPath || obj.cert || path.join(liveDir, 'cert.pem'); | ||||
|   var fullchainPath = args.fullchainPath || obj.fullchain || path.join(liveDir, 'fullchain.pem'); | ||||
|   var chainPath = args.chainPath || obj.chain || path.join(liveDir, 'chain.pem'); | ||||
|   var privkeyPath = args.privkeyPath || obj.privkey | ||||
|     //|| args.domainPrivateKeyPath || args.domainKeyPath || obj.keyPath
 | ||||
|     || path.join(liveDir, 'privkey.pem'); | ||||
| 
 | ||||
|   log('[le/core.js] privkeyPath', privkeyPath); | ||||
| 
 | ||||
|   var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]); | ||||
| 
 | ||||
|   var checkpoints = obj.checkpoints.toString(); | ||||
|   var certArchive = path.join(archiveDir, 'cert' + checkpoints + '.pem'); | ||||
|   var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoints + '.pem'); | ||||
|   var chainArchive = path.join(archiveDir, 'chain'+ checkpoints + '.pem'); | ||||
|   var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoints + '.pem'); | ||||
| 
 | ||||
|   return mkdirpAsync(archiveDir).then(function () { | ||||
|     return PromiseA.all([ | ||||
|       sfs.writeFileAsync(certArchive, pems.cert, 'ascii') | ||||
|     , sfs.writeFileAsync(chainArchive, (pems.chain || pems.ca), 'ascii') | ||||
|     , sfs.writeFileAsync(fullchainArchive, pems.fullchain, 'ascii') | ||||
|     , sfs.writeFileAsync( | ||||
|         privkeyArchive | ||||
|         // TODO nix args.key, args.domainPrivateKeyPem ??
 | ||||
|       , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair)
 | ||||
|       , 'ascii' | ||||
|       ) | ||||
|     ]); | ||||
|   }).then(function () { | ||||
|     return mkdirpAsync(liveDir); | ||||
|   }).then(function () { | ||||
|     return PromiseA.all([ | ||||
|       sfs.writeFileAsync(certPath, pems.cert, 'ascii') | ||||
|     , sfs.writeFileAsync(chainPath, (pems.chain || pems.ca), 'ascii') | ||||
|     , sfs.writeFileAsync(fullchainPath, pems.fullchain, 'ascii') | ||||
|     , sfs.writeFileAsync( | ||||
|         privkeyPath | ||||
|         // TODO nix args.key, args.domainPrivateKeyPem ??
 | ||||
|       , (pems.privkey || pems.key) // || RSA.exportPrivatePem(args.domainKeypair)
 | ||||
|       , 'ascii' | ||||
|       ) | ||||
|     ]); | ||||
|   }).then(function () { | ||||
|     obj.checkpoints += 1; | ||||
|     args.checkpoints += 1; | ||||
| 
 | ||||
|     return writeRenewalConfig(args); | ||||
|   }).then(function () { | ||||
|     return { | ||||
|       certPath: certPath | ||||
|     , chainPath: chainPath | ||||
|     , fullchainPath: fullchainPath | ||||
|     , privkeyPath: privkeyPath | ||||
| 
 | ||||
|       // TODO nix keypair
 | ||||
|     , keypair: args.domainKeypair | ||||
| 
 | ||||
|       // TODO nix args.key, args.domainPrivateKeyPem ??
 | ||||
|       // some ambiguity here...
 | ||||
|     , privkey: (pems.privkey || pems.key) //|| RSA.exportPrivatePem(args.domainKeypair)
 | ||||
|     , fullchain: pems.fullchain || (pems.cert + '\n' + pems.chain) | ||||
|     , chain:  (pems.chain || pems.ca) | ||||
|       // especially this one... might be cert only, might be fullchain
 | ||||
|     , cert: pems.cert | ||||
| 
 | ||||
|       /* | ||||
|     , issuedAt: Date.now() | ||||
|     , expiresAt: Date.now() + (90 * 24 * 60 * 60 * 100) | ||||
|       */ | ||||
|     }; | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function pyToJson(pyobj) { | ||||
|   if (!pyobj) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   var jsobj = {}; | ||||
|   Object.keys(pyobj).forEach(function (key) { | ||||
|     jsobj[key] = pyobj[key]; | ||||
|   }); | ||||
|   jsobj.__lines = undefined; | ||||
|   jsobj.__keys = undefined; | ||||
| 
 | ||||
|   return jsobj; | ||||
| } | ||||
| 
 | ||||
| var defaults = { | ||||
|   configDir: '~/letsencrypt/etc'                          // /etc/letsencrypt/
 | ||||
| , logsDir: '~/letsencrypt/var/log'                        // /var/log/letsencrypt/
 | ||||
| , workDir: '~/letsencrypt/var/lib'                        // /var/lib/letsencrypt/
 | ||||
| 
 | ||||
| , accountsDir: ':config/accounts/:server' | ||||
| , renewalPath: ':config/renewal/:hostname.conf' | ||||
| , renewalDir: ':config/renewal/' | ||||
| 
 | ||||
| , privkeyPath: ':config/live/:hostname/privkey.pem' | ||||
| , fullchainPath: ':config/live/:hostname/fullchain.pem' | ||||
| , certPath: ':config/live/:hostname/cert.pem' | ||||
| , chainPath: ':config/live/:hostname/chain.pem' | ||||
| 
 | ||||
| , rsaKeySize: 2048 | ||||
| , webrootPath: '~/letsencrypt/srv/www/:hostname/.well-known/acme-challenge' | ||||
| }; | ||||
| 
 | ||||
| module.exports.create = function (configs) { | ||||
|   var mergedConfigs; | ||||
| 
 | ||||
|   function getConfigAsync(copy) { | ||||
|     copy.domains = []; | ||||
| 
 | ||||
|     return readRenewalConfig(copy).then(function (pyobj) { | ||||
|       var exists = pyobj.checkpoints >= 0; | ||||
|       if (!exists) { | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       return pyToJson(pyobj); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     getDefaults: function () { | ||||
|       if (mergedConfigs) { | ||||
|         return configs; | ||||
|       } | ||||
| 
 | ||||
|       Object.keys(defaults).forEach(function (key) { | ||||
|         if (!(key in configs)) { | ||||
|           configs[key] = defaults[key]; | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       mergedConfigs = configs; | ||||
|       return configs; | ||||
|     } | ||||
|   , getPrivatePemAsync: function (args) { | ||||
|       return fs.readFileAsync(args.domainKeyPath, 'ascii'); | ||||
|     } | ||||
|   , setPrivatePemAsync: function (args, keypair) { | ||||
|       return mkdirpAsync(path.dirname(args.domainKeyPath)).then(function () { | ||||
|         return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii').then(function () { | ||||
|           return keypair; | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|   , setRegistrationAsync: function (args) { | ||||
|       return writeCertificateAsync(args); | ||||
|     } | ||||
| 
 | ||||
|   , getRegistrationAsync: function (args) { | ||||
|       return fetchFromConfigLiveDir(args); | ||||
|     } | ||||
|   , getOrCreateRenewalAsync: function (args) { | ||||
|       return getOrCreateRenewal(args); | ||||
|     } | ||||
|   , getConfigAsync: getConfigAsync | ||||
|   , getConfigsAsync: function (copy) { | ||||
|       copy.domains = []; | ||||
| 
 | ||||
|       return fs.readdirAsync(copy.renewalDir).then(function (nodes) { | ||||
|         nodes = nodes.filter(function (node) { | ||||
|           return /^[a-z0-9]+.*\.conf$/.test(node); | ||||
|         }); | ||||
| 
 | ||||
|         return PromiseA.all(nodes.map(function (node) { | ||||
|           copy.domains = [node.replace(/\.conf$/, '')]; | ||||
|           return getConfigAsync(copy); | ||||
|         })); | ||||
|       }); | ||||
|     } | ||||
|   , fetchAsync: function (args) { | ||||
|       return fetchFromConfigLiveDir(args); | ||||
|     } | ||||
|   , getAccountIdByEmailAsync: getAccountIdByEmail | ||||
|   , getAccountAsync: getAccount | ||||
|   , setAccountAsync: function (args, account) { | ||||
|       var isoDate = new Date().toISOString(); | ||||
|       var os = require("os"); | ||||
|       var localname = os.hostname(); | ||||
|       var accountDir = path.join(args.accountsDir, account.accountId); | ||||
| 
 | ||||
|       account.meta = account.meta || { | ||||
|         creation_host: localname | ||||
|       , creation_dt: isoDate | ||||
|       }; | ||||
| 
 | ||||
|       return mkdirpAsync(accountDir).then(function () { | ||||
|         var RSA = require('rsa-compat').RSA; | ||||
| 
 | ||||
|         // TODO abstract file writing
 | ||||
|         return PromiseA.all([ | ||||
|           // meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"}
 | ||||
|           fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(account.meta), 'utf8') | ||||
|           // private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" }
 | ||||
|         , fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(RSA.exportPrivateJwk(account.keypair)), 'utf8') | ||||
|           // regr.json:
 | ||||
|           /* | ||||
|           { body: { contact: [ 'mailto:coolaj86@gmail.com' ], | ||||
|            agreement: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf', | ||||
|            key: { e: 'AQAB', kty: 'RSA', n: '...' } }, | ||||
|             uri: 'https://acme-v01.api.letsencrypt.org/acme/reg/71272', | ||||
|             new_authzr_uri: 'https://acme-v01.api.letsencrypt.org/acme/new-authz', | ||||
|             terms_of_service: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf' } | ||||
|            */ | ||||
|         , fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify(account.regr), 'utf8') | ||||
|         ]); | ||||
|       }); | ||||
|     } | ||||
|   , getAccountIdAsync: function (args) { | ||||
|       var pyconf = PromiseA.promisifyAll(require('pyconf')); | ||||
| 
 | ||||
|       return pyconf.readFileAsync(args.renewalPath).then(function (renewal) { | ||||
|         var accountId = renewal.account; | ||||
|         renewal = renewal.account; | ||||
| 
 | ||||
|         return accountId; | ||||
|       }, function (err) { | ||||
|         if ("ENOENT" === err.code) { | ||||
|           return getAccountIdByEmail(args); | ||||
|         } | ||||
| 
 | ||||
|         return PromiseA.reject(err); | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| }; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user