Skip to content

Commit

Permalink
feat: Migrate from safetynet to playintegrity
Browse files Browse the repository at this point in the history
  • Loading branch information
acezard committed Jan 17, 2024
1 parent 2ded951 commit d8ae50f
Show file tree
Hide file tree
Showing 6 changed files with 25 additions and 30 deletions.
4 changes: 2 additions & 2 deletions packages/cozy-client/package.json
Expand Up @@ -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
}
27 changes: 11 additions & 16 deletions packages/cozy-client/src/flagship-certification/README.md
Expand Up @@ -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

Expand Down Expand Up @@ -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'
}
},
```
Expand Down
Expand Up @@ -44,7 +44,7 @@ const mockCorrectStoreApiRequest = () => {

const mockCorrectCertificationConfig = () => {
return {
androidSafetyNetApiKey: 'SOME_ANDROID_SAFETY_NET_API_KEY'
cloudProjectNumber: 'SOME_CLOUD_PROJECT_NUMBER'
}
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
@@ -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
Expand All @@ -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
)
}
Expand Down
Expand Up @@ -15,7 +15,7 @@

/**
* @typedef {object} CertificationConfig - Configuration to access the stores certification API
* @property {string} androidSafetyNetApiKey
* @property {string} cloudProjectNumber
*/

export default {}
Expand Up @@ -20,5 +20,5 @@ export type AttestationResult = {
* - Configuration to access the stores certification API
*/
export type CertificationConfig = {
androidSafetyNetApiKey: string;
cloudProjectNumber: string;
};

0 comments on commit d8ae50f

Please sign in to comment.