diff --git a/src/apps.js b/src/apps.js index 4a0e128f0..e170ef0f2 100644 --- a/src/apps.js +++ b/src/apps.js @@ -609,8 +609,7 @@ function install(data, user, auditSource, callback) { if (error) return callback(new AppsError(AppsError.BAD_FIELD, 'Bad location: ' + error.message)); if (cert && key) { - let fqdn = domains.fqdn(location, domainObject); - error = reverseProxy.validateCertificate(fqdn, cert, key); + error = reverseProxy.validateCertificate(location, domainObject, cert, key); if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); } @@ -653,9 +652,8 @@ function install(data, user, auditSource, callback) { // save cert to boxdata/certs if (cert && key) { - let fqdn = domains.fqdn(location, domainObject); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, fqdn + '.user.cert'), cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, fqdn + '.user.key'), key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); + let error = reverseProxy.setAppCertificateSync(location, domainObject, cert, key); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error setting cert: ' + error.message)); } taskmanager.restartAppTask(appId); @@ -765,18 +763,13 @@ function configure(appId, data, user, auditSource, callback) { // save cert to boxdata/certs. TODO: move this to apptask when we have a real task queue if ('cert' in data && 'key' in data) { - let fqdn = domains.fqdn(location, domainObject); - if (data.cert && data.key) { - error = reverseProxy.validateCertificate(fqdn, data.cert, data.key); + error = reverseProxy.validateCertificate(location, domainObject, data.cert, data.key); if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); - - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.cert`), data.cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.key`), data.key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); - } else { // remove existing cert/key - if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.cert`))) debug('Error removing cert: ' + safe.error.message); - if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.key`))) debug('Error removing key: ' + safe.error.message); } + + error = reverseProxy.setAppCertificateSync(location, domainObject, data.cert, data.key); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error setting cert: ' + error.message)); } if ('enableBackup' in data) values.enableBackup = data.enableBackup; @@ -1050,7 +1043,7 @@ function clone(appId, data, user, auditSource, callback) { // if purchase failed, rollback the appdb record if (appstoreError) { appdb.del(newAppId, function (error) { - if (error) console.error('Failed to rollback app installation.', error); + if (error) debug('install: Failed to rollback app installation.', error); if (appstoreError.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, appstoreError.message)); if (appstoreError && appstoreError.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, appstoreError.message)); diff --git a/src/domains.js b/src/domains.js index 0fff1737f..ba85db0d0 100644 --- a/src/domains.js +++ b/src/domains.js @@ -26,6 +26,8 @@ module.exports = exports = { makeWildcard: makeWildcard, + parentDomain: parentDomain, + DomainsError: DomainsError, // exported for testing @@ -102,6 +104,11 @@ function api(provider) { } } +function parentDomain(domain) { + assert.strictEqual(typeof domain, 'string'); + return domain.replace(/^\S+?\./, ''); // +? means non-greedy +} + function verifyDnsConfig(dnsConfig, domain, zoneName, provider, ip, callback) { assert(dnsConfig && typeof dnsConfig === 'object'); // the dns config to test with assert.strictEqual(typeof domain, 'string'); @@ -212,9 +219,11 @@ function add(domain, zoneName, provider, dnsConfig, fallbackCertificate, tlsConf } if (fallbackCertificate) { - let subdomain = dnsConfig.hyphenatedSubdomains ? `test-${domain}` : `test.${domain}`; - let error = reverseProxy.validateCertificate(subdomain, fallbackCertificate.cert, fallbackCertificate.key); + let error = reverseProxy.validateCertificate('test', { config: dnsConfig }, fallbackCertificate.cert, fallbackCertificate.key); if (error) return callback(new DomainsError(DomainsError.BAD_FIELD, error.message)); + } else { + fallbackCertificate = reverseProxy.generateFallbackCertificateSync({ domain: domain, config: dnsConfig }); + if (fallbackCertificate.error) return callback(new DomainsError(DomainsError.INTERNAL_ERROR, fallbackCertificate.error)); } let error = validateTlsConfig(tlsConfig, provider); @@ -291,19 +300,18 @@ function update(domain, zoneName, provider, dnsConfig, fallbackCertificate, tlsC assert.strictEqual(typeof tlsConfig, 'object'); assert.strictEqual(typeof callback, 'function'); - domaindb.get(domain, function (error, result) { + domaindb.get(domain, function (error, domainObject) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new DomainsError(DomainsError.NOT_FOUND)); if (error) return callback(new DomainsError(DomainsError.INTERNAL_ERROR, error)); if (zoneName) { if (!tld.isValid(zoneName)) return callback(new DomainsError(DomainsError.BAD_FIELD, 'Invalid zoneName')); } else { - zoneName = result.zoneName; + zoneName = domainObject.zoneName; } if (fallbackCertificate) { - let subdomain = dnsConfig.hyphenatedSubdomains ? `test-${domain}` : `test.${domain}`; - let error = reverseProxy.validateCertificate(subdomain, fallbackCertificate.cert, fallbackCertificate.key); + let error = reverseProxy.validateCertificate('test', domainObject, fallbackCertificate.cert, fallbackCertificate.key); if (error) return callback(new DomainsError(DomainsError.BAD_FIELD, error.message)); } diff --git a/src/reverseproxy.js b/src/reverseproxy.js index db4a49062..e0efa5b40 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -6,6 +6,9 @@ exports = module.exports = { setFallbackCertificate: setFallbackCertificate, getFallbackCertificate: getFallbackCertificate, + generateFallbackCertificateSync: generateFallbackCertificateSync, + setAppCertificateSync: setAppCertificateSync, + validateCertificate: validateCertificate, getCertificate: getCertificate, @@ -148,8 +151,9 @@ function providerMatchesSync(certFilePath, apiOptions) { // note: https://tools.ietf.org/html/rfc4346#section-7.4.2 (certificate_list) requires that the // servers certificate appears first (and not the intermediate cert) -function validateCertificate(domain, cert, key) { - assert.strictEqual(typeof domain, 'string'); +function validateCertificate(location, domain, cert, key) { + assert.strictEqual(typeof location, 'string'); + assert.strictEqual(typeof domain, 'object'); assert.strictEqual(typeof cert, 'string'); assert.strictEqual(typeof key, 'string'); @@ -158,7 +162,9 @@ function validateCertificate(domain, cert, key) { if (cert && !key) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, 'missing key'); // -checkhost checks for SAN or CN exclusively. SAN takes precedence and if present, ignores the CN. - var result = safe.child_process.execSync(`openssl x509 -noout -checkhost "${domain}"`, { encoding: 'utf8', input: cert }); + const fqdn = domains.fqdn(location, domain); + + var result = safe.child_process.execSync(`openssl x509 -noout -checkhost "${fqdn}"`, { encoding: 'utf8', input: cert }); if (!result) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, 'Unable to get certificate subject.'); if (result.indexOf('does match certificate') === -1) return new ReverseProxyError(ReverseProxyError.INVALID_CERT, `Certificate is not valid for this domain. Expecting ${domain}`); @@ -181,31 +187,51 @@ function reload(callback) { shell.sudo('reload', [ RELOAD_NGINX_CMD ], callback); } +function generateFallbackCertificateSync(domainObject) { + assert.strictEqual(typeof domainObject, 'object'); + + const domain = domainObject.domain; + const certFilePath = path.join(os.tmpdir(), `${domain}-${crypto.randomBytes(4).readUInt32LE(0)}.cert`); + const keyFilePath = path.join(os.tmpdir(), `${domain}-${crypto.randomBytes(4).readUInt32LE(0)}.key`); + + let opensslConf = safe.fs.readFileSync('/etc/ssl/openssl.cnf', 'utf8'); + // SAN must contain all the domains since CN check is based on implementation if SAN is found. -checkhost also checks only SAN if present! + let opensslConfWithSan; + if (domainObject.config.hyphenatedSubdomains) { + let parentDomain = domains.parentDomain(domain); + opensslConfWithSan = `${opensslConf}\n[SAN]\nsubjectAltName=DNS:${domain},DNS:*.${parentDomain}\n`; + } else { + opensslConfWithSan = `${opensslConf}\n[SAN]\nsubjectAltName=DNS:${domain},DNS:*.${domain}\n`; + } + let configFile = path.join(os.tmpdir(), 'openssl-' + crypto.randomBytes(4).readUInt32LE(0) + '.conf'); + safe.fs.writeFileSync(configFile, opensslConfWithSan, 'utf8'); + let certCommand = util.format(`openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 3650 -subj /CN=*.${domain} -extensions SAN -config ${configFile} -nodes`); + if (!safe.child_process.execSync(certCommand)) return { error: new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message) }; + safe.fs.unlinkSync(configFile); + + const cert = safe.fs.readFileSync(certFilePath, 'utf8'); + if (!cert) return { error: safe.error }; + safe.fs.unlinkSync(certFilePath); + + const key = safe.fs.readFileSync(keyFilePath, 'utf8'); + if (!key) return { error: safe.error }; + safe.fs.unlinkSync(keyFilePath); + + return { cert: cert, key: key, error: null }; +} + function setFallbackCertificate(domain, fallback, callback) { assert.strictEqual(typeof domain, 'string'); + assert(fallback && typeof fallback === 'object'); assert.strictEqual(typeof fallback, 'object'); assert.strictEqual(typeof callback, 'function'); - const certFilePath = path.join(paths.APP_CERTS_DIR, `${domain}.host.cert`); - const keyFilePath = path.join(paths.APP_CERTS_DIR, `${domain}.host.key`); - - if (fallback) { - if (fallback.restricted) { // restricted certs are not backed up - if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.cert`), fallback.cert)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); - if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.key`), fallback.key)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); - } else { - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${domain}.host.cert`), fallback.cert)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${domain}.host.key`), fallback.key)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); - } - } else if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { // generate it - let opensslConf = safe.fs.readFileSync('/etc/ssl/openssl.cnf', 'utf8'); - // SAN must contain all the domains since CN check is based on implementation if SAN is found. -checkhost also checks only SAN if present! - let opensslConfWithSan = `${opensslConf}\n[SAN]\nsubjectAltName=DNS:${domain},DNS:*.${domain}\n`; - let configFile = path.join(os.tmpdir(), 'openssl-' + crypto.randomBytes(4).readUInt32LE(0) + '.conf'); - safe.fs.writeFileSync(configFile, opensslConfWithSan, 'utf8'); - let certCommand = util.format(`openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 3650 -subj /CN=*.${domain} -extensions SAN -config ${configFile} -nodes`); - if (!safe.child_process.execSync(certCommand)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); - safe.fs.unlinkSync(configFile); + if (fallback.restricted) { // restricted certs are not backed up + if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.cert`), fallback.cert)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); + if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.key`), fallback.key)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); + } else { + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${domain}.host.cert`), fallback.cert)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${domain}.host.key`), fallback.key)) return callback(new ReverseProxyError(ReverseProxyError.INTERNAL_ERROR, safe.error.message)); } platform.handleCertChanged('*.' + domain); @@ -234,6 +260,24 @@ function getFallbackCertificate(domain, callback) { callback(null, { certFilePath, keyFilePath, type: 'fallback' }); } +function setAppCertificateSync(location, domainObject, cert, key) { + assert.strictEqual(typeof location, 'string'); + assert.strictEqual(typeof domainObject, 'object'); + assert.strictEqual(typeof cert, 'string'); + assert.strictEqual(typeof key, 'string'); + + let fqdn = domains.fqdn(location, domainObject); + if (cert && key) { + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.cert`), cert)) return safe.error; + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.key`), key)) return safe.error; + } else { // remove existing cert/key + if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.cert`))) debug('Error removing cert: ' + safe.error.message); + if (!safe.fs.unlinkSync(path.join(paths.APP_CERTS_DIR, `${fqdn}.user.key`))) debug('Error removing key: ' + safe.error.message); + } + + return null; +} + function getCertificateByHostname(hostname, domain, callback) { assert.strictEqual(typeof hostname, 'string'); assert.strictEqual(typeof domain, 'string'); diff --git a/src/test/reverseproxy-test.js b/src/test/reverseproxy-test.js index 7531b1e4b..508d603fc 100644 --- a/src/test/reverseproxy-test.js +++ b/src/test/reverseproxy-test.js @@ -39,6 +39,15 @@ function cleanup(done) { describe('Certificates', function () { describe('validateCertificate', function () { + let foobarDomain = { + domain: 'foobar.com', + config: { hypenatedSubdomains: false } + }; + + let amazingDomain = { + domain: 'amazing.com', + config: {} + }; /* Generate these with: openssl genrsa -out server.key 512 @@ -65,53 +74,94 @@ describe('Certificates', function () { var validKey3 = '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+nx2TXe7i+YB8\np9E047z6tVDyr6/JHyxxz0S6PSlOh0sF+UzYrpmTSKyKtHMIv+N3ifUDpCs94n6v\nYnjG0eTaP6lq+3xJrYajYzOkB7c5O4ipgl9S3sibd/YtvruK1M+ukDZNHcD90MWV\nrOO754F3e3fj3oOjxoE+lcB05pt8+yu/ecTH3u+uvi3N4reQb27sZi6h3mDKLAXa\n3nBJ4xZsWTaiqQ4K0dFcTvskMpXEgaJ6mdROeRb1vuMiSIFDXHTEcbiyh7BXbKYY\ndLZetQdnjkUUwbvJVinK0pP0jP23L/PlVwtno3OBT+kp9Boh7wymgOlV/lQ14HYM\n4AASgkorAgMBAAECggEAdVSVLMcNqlGuv4vAHtDq2lpOaAKxrZbtkWPlxsisqzRl\nfljT7y+RQfHimkG16LXL+iFFWadsIlxOY/+1nZNGTPwQeNQwzVzs2ZbPC3DgW28E\nkGm56NVOHzu4oLGc2DhjWOxVMCRXTSN66sUPK/K0YunxgqXM2zrtBKvCWXI0VLlo\nN/UWAwHf4i0GWRl8u8PvxgMXlSW9p9l6gSsivWRMag9ADwRQ/NSKrRYkiOoRe3vz\nLxXARBvzeZXvOPVLGVRX4SIR7OmS8cC6Ol/rp1/ZFFID7aN+wdzphPSL1UNUriw4\nDv1mxz73SNakgeYSFBoWRS5BsJI01JoCoILsnhVCiQKBgQDyW+k5+j4K17fzwsmi\nyxZ0Nz/ncpkqxVrWYZM3pn7OVkb2NDArimEk53kmJ0hrT84kKJUYDx55R2TpnzpV\nMLmjxgs9TUrzZzsL/DP2ppkfE3OrPS+06OGa5GbURxD6KPvqDtOmU3oFyJ3f4YJR\nVK7RW+zO4sXEpHIxwdBXbYov1QKBgQDJWbt+W5M0sA2D5LrUBNMTvMdNnKH0syc2\nZlcIOdj6HuUIveYpBRq64Jn9VJpXMxQanwE+IUjCpPTa8wF0OA6MZPy6cfovqb8a\ni1/M/lvCoYVS3KHLcTOvTGD3xej0EUj13xWGNu8y3i7Z9/Bl21hEyjd0q0I5OqJx\no9Qa5TGR/wKBgBPfkYpdiMTe14i3ik09FgRFm4nhDcpCEKbPrYC8uF03Ge6KbQDF\nAh5ClN6aDggurRqt8Tvd0YPkZNP7aI8fxbk2PimystiuuFrNPX2WP6warjt2cvkE\nt6s522zAvxWkUrPor1ZONg1PXBLFrSf6J7OnNA3q7oina23FFM52fwRZAoGAZ7l7\nFffU2IKNI9HT0N7/YZ6RSVEUOXuFCsgjs5AhT5BUynERPTZs87I6gb9wltUwWRpq\nSHhbBDJ4FMa0jAtIq1hmvSF0EdOvJ9x+qJqr6JLOnMYd7zDMwFRna5yfigPRgx+9\n9dsc1CaTGiRYyg/5484MTWTgA51KC6Kq5IQHSj8CgYBr9rWgqM8hVCKSt1cMguQV\nTPaV97+u3kV2jFd/aVgDtCDIVvp5TPuqfskE1v3MsSjJ8hfHdYvyxZB8h8T4LlTD\n2HdxwCjVh2qirAvkar2b1mfA6R8msmVaIxBu4MqDcIPqR823klF7A8jSD3MGzYcU\nbnnxMdwgWQkmx0/6/90ZCg==\n-----END PRIVATE KEY-----\n'; it('does not allow empty string for cert', function () { - expect(reverseProxy.validateCertificate('foobar.com', '', 'key')).to.be.an(Error); + expect(reverseProxy.validateCertificate('', foobarDomain, '', 'key')).to.be.an(Error); }); it('does not allow empty string for key', function () { - expect(reverseProxy.validateCertificate('foobar.com', 'cert', '')).to.be.an(Error); + expect(reverseProxy.validateCertificate('', foobarDomain, 'cert', '')).to.be.an(Error); }); it('does not allow invalid cert', function () { - expect(reverseProxy.validateCertificate('foobar.com', 'someinvalidcert', validKey0)).to.be.an(Error); + expect(reverseProxy.validateCertificate('', foobarDomain, 'someinvalidcert', validKey0)).to.be.an(Error); }); it('does not allow invalid key', function () { - expect(reverseProxy.validateCertificate('foobar.com', validCert0, 'invalidkey')).to.be.an(Error); + expect(reverseProxy.validateCertificate('', foobarDomain, validCert0, 'invalidkey')).to.be.an(Error); }); it('does not allow cert without matching domain', function () { - expect(reverseProxy.validateCertificate('cloudron.io', validCert0, validKey0)).to.be.an(Error); + expect(reverseProxy.validateCertificate('', { domain: 'cloudron.io' }, validCert0, validKey0)).to.be.an(Error); + expect(reverseProxy.validateCertificate('cloudron.io', foobarDomain, validCert0, validKey0)).to.be.an(Error); }); it('allows valid cert with matching domain', function () { - expect(reverseProxy.validateCertificate('foobar.com', validCert0, validKey0)).to.be(null); + expect(reverseProxy.validateCertificate('', foobarDomain, validCert0, validKey0)).to.be(null); }); it('allows valid cert with matching domain (wildcard)', function () { - expect(reverseProxy.validateCertificate('abc.foobar.com', validCert1, validKey1)).to.be(null); + expect(reverseProxy.validateCertificate('abc', foobarDomain, validCert1, validKey1)).to.be(null); }); it('does now allow cert without matching domain (wildcard)', function () { - expect(reverseProxy.validateCertificate('foobar.com', validCert1, validKey1)).to.be.an(Error); - expect(reverseProxy.validateCertificate('bar.abc.foobar.com', validCert1, validKey1)).to.be.an(Error); + expect(reverseProxy.validateCertificate('', foobarDomain, validCert1, validKey1)).to.be.an(Error); + expect(reverseProxy.validateCertificate('bar.abc', foobarDomain, validCert1, validKey1)).to.be.an(Error); }); it('allows valid cert with matching domain (subdomain)', function () { - expect(reverseProxy.validateCertificate('baz.foobar.com', validCert2, validKey2)).to.be(null); + expect(reverseProxy.validateCertificate('baz', foobarDomain, validCert2, validKey2)).to.be(null); }); it('does not allow cert without matching domain (subdomain)', function () { - expect(reverseProxy.validateCertificate('baz.foobar.com', validCert0, validKey0)).to.be.an(Error); + expect(reverseProxy.validateCertificate('baz', foobarDomain, validCert0, validKey0)).to.be.an(Error); }); it('does not allow invalid cert/key tuple', function () { - expect(reverseProxy.validateCertificate('foobar.com', validCert0, validKey1)).to.be.an(Error); + expect(reverseProxy.validateCertificate('', foobarDomain, validCert0, validKey1)).to.be.an(Error); }); it('picks certificate in SAN', function () { - expect(reverseProxy.validateCertificate('amazing.com', validCert3, validKey3)).to.be(null); - expect(reverseProxy.validateCertificate('subdomain.amazing.com', validCert3, validKey3)).to.be(null); + expect(reverseProxy.validateCertificate('', amazingDomain, validCert3, validKey3)).to.be(null); + expect(reverseProxy.validateCertificate('subdomain', amazingDomain, validCert3, validKey3)).to.be(null); + }); + }); + + describe('generateFallbackCertificiate - non-hyphenated', function () { + let domainObject = { + domain: 'cool.com', + config: {} + }; + let result; + + it('can generate fallback certs', function () { + result = reverseProxy.generateFallbackCertificateSync(domainObject); + expect(result).to.be.ok(); + expect(result.error).to.be(null); + }); + + it('can validate the certs', function () { + expect(reverseProxy.validateCertificate('foo', domainObject, result.cert, result.key)).to.be(null); + expect(reverseProxy.validateCertificate('', domainObject, result.cert, result.key)).to.be(null); + }); + }); + + describe('generateFallbackCertificiate - hyphenated', function () { + let domainObject = { + domain: 'customer.cool.com', + config: { hyphenatedSubdomains: true } + }; + let result; + + it('can generate fallback certs', function () { + result = reverseProxy.generateFallbackCertificateSync(domainObject); + expect(result).to.be.ok(); + expect(result.error).to.be(null); + }); + + it('can validate the certs', function () { + expect(reverseProxy.validateCertificate('foo', domainObject, result.cert, result.key)).to.be(null); + expect(reverseProxy.validateCertificate('', domainObject, result.cert, result.key)).to.be(null); + + expect(reverseProxy.validateCertificate('foo', { domain: 'customer.cool.com', config: {} }, result.cert, result.key)).to.be.an(Error); }); });