|
|
@ -31,131 +31,205 @@ function getAcmeUrls(args) { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
function writeCertificateAsync(result, args, defaults, handlers) { |
|
|
|
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) { |
|
|
|
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'); |
|
|
|
|
|
|
|
if (args.debug) { |
|
|
|
console.log("got certificate!"); |
|
|
|
console.log('################ privkeyPath ################'); |
|
|
|
console.log(privkeyPath); |
|
|
|
} |
|
|
|
|
|
|
|
result.fullchain = result.cert + '\n' + result.ca; |
|
|
|
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 |
|
|
|
}; |
|
|
|
|
|
|
|
var pyconf = PromiseA.promisifyAll(require('pyconf')); |
|
|
|
// final section is completely dynamic
|
|
|
|
// :hostname = :webroot_path
|
|
|
|
args.domains.forEach(function (hostname) { |
|
|
|
updates[hostname] = args.webrootPath; |
|
|
|
}); |
|
|
|
|
|
|
|
return pyconf.readFileAsync(args.renewalPath).then(function (obj) { |
|
|
|
return obj; |
|
|
|
}, function () { |
|
|
|
return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (obj) { |
|
|
|
return obj; |
|
|
|
}); |
|
|
|
}).then(function (obj) { |
|
|
|
obj.checkpoint = parseInt(obj.checkpoint, 10) || 0; |
|
|
|
// 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 () { |
|
|
|
return pyobj; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]); |
|
|
|
function getOrCreateRenewal(args) { |
|
|
|
return readRenewalConfig(args).then(function (pyobj) { |
|
|
|
var minver = pyobj.checkpoints >= 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'); |
|
|
|
args.pyobj = pyobj; |
|
|
|
|
|
|
|
if (args.debug) { |
|
|
|
console.log('################ privkeyPath ################'); |
|
|
|
console.log(privkeyPath); |
|
|
|
if (!minver) { |
|
|
|
args.checkpoints = 0; |
|
|
|
pyobj.checkpoints = 0; |
|
|
|
return writeRenewalConfig(args); |
|
|
|
} |
|
|
|
|
|
|
|
var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]); |
|
|
|
|
|
|
|
var checkpoint = obj.checkpoint.toString(); |
|
|
|
var certArchive = path.join(archiveDir, 'cert' + checkpoint + '.pem'); |
|
|
|
var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoint + '.pem'); |
|
|
|
var chainArchive = path.join(archiveDir, 'chain'+ checkpoint + '.pem'); |
|
|
|
var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoint + '.pem'); |
|
|
|
|
|
|
|
return mkdirpAsync(archiveDir).then(function () { |
|
|
|
return PromiseA.all([ |
|
|
|
sfs.writeFileAsync(certArchive, result.cert, 'ascii') |
|
|
|
, sfs.writeFileAsync(chainArchive, result.ca || result.chain, 'ascii') |
|
|
|
, sfs.writeFileAsync(fullchainArchive, result.fullchain, 'ascii') |
|
|
|
, sfs.writeFileAsync(privkeyArchive, result.key || result.privkey || args.domainPrivateKeyPem, 'ascii') |
|
|
|
]); |
|
|
|
}).then(function () { |
|
|
|
return mkdirpAsync(liveDir); |
|
|
|
}).then(function () { |
|
|
|
return PromiseA.all([ |
|
|
|
sfs.writeFileAsync(certPath, result.cert, 'ascii') |
|
|
|
, sfs.writeFileAsync(chainPath, result.ca || result.chain, 'ascii') |
|
|
|
, sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii') |
|
|
|
, sfs.writeFileAsync(privkeyPath, result.key || result.privkey || args.domainPrivateKeyPem, 'ascii') |
|
|
|
]); |
|
|
|
}).then(function () { |
|
|
|
obj.checkpoint += 1; |
|
|
|
|
|
|
|
var updates = { |
|
|
|
account: args.accountId || args.account.id |
|
|
|
|
|
|
|
, cert: certPath |
|
|
|
, privkey: privkeyPath |
|
|
|
, chain: chainPath |
|
|
|
, fullchain: fullchainPath |
|
|
|
, configDir: args.configDir |
|
|
|
, workDir: args.workDir |
|
|
|
, tos: args.agreeTos && true |
|
|
|
, http01Port: args.http01Port |
|
|
|
, keyPath: args.domainPrivateKeyPath || args.privkeyPath |
|
|
|
, email: args.email |
|
|
|
, domains: args.domains |
|
|
|
, rsaKeySize: args.rsaKeySize |
|
|
|
, checkpoints: obj.checkpoint |
|
|
|
// 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
|
|
|
|
// yes, it's an array. weird, right?
|
|
|
|
, webrootPath: args.webrootPath && [args.webrootPath] || [] |
|
|
|
, server: args.server || args.acmeDiscoveryUrl |
|
|
|
, logsDir: args.logsDir |
|
|
|
}; |
|
|
|
|
|
|
|
// final section is completely dynamic
|
|
|
|
// :hostname = :webroot_path
|
|
|
|
args.domains.forEach(function (hostname) { |
|
|
|
updates[hostname] = args.webrootPath; |
|
|
|
}); |
|
|
|
// args.account.id = pyobj.account
|
|
|
|
// args.configDir = args.configDir || pyobj.configDir;
|
|
|
|
|
|
|
|
// must write back to the original object or
|
|
|
|
// annotations will be lost
|
|
|
|
Object.keys(updates).forEach(function (key) { |
|
|
|
obj[key] = updates[key]; |
|
|
|
}); |
|
|
|
args.checkpoints = pyobj.checkpoints; |
|
|
|
|
|
|
|
return mkdirpAsync(path.dirname(args.renewalPath)).then(function () { |
|
|
|
return pyconf.writeFileAsync(args.renewalPath, obj); |
|
|
|
}); |
|
|
|
}).then(function () { |
|
|
|
|
|
|
|
return { |
|
|
|
certPath: certPath |
|
|
|
, chainPath: chainPath |
|
|
|
, fullchainPath: fullchainPath |
|
|
|
, privkeyPath: privkeyPath |
|
|
|
|
|
|
|
// some ambiguity here...
|
|
|
|
, privkey: result.key || result.privkey || args.domainPrivateKeyPem |
|
|
|
, fullchain: result.fullchain || result.cert |
|
|
|
, chain: result.ca || result.chain |
|
|
|
// especially this one... might be cert only, might be fullchain
|
|
|
|
, cert: result.cert |
|
|
|
|
|
|
|
, issuedAt: Date.now() |
|
|
|
, lifetime: defaults.lifetime || handlers.lifetime |
|
|
|
}; |
|
|
|
}); |
|
|
|
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, defaults, handlers) { |
|
|
|
if (args.debug) { |
|
|
|
console.log("got certificate!"); |
|
|
|
} |
|
|
|
|
|
|
|
var obj = args.pyobj; |
|
|
|
var result = args.pems; |
|
|
|
|
|
|
|
result.fullchain = result.cert + '\n' + result.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'); |
|
|
|
|
|
|
|
if (args.debug) { |
|
|
|
console.log('################ privkeyPath ################'); |
|
|
|
console.log(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, result.cert, 'ascii') |
|
|
|
, sfs.writeFileAsync(chainArchive, result.ca || result.chain, 'ascii') |
|
|
|
, sfs.writeFileAsync(fullchainArchive, result.fullchain, 'ascii') |
|
|
|
, sfs.writeFileAsync(privkeyArchive, result.key || result.privkey || args.domainPrivateKeyPem, 'ascii') |
|
|
|
]); |
|
|
|
}).then(function () { |
|
|
|
return mkdirpAsync(liveDir); |
|
|
|
}).then(function () { |
|
|
|
return PromiseA.all([ |
|
|
|
sfs.writeFileAsync(certPath, result.cert, 'ascii') |
|
|
|
, sfs.writeFileAsync(chainPath, result.ca || result.chain, 'ascii') |
|
|
|
, sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii') |
|
|
|
, sfs.writeFileAsync(privkeyPath, result.key || result.privkey || args.domainPrivateKeyPem, 'ascii') |
|
|
|
]); |
|
|
|
}).then(function () { |
|
|
|
obj.checkpoints += 1; |
|
|
|
args.checkpoints += 1; |
|
|
|
|
|
|
|
return writeRenewalConfig(args); |
|
|
|
}).then(function () { |
|
|
|
|
|
|
|
return { |
|
|
|
certPath: certPath |
|
|
|
, chainPath: chainPath |
|
|
|
, fullchainPath: fullchainPath |
|
|
|
, privkeyPath: privkeyPath |
|
|
|
|
|
|
|
// some ambiguity here...
|
|
|
|
, privkey: result.key || result.privkey || args.domainPrivateKeyPem |
|
|
|
, fullchain: result.fullchain || result.cert |
|
|
|
, chain: result.ca || result.chain |
|
|
|
// especially this one... might be cert only, might be fullchain
|
|
|
|
, cert: result.cert |
|
|
|
|
|
|
|
, issuedAt: Date.now() |
|
|
|
, lifetime: defaults.lifetime || handlers.lifetime |
|
|
|
}; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
function getCertificateAsync(account, args, defaults, handlers) { |
|
|
|
function getCertificateAsync(args, defaults, handlers) { |
|
|
|
var account = args.account; |
|
|
|
|
|
|
|
return leCrypto.generateRsaKeypairAsync(args.rsaKeySize, 65537).then(function (domainKey) { |
|
|
|
if (args.debug) { |
|
|
|
console.log("get certificate"); |
|
|
@ -214,11 +288,12 @@ function getCertificateAsync(account, args, defaults, handlers) { |
|
|
|
} |
|
|
|
}); |
|
|
|
}).then(function (results) { |
|
|
|
return writeCertificateAsync(results, args, defaults, handlers); |
|
|
|
args.pems = results; |
|
|
|
return writeCertificateAsync(args, defaults, handlers); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
function getOrCreateDomainCertificate(account, args, defaults, handlers) { |
|
|
|
function getOrCreateDomainCertificate(args, defaults, handlers) { |
|
|
|
return fetchFromConfigLiveDir(args).then(function (certs) { |
|
|
|
// if nothing, register and save
|
|
|
|
// if something, check date (don't register unless 30+ days)
|
|
|
@ -228,15 +303,15 @@ function getOrCreateDomainCertificate(account, args, defaults, handlers) { |
|
|
|
//console.log(certs);
|
|
|
|
if (!certs) { |
|
|
|
// no certs, seems like a good time to get some
|
|
|
|
return getCertificateAsync(account, args, defaults, handlers); |
|
|
|
return getCertificateAsync(args, defaults, handlers); |
|
|
|
} |
|
|
|
else if (certs.issuedAt > (27 * 24 * 60 * 60 * 1000)) { |
|
|
|
// cert is at least 27 days old we can renew that
|
|
|
|
return getCertificateAsync(account, args, defaults, handlers); |
|
|
|
return getCertificateAsync(args, defaults, handlers); |
|
|
|
} |
|
|
|
else if (args.duplicate) { |
|
|
|
// YOLO! I be gettin' fresh certs 'erday! Yo!
|
|
|
|
return getCertificateAsync(account, args, defaults, handlers); |
|
|
|
return getCertificateAsync(args, defaults, handlers); |
|
|
|
} |
|
|
|
else { |
|
|
|
console.warn('[WARN] Ignoring renewal attempt for certificate less than 27 days old. Use args.duplicate to force.'); |
|
|
@ -244,16 +319,10 @@ function getOrCreateDomainCertificate(account, args, defaults, handlers) { |
|
|
|
return certs; |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
function getOrCreateAcmeAccount(args, defaults, handlers) { |
|
|
|
var pyconf = PromiseA.promisifyAll(require('pyconf')); |
|
|
|
var server = args.server; |
|
|
|
var acmeHostname = require('url').parse(server).hostname; |
|
|
|
var configDir = args.configDir; |
|
|
|
|
|
|
|
args.renewalPath = args.renewalPath || path.join(configDir, 'renewal', args.domains[0] + '.conf'); |
|
|
|
args.accountsDir = args.accountsDir || path.join(configDir, 'accounts', acmeHostname, 'directory'); |
|
|
|
|
|
|
|
return pyconf.readFileAsync(args.renewalPath).then(function (renewal) { |
|
|
|
var accountId = renewal.account; |
|
|
@ -279,7 +348,8 @@ function getOrCreateAcmeAccount(args, defaults, handlers) { |
|
|
|
if (args.debug) { |
|
|
|
console.log('[LE] use account'); |
|
|
|
} |
|
|
|
return Accounts.getAccount(accountId, args, handlers); |
|
|
|
args.accountId = accountId; |
|
|
|
return Accounts.getAccount(args, handlers); |
|
|
|
} else { |
|
|
|
if (args.debug) { |
|
|
|
console.log('[LE] create account'); |
|
|
@ -322,13 +392,25 @@ module.exports.create = function (defaults, handlers) { |
|
|
|
copy = merge(args, defaults); |
|
|
|
tplCopy(copy); |
|
|
|
|
|
|
|
if (args.debug) { |
|
|
|
if (copy.debug) { |
|
|
|
console.log('[LE DEBUG] reg domains', args.domains); |
|
|
|
} |
|
|
|
|
|
|
|
var url = require('url'); |
|
|
|
var acmeLocation = url.parse(copy.server); |
|
|
|
var acmeHostpath = path.join(acmeLocation.hostname, acmeLocation.pathname); |
|
|
|
copy.renewalPath = copy.renewalPath || path.join(copy.configDir, 'renewal', copy.domains[0] + '.conf'); |
|
|
|
copy.accountsDir = copy.accountsDir || path.join(copy.configDir, 'accounts', acmeHostpath); |
|
|
|
|
|
|
|
return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) { |
|
|
|
console.log("account", account); |
|
|
|
args.account = account; |
|
|
|
return getOrCreateDomainCertificate(account, copy, defaults, handlers); |
|
|
|
//console.log("account", account);
|
|
|
|
copy.account = account; |
|
|
|
|
|
|
|
return getOrCreateRenewal(copy).then(function (pyobj) { |
|
|
|
|
|
|
|
copy.pyobj = pyobj; |
|
|
|
return getOrCreateDomainCertificate(copy, defaults, handlers); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
, fetchAsync: function (args) { |
|
|
|