@symbiote-native/app-integrity 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +2 -0
- package/build/core/app-integrity.d.ts +18 -0
- package/build/core/app-integrity.js +66 -0
- package/build/core/index.d.ts +1 -0
- package/build/core/index.js +1 -0
- package/build/core/native-module.d.ts +12 -0
- package/build/core/native-module.js +3 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +3 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/app-integrity.d.ts +18 -0
- package/build-ngc/core/app-integrity.js +67 -0
- package/build-ngc/core/app-integrity.js.map +1 -0
- package/build-ngc/core/index.d.ts +1 -0
- package/build-ngc/core/index.js +2 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +12 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +150 -0
- package/src/angular/index.ts +2 -0
- package/src/core/app-integrity.ts +116 -0
- package/src/core/index.ts +11 -0
- package/src/core/native-module.ts +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# @symbiote-native/app-integrity
|
|
2
|
+
|
|
3
|
+
Let your backend tell your real app on a real device apart from a modified app, a script or an
|
|
4
|
+
emulator. One API for every [SymbioteNative](../../README.md) adapter (React, Vue, Svelte, Solid
|
|
5
|
+
and Angular).
|
|
6
|
+
|
|
7
|
+
It wraps [`@expo/app-integrity`](https://github.com/expo/expo/tree/main/packages/expo-app-integrity)
|
|
8
|
+
(Apple's App Attest, Google Play Integrity, and Android hardware-attested key generation). Like
|
|
9
|
+
[`@symbiote-native/application`](../application), everything here is a one-shot async call (or a
|
|
10
|
+
plain sync constant) with no per-instance state, so there is no hook, composable or service to
|
|
11
|
+
wrap: every adapter entry point is a plain re-export of the same `core`.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
**New app:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @symbiote-native/cli new my-app --app-integrity
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Existing SymbioteNative app:**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @symbiote-native/cli add --app-integrity
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Either way: installs `@symbiote-native/app-integrity` and wires the native autolinking
|
|
28
|
+
automatically, see [`@symbiote-native/cli`](../cli).
|
|
29
|
+
|
|
30
|
+
<details>
|
|
31
|
+
<summary>Manual install (no CLI, wiring native autolinking by hand)</summary>
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @symbiote-native/app-integrity
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`@expo/app-integrity` and `expo-modules-core` come along as regular dependencies, pinned to exact
|
|
38
|
+
versions. Never install either yourself, and never add the `expo` meta-package to your project
|
|
39
|
+
(it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
|
|
40
|
+
|
|
41
|
+
## Required one-time step: native autolinking wiring
|
|
42
|
+
|
|
43
|
+
Unlike a plain RN native module, `@expo/app-integrity`'s native code is discovered by
|
|
44
|
+
`expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism. This needs wiring
|
|
45
|
+
into the native host app **once**, covering this package and every other `expo-modules-core`
|
|
46
|
+
package with zero further changes:
|
|
47
|
+
|
|
48
|
+
| Platform | Touches |
|
|
49
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
50
|
+
| iOS | `ios/Podfile`, add `use_expo_modules!` |
|
|
51
|
+
| iOS | `AppDelegate.swift`, Expo's runtime-bootstrap hook |
|
|
52
|
+
| Android | `settings.gradle` / `app/build.gradle`, resolve and include the Expo Gradle projects |
|
|
53
|
+
| Android | `MainApplication.kt`, Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
|
|
54
|
+
|
|
55
|
+
Full mechanics live in the `symbiote-expo-native-module` project skill. Reference
|
|
56
|
+
implementation: `examples/expo-react/ios/Podfile` and
|
|
57
|
+
`examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
|
|
58
|
+
|
|
59
|
+
No app-level permission strings are needed. App Attest, Play Integrity, and hardware attestation
|
|
60
|
+
are all system services with no user-facing permission prompt.
|
|
61
|
+
|
|
62
|
+
Each platform needs one setup step of its own: on iOS, add the **App Attest** capability in Xcode
|
|
63
|
+
(Signing & Capabilities; the app needs a registered App ID); on Android, enable the Play Integrity
|
|
64
|
+
API for your app and note your Google Cloud project number.
|
|
65
|
+
|
|
66
|
+
</details>
|
|
67
|
+
|
|
68
|
+
## Shape
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
src/core/ isSupported constant, generateKeyAsync / attestKeyAsync / generateAssertionAsync
|
|
72
|
+
(App Attest, ios), prepareIntegrityTokenProviderAsync / requestIntegrityCheckAsync
|
|
73
|
+
(Play Integrity, android), isHardwareAttestationSupportedAsync /
|
|
74
|
+
generateHardwareAttestedKeyAsync / getAttestationCertificateChainAsync (hardware
|
|
75
|
+
attestation, android). native-module.ts resolves the native module via
|
|
76
|
+
expo-modules-core's requireNativeModule.
|
|
77
|
+
src/angular/ @symbiote-native/app-integrity/angular, export * from '../core'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core/`,
|
|
81
|
+
no physical per-framework file, since there's nothing to subscribe to or clean up. `./angular`
|
|
82
|
+
stays a physical file/subpath since Angular ships through a separate `ngc`/AOT build (`build-ngc/`).
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
isSupported: boolean // ios: real App Attest support; off ios: always true
|
|
88
|
+
|
|
89
|
+
generateKeyAsync(): Promise<string> // ios only
|
|
90
|
+
attestKeyAsync(keyId: string, challenge: string): Promise<string> // ios only
|
|
91
|
+
generateAssertionAsync(keyId: string, challenge: string): Promise<string> // ios only
|
|
92
|
+
|
|
93
|
+
prepareIntegrityTokenProviderAsync(cloudProjectNumber: string): Promise<void> // android only
|
|
94
|
+
requestIntegrityCheckAsync(requestHash: string): Promise<string> // android only
|
|
95
|
+
|
|
96
|
+
isHardwareAttestationSupportedAsync(): Promise<boolean> // android only, false elsewhere
|
|
97
|
+
generateHardwareAttestedKeyAsync(keyAlias: string, challenge: string): Promise<void> // android only
|
|
98
|
+
getAttestationCertificateChainAsync(keyAlias: string): Promise<string[]> // android only
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import {
|
|
103
|
+
generateKeyAsync,
|
|
104
|
+
isSupported,
|
|
105
|
+
} from '@symbiote-native/app-integrity';
|
|
106
|
+
// or the framework-scoped entry points, identical surface, re-exported verbatim:
|
|
107
|
+
import { generateKeyAsync } from '@symbiote-native/app-integrity/react';
|
|
108
|
+
import { generateKeyAsync } from '@symbiote-native/app-integrity/vue';
|
|
109
|
+
import { generateKeyAsync } from '@symbiote-native/app-integrity/angular';
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Notes
|
|
113
|
+
|
|
114
|
+
- **Every function throws `UnavailabilityError` when called on the wrong platform** (an
|
|
115
|
+
iOS-only App Attest function on Android, or an Android-only function on iOS), except
|
|
116
|
+
`isHardwareAttestationSupportedAsync`, which resolves `false` instead, matching upstream.
|
|
117
|
+
- `isSupported` is a plain constant read once at import time from the native `Constant`, `true`
|
|
118
|
+
on any non-iOS platform since App Attest has no Android/web equivalent to report on.
|
|
119
|
+
- The App Attest trio (`generateKeyAsync`/`attestKeyAsync`/`generateAssertionAsync`) is a
|
|
120
|
+
three-step flow: generate a key, have Apple attest it once, then generate per-request
|
|
121
|
+
assertions against the attested key. Send the attestation and each assertion to your own
|
|
122
|
+
server for verification, this package only produces the client-side artifacts.
|
|
123
|
+
- `generateHardwareAttestedKeyAsync`/`getAttestationCertificateChainAsync` target the Android
|
|
124
|
+
Keystore directly (works on GrapheneOS and other secure Android distributions), independent of
|
|
125
|
+
Play Integrity's `prepareIntegrityTokenProviderAsync`/`requestIntegrityCheckAsync` pair.
|
|
126
|
+
- If the Android token provider is reused for too long, the next token request fails with
|
|
127
|
+
`ERR_APP_INTEGRITY_PROVIDER_INVALID`. Call `prepareIntegrityTokenProviderAsync` again and retry.
|
|
128
|
+
- Verify on your server, never on the device: a check that runs only in the app can be bypassed by
|
|
129
|
+
the clients you are guarding against. The canary `AppIntegrityScreen` in the `examples/expo-*`
|
|
130
|
+
apps exercises every call.
|
|
131
|
+
|
|
132
|
+
## Common questions
|
|
133
|
+
|
|
134
|
+
- **`ERR_APP_INTEGRITY_PROVIDER_INVALID`.** The token provider expired; call
|
|
135
|
+
`prepareIntegrityTokenProviderAsync` again.
|
|
136
|
+
- **Per-platform flow?** Android: Play Integrity Standard request (prepare once, token per action).
|
|
137
|
+
iOS: App Attest key per user per device, attested once.
|
|
138
|
+
- **Enough alone?** No, verify the token on your server.
|
|
139
|
+
|
|
140
|
+
Sources: [Expo docs: AppIntegrity](https://docs.expo.dev/versions/latest/sdk/app-integrity/),
|
|
141
|
+
[Expo blog: Introducing Expo App Integrity](https://expo.dev/blog/expo-app-integrity).
|
|
142
|
+
|
|
143
|
+
## Test it
|
|
144
|
+
|
|
145
|
+
No Fabric/Descriptor angle at all, every export here is a one-shot async function or a plain
|
|
146
|
+
constant, never a view or per-instance state. Tests inject a fake native-module object in place
|
|
147
|
+
of the real `requireNativeModule` resolution (`src/core/app-integrity.test.ts`, `vitest`), no
|
|
148
|
+
`installFabric()`, no ViewConfig. Native behavior itself is verified on-device (see the parent
|
|
149
|
+
[README](../../README.md) for the project's testing model).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** @platform ios, true off ios since App Attest support has no Android/web equivalent */
|
|
2
|
+
export declare const isSupported: boolean;
|
|
3
|
+
/** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
|
|
4
|
+
export declare function generateKeyAsync(): Promise<string>;
|
|
5
|
+
/** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
|
|
6
|
+
export declare function attestKeyAsync(keyId: string, challenge: string): Promise<string>;
|
|
7
|
+
/** @platform ios */
|
|
8
|
+
export declare function generateAssertionAsync(keyId: string, challenge: string): Promise<string>;
|
|
9
|
+
/** @platform android */
|
|
10
|
+
export declare function prepareIntegrityTokenProviderAsync(cloudProjectNumber: string): Promise<void>;
|
|
11
|
+
/** @platform android */
|
|
12
|
+
export declare function requestIntegrityCheckAsync(requestHash: string): Promise<string>;
|
|
13
|
+
/** @platform android, `false` off android rather than throwing */
|
|
14
|
+
export declare function isHardwareAttestationSupportedAsync(): Promise<boolean>;
|
|
15
|
+
/** Usable on GrapheneOS and other secure Android distributions. @platform android */
|
|
16
|
+
export declare function generateHardwareAttestedKeyAsync(keyAlias: string, challenge: string): Promise<void>;
|
|
17
|
+
/** Base64-encoded X.509 certificates, verify server-side. @platform android */
|
|
18
|
+
export declare function getAttestationCertificateChainAsync(keyAlias: string): Promise<string[]>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import { expoAppIntegrity } from './native-module.js';
|
|
3
|
+
const NATIVE_MODULE_NAME = 'expo-app-integrity';
|
|
4
|
+
/** @platform ios, true off ios since App Attest support has no Android/web equivalent */
|
|
5
|
+
export const isSupported = Platform.OS === 'ios' ? (expoAppIntegrity.isSupported ?? false) : true;
|
|
6
|
+
/** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
|
|
7
|
+
export async function generateKeyAsync() {
|
|
8
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.generateKeyAsync) {
|
|
9
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateKeyAsync');
|
|
10
|
+
}
|
|
11
|
+
return expoAppIntegrity.generateKeyAsync();
|
|
12
|
+
}
|
|
13
|
+
/** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
|
|
14
|
+
export async function attestKeyAsync(keyId, challenge) {
|
|
15
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.attestKeyAsync) {
|
|
16
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'attestKeyAsync');
|
|
17
|
+
}
|
|
18
|
+
return expoAppIntegrity.attestKeyAsync(keyId, challenge);
|
|
19
|
+
}
|
|
20
|
+
/** @platform ios */
|
|
21
|
+
export async function generateAssertionAsync(keyId, challenge) {
|
|
22
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.generateAssertionAsync) {
|
|
23
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateAssertionAsync');
|
|
24
|
+
}
|
|
25
|
+
return expoAppIntegrity.generateAssertionAsync(keyId, challenge);
|
|
26
|
+
}
|
|
27
|
+
/** @platform android */
|
|
28
|
+
export async function prepareIntegrityTokenProviderAsync(cloudProjectNumber) {
|
|
29
|
+
if (Platform.OS !== 'android' ||
|
|
30
|
+
!expoAppIntegrity.prepareIntegrityTokenProviderAsync) {
|
|
31
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'prepareIntegrityTokenProviderAsync');
|
|
32
|
+
}
|
|
33
|
+
return expoAppIntegrity.prepareIntegrityTokenProviderAsync(cloudProjectNumber);
|
|
34
|
+
}
|
|
35
|
+
/** @platform android */
|
|
36
|
+
export async function requestIntegrityCheckAsync(requestHash) {
|
|
37
|
+
if (Platform.OS !== 'android' ||
|
|
38
|
+
!expoAppIntegrity.requestIntegrityCheckAsync) {
|
|
39
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestIntegrityCheckAsync');
|
|
40
|
+
}
|
|
41
|
+
return expoAppIntegrity.requestIntegrityCheckAsync(requestHash);
|
|
42
|
+
}
|
|
43
|
+
/** @platform android, `false` off android rather than throwing */
|
|
44
|
+
export async function isHardwareAttestationSupportedAsync() {
|
|
45
|
+
if (Platform.OS !== 'android' ||
|
|
46
|
+
!expoAppIntegrity.isHardwareAttestationSupportedAsync) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return expoAppIntegrity.isHardwareAttestationSupportedAsync();
|
|
50
|
+
}
|
|
51
|
+
/** Usable on GrapheneOS and other secure Android distributions. @platform android */
|
|
52
|
+
export async function generateHardwareAttestedKeyAsync(keyAlias, challenge) {
|
|
53
|
+
if (Platform.OS !== 'android' ||
|
|
54
|
+
!expoAppIntegrity.generateHardwareAttestedKeyAsync) {
|
|
55
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateHardwareAttestedKeyAsync');
|
|
56
|
+
}
|
|
57
|
+
return expoAppIntegrity.generateHardwareAttestedKeyAsync(keyAlias, challenge);
|
|
58
|
+
}
|
|
59
|
+
/** Base64-encoded X.509 certificates, verify server-side. @platform android */
|
|
60
|
+
export async function getAttestationCertificateChainAsync(keyAlias) {
|
|
61
|
+
if (Platform.OS !== 'android' ||
|
|
62
|
+
!expoAppIntegrity.getAttestationCertificateChainAsync) {
|
|
63
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getAttestationCertificateChainAsync');
|
|
64
|
+
}
|
|
65
|
+
return expoAppIntegrity.getAttestationCertificateChainAsync(keyAlias);
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type INativeAppIntegrityModule = {
|
|
2
|
+
isSupported?: boolean;
|
|
3
|
+
generateKeyAsync?(): Promise<string>;
|
|
4
|
+
attestKeyAsync?(keyId: string, challenge: string): Promise<string>;
|
|
5
|
+
generateAssertionAsync?(keyId: string, challenge: string): Promise<string>;
|
|
6
|
+
prepareIntegrityTokenProviderAsync?(cloudProjectNumber: string): Promise<void>;
|
|
7
|
+
requestIntegrityCheckAsync?(requestHash: string): Promise<string>;
|
|
8
|
+
isHardwareAttestationSupportedAsync?(): Promise<boolean>;
|
|
9
|
+
generateHardwareAttestedKeyAsync?(keyAlias: string, challenge: string): Promise<void>;
|
|
10
|
+
getAttestationCertificateChainAsync?(keyAlias: string): Promise<string[]>;
|
|
11
|
+
};
|
|
12
|
+
export declare const expoAppIntegrity: INativeAppIntegrityModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** @platform ios, true off ios since App Attest support has no Android/web equivalent */
|
|
2
|
+
export declare const isSupported: boolean;
|
|
3
|
+
/** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
|
|
4
|
+
export declare function generateKeyAsync(): Promise<string>;
|
|
5
|
+
/** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
|
|
6
|
+
export declare function attestKeyAsync(keyId: string, challenge: string): Promise<string>;
|
|
7
|
+
/** @platform ios */
|
|
8
|
+
export declare function generateAssertionAsync(keyId: string, challenge: string): Promise<string>;
|
|
9
|
+
/** @platform android */
|
|
10
|
+
export declare function prepareIntegrityTokenProviderAsync(cloudProjectNumber: string): Promise<void>;
|
|
11
|
+
/** @platform android */
|
|
12
|
+
export declare function requestIntegrityCheckAsync(requestHash: string): Promise<string>;
|
|
13
|
+
/** @platform android, `false` off android rather than throwing */
|
|
14
|
+
export declare function isHardwareAttestationSupportedAsync(): Promise<boolean>;
|
|
15
|
+
/** Usable on GrapheneOS and other secure Android distributions. @platform android */
|
|
16
|
+
export declare function generateHardwareAttestedKeyAsync(keyAlias: string, challenge: string): Promise<void>;
|
|
17
|
+
/** Base64-encoded X.509 certificates, verify server-side. @platform android */
|
|
18
|
+
export declare function getAttestationCertificateChainAsync(keyAlias: string): Promise<string[]>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import { expoAppIntegrity } from './native-module';
|
|
3
|
+
const NATIVE_MODULE_NAME = 'expo-app-integrity';
|
|
4
|
+
/** @platform ios, true off ios since App Attest support has no Android/web equivalent */
|
|
5
|
+
export const isSupported = Platform.OS === 'ios' ? (expoAppIntegrity.isSupported ?? false) : true;
|
|
6
|
+
/** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
|
|
7
|
+
export async function generateKeyAsync() {
|
|
8
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.generateKeyAsync) {
|
|
9
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateKeyAsync');
|
|
10
|
+
}
|
|
11
|
+
return expoAppIntegrity.generateKeyAsync();
|
|
12
|
+
}
|
|
13
|
+
/** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
|
|
14
|
+
export async function attestKeyAsync(keyId, challenge) {
|
|
15
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.attestKeyAsync) {
|
|
16
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'attestKeyAsync');
|
|
17
|
+
}
|
|
18
|
+
return expoAppIntegrity.attestKeyAsync(keyId, challenge);
|
|
19
|
+
}
|
|
20
|
+
/** @platform ios */
|
|
21
|
+
export async function generateAssertionAsync(keyId, challenge) {
|
|
22
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.generateAssertionAsync) {
|
|
23
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateAssertionAsync');
|
|
24
|
+
}
|
|
25
|
+
return expoAppIntegrity.generateAssertionAsync(keyId, challenge);
|
|
26
|
+
}
|
|
27
|
+
/** @platform android */
|
|
28
|
+
export async function prepareIntegrityTokenProviderAsync(cloudProjectNumber) {
|
|
29
|
+
if (Platform.OS !== 'android' ||
|
|
30
|
+
!expoAppIntegrity.prepareIntegrityTokenProviderAsync) {
|
|
31
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'prepareIntegrityTokenProviderAsync');
|
|
32
|
+
}
|
|
33
|
+
return expoAppIntegrity.prepareIntegrityTokenProviderAsync(cloudProjectNumber);
|
|
34
|
+
}
|
|
35
|
+
/** @platform android */
|
|
36
|
+
export async function requestIntegrityCheckAsync(requestHash) {
|
|
37
|
+
if (Platform.OS !== 'android' ||
|
|
38
|
+
!expoAppIntegrity.requestIntegrityCheckAsync) {
|
|
39
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestIntegrityCheckAsync');
|
|
40
|
+
}
|
|
41
|
+
return expoAppIntegrity.requestIntegrityCheckAsync(requestHash);
|
|
42
|
+
}
|
|
43
|
+
/** @platform android, `false` off android rather than throwing */
|
|
44
|
+
export async function isHardwareAttestationSupportedAsync() {
|
|
45
|
+
if (Platform.OS !== 'android' ||
|
|
46
|
+
!expoAppIntegrity.isHardwareAttestationSupportedAsync) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return expoAppIntegrity.isHardwareAttestationSupportedAsync();
|
|
50
|
+
}
|
|
51
|
+
/** Usable on GrapheneOS and other secure Android distributions. @platform android */
|
|
52
|
+
export async function generateHardwareAttestedKeyAsync(keyAlias, challenge) {
|
|
53
|
+
if (Platform.OS !== 'android' ||
|
|
54
|
+
!expoAppIntegrity.generateHardwareAttestedKeyAsync) {
|
|
55
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateHardwareAttestedKeyAsync');
|
|
56
|
+
}
|
|
57
|
+
return expoAppIntegrity.generateHardwareAttestedKeyAsync(keyAlias, challenge);
|
|
58
|
+
}
|
|
59
|
+
/** Base64-encoded X.509 certificates, verify server-side. @platform android */
|
|
60
|
+
export async function getAttestationCertificateChainAsync(keyAlias) {
|
|
61
|
+
if (Platform.OS !== 'android' ||
|
|
62
|
+
!expoAppIntegrity.getAttestationCertificateChainAsync) {
|
|
63
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getAttestationCertificateChainAsync');
|
|
64
|
+
}
|
|
65
|
+
return expoAppIntegrity.getAttestationCertificateChainAsync(keyAlias);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=app-integrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-integrity.js","sourceRoot":"","sources":["../../src/core/app-integrity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { attestKeyAsync, generateAssertionAsync, generateHardwareAttestedKeyAsync, generateKeyAsync, getAttestationCertificateChainAsync, isHardwareAttestationSupportedAsync, isSupported, prepareIntegrityTokenProviderAsync, requestIntegrityCheckAsync, } from './app-integrity';
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,mCAAmC,EACnC,mCAAmC,EACnC,WAAW,EACX,kCAAkC,EAClC,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type INativeAppIntegrityModule = {
|
|
2
|
+
isSupported?: boolean;
|
|
3
|
+
generateKeyAsync?(): Promise<string>;
|
|
4
|
+
attestKeyAsync?(keyId: string, challenge: string): Promise<string>;
|
|
5
|
+
generateAssertionAsync?(keyId: string, challenge: string): Promise<string>;
|
|
6
|
+
prepareIntegrityTokenProviderAsync?(cloudProjectNumber: string): Promise<void>;
|
|
7
|
+
requestIntegrityCheckAsync?(requestHash: string): Promise<string>;
|
|
8
|
+
isHardwareAttestationSupportedAsync?(): Promise<boolean>;
|
|
9
|
+
generateHardwareAttestedKeyAsync?(keyAlias: string, challenge: string): Promise<void>;
|
|
10
|
+
getAttestationCertificateChainAsync?(keyAlias: string): Promise<string[]>;
|
|
11
|
+
};
|
|
12
|
+
export declare const expoAppIntegrity: INativeAppIntegrityModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,8BAA8B,GAAG,kBAAkB,CAAC;AAsB1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CACjD,8BAA8B,CAC/B,CAAC"}
|
package/native-link.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@symbiote-native/app-integrity",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "@expo/app-integrity wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Apple's App Attest, Android's Play Integrity, and Android hardware-attested key generation.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react-native",
|
|
8
|
+
"symbiote-native",
|
|
9
|
+
"app-integrity",
|
|
10
|
+
"react",
|
|
11
|
+
"vue",
|
|
12
|
+
"svelte",
|
|
13
|
+
"solid",
|
|
14
|
+
"angular",
|
|
15
|
+
"app-attest",
|
|
16
|
+
"play-integrity"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
21
|
+
"directory": "packages/app-integrity"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/app-integrity#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
26
|
+
},
|
|
27
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./build/core/index.js",
|
|
30
|
+
"module": "./build/core/index.js",
|
|
31
|
+
"types": "./build/core/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./build/core/index.d.ts",
|
|
35
|
+
"default": "./build/core/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./vue": {
|
|
38
|
+
"types": "./build/core/index.d.ts",
|
|
39
|
+
"default": "./build/core/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./react": {
|
|
42
|
+
"types": "./build/core/index.d.ts",
|
|
43
|
+
"default": "./build/core/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./svelte": {
|
|
46
|
+
"types": "./build/core/index.d.ts",
|
|
47
|
+
"default": "./build/core/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./solid": {
|
|
50
|
+
"types": "./build/core/index.d.ts",
|
|
51
|
+
"default": "./build/core/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./angular": {
|
|
54
|
+
"types": "./build-ngc/angular/index.d.ts",
|
|
55
|
+
"react-native": "./build-ngc/angular/index.js",
|
|
56
|
+
"default": "./src/angular/index.ts"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"src",
|
|
61
|
+
"build",
|
|
62
|
+
"build-ngc",
|
|
63
|
+
"native-link.json",
|
|
64
|
+
"!src/**/*.test.*",
|
|
65
|
+
"!src/**/*.spec.*",
|
|
66
|
+
"!src/**/*.detox.*"
|
|
67
|
+
],
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@expo/app-integrity": "57.0.2",
|
|
73
|
+
"expo-modules-core": "57.0.20"
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"@angular/core": ">=20",
|
|
77
|
+
"@vue/runtime-core": "^3.5.13",
|
|
78
|
+
"react": ">=19.0.0",
|
|
79
|
+
"react-native": ">=0.86",
|
|
80
|
+
"solid-js": ">=1.9.0",
|
|
81
|
+
"svelte": ">=5.56.0",
|
|
82
|
+
"vue": ">=3.5.0",
|
|
83
|
+
"@symbiote-native/angular": "^3.1.3",
|
|
84
|
+
"@symbiote-native/engine": "^1.4.0",
|
|
85
|
+
"@symbiote-native/solid": "^3.0.5",
|
|
86
|
+
"@symbiote-native/react": "^3.1.0",
|
|
87
|
+
"@symbiote-native/svelte": "^3.0.5",
|
|
88
|
+
"@symbiote-native/vue": "^3.1.0"
|
|
89
|
+
},
|
|
90
|
+
"peerDependenciesMeta": {
|
|
91
|
+
"@symbiote-native/angular": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@symbiote-native/react": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@symbiote-native/solid": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@symbiote-native/svelte": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"@symbiote-native/vue": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"@angular/core": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
109
|
+
"@vue/runtime-core": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"react": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"solid-js": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"svelte": {
|
|
119
|
+
"optional": true
|
|
120
|
+
},
|
|
121
|
+
"vue": {
|
|
122
|
+
"optional": true
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"devDependencies": {
|
|
126
|
+
"@angular/compiler": "~22.0.8",
|
|
127
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
128
|
+
"@angular/core": "~22.0.8",
|
|
129
|
+
"@types/node": "^26.0.0",
|
|
130
|
+
"@types/react": "^19.2.0",
|
|
131
|
+
"@vue/runtime-core": "^3.5.13",
|
|
132
|
+
"react": "19.2.3",
|
|
133
|
+
"solid-js": "^1.9.14",
|
|
134
|
+
"svelte": "^5.56.0",
|
|
135
|
+
"typescript": "~6.0.0",
|
|
136
|
+
"@symbiote-native/engine": "1.4.0",
|
|
137
|
+
"@symbiote-native/svelte": "3.0.5",
|
|
138
|
+
"@symbiote-native/react": "3.1.0",
|
|
139
|
+
"@symbiote-native/angular": "3.1.3",
|
|
140
|
+
"@symbiote-native/solid": "3.0.5",
|
|
141
|
+
"@symbiote-native/vue": "3.1.0",
|
|
142
|
+
"@symbiote-native/test-utils": "0.4.5"
|
|
143
|
+
},
|
|
144
|
+
"scripts": {
|
|
145
|
+
"typecheck": "tsc --build",
|
|
146
|
+
"clean": "rm -rf build-ngc",
|
|
147
|
+
"ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
|
|
148
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import { expoAppIntegrity } from './native-module';
|
|
3
|
+
|
|
4
|
+
const NATIVE_MODULE_NAME = 'expo-app-integrity';
|
|
5
|
+
|
|
6
|
+
/** @platform ios, true off ios since App Attest support has no Android/web equivalent */
|
|
7
|
+
export const isSupported: boolean =
|
|
8
|
+
Platform.OS === 'ios' ? (expoAppIntegrity.isSupported ?? false) : true;
|
|
9
|
+
|
|
10
|
+
/** Creates a new App Attest key, stored in the Secure Enclave. @platform ios */
|
|
11
|
+
export async function generateKeyAsync(): Promise<string> {
|
|
12
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.generateKeyAsync) {
|
|
13
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateKeyAsync');
|
|
14
|
+
}
|
|
15
|
+
return expoAppIntegrity.generateKeyAsync();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Asks Apple to attest to the validity of a key generated by `generateKeyAsync`. @platform ios */
|
|
19
|
+
export async function attestKeyAsync(
|
|
20
|
+
keyId: string,
|
|
21
|
+
challenge: string,
|
|
22
|
+
): Promise<string> {
|
|
23
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.attestKeyAsync) {
|
|
24
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'attestKeyAsync');
|
|
25
|
+
}
|
|
26
|
+
return expoAppIntegrity.attestKeyAsync(keyId, challenge);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @platform ios */
|
|
30
|
+
export async function generateAssertionAsync(
|
|
31
|
+
keyId: string,
|
|
32
|
+
challenge: string,
|
|
33
|
+
): Promise<string> {
|
|
34
|
+
if (Platform.OS !== 'ios' || !expoAppIntegrity.generateAssertionAsync) {
|
|
35
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'generateAssertionAsync');
|
|
36
|
+
}
|
|
37
|
+
return expoAppIntegrity.generateAssertionAsync(keyId, challenge);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @platform android */
|
|
41
|
+
export async function prepareIntegrityTokenProviderAsync(
|
|
42
|
+
cloudProjectNumber: string,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
if (
|
|
45
|
+
Platform.OS !== 'android' ||
|
|
46
|
+
!expoAppIntegrity.prepareIntegrityTokenProviderAsync
|
|
47
|
+
) {
|
|
48
|
+
throw new UnavailabilityError(
|
|
49
|
+
NATIVE_MODULE_NAME,
|
|
50
|
+
'prepareIntegrityTokenProviderAsync',
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return expoAppIntegrity.prepareIntegrityTokenProviderAsync(
|
|
54
|
+
cloudProjectNumber,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @platform android */
|
|
59
|
+
export async function requestIntegrityCheckAsync(
|
|
60
|
+
requestHash: string,
|
|
61
|
+
): Promise<string> {
|
|
62
|
+
if (
|
|
63
|
+
Platform.OS !== 'android' ||
|
|
64
|
+
!expoAppIntegrity.requestIntegrityCheckAsync
|
|
65
|
+
) {
|
|
66
|
+
throw new UnavailabilityError(
|
|
67
|
+
NATIVE_MODULE_NAME,
|
|
68
|
+
'requestIntegrityCheckAsync',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return expoAppIntegrity.requestIntegrityCheckAsync(requestHash);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @platform android, `false` off android rather than throwing */
|
|
75
|
+
export async function isHardwareAttestationSupportedAsync(): Promise<boolean> {
|
|
76
|
+
if (
|
|
77
|
+
Platform.OS !== 'android' ||
|
|
78
|
+
!expoAppIntegrity.isHardwareAttestationSupportedAsync
|
|
79
|
+
) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return expoAppIntegrity.isHardwareAttestationSupportedAsync();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Usable on GrapheneOS and other secure Android distributions. @platform android */
|
|
86
|
+
export async function generateHardwareAttestedKeyAsync(
|
|
87
|
+
keyAlias: string,
|
|
88
|
+
challenge: string,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
if (
|
|
91
|
+
Platform.OS !== 'android' ||
|
|
92
|
+
!expoAppIntegrity.generateHardwareAttestedKeyAsync
|
|
93
|
+
) {
|
|
94
|
+
throw new UnavailabilityError(
|
|
95
|
+
NATIVE_MODULE_NAME,
|
|
96
|
+
'generateHardwareAttestedKeyAsync',
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return expoAppIntegrity.generateHardwareAttestedKeyAsync(keyAlias, challenge);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Base64-encoded X.509 certificates, verify server-side. @platform android */
|
|
103
|
+
export async function getAttestationCertificateChainAsync(
|
|
104
|
+
keyAlias: string,
|
|
105
|
+
): Promise<string[]> {
|
|
106
|
+
if (
|
|
107
|
+
Platform.OS !== 'android' ||
|
|
108
|
+
!expoAppIntegrity.getAttestationCertificateChainAsync
|
|
109
|
+
) {
|
|
110
|
+
throw new UnavailabilityError(
|
|
111
|
+
NATIVE_MODULE_NAME,
|
|
112
|
+
'getAttestationCertificateChainAsync',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return expoAppIntegrity.getAttestationCertificateChainAsync(keyAlias);
|
|
116
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
attestKeyAsync,
|
|
3
|
+
generateAssertionAsync,
|
|
4
|
+
generateHardwareAttestedKeyAsync,
|
|
5
|
+
generateKeyAsync,
|
|
6
|
+
getAttestationCertificateChainAsync,
|
|
7
|
+
isHardwareAttestationSupportedAsync,
|
|
8
|
+
isSupported,
|
|
9
|
+
prepareIntegrityTokenProviderAsync,
|
|
10
|
+
requestIntegrityCheckAsync,
|
|
11
|
+
} from './app-integrity';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
|
|
3
|
+
const EXPO_APP_INTEGRITY_MODULE_NAME = 'ExpoAppIntegrity';
|
|
4
|
+
|
|
5
|
+
// The App Attest trio and `isSupported` exist only on iOS, the Play Integrity/hardware-attestation
|
|
6
|
+
// methods only on Android, so app-integrity.ts checks Platform.OS before calling through, same
|
|
7
|
+
// convention as packages/application/src/core/native-module.ts
|
|
8
|
+
export type INativeAppIntegrityModule = {
|
|
9
|
+
isSupported?: boolean;
|
|
10
|
+
generateKeyAsync?(): Promise<string>;
|
|
11
|
+
attestKeyAsync?(keyId: string, challenge: string): Promise<string>;
|
|
12
|
+
generateAssertionAsync?(keyId: string, challenge: string): Promise<string>;
|
|
13
|
+
prepareIntegrityTokenProviderAsync?(
|
|
14
|
+
cloudProjectNumber: string,
|
|
15
|
+
): Promise<void>;
|
|
16
|
+
requestIntegrityCheckAsync?(requestHash: string): Promise<string>;
|
|
17
|
+
isHardwareAttestationSupportedAsync?(): Promise<boolean>;
|
|
18
|
+
generateHardwareAttestedKeyAsync?(
|
|
19
|
+
keyAlias: string,
|
|
20
|
+
challenge: string,
|
|
21
|
+
): Promise<void>;
|
|
22
|
+
getAttestationCertificateChainAsync?(keyAlias: string): Promise<string[]>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const expoAppIntegrity = requireNativeModule<INativeAppIntegrityModule>(
|
|
26
|
+
EXPO_APP_INTEGRITY_MODULE_NAME,
|
|
27
|
+
);
|