From d8ae50fbf8a82023b0695eecaff4ea75c0af2c90 Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Tue, 16 Jan 2024 15:56:24 +0100 Subject: [PATCH] feat: Migrate from safetynet to playintegrity --- packages/cozy-client/package.json | 4 +-- .../src/flagship-certification/README.md | 27 ++++++++----------- .../flagship-certification.spec.js | 8 +++--- .../store-attestation.android.js | 12 ++++----- .../src/flagship-certification/typedefs.js | 2 +- .../flagship-certification/typedefs.d.ts | 2 +- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/packages/cozy-client/package.json b/packages/cozy-client/package.json index ac36ef5f8..ffe9d5c20 100644 --- a/packages/cozy-client/package.json +++ b/packages/cozy-client/package.json @@ -70,9 +70,9 @@ "cozy-ui": ">=93.1.1", "react": "^16.7.0", "react-native": "^0.65.0", - "react-native-google-safetynet": "npm:cozy-react-native-google-safetynet@^1.0.0", "react-native-inappbrowser-reborn": "^3.5.1", - "react-native-ios11-devicecheck": "https://github.com/cozy/react-native-devicecheck#app-attest-v0.1" + "react-native-ios11-devicecheck": "https://github.com/cozy/react-native-devicecheck#app-attest-v0.1", + "react-native-google-play-integrity": "github:cozy/react-native-google-play-integrity#1.0.1" }, "sideEffects": false } diff --git a/packages/cozy-client/src/flagship-certification/README.md b/packages/cozy-client/src/flagship-certification/README.md index 6f6fc07ce..735a630ca 100644 --- a/packages/cozy-client/src/flagship-certification/README.md +++ b/packages/cozy-client/src/flagship-certification/README.md @@ -11,32 +11,27 @@ This verification is done by querying an app certificate from the app store (App - `attestation`: result from the `store certification` - `challenge`: unique token given to the app by `cozy-stack` that may be encrypted in the `attestation` as a proof of authenticity - `nonce`: data type used to store the `challenge` token -- `SafetyNet`: Google's implementation of the `store certification` +- `Play Integrity API`: Google's implementation of the `store certification` - `AppAttest`: Apple's implementation of the `store certification` ## Android certification -Android certification is based on [SafetyNet](https://developer.android.com/training/safetynet/index.html). +Android certification is based on [Play Integrity API](https://developer.android.com/google/play/integrity/overview). -This process requires to query a `challenge` from `cozy-stack` and to use it to init the `store certification` process through `SafetyNet`. Then the received `attestation` is send to `cozy-stack` for verification. +This process requires to query a `challenge` from `cozy-stack` and to use it to init the `store certification` process through `Play Integrity API`. Then the received `attestation` is send to `cozy-stack` for verification. The resulting `attestation` is in the form of a `JSON Web Signature` that embbed the following `JSON`: -```json +```js { - "apkCertificateDigestSha256": [ - "base64 encoded, SHA-256 hash of the certificate used to sign requesting app=" - ], - "apkDigestSha256": "kNv83tJLFqwliYQ/6HUPCeGkBzLLCX/nvT+EF3OEB2I=", - "apkPackageName": "com.package.name.of.requesting.app", - "basicIntegrity": true, - "ctsProfileMatch": true, - "evaluationType": "BASIC", - "nonce": "R2Rra24fVm5xa2Mg", - "timestampMs": 9860437986543 + requestDetails: { ... } + appIntegrity: { ... } + deviceIntegrity: { ... } + accountDetails: { ... } + environmentDetails: { ... } } ``` -The `attestation`'s content is described in the SafetyNet's documentation: https://developer.android.com/training/safetynet/attestation#use-response-server +The `attestation`'s content is described in the Play Integrity API's documentation: https://developer.android.com/google/play/integrity/verdicts#returned-verdict-format ## iOS certification @@ -65,7 +60,7 @@ const client = await initClient(uri, { clientName: 'YOUR_APP_NAME', shouldRequireFlagshipPermissions: true, certificationConfig: { - androidSafetyNetApiKey: 'YOUR_GOOGLE_SAFETY_NET_API_KEY' + cloudProjectNumber: 'YOUR_CLOUD_PROJECT_NUMBER' } }, ``` diff --git a/packages/cozy-client/src/flagship-certification/flagship-certification.spec.js b/packages/cozy-client/src/flagship-certification/flagship-certification.spec.js index ba507d5ea..b0a8ec6ef 100644 --- a/packages/cozy-client/src/flagship-certification/flagship-certification.spec.js +++ b/packages/cozy-client/src/flagship-certification/flagship-certification.spec.js @@ -44,7 +44,7 @@ const mockCorrectStoreApiRequest = () => { const mockCorrectCertificationConfig = () => { return { - androidSafetyNetApiKey: 'SOME_ANDROID_SAFETY_NET_API_KEY' + cloudProjectNumber: 'SOME_CLOUD_PROJECT_NUMBER' } } @@ -90,7 +90,7 @@ describe('certifyFlagship', () => { ) expect(getAppAttestationFromStore).toHaveBeenCalledWith('SOME_NONCE', { - androidSafetyNetApiKey: 'SOME_ANDROID_SAFETY_NET_API_KEY' + cloudProjectNumber: 'SOME_CLOUD_PROJECT_NUMBER' }) expect(client.stackClient.fetchJSON).toHaveBeenCalledWith( @@ -171,7 +171,7 @@ describe('certifyFlagship', () => { ) expect(getAppAttestationFromStore).toHaveBeenCalledWith('SOME_NONCE', { - androidSafetyNetApiKey: 'SOME_ANDROID_SAFETY_NET_API_KEY' + cloudProjectNumber: 'SOME_CLOUD_PROJECT_NUMBER' }) expect(console.warn).toHaveBeenCalledWith( @@ -201,7 +201,7 @@ describe('certifyFlagship', () => { ) expect(getAppAttestationFromStore).toHaveBeenCalledWith('SOME_NONCE', { - androidSafetyNetApiKey: 'SOME_ANDROID_SAFETY_NET_API_KEY' + cloudProjectNumber: 'SOME_CLOUD_PROJECT_NUMBER' }) expect(client.stackClient.fetchJSON).toHaveBeenCalledWith( diff --git a/packages/cozy-client/src/flagship-certification/store-attestation.android.js b/packages/cozy-client/src/flagship-certification/store-attestation.android.js index 50980a7d1..036f8995f 100644 --- a/packages/cozy-client/src/flagship-certification/store-attestation.android.js +++ b/packages/cozy-client/src/flagship-certification/store-attestation.android.js @@ -1,5 +1,5 @@ -//@ts-ignore next-line -import RNGoogleSafetyNet from 'react-native-google-safetynet' +//@ts-ignore next-line - this is a react-native module that is not installed in the monorepo, only in the consumer app +import PlayIntegrity from 'react-native-google-play-integrity' /** * Retrieve the app's attestation from the Google Play store @@ -13,18 +13,18 @@ export const getAppAttestationFromStore = async ( certificationConfig ) => { try { - const attestationResult = await RNGoogleSafetyNet.sendAttestationRequestJWT( + const integrityToken = await PlayIntegrity.requestIntegrityToken( nonce, - certificationConfig.androidSafetyNetApiKey + certificationConfig.cloudProjectNumber ) return { platform: 'android', - attestation: attestationResult + attestation: integrityToken } } catch (e) { throw new Error( - '[FLAGSHIP_CERTIFICATION] Something went wrong while requesting an attestation from Google Safetynet:\n' + + '[FLAGSHIP_CERTIFICATION] Something went wrong while requesting an attestation from Google Play Integrity API:\n' + e.message ) } diff --git a/packages/cozy-client/src/flagship-certification/typedefs.js b/packages/cozy-client/src/flagship-certification/typedefs.js index ff2b0d1e3..d36239049 100644 --- a/packages/cozy-client/src/flagship-certification/typedefs.js +++ b/packages/cozy-client/src/flagship-certification/typedefs.js @@ -15,7 +15,7 @@ /** * @typedef {object} CertificationConfig - Configuration to access the stores certification API - * @property {string} androidSafetyNetApiKey + * @property {string} cloudProjectNumber */ export default {} diff --git a/packages/cozy-client/types/flagship-certification/typedefs.d.ts b/packages/cozy-client/types/flagship-certification/typedefs.d.ts index aa4ddf849..e4a177c6d 100644 --- a/packages/cozy-client/types/flagship-certification/typedefs.d.ts +++ b/packages/cozy-client/types/flagship-certification/typedefs.d.ts @@ -20,5 +20,5 @@ export type AttestationResult = { * - Configuration to access the stores certification API */ export type CertificationConfig = { - androidSafetyNetApiKey: string; + cloudProjectNumber: string; };