| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var U = module.exports; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var promisify = require('util').promisify; | 
					
						
							| 
									
										
										
										
											2019-10-27 04:38:05 -06:00
										 |  |  | //var resolveSoa = promisify(require('dns').resolveSoa);
 | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | var resolveMx = promisify(require('dns').resolveMx); | 
					
						
							|  |  |  | var punycode = require('punycode'); | 
					
						
							|  |  |  | var Keypairs = require('@root/keypairs'); | 
					
						
							|  |  |  | // TODO move to @root
 | 
					
						
							|  |  |  | var certParser = require('cert-info'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._parseDuration = function(str) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     if ('number' === typeof str) { | 
					
						
							|  |  |  |         return str; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var pattern = /^(\-?\d+(\.\d+)?)([wdhms]|ms)$/; | 
					
						
							|  |  |  |     var matches = str.match(pattern); | 
					
						
							|  |  |  |     if (!matches || !matches[0]) { | 
					
						
							|  |  |  |         throw new Error('invalid duration string: ' + str); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var n = parseInt(matches[1], 10); | 
					
						
							|  |  |  |     var unit = matches[3]; | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     switch (unit) { | 
					
						
							|  |  |  |         case 'w': | 
					
						
							|  |  |  |             n *= 7; | 
					
						
							|  |  |  |         /*falls through*/ | 
					
						
							|  |  |  |         case 'd': | 
					
						
							|  |  |  |             n *= 24; | 
					
						
							|  |  |  |         /*falls through*/ | 
					
						
							|  |  |  |         case 'h': | 
					
						
							|  |  |  |             n *= 60; | 
					
						
							|  |  |  |         /*falls through*/ | 
					
						
							|  |  |  |         case 'm': | 
					
						
							|  |  |  |             n *= 60; | 
					
						
							|  |  |  |         /*falls through*/ | 
					
						
							|  |  |  |         case 's': | 
					
						
							|  |  |  |             n *= 1000; | 
					
						
							|  |  |  |         /*falls through*/ | 
					
						
							|  |  |  |         case 'ms': | 
					
						
							|  |  |  |             n *= 1; // for completeness
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return n; | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._encodeName = function(str) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return punycode.toASCII(str.toLowerCase(str)); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._validName = function(str) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     // A quick check of the 38 and two ½ valid characters
 | 
					
						
							|  |  |  |     // 253 char max full domain, including dots
 | 
					
						
							|  |  |  |     // 63 char max each label segment
 | 
					
						
							|  |  |  |     // Note: * is not allowed, but it's allowable here
 | 
					
						
							|  |  |  |     // Note: _ (underscore) is only allowed for "domain names", not "hostnames"
 | 
					
						
							|  |  |  |     // Note: - (hyphen) is not allowed as a first character (but a number is)
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |         /^(\*\.)?[a-z0-9_\.\-]+$/.test(str) && | 
					
						
							|  |  |  |         str.length < 254 && | 
					
						
							|  |  |  |         str.split('.').every(function(label) { | 
					
						
							|  |  |  |             return label.length > 0 && label.length < 64; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._validMx = function(email) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var host = email.split('@').slice(1)[0]; | 
					
						
							|  |  |  |     // try twice, just because DNS hiccups sometimes
 | 
					
						
							|  |  |  |     // Note: we don't care if the domain exists, just that it *can* exist
 | 
					
						
							|  |  |  |     return resolveMx(host).catch(function() { | 
					
						
							|  |  |  |         return U._timeout(1000).then(function() { | 
					
						
							|  |  |  |             return resolveMx(host); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // should be called after _validName
 | 
					
						
							|  |  |  | U._validDomain = function(str) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     // TODO use @root/dns (currently dns-suite)
 | 
					
						
							|  |  |  |     // because node's dns can't read Authority records
 | 
					
						
							|  |  |  |     return Promise.resolve(str); | 
					
						
							|  |  |  |     /* | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 	// try twice, just because DNS hiccups sometimes
 | 
					
						
							|  |  |  | 	// Note: we don't care if the domain exists, just that it *can* exist
 | 
					
						
							|  |  |  | 	return resolveSoa(str).catch(function() { | 
					
						
							|  |  |  | 		return U._timeout(1000).then(function() { | 
					
						
							|  |  |  | 			return resolveSoa(str); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  |   */ | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // foo.example.com and *.example.com overlap
 | 
					
						
							|  |  |  | // should be called after _validName
 | 
					
						
							|  |  |  | // (which enforces *. or no *)
 | 
					
						
							|  |  |  | U._uniqueNames = function(altnames) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var dups = {}; | 
					
						
							|  |  |  |     var wilds = {}; | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |         altnames.some(function(w) { | 
					
						
							|  |  |  |             if ('*.' !== w.slice(0, 2)) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (wilds[w]) { | 
					
						
							|  |  |  |                 return true; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             wilds[w] = true; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return altnames.every(function(name) { | 
					
						
							|  |  |  |         var w; | 
					
						
							|  |  |  |         if ('*.' !== name.slice(0, 2)) { | 
					
						
							|  |  |  |             w = | 
					
						
							|  |  |  |                 '*.' + | 
					
						
							|  |  |  |                 name | 
					
						
							|  |  |  |                     .split('.') | 
					
						
							|  |  |  |                     .slice(1) | 
					
						
							|  |  |  |                     .join('.'); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |         if (!dups[name] && !dups[w]) { | 
					
						
							|  |  |  |             dups[name] = true; | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._timeout = function(d) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return new Promise(function(resolve) { | 
					
						
							|  |  |  |         setTimeout(resolve, d); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._genKeypair = function(keyType) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var keyopts; | 
					
						
							|  |  |  |     var len = parseInt(keyType.replace(/.*?(\d)/, '$1') || 0, 10); | 
					
						
							|  |  |  |     if (/RSA/.test(keyType)) { | 
					
						
							|  |  |  |         keyopts = { | 
					
						
							|  |  |  |             kty: 'RSA', | 
					
						
							|  |  |  |             modulusLength: len || 2048 | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } else if (/^(EC|P\-?\d)/i.test(keyType)) { | 
					
						
							|  |  |  |         keyopts = { | 
					
						
							|  |  |  |             kty: 'EC', | 
					
						
							|  |  |  |             namedCurve: 'P-' + (len || 256) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         // TODO put in ./errors.js
 | 
					
						
							|  |  |  |         throw new Error('invalid key type: ' + keyType); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return Keypairs.generate(keyopts).then(function(pair) { | 
					
						
							|  |  |  |         return U._jwkToSet(pair.private); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TODO use ACME._importKeypair ??
 | 
					
						
							|  |  |  | U._importKeypair = function(keypair) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     // this should import all formats equally well:
 | 
					
						
							|  |  |  |     // 'object' (JWK), 'string' (private key pem), kp.privateKeyPem, kp.privateKeyJwk
 | 
					
						
							|  |  |  |     if (keypair.private || keypair.d) { | 
					
						
							|  |  |  |         return U._jwkToSet(keypair.private || keypair); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (keypair.privateKeyJwk) { | 
					
						
							|  |  |  |         return U._jwkToSet(keypair.privateKeyJwk); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     if ('string' !== typeof keypair && !keypair.privateKeyPem) { | 
					
						
							|  |  |  |         // TODO put in errors
 | 
					
						
							|  |  |  |         throw new Error('missing private key'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return Keypairs.import({ pem: keypair.privateKeyPem || keypair }).then( | 
					
						
							|  |  |  |         function(priv) { | 
					
						
							|  |  |  |             if (!priv.d) { | 
					
						
							|  |  |  |                 throw new Error('missing private key'); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return U._jwkToSet(priv); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._jwkToSet = function(jwk) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var keypair = { | 
					
						
							|  |  |  |         privateKeyJwk: jwk | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     return Promise.all([ | 
					
						
							|  |  |  |         Keypairs.export({ | 
					
						
							|  |  |  |             jwk: jwk, | 
					
						
							|  |  |  |             encoding: 'pem' | 
					
						
							|  |  |  |         }).then(function(pem) { | 
					
						
							|  |  |  |             keypair.privateKeyPem = pem; | 
					
						
							|  |  |  |         }), | 
					
						
							|  |  |  |         Keypairs.export({ | 
					
						
							|  |  |  |             jwk: jwk, | 
					
						
							|  |  |  |             encoding: 'pem', | 
					
						
							|  |  |  |             public: true | 
					
						
							|  |  |  |         }).then(function(pem) { | 
					
						
							|  |  |  |             keypair.publicKeyPem = pem; | 
					
						
							|  |  |  |         }), | 
					
						
							|  |  |  |         Keypairs.publish({ | 
					
						
							|  |  |  |             jwk: jwk | 
					
						
							|  |  |  |         }).then(function(pub) { | 
					
						
							|  |  |  |             keypair.publicKeyJwk = pub; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     ]).then(function() { | 
					
						
							|  |  |  |         return keypair; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._attachCertInfo = function(results) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var certInfo = certParser.info(results.cert); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     // subject, altnames, issuedAt, expiresAt
 | 
					
						
							|  |  |  |     Object.keys(certInfo).forEach(function(key) { | 
					
						
							|  |  |  |         results[key] = certInfo[key]; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return results; | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._certHasDomain = function(certInfo, _domain) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var names = (certInfo.altnames || []).slice(0); | 
					
						
							|  |  |  |     return names.some(function(name) { | 
					
						
							|  |  |  |         var domain = _domain.toLowerCase(); | 
					
						
							|  |  |  |         name = name.toLowerCase(); | 
					
						
							|  |  |  |         if ('*.' === name.substr(0, 2)) { | 
					
						
							|  |  |  |             name = name.substr(2); | 
					
						
							|  |  |  |             domain = domain | 
					
						
							|  |  |  |                 .split('.') | 
					
						
							|  |  |  |                 .slice(1) | 
					
						
							|  |  |  |                 .join('.'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return name === domain; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // a bit heavy to be labeled 'utils'... perhaps 'common' would be better?
 | 
					
						
							|  |  |  | U._getOrCreateKeypair = function(db, subject, query, keyType, mustExist) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     var exists = false; | 
					
						
							|  |  |  |     return db | 
					
						
							|  |  |  |         .checkKeypair(query) | 
					
						
							|  |  |  |         .then(function(kp) { | 
					
						
							|  |  |  |             if (kp) { | 
					
						
							|  |  |  |                 exists = true; | 
					
						
							|  |  |  |                 return U._importKeypair(kp); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |             if (mustExist) { | 
					
						
							|  |  |  |                 // TODO put in errors
 | 
					
						
							|  |  |  |                 throw new Error( | 
					
						
							|  |  |  |                     'required keypair not found: ' + | 
					
						
							|  |  |  |                         (subject || '') + | 
					
						
							|  |  |  |                         ' ' + | 
					
						
							|  |  |  |                         JSON.stringify(query) | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |             return U._genKeypair(keyType); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .then(function(keypair) { | 
					
						
							|  |  |  |             return { exists: exists, keypair: keypair }; | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | U._getKeypair = function(db, subject, query) { | 
					
						
							| 
									
										
										
										
											2019-10-31 16:26:18 -06:00
										 |  |  |     return U._getOrCreateKeypair(db, subject, query, '', true).then(function( | 
					
						
							|  |  |  |         result | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         return result.keypair; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-10-20 02:51:19 -06:00
										 |  |  | }; |