diff --git a/package.json b/package.json index ccbc9ea96..dd15829f2 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "codemirror": "^5.25.0", "cross-env": "^5.2.0", "dashify": "^2.0.0", + "dateformat": "^3.0.3", "deep-equal": "^1.0.1", "detect-port": "^1.1.1", "electron-devtools-installer": "^2.2.0", diff --git a/src/utils/CertificateProvider.js b/src/utils/CertificateProvider.js index 79466e9fb..f571a9f76 100644 --- a/src/utils/CertificateProvider.js +++ b/src/utils/CertificateProvider.js @@ -21,6 +21,9 @@ import iosUtil from '../fb-stubs/iOSContainerUtility'; import {reportPlatformFailures} from './metrics'; import {getAdbClient} from './adbClient'; import * as androidUtil from './androidContainerUtility'; +import dateFormat from 'dateformat'; +const writeFile = promisify(fs.writeFile); +const exists = promisify(fs.exists); // Desktop file paths const os = require('os'); @@ -28,16 +31,20 @@ const caKey = getFilePath('ca.key'); const caCert = getFilePath('ca.crt'); const serverKey = getFilePath('server.key'); const serverCsr = getFilePath('server.csr'); -const serverSrl = getFilePath('server.srl'); const serverCert = getFilePath('server.crt'); +const configFile = getFilePath('openssl.cfg'); +const certs = getFilePath('certs'); +const newCerts = getFilePath('newcerts'); +const database = getFilePath('index.txt'); +const caRequiredFiles = [caKey, caCert, newCerts, database, configFile]; // Device file paths const csrFileName = 'app.csr'; const deviceCAcertFile = 'sonarCA.crt'; const deviceClientCertFile = 'device.crt'; -const caSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=SonarCA'; -const serverSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=localhost'; +const caSubject = '/C=US/ST=CA/L=Menlo Park/O=Flipper/CN=SonarCA'; +const serverSubject = '/C=US/ST=CA/L=Menlo Park/O=Flipper/CN=localhost'; const minCertExpiryWindowSeconds = 24 * 60 * 60; const allowedAppNameRegex = /^[a-zA-Z0-9._\-]+$/; const logTag = 'CertificateProvider'; @@ -48,6 +55,42 @@ const logTag = 'CertificateProvider'; */ const x509SubjectCNRegex = /[=,]\s*CN=([^,]*)(,.*)?$/; +const opensslConfig = `#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ${getFilePath('')} # Where everything is kept +certs = ${certs} # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = ${database} # database index file. +unique_subject = no # allow creation of several certs with same subject. +new_certs_dir = ${newCerts} # default place for new certs. +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number +crl = $dir/crl.pem # The current CRL + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional`; + export type SecureServerConfig = {| key: Buffer, cert: Buffer, @@ -56,6 +99,12 @@ export type SecureServerConfig = {| rejectUnauthorized: boolean, |}; +function getCertStartDate(): string { + const date = new Date(); + date.setHours(date.getHours() - 1); + return dateFormat(date, 'yyyymmddHHMMss') + 'Z'; +} + /* * This class is responsible for generating and deploying server and client * certificates to allow for secure communication between Flipper and apps. @@ -77,8 +126,10 @@ export default class CertificateProvider { this.logger = logger; this.adb = getAdbClient(); this.certificateSetup = reportPlatformFailures( - this.ensureServerCertExists(), - 'ensureServerCertExists', + this.ensureCertificateAuthorityExists().then(_ => + this.ensureServerCertExists(), + ), + 'certificateSetup', ); this.server = server; } @@ -162,13 +213,14 @@ export default class CertificateProvider { console.debug('Creating new client cert', logTag); return this.writeToTempFile(csr).then(path => { - return openssl('x509', { - req: true, + return openssl('ca', { + cert: caCert, in: path, - CA: caCert, - CAkey: caKey, - CAcreateserial: true, - CAserial: serverSrl, + keyfile: caKey, + config: configFile, + batch: true, + startdate: getCertStartDate(), + create_serial: true, }); }); } @@ -200,35 +252,33 @@ export default class CertificateProvider { ); } if (os === 'iOS' || os === 'windows') { - return promisify(fs.writeFile)(destination + filename, contents).catch( - err => { - if (os === 'iOS') { - // Writing directly to FS failed. It's probably a physical device. - const relativePathInsideApp = this.getRelativePathInAppContainer( - destination, - ); - return appNamePromise - .then(appName => - this.getTargetiOSDeviceId(appName, destination, csr), - ) - .then(udid => { - return appNamePromise.then(appName => - this.pushFileToiOSDevice( - udid, - appName, - relativePathInsideApp, - filename, - contents, - ), - ); - }); - } - throw new Error( - `Invalid appDirectory recieved from ${os} device: ${destination}: ` + - err.toString(), + return writeFile(destination + filename, contents).catch(err => { + if (os === 'iOS') { + // Writing directly to FS failed. It's probably a physical device. + const relativePathInsideApp = this.getRelativePathInAppContainer( + destination, ); - }, - ); + return appNamePromise + .then(appName => + this.getTargetiOSDeviceId(appName, destination, csr), + ) + .then(udid => { + return appNamePromise.then(appName => + this.pushFileToiOSDevice( + udid, + appName, + relativePathInsideApp, + filename, + contents, + ), + ); + }); + } + throw new Error( + `Invalid appDirectory recieved from ${os} device: ${destination}: ` + + err.toString(), + ); + }); } return Promise.reject(new Error(`Unsupported device os: ${os}`)); } @@ -242,7 +292,7 @@ export default class CertificateProvider { ): Promise { return tmpDir({unsafeCleanup: true}).then(dir => { const filePath = path.resolve(dir, filename); - promisify(fs.writeFile)(filePath, contents).then(() => + writeFile(filePath, contents).then(() => iosUtil.push(udid, filePath, bundleId, destination), ); }); @@ -438,12 +488,15 @@ export default class CertificateProvider { } ensureCertificateAuthorityExists(): Promise { - if (!fs.existsSync(caKey)) { - return this.generateCertificateAuthority(); - } - return this.checkCertIsValid(caCert).catch(e => - this.generateCertificateAuthority(), - ); + return Promise.all(caRequiredFiles.map(exists)) + .then(results => results.every(Boolean)) + .then(hasRequiredFiles => + hasRequiredFiles + ? Promise.resolve() + : this.generateCertificateAuthority(), + ) + .then(_ => this.checkCertIsValid(caCert)) + .catch(e => this.generateCertificateAuthority()); } checkCertIsValid(filename: string): Promise { @@ -505,11 +558,12 @@ export default class CertificateProvider { } generateCertificateAuthority(): Promise { - if (!fs.existsSync(getFilePath(''))) { - fs.mkdirSync(getFilePath('')); - } console.log('Generating new CA', logTag); - return openssl('genrsa', {out: caKey, '2048': false}) + return promisify(fs.mkdir)(getFilePath(''), {recursive: true}) + .then(_ => writeFile(configFile, opensslConfig)) + .then(_ => writeFile(database, '')) + .then(_ => promisify(fs.mkdir)(newCerts, {recursive: true})) + .then(_ => openssl('genrsa', {out: caKey, '2048': false})) .then(_ => openssl('req', { new: true, @@ -552,23 +606,24 @@ export default class CertificateProvider { subj: serverSubject, }), ) - .then(_ => - openssl('x509', { - req: true, + .then(_ => { + return openssl('ca', { + cert: caCert, in: serverCsr, - CA: caCert, - CAkey: caKey, - CAcreateserial: true, - CAserial: serverSrl, + keyfile: caKey, out: serverCert, - }), - ) + config: configFile, + batch: true, + startdate: getCertStartDate(), + create_serial: true, + }); + }) .then(_ => undefined); } writeToTempFile(content: string): Promise { return tmpFile().then((path, fd, cleanupCallback) => - promisify(fs.writeFile)(path, content).then(_ => path), + writeFile(path, content).then(_ => path), ); } } diff --git a/yarn.lock b/yarn.lock index d62aac0a0..d09fba75f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2475,6 +2475,11 @@ data-urls@^1.0.0: whatwg-mimetype "^2.1.0" whatwg-url "^7.0.0" +dateformat@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== + debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9, debug@~2.6.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"