Extract WWW certificate provider

Summary: Extract WWW certificate provider from the iOS certificate provider. Hide its implementation from OSS since it is not relevant for OSS folks.

Reviewed By: mweststrate

Differential Revision: D33895378

fbshipit-source-id: 376afda3b5fa3857c0eb280b92555314eb1a0d1f
This commit is contained in:
Andrey Goncharov
2022-02-02 03:05:34 -08:00
committed by Facebook GitHub Bot
parent 29f6d0e711
commit fd13399cb9
8 changed files with 52 additions and 129 deletions

View File

@@ -46,6 +46,7 @@ import {
loadSecureServerConfig, loadSecureServerConfig,
} from '../utils/certificateUtils'; } from '../utils/certificateUtils';
import DesktopCertificateProvider from '../devices/desktop/DesktopCertificateProvider'; import DesktopCertificateProvider from '../devices/desktop/DesktopCertificateProvider';
import WWWCertificateProvider from '../fb-stubs/WWWCertificateProvider';
type ClientTimestampTracker = { type ClientTimestampTracker = {
insecureStart?: number; insecureStart?: number;
@@ -285,15 +286,19 @@ class ServerController extends EventEmitter implements ServerEventsListener {
} }
case 'iOS': { case 'iOS': {
certificateProvider = this.flipperServer.ios.certificateProvider; certificateProvider = this.flipperServer.ios.certificateProvider;
if (medium === 'WWW') {
certificateProvider = new WWWCertificateProvider(
this.flipperServer.keytarManager,
);
}
break; break;
} }
// Used by Spark AR studio (search for SkylightFlipperClient) // Used by Spark AR studio (search for SkylightFlipperClient)
// See D30992087 // See D30992087
case 'MacOS': case 'MacOS':
case 'Windows': { case 'Windows': {
certificateProvider = new DesktopCertificateProvider( certificateProvider = new DesktopCertificateProvider();
this.flipperServer.keytarManager,
);
break; break;
} }
default: { default: {
@@ -309,7 +314,6 @@ class ServerController extends EventEmitter implements ServerEventsListener {
unsanitizedCSR, unsanitizedCSR,
clientQuery.os, clientQuery.os,
appDirectory, appDirectory,
medium,
), ),
'processCertificateSigningRequest', 'processCertificateSigningRequest',
) )

View File

@@ -9,15 +9,14 @@
import CertificateProvider from '../../utils/CertificateProvider'; import CertificateProvider from '../../utils/CertificateProvider';
import {Client} from 'adbkit'; import {Client} from 'adbkit';
import {KeytarManager} from '../../utils/keytar';
import * as androidUtil from './androidContainerUtility'; import * as androidUtil from './androidContainerUtility';
import {csrFileName} from '../../utils/certificateUtils'; import {csrFileName, extractAppNameFromCSR} from '../../utils/certificateUtils';
const logTag = 'AndroidCertificateProvider'; const logTag = 'AndroidCertificateProvider';
export default class AndroidCertificateProvider extends CertificateProvider { export default class AndroidCertificateProvider extends CertificateProvider {
constructor(keytarManager: KeytarManager, private adb: Client) { constructor(private adb: Client) {
super(keytarManager); super();
} }
async getTargetDeviceId( async getTargetDeviceId(
@@ -75,13 +74,13 @@ export default class AndroidCertificateProvider extends CertificateProvider {
return matchingIds[0]; return matchingIds[0];
} }
protected async handleFSBasedDeploy( protected async deployOrStageFileForDevice(
destination: string, destination: string,
filename: string, filename: string,
contents: string, contents: string,
csr: string, csr: string,
appName: string,
) { ) {
const appName = await extractAppNameFromCSR(csr);
const deviceId = await this.getTargetDeviceId(appName, destination, csr); const deviceId = await this.getTargetDeviceId(appName, destination, csr);
await androidUtil.push( await androidUtil.push(
this.adb, this.adb,

View File

@@ -31,10 +31,7 @@ export class AndroidDeviceManager {
this.adbClient, this.adbClient,
'AndroidDeviceManager.certificateProvider -> missing adbClient', 'AndroidDeviceManager.certificateProvider -> missing adbClient',
); );
return new AndroidCertificateProvider( return new AndroidCertificateProvider(this.adbClient);
this.flipperServer.keytarManager,
this.adbClient,
);
} }
private createDevice( private createDevice(

View File

@@ -18,7 +18,7 @@ export default class DesktopCertificateProvider extends CertificateProvider {
return ''; return '';
} }
protected async handleFSBasedDeploy( protected async deployOrStageFileForDevice(
destination: string, destination: string,
filename: string, filename: string,
contents: string, contents: string,

View File

@@ -8,21 +8,20 @@
*/ */
import {IdbConfig} from './idbConfig'; import {IdbConfig} from './idbConfig';
import {KeytarManager} from '../../utils/keytar';
import CertificateProvider from '../../utils/CertificateProvider'; import CertificateProvider from '../../utils/CertificateProvider';
import iosUtil from './iOSContainerUtility'; import iosUtil from './iOSContainerUtility';
import fs from 'fs-extra'; import fs from 'fs-extra';
import {promisify} from 'util'; import {promisify} from 'util';
import tmp, {DirOptions} from 'tmp'; import tmp, {DirOptions} from 'tmp';
import {csrFileName} from '../../utils/certificateUtils'; import {csrFileName, extractAppNameFromCSR} from '../../utils/certificateUtils';
import path from 'path'; import path from 'path';
const tmpDir = promisify(tmp.dir) as (options?: DirOptions) => Promise<string>; const tmpDir = promisify(tmp.dir) as (options?: DirOptions) => Promise<string>;
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
export default class iOSCertificateProvider extends CertificateProvider { export default class iOSCertificateProvider extends CertificateProvider {
constructor(keytarManager: KeytarManager, private idbConfig: IdbConfig) { constructor(private idbConfig: IdbConfig) {
super(keytarManager); super();
} }
async getTargetDeviceId( async getTargetDeviceId(
@@ -62,13 +61,13 @@ export default class iOSCertificateProvider extends CertificateProvider {
return matchingIds[0]; return matchingIds[0];
} }
protected async handleFSBasedDeploy( protected async deployOrStageFileForDevice(
destination: string, destination: string,
filename: string, filename: string,
contents: string, contents: string,
csr: string, csr: string,
appName: string,
) { ) {
const appName = await extractAppNameFromCSR(csr);
try { try {
await fs.writeFile(destination + filename, contents); await fs.writeFile(destination + filename, contents);
} catch (err) { } catch (err) {

View File

@@ -46,10 +46,7 @@ export class IOSDeviceManager {
this.idbConfig, this.idbConfig,
'IOSDeviceManager.certificateProvider -> missing idbConfig', 'IOSDeviceManager.certificateProvider -> missing idbConfig',
); );
return new iOSCertificateProvider( return new iOSCertificateProvider(this.idbConfig);
this.flipperServer.keytarManager,
this.idbConfig,
);
} }
private forwardPort(port: number, multiplexChannelPort: number) { private forwardPort(port: number, multiplexChannelPort: number) {

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {KeytarManager} from '../utils/keytar';
import CertificateProvider from '../utils/CertificateProvider';
export default class WWWCertificateProvider extends CertificateProvider {
constructor(private keytarManager: KeytarManager) {
super();
}
async processCertificateSigningRequest(): Promise<{deviceId: string}> {
throw new Error('WWWCertificateProvider is not implemented');
}
async getTargetDeviceId(): Promise<string> {
throw new Error('WWWCertificateProvider is not implemented');
}
protected async deployOrStageFileForDevice(): Promise<void> {
throw new Error('WWWCertificateProvider is not implemented');
}
}

View File

@@ -7,14 +7,6 @@
* @format * @format
*/ */
import {promisify} from 'util';
import fs from 'fs-extra';
import tmp from 'tmp';
import {reportPlatformFailures} from 'flipper-common';
import archiver from 'archiver';
import {timeout} from 'flipper-common';
import {v4 as uuid} from 'uuid';
import {internGraphPOSTAPIRequest} from '../fb-stubs/internRequests';
import { import {
deviceCAcertFile, deviceCAcertFile,
deviceClientCertFile, deviceClientCertFile,
@@ -23,62 +15,26 @@ import {
generateClientCertificate, generateClientCertificate,
getCACertificate, getCACertificate,
} from './certificateUtils'; } from './certificateUtils';
import {KeytarManager, SERVICE_FLIPPER} from './keytar';
export type CertificateExchangeMedium = 'FS_ACCESS' | 'WWW' | 'NONE'; export type CertificateExchangeMedium = 'FS_ACCESS' | 'WWW' | 'NONE';
export default abstract class CertificateProvider { export default abstract class CertificateProvider {
constructor(private readonly keytarManager: KeytarManager) {}
private uploadFiles = async (
zipPath: string,
deviceID: string,
): Promise<void> => {
return reportPlatformFailures(
timeout(
5 * 60 * 1000,
internGraphPOSTAPIRequest(
'flipper/certificates',
{
device_id: deviceID,
},
{
certificate_zip: {
path: zipPath,
filename: 'certs.zip',
},
},
{timeout: 5 * 60 * 1000},
await this.keytarManager.retrieveToken(SERVICE_FLIPPER),
).then(() => {}),
'Timed out uploading Flipper certificates to WWW.',
),
'uploadCertificates',
);
};
async processCertificateSigningRequest( async processCertificateSigningRequest(
unsanitizedCsr: string, unsanitizedCsr: string,
os: string, os: string,
appDirectory: string, appDirectory: string,
medium: CertificateExchangeMedium,
): Promise<{deviceId: string}> { ): Promise<{deviceId: string}> {
const csr = this.santitizeString(unsanitizedCsr); const csr = this.santitizeString(unsanitizedCsr);
if (csr === '') { if (csr === '') {
return Promise.reject(new Error(`Received empty CSR from ${os} device`)); return Promise.reject(new Error(`Received empty CSR from ${os} device`));
} }
await ensureOpenSSLIsAvailable(); await ensureOpenSSLIsAvailable();
const rootFolder = await promisify(tmp.dir)();
const certFolder = rootFolder + '/FlipperCerts/';
const certsZipPath = rootFolder + '/certs.zip';
const caCert = await getCACertificate(); const caCert = await getCACertificate();
await this.deployOrStageFileForDevice( await this.deployOrStageFileForDevice(
appDirectory, appDirectory,
deviceCAcertFile, deviceCAcertFile,
caCert, caCert,
csr, csr,
medium,
certFolder,
); );
const clientCert = await generateClientCertificate(csr); const clientCert = await generateClientCertificate(csr);
await this.deployOrStageFileForDevice( await this.deployOrStageFileForDevice(
@@ -86,39 +42,9 @@ export default abstract class CertificateProvider {
deviceClientCertFile, deviceClientCertFile,
clientCert, clientCert,
csr, csr,
medium,
certFolder,
); );
const appName = await extractAppNameFromCSR(csr); const appName = await extractAppNameFromCSR(csr);
const deviceId = const deviceId = await this.getTargetDeviceId(appName, appDirectory, csr);
medium === 'FS_ACCESS'
? await this.getTargetDeviceId(appName, appDirectory, csr)
: uuid();
if (medium === 'WWW') {
const zipPromise = new Promise((resolve, reject) => {
const output = fs.createWriteStream(certsZipPath);
const archive = archiver('zip', {
zlib: {level: 9}, // Sets the compression level.
});
archive.directory(certFolder, false);
output.on('close', function () {
resolve(certsZipPath);
});
archive.on('warning', reject);
archive.on('error', reject);
archive.pipe(output);
archive.finalize();
});
await reportPlatformFailures(
zipPromise,
'www-certs-exchange-zipping-certs',
);
await reportPlatformFailures(
this.uploadFiles(certsZipPath, deviceId),
'www-certs-exchange-uploading-certs',
);
}
return { return {
deviceId, deviceId,
}; };
@@ -130,39 +56,11 @@ export default abstract class CertificateProvider {
_csr: string, _csr: string,
): Promise<string>; ): Promise<string>;
private async deployOrStageFileForDevice( protected abstract deployOrStageFileForDevice(
destination: string, destination: string,
filename: string, filename: string,
contents: string, contents: string,
csr: string, csr: string,
medium: CertificateExchangeMedium,
certFolder: string,
): Promise<void> {
if (medium === 'WWW') {
const certPathExists = await fs.pathExists(certFolder);
if (!certPathExists) {
await fs.mkdir(certFolder);
}
try {
await fs.writeFile(certFolder + filename, contents);
return;
} catch (e) {
throw new Error(
`Failed to write ${filename} to temporary folder. Error: ${e}`,
);
}
}
const appName = await extractAppNameFromCSR(csr);
this.handleFSBasedDeploy(destination, filename, contents, csr, appName);
}
protected abstract handleFSBasedDeploy(
_destination: string,
_filename: string,
_contents: string,
_csr: string,
_appName: string,
): Promise<void>; ): Promise<void>;
protected santitizeString(csrString: string): string { protected santitizeString(csrString: string): string {