Add 1h tolerance to ssl certs
Summary: This changes the start date of all generated certificates to be 1h in the past. Why? To allow for clock skew, and allow clock manipulation in tests. To do this, I had to switch from `openssl x509 req`, to `openssl ca` for generating them, because it has the startdate parameter. This variant is a bit more complicated to use, so I've added an openssl.conf and some extra files. I also changed the org from Sonar to Flipper because it now needs to match the CSRs coming from the mobile apps, and they use Flipper. Reviewed By: passy Differential Revision: D16223722 fbshipit-source-id: bdbd61bce1bc1b54d7b0b3cc6741675aa68d2cf6
This commit is contained in:
committed by
Facebook Github Bot
parent
ba95e73a8d
commit
9a97b3b45b
@@ -83,6 +83,7 @@
|
|||||||
"codemirror": "^5.25.0",
|
"codemirror": "^5.25.0",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"dashify": "^2.0.0",
|
"dashify": "^2.0.0",
|
||||||
|
"dateformat": "^3.0.3",
|
||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"detect-port": "^1.1.1",
|
"detect-port": "^1.1.1",
|
||||||
"electron-devtools-installer": "^2.2.0",
|
"electron-devtools-installer": "^2.2.0",
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ import iosUtil from '../fb-stubs/iOSContainerUtility';
|
|||||||
import {reportPlatformFailures} from './metrics';
|
import {reportPlatformFailures} from './metrics';
|
||||||
import {getAdbClient} from './adbClient';
|
import {getAdbClient} from './adbClient';
|
||||||
import * as androidUtil from './androidContainerUtility';
|
import * as androidUtil from './androidContainerUtility';
|
||||||
|
import dateFormat from 'dateformat';
|
||||||
|
const writeFile = promisify(fs.writeFile);
|
||||||
|
const exists = promisify(fs.exists);
|
||||||
|
|
||||||
// Desktop file paths
|
// Desktop file paths
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
@@ -28,16 +31,20 @@ const caKey = getFilePath('ca.key');
|
|||||||
const caCert = getFilePath('ca.crt');
|
const caCert = getFilePath('ca.crt');
|
||||||
const serverKey = getFilePath('server.key');
|
const serverKey = getFilePath('server.key');
|
||||||
const serverCsr = getFilePath('server.csr');
|
const serverCsr = getFilePath('server.csr');
|
||||||
const serverSrl = getFilePath('server.srl');
|
|
||||||
const serverCert = getFilePath('server.crt');
|
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
|
// Device file paths
|
||||||
const csrFileName = 'app.csr';
|
const csrFileName = 'app.csr';
|
||||||
const deviceCAcertFile = 'sonarCA.crt';
|
const deviceCAcertFile = 'sonarCA.crt';
|
||||||
const deviceClientCertFile = 'device.crt';
|
const deviceClientCertFile = 'device.crt';
|
||||||
|
|
||||||
const caSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=SonarCA';
|
const caSubject = '/C=US/ST=CA/L=Menlo Park/O=Flipper/CN=SonarCA';
|
||||||
const serverSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=localhost';
|
const serverSubject = '/C=US/ST=CA/L=Menlo Park/O=Flipper/CN=localhost';
|
||||||
const minCertExpiryWindowSeconds = 24 * 60 * 60;
|
const minCertExpiryWindowSeconds = 24 * 60 * 60;
|
||||||
const allowedAppNameRegex = /^[a-zA-Z0-9._\-]+$/;
|
const allowedAppNameRegex = /^[a-zA-Z0-9._\-]+$/;
|
||||||
const logTag = 'CertificateProvider';
|
const logTag = 'CertificateProvider';
|
||||||
@@ -48,6 +55,42 @@ const logTag = 'CertificateProvider';
|
|||||||
*/
|
*/
|
||||||
const x509SubjectCNRegex = /[=,]\s*CN=([^,]*)(,.*)?$/;
|
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 = {|
|
export type SecureServerConfig = {|
|
||||||
key: Buffer,
|
key: Buffer,
|
||||||
cert: Buffer,
|
cert: Buffer,
|
||||||
@@ -56,6 +99,12 @@ export type SecureServerConfig = {|
|
|||||||
rejectUnauthorized: boolean,
|
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
|
* This class is responsible for generating and deploying server and client
|
||||||
* certificates to allow for secure communication between Flipper and apps.
|
* certificates to allow for secure communication between Flipper and apps.
|
||||||
@@ -77,8 +126,10 @@ export default class CertificateProvider {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.adb = getAdbClient();
|
this.adb = getAdbClient();
|
||||||
this.certificateSetup = reportPlatformFailures(
|
this.certificateSetup = reportPlatformFailures(
|
||||||
this.ensureServerCertExists(),
|
this.ensureCertificateAuthorityExists().then(_ =>
|
||||||
'ensureServerCertExists',
|
this.ensureServerCertExists(),
|
||||||
|
),
|
||||||
|
'certificateSetup',
|
||||||
);
|
);
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
@@ -162,13 +213,14 @@ export default class CertificateProvider {
|
|||||||
console.debug('Creating new client cert', logTag);
|
console.debug('Creating new client cert', logTag);
|
||||||
|
|
||||||
return this.writeToTempFile(csr).then(path => {
|
return this.writeToTempFile(csr).then(path => {
|
||||||
return openssl('x509', {
|
return openssl('ca', {
|
||||||
req: true,
|
cert: caCert,
|
||||||
in: path,
|
in: path,
|
||||||
CA: caCert,
|
keyfile: caKey,
|
||||||
CAkey: caKey,
|
config: configFile,
|
||||||
CAcreateserial: true,
|
batch: true,
|
||||||
CAserial: serverSrl,
|
startdate: getCertStartDate(),
|
||||||
|
create_serial: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -200,35 +252,33 @@ export default class CertificateProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (os === 'iOS' || os === 'windows') {
|
if (os === 'iOS' || os === 'windows') {
|
||||||
return promisify(fs.writeFile)(destination + filename, contents).catch(
|
return writeFile(destination + filename, contents).catch(err => {
|
||||||
err => {
|
if (os === 'iOS') {
|
||||||
if (os === 'iOS') {
|
// Writing directly to FS failed. It's probably a physical device.
|
||||||
// Writing directly to FS failed. It's probably a physical device.
|
const relativePathInsideApp = this.getRelativePathInAppContainer(
|
||||||
const relativePathInsideApp = this.getRelativePathInAppContainer(
|
destination,
|
||||||
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 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}`));
|
return Promise.reject(new Error(`Unsupported device os: ${os}`));
|
||||||
}
|
}
|
||||||
@@ -242,7 +292,7 @@ export default class CertificateProvider {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return tmpDir({unsafeCleanup: true}).then(dir => {
|
return tmpDir({unsafeCleanup: true}).then(dir => {
|
||||||
const filePath = path.resolve(dir, filename);
|
const filePath = path.resolve(dir, filename);
|
||||||
promisify(fs.writeFile)(filePath, contents).then(() =>
|
writeFile(filePath, contents).then(() =>
|
||||||
iosUtil.push(udid, filePath, bundleId, destination),
|
iosUtil.push(udid, filePath, bundleId, destination),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -438,12 +488,15 @@ export default class CertificateProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensureCertificateAuthorityExists(): Promise<void> {
|
ensureCertificateAuthorityExists(): Promise<void> {
|
||||||
if (!fs.existsSync(caKey)) {
|
return Promise.all(caRequiredFiles.map(exists))
|
||||||
return this.generateCertificateAuthority();
|
.then(results => results.every(Boolean))
|
||||||
}
|
.then(hasRequiredFiles =>
|
||||||
return this.checkCertIsValid(caCert).catch(e =>
|
hasRequiredFiles
|
||||||
this.generateCertificateAuthority(),
|
? Promise.resolve()
|
||||||
);
|
: this.generateCertificateAuthority(),
|
||||||
|
)
|
||||||
|
.then(_ => this.checkCertIsValid(caCert))
|
||||||
|
.catch(e => this.generateCertificateAuthority());
|
||||||
}
|
}
|
||||||
|
|
||||||
checkCertIsValid(filename: string): Promise<void> {
|
checkCertIsValid(filename: string): Promise<void> {
|
||||||
@@ -505,11 +558,12 @@ export default class CertificateProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateCertificateAuthority(): Promise<void> {
|
generateCertificateAuthority(): Promise<void> {
|
||||||
if (!fs.existsSync(getFilePath(''))) {
|
|
||||||
fs.mkdirSync(getFilePath(''));
|
|
||||||
}
|
|
||||||
console.log('Generating new CA', logTag);
|
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(_ =>
|
.then(_ =>
|
||||||
openssl('req', {
|
openssl('req', {
|
||||||
new: true,
|
new: true,
|
||||||
@@ -552,23 +606,24 @@ export default class CertificateProvider {
|
|||||||
subj: serverSubject,
|
subj: serverSubject,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.then(_ =>
|
.then(_ => {
|
||||||
openssl('x509', {
|
return openssl('ca', {
|
||||||
req: true,
|
cert: caCert,
|
||||||
in: serverCsr,
|
in: serverCsr,
|
||||||
CA: caCert,
|
keyfile: caKey,
|
||||||
CAkey: caKey,
|
|
||||||
CAcreateserial: true,
|
|
||||||
CAserial: serverSrl,
|
|
||||||
out: serverCert,
|
out: serverCert,
|
||||||
}),
|
config: configFile,
|
||||||
)
|
batch: true,
|
||||||
|
startdate: getCertStartDate(),
|
||||||
|
create_serial: true,
|
||||||
|
});
|
||||||
|
})
|
||||||
.then(_ => undefined);
|
.then(_ => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeToTempFile(content: string): Promise<string> {
|
writeToTempFile(content: string): Promise<string> {
|
||||||
return tmpFile().then((path, fd, cleanupCallback) =>
|
return tmpFile().then((path, fd, cleanupCallback) =>
|
||||||
promisify(fs.writeFile)(path, content).then(_ => path),
|
writeFile(path, content).then(_ => path),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2475,6 +2475,11 @@ data-urls@^1.0.0:
|
|||||||
whatwg-mimetype "^2.1.0"
|
whatwg-mimetype "^2.1.0"
|
||||||
whatwg-url "^7.0.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:
|
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"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
|||||||
Reference in New Issue
Block a user