From f6269254430162d86b6d3e87d9c5101cc08bac5a Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Mon, 17 Aug 2020 06:50:15 -0700 Subject: [PATCH] Upload/Download certs zip from Flipper Summary: This diff adds upload and download logic for certs. It makes changes on both Flipper Client and Desktop side. With this we enable cert exchange through WWW. Next Diffs: 1) Add Flipper state in cert provider for more debug data 2) Tests Reviewed By: jknoxville Differential Revision: D23092706 fbshipit-source-id: e576253606b64b62848b70203db7e09a3bd77fd9 --- desktop/app/package.json | 5 +- .../__tests__/ExportDataPluginSheet.node.tsx | 7 +- desktop/app/src/devices/ClientDevice.tsx | 17 +++ desktop/app/src/server.tsx | 46 ++++++- desktop/app/src/utils/CertificateProvider.tsx | 97 ++++++++++--- .../src/utils/__tests__/exportData.node.tsx | 91 +++++++++++-- desktop/yarn.lock | 128 +++++++++++++++++- xplat/Flipper/FlipperCertificateProvider.h | 12 ++ .../Flipper/FlipperConnectionManagerImpl.cpp | 43 +++++- 9 files changed, 400 insertions(+), 46 deletions(-) create mode 100644 desktop/app/src/devices/ClientDevice.tsx diff --git a/desktop/app/package.json b/desktop/app/package.json index 6ed10c00c..c99a9d9bd 100644 --- a/desktop/app/package.json +++ b/desktop/app/package.json @@ -14,10 +14,13 @@ "@emotion/core": "^10.0.22", "@emotion/styled": "^10.0.23", "@iarna/toml": "^2.2.5", + "@types/archiver": "^3.1.0", + "@types/uuid": "^8.0.1", "JSONStream": "^1.3.1", "adbkit": "^2.11.1", "adbkit-logcat": "^2.0.1", "algoliasearch": "^4.0.0", + "archiver": "^5.0.0", "async-mutex": "^0.1.3", "axios": "^0.19.2", "deep-equal": "^2.0.1", @@ -61,7 +64,7 @@ "semver": "^7.3.2", "string-natural-compare": "^3.0.0", "tmp": "^0.2.1", - "uuid": "^8.1.0", + "uuid": "^8.3.0", "which": "^2.0.1", "ws": "^7.3.0", "xdg-basedir": "^4.0.0" diff --git a/desktop/app/src/chrome/__tests__/ExportDataPluginSheet.node.tsx b/desktop/app/src/chrome/__tests__/ExportDataPluginSheet.node.tsx index 251d0b866..c882ef355 100644 --- a/desktop/app/src/chrome/__tests__/ExportDataPluginSheet.node.tsx +++ b/desktop/app/src/chrome/__tests__/ExportDataPluginSheet.node.tsx @@ -72,7 +72,12 @@ function getStore(selectedPlugins: Array) { const client = new Client( clientId, - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial', + }, null, logger, // @ts-ignore diff --git a/desktop/app/src/devices/ClientDevice.tsx b/desktop/app/src/devices/ClientDevice.tsx new file mode 100644 index 000000000..c745a73ff --- /dev/null +++ b/desktop/app/src/devices/ClientDevice.tsx @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 BaseDevice, {OS} from './BaseDevice'; + +export default class ClientDevice extends BaseDevice { + constructor(serial: string, title: string, os: OS) { + super(serial, 'emulator', title, os); + this.devicePlugins = []; + } +} diff --git a/desktop/app/src/server.tsx b/desktop/app/src/server.tsx index 0afb889b9..9edcd24e5 100644 --- a/desktop/app/src/server.tsx +++ b/desktop/app/src/server.tsx @@ -37,6 +37,7 @@ import querystring from 'querystring'; import {IncomingMessage} from 'http'; import ws from 'ws'; import {initSelfInpector} from './utils/self-inspection/selfInspectionUtils'; +import ClientDevice from './devices/ClientDevice'; type ClientInfo = { connection: FlipperClientConnection | null | undefined; @@ -51,9 +52,9 @@ type ClientCsrQuery = { function transformCertificateExchangeMediumToType( medium: number | undefined, ): CertificateExchangeMedium { - if (medium === 1) { + if (medium == 1) { return 'FS_ACCESS'; - } else if (medium === 2) { + } else if (medium == 2) { return 'WWW'; } else { return 'FS_ACCESS'; @@ -226,6 +227,7 @@ class Server extends EventEmitter { device: 'device', device_id: deviceId, sdk_version: 1, + medium: 'FS_ACCESS', }, {}, ).then((c) => (resolvedClient = c)); @@ -277,14 +279,43 @@ class Server extends EventEmitter { if (!payload.data) { return {}; } - const clientData: ClientQuery & ClientCsrQuery = JSON.parse(payload.data); + const clientData: ClientQuery & + ClientCsrQuery & {medium: number | undefined} = JSON.parse(payload.data); this.connectionTracker.logConnectionAttempt(clientData); - const {app, os, device, device_id, sdk_version, csr, csr_path} = clientData; + const { + app, + os, + device, + device_id, + sdk_version, + csr, + csr_path, + medium, + } = clientData; + const transformedMedium = transformCertificateExchangeMediumToType(medium); + const duplicateDevices = this.store + .getState() + .connections.devices.filter((device) => device.serial === device_id); + // When user switches from WWW to FS_ACCESS, we reset the certs folder, but we don't do it other way around. Thus when user switches to WWW from FS_ACCESS and if certs arepresent then the device id sent by Flipper SDK is the original one and in that case we need not create a device. + if (transformedMedium === 'WWW' && duplicateDevices.length == 0) { + // TODO unregister previous ClientDevice's for a particular device + this.store.dispatch({ + type: 'REGISTER_DEVICE', + payload: new ClientDevice(device_id, app, os), + }); + } const client: Promise = this.addConnection( socket, - {app, os, device, device_id, sdk_version}, + { + app, + os, + device, + device_id, + sdk_version, + medium: transformedMedium, + }, {csr, csr_path}, ).then((client) => { return (resolvedClient = client); @@ -364,6 +395,7 @@ class Server extends EventEmitter { destination: string; medium: number | undefined; // OSS's older Client SDK might not send medium information. This is not an issue for internal FB users, as Flipper release is insync with client SDK through launcher. } = rawData; + if (json.method === 'signCertificate') { console.debug('CSR received from device', 'server'); @@ -461,7 +493,7 @@ class Server extends EventEmitter { async addConnection( conn: FlipperClientConnection, - query: ClientQuery, + query: ClientQuery & {medium: CertificateExchangeMedium}, csrQuery: ClientCsrQuery, ): Promise { invariant(query, 'expected query'); @@ -469,7 +501,7 @@ class Server extends EventEmitter { // try to get id by comparing giving `csr` to file from `csr_path` // otherwise, use given device_id const {csr_path, csr} = csrQuery; - return (csr_path && csr + return (csr_path && csr && query.medium === 'FS_ACCESS' ? this.certificateProvider.extractAppNameFromCSR(csr).then((appName) => { return this.certificateProvider.getTargetDeviceId( query.os, diff --git a/desktop/app/src/utils/CertificateProvider.tsx b/desktop/app/src/utils/CertificateProvider.tsx index 731f43748..121c642a1 100644 --- a/desktop/app/src/utils/CertificateProvider.tsx +++ b/desktop/app/src/utils/CertificateProvider.tsx @@ -8,9 +8,12 @@ */ import {Logger} from '../fb-interfaces/Logger'; +import {internGraphPOSTAPIRequest} from '../fb-stubs/user'; import Server from '../server'; import {promisify} from 'util'; import fs from 'fs'; +import fsExtra from 'fs-extra'; + import { openssl, isInstalled as opensslInstalled, @@ -24,6 +27,9 @@ import * as androidUtil from './androidContainerUtility'; import os from 'os'; import {Client as ADBClient} from 'adbkit'; import {Store} from '../reducers/index'; +import archiver from 'archiver'; +import promiseTimeout from '../utils/promiseTimeout'; +import {v4 as uuid} from 'uuid'; export type CertificateExchangeMedium = 'FS_ACCESS' | 'WWW'; @@ -94,47 +100,90 @@ export default class CertificateProvider { this.server = server; } - processCertificateSigningRequest( + uploadFiles = async (zipPath: string, deviceID: string): Promise => { + const buff = await fsExtra.readFile(zipPath); + const file = new File([buff], 'certs.zip'); + return reportPlatformFailures( + promiseTimeout( + 5 * 60 * 1000, + internGraphPOSTAPIRequest('flipper/certificates', { + certificate_zip: file, + device_id: deviceID, + }), + 'Timed out uploading Flipper export.', + ), + 'uploadCertificates', + ).catch((e) => console.error(`Failed to upload certificates due to ${e}`)); + }; + + async processCertificateSigningRequest( unsanitizedCsr: string, os: string, appDirectory: string, medium: CertificateExchangeMedium, ): Promise<{deviceId: string}> { - // TODO: Add implementations for each of these conditions - if (medium === 'FS_ACCESS') { - // Use IDB for cert exchange - } else if (medium === 'WWW') { - // Use WWWW - } const csr = this.santitizeString(unsanitizedCsr); if (csr === '') { return Promise.reject(new Error(`Received empty CSR from ${os} device`)); } this.ensureOpenSSLIsAvailable(); + const rootFolder = await promisify(tmp.dir)(); + const certFolder = rootFolder + '/FlipperCerts/'; + const certsZipPath = rootFolder + '/certs.zip'; return this.certificateSetup .then((_) => this.getCACertificate()) .then((caCert) => - this.deployFileToMobileApp( + this.deployOrStageFileForMobileApp( appDirectory, deviceCAcertFile, caCert, csr, os, + medium, + certFolder, ), ) .then((_) => this.generateClientCertificate(csr)) .then((clientCert) => - this.deployFileToMobileApp( + this.deployOrStageFileForMobileApp( appDirectory, deviceClientCertFile, clientCert, csr, os, + medium, + certFolder, ), ) - .then((_) => this.extractAppNameFromCSR(csr)) - .then((appName) => this.getTargetDeviceId(os, appName, appDirectory, csr)) - .then((deviceId) => { + .then((_) => { + return this.extractAppNameFromCSR(csr); + }) + .then((appName) => { + if (medium === 'FS_ACCESS') { + return this.getTargetDeviceId(os, appName, appDirectory, csr); + } else { + return uuid(); + } + }) + .then(async (deviceId) => { + 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 zipPromise; + await this.uploadFiles(certsZipPath, deviceId); + } return { deviceId, }; @@ -201,15 +250,31 @@ export default class CertificateProvider { throw new Error("Path didn't match expected pattern: " + absolutePath); } - deployFileToMobileApp( + async deployOrStageFileForMobileApp( destination: string, filename: string, contents: string, csr: string, os: string, + medium: CertificateExchangeMedium, + certFolder: string, ): Promise { const appNamePromise = this.extractAppNameFromCSR(csr); + if (medium === 'WWW') { + const certPathExists = await fsExtra.pathExists(certFolder); + if (!certPathExists) { + await fsExtra.mkdir(certFolder); + } + return promisify(fs.writeFile)(certFolder + filename, contents).catch( + (e) => { + throw new Error( + `Failed to write ${filename} to temporary folder. Error: ${e}`, + ); + }, + ); + } + if (os === 'Android') { const deviceIdPromise = appNamePromise.then((app) => this.getTargetAndroidDeviceId(app, destination, csr), @@ -237,9 +302,9 @@ export default class CertificateProvider { destination, ); return appNamePromise - .then((appName) => - this.getTargetiOSDeviceId(appName, destination, csr), - ) + .then((appName) => { + return this.getTargetiOSDeviceId(appName, destination, csr); + }) .then((udid) => { return appNamePromise.then((appName) => this.pushFileToiOSDevice( diff --git a/desktop/app/src/utils/__tests__/exportData.node.tsx b/desktop/app/src/utils/__tests__/exportData.node.tsx index 8949bef9d..0129704a2 100644 --- a/desktop/app/src/utils/__tests__/exportData.node.tsx +++ b/desktop/app/src/utils/__tests__/exportData.node.tsx @@ -716,7 +716,12 @@ test('test determinePluginsToProcess for mutilple clients having plugins present const device1 = new BaseDevice('serial1', 'emulator', 'TestiPhone', 'iOS'); const client1 = new Client( generateClientIdentifier(device1, 'app'), - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -724,7 +729,12 @@ test('test determinePluginsToProcess for mutilple clients having plugins present ); const client2 = new Client( generateClientIdentifier(device1, 'app2'), - {app: 'app2', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app2', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -732,7 +742,12 @@ test('test determinePluginsToProcess for mutilple clients having plugins present ); const client3 = new Client( generateClientIdentifier(device1, 'app3'), - {app: 'app3', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app3', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -777,7 +792,12 @@ test('test determinePluginsToProcess for no selected plugin present in any clien const device1 = new BaseDevice('serial1', 'emulator', 'TestiPhone', 'iOS'); const client1 = new Client( generateClientIdentifier(device1, 'app'), - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -785,7 +805,12 @@ test('test determinePluginsToProcess for no selected plugin present in any clien ); const client2 = new Client( generateClientIdentifier(device1, 'app2'), - {app: 'app2', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app2', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -811,7 +836,12 @@ test('test determinePluginsToProcess for multiple clients on same device', async const device1 = new BaseDevice('serial1', 'emulator', 'TestiPhone', 'iOS'); const client1 = new Client( generateClientIdentifier(device1, 'app'), - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -819,7 +849,12 @@ test('test determinePluginsToProcess for multiple clients on same device', async ); const client2 = new Client( generateClientIdentifier(device1, 'app2'), - {app: 'app2', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app2', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -852,7 +887,12 @@ test('test determinePluginsToProcess for multiple clients on different device', const device2 = new BaseDevice('serial2', 'emulator', 'TestiPhone', 'iOS'); const client1Device1 = new Client( generateClientIdentifier(device1, 'app'), - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -860,7 +900,12 @@ test('test determinePluginsToProcess for multiple clients on different device', ); const client2Device1 = new Client( generateClientIdentifier(device1, 'app2'), - {app: 'app1', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'}, + { + app: 'app1', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial1', + }, null, logger, mockStore, @@ -868,7 +913,12 @@ test('test determinePluginsToProcess for multiple clients on different device', ); const client1Device2 = new Client( generateClientIdentifier(device2, 'app'), - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial2'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial2', + }, null, logger, mockStore, @@ -876,7 +926,12 @@ test('test determinePluginsToProcess for multiple clients on different device', ); const client2Device2 = new Client( generateClientIdentifier(device2, 'app2'), - {app: 'app1', os: 'iOS', device: 'TestiPhone', device_id: 'serial2'}, + { + app: 'app1', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial2', + }, null, logger, mockStore, @@ -934,7 +989,12 @@ test('test determinePluginsToProcess to ignore archived clients', async () => { const mockStore = configureStore([])(); const client = new Client( generateClientIdentifier(selectedDevice, 'app'), - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial', + }, null, logger, mockStore, @@ -942,7 +1002,12 @@ test('test determinePluginsToProcess to ignore archived clients', async () => { ); const archivedClient = new Client( generateClientIdentifier(archivedDevice, 'app'), - {app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial-archived'}, + { + app: 'app', + os: 'iOS', + device: 'TestiPhone', + device_id: 'serial-archived', + }, null, logger, mockStore, diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 41a303c6b..073c122cf 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -1882,6 +1882,13 @@ resolved "https://registry.yarnpkg.com/@types/algoliasearch/-/algoliasearch-3.34.5.tgz#c40e346a6c5526f9b27af7863117d1200456e7d2" integrity sha512-JS+5KT9SfwzGIkoCsj7EyYHzhrsagj321BEPH3oroHYnuKUfPVoei58qLsBPo9RRa27Vb79WJssSSPBicnaAng== +"@types/archiver@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/archiver/-/archiver-3.1.0.tgz#0d5bd922ba5cf06e137cd6793db7942439b1805e" + integrity sha512-nTvHwgWONL+iXG+9CX+gnQ/tTOV+qucAjwpXqeUn4OCRMxP42T29FFP/7XaOo0EqqO3TlENhObeZEe7RUJAriw== + dependencies: + "@types/glob" "*" + "@types/aria-query@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" @@ -2049,6 +2056,14 @@ dependencies: "@types/node" "*" +"@types/glob@*": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -2467,6 +2482,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== +"@types/uuid@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.1.tgz#42958a1a880640b139eea97a1640e1a3f61016d2" + integrity sha512-2kE8rEFgJpbBAPw5JghccEevQb0XVU0tewF/8h7wPQTeCtoJ6h8qmBIwuzUVm2MutmzC/cpCkwxudixoNYDp1A== + "@types/webdriverio@^4.8.0": version "4.13.3" resolved "https://registry.yarnpkg.com/@types/webdriverio/-/webdriverio-4.13.3.tgz#c1571c4e62724135c0b11e7d7e36b07af5168856" @@ -2919,6 +2939,35 @@ archiver-utils@^1.3.0: normalize-path "^2.0.0" readable-stream "^2.0.0" +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.0.0.tgz#b1e7dc075a4e18e0aa59afdd7c3e5f3d3321cbeb" + integrity sha512-AEWhJz6Yi6hWtN1Sqy/H4sZo/lLMJ/NftXxGaDy/TnOMmmjsRaZc/Ts+U4BsPoBQkuunTN6t8hk7iU9A+HBxLw== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.0" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.0.0" + tar-stream "^2.1.2" + zip-stream "^4.0.0" + archiver@~2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/archiver/-/archiver-2.1.1.tgz#ff662b4a78201494a3ee544d3a33fe7496509ebc" @@ -3074,6 +3123,11 @@ async@^2.0.0, async@^2.4.0: dependencies: lodash "^4.17.14" +async@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + async@~0.2.9: version "0.2.10" resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" @@ -3540,7 +3594,7 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" -buffer-crc32@^0.2.1, buffer-crc32@~0.2.3: +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= @@ -4025,6 +4079,16 @@ compress-commons@^1.2.0: normalize-path "^2.0.0" readable-stream "^2.0.0" +compress-commons@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.0.1.tgz#c5fa908a791a0c71329fba211d73cd2a32005ea8" + integrity sha512-xZm9o6iikekkI0GnXCmAl3LQGZj5TBDj0zLowsqi7tJtEa3FMGSEcHcqrSJIrOAk1UG/NBbDn/F1q+MG/p/EsA== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -4171,6 +4235,14 @@ crc32-stream@^2.0.0: crc "^3.4.4" readable-stream "^2.0.0" +crc32-stream@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.0.tgz#05b7ca047d831e98c215538666f372b756d91893" + integrity sha512-tyMw2IeUX6t9jhgXI6um0eKfWq4EIDpfv5m7GX4Jzp7eVelQ360xd8EPXJhp2mHwLQIkqlnMLjzqSZI3a+0wRw== + dependencies: + crc "^3.4.4" + readable-stream "^3.4.0" + crc@^3.4.4: version "3.8.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" @@ -8212,6 +8284,21 @@ lodash.debounce@4.0.8, lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -8247,6 +8334,11 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= + lodash@^4.0.1, lodash@^4.16.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.12, lodash@~4.17.4: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" @@ -10107,7 +10199,7 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.1.4, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -10116,6 +10208,13 @@ readable-stream@^3.1.1, readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readdir-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.0.0.tgz#a495436934bbe57be6a68039d16e8946621eb8c5" + integrity sha512-km0DIcwQVZ1ZUhXhMWpF74/Wm5aFEd5/jDiVWF1Hkw2myPQovG8vCQ8+FQO2KXE9npQQvCnAMZhhWuUee4WcCQ== + dependencies: + minimatch "^3.0.4" + realpath-native@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866" @@ -11485,6 +11584,17 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar-stream@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41" + integrity sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA== + dependencies: + bl "^4.0.1" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39" @@ -12141,6 +12251,11 @@ uuid@^8.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== +uuid@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" + integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" @@ -12613,3 +12728,12 @@ zip-stream@^1.2.0: compress-commons "^1.2.0" lodash "^4.8.0" readable-stream "^2.0.0" + +zip-stream@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.0.2.tgz#3a20f1bd7729c2b59fd4efa04df5eb7a5a217d2e" + integrity sha512-TGxB2g+1ur6MHkvM644DuZr8Uzyz0k0OYWtS3YlpfWBEmK4woaC2t3+pozEL3dBfIPmpgmClR5B2QRcMgGt22g== + dependencies: + archiver-utils "^2.1.0" + compress-commons "^4.0.0" + readable-stream "^3.6.0" diff --git a/xplat/Flipper/FlipperCertificateProvider.h b/xplat/Flipper/FlipperCertificateProvider.h index 1b751fafa..4b41c88e2 100644 --- a/xplat/Flipper/FlipperCertificateProvider.h +++ b/xplat/Flipper/FlipperCertificateProvider.h @@ -28,10 +28,22 @@ class FlipperCertificateProvider { const std::string& path, const std::string& deviceID) = 0; + /** + * Sets certificate exchange medium + */ virtual void setCertificateExchangeMedium( const FlipperCertificateExchangeMedium medium) = 0; + /** + * Gets certificate exchange medium + */ virtual FlipperCertificateExchangeMedium getCertificateExchangeMedium() = 0; + + /** + * This lets the Client know if it should reset the connection folder when + * `stop` is called. + */ + virtual bool shouldResetCertificateFolder() = 0; }; } // namespace flipper diff --git a/xplat/Flipper/FlipperConnectionManagerImpl.cpp b/xplat/Flipper/FlipperConnectionManagerImpl.cpp index f0049d30f..a3fd84655 100644 --- a/xplat/Flipper/FlipperConnectionManagerImpl.cpp +++ b/xplat/Flipper/FlipperConnectionManagerImpl.cpp @@ -235,12 +235,15 @@ bool FlipperConnectionManagerImpl::connectSecurely() { if (deviceId.compare("unknown")) { loadingDeviceId->complete(); } + int medium = certProvider_ != nullptr + ? certProvider_->getCertificateExchangeMedium() + : FlipperCertificateExchangeMedium::FS_ACCESS; parameters.payload = rsocket::Payload(folly::toJson(folly::dynamic::object( "csr", contextStore_->getCertificateSigningRequest().c_str())( "csr_path", contextStore_->getCertificateDirectoryPath().c_str())( "os", deviceData_.os)("device", deviceData_.device)( - "device_id", deviceId)("app", deviceData_.app)( + "device_id", deviceId)("app", deviceData_.app)("medium", medium)( "sdk_version", sdkVersion))); address.setFromHostPort(deviceData_.host, securePort); @@ -293,6 +296,9 @@ void FlipperConnectionManagerImpl::reconnect() { } void FlipperConnectionManagerImpl::stop() { + if (certProvider_ && certProvider_->shouldResetCertificateFolder()) { + contextStore_->resetState(); + } if (!isStarted_) { log("Not started"); return; @@ -353,10 +359,13 @@ void FlipperConnectionManagerImpl::requestSignedCertFromFlipper() { auto generatingCSR = flipperState_->start("Generate CSR"); std::string csr = contextStore_->getCertificateSigningRequest(); generatingCSR->complete(); - + int medium = certProvider_ != nullptr + ? certProvider_->getCertificateExchangeMedium() + : FlipperCertificateExchangeMedium::FS_ACCESS; folly::dynamic message = folly::dynamic::object("method", "signCertificate")("csr", csr.c_str())( - "destination", contextStore_->getCertificateDirectoryPath().c_str()); + "destination", contextStore_->getCertificateDirectoryPath().c_str())( + "medium", medium); auto gettingCert = flipperState_->start("Getting cert from desktop"); flipperEventBase_->add([this, message, gettingCert]() { @@ -369,10 +378,32 @@ void FlipperConnectionManagerImpl::requestSignedCertFromFlipper() { folly::dynamic config = folly::parseJson(response); contextStore_->storeConnectionConfig(config); } - gettingCert->complete(); + if (certProvider_) { + auto gettingCertFromProvider = + flipperState_->start("Getting cert from Cert Provider"); + + try { + // Certificates should be present in app's sandbox after it is + // returned. The reason we can't have a completion block here + // is because if the certs are not present after it returns + // then the flipper tries to reconnect on insecured channel + // and recreates the app.csr. By the time completion block is + // called the DeviceCA cert doesn't match app's csr and it + // throws an SSL error. + certProvider_->getCertificates( + contextStore_->getCertificateDirectoryPath(), + contextStore_->getDeviceId()); + gettingCertFromProvider->complete(); + } catch (std::exception& e) { + gettingCertFromProvider->fail(e.what()); + gettingCert->fail(e.what()); + } catch (...) { + gettingCertFromProvider->fail("Exception from certProvider"); + gettingCert->fail("Exception from certProvider"); + } + } log("Certificate exchange complete."); - // TODO: Use Certificate provider get Certificates - // `certProvider_->getCertificates("path", "device");` + gettingCert->complete(); // Disconnect after message sending is complete. // This will trigger a reconnect which should use the secure