@burnt-labs/expo-satya-attest 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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/PROVIDER_REGISTRY.md +271 -0
  3. package/PUBLISHING.md +291 -0
  4. package/README.md +374 -0
  5. package/android/build.gradle +37 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/expo/modules/satyaattest/SatyaAttestModule.kt +620 -0
  8. package/android/src/main/java/uniffi/satya_ffi/satya_ffi.kt +1423 -0
  9. package/android/src/main/jniLibs/arm64-v8a/libsatya_ffi.so +0 -0
  10. package/android/src/main/jniLibs/armeabi-v7a/libsatya_ffi.so +0 -0
  11. package/android/src/main/jniLibs/x86_64/libsatya_ffi.so +0 -0
  12. package/app.plugin.js +131 -0
  13. package/build/SatyaAttest.types.d.ts +200 -0
  14. package/build/SatyaAttest.types.d.ts.map +1 -0
  15. package/build/SatyaAttest.types.js +10 -0
  16. package/build/SatyaAttest.types.js.map +1 -0
  17. package/build/SatyaAttestModule.d.ts +29 -0
  18. package/build/SatyaAttestModule.d.ts.map +1 -0
  19. package/build/SatyaAttestModule.js +3 -0
  20. package/build/SatyaAttestModule.js.map +1 -0
  21. package/build/SatyaAttestModule.web.d.ts +14 -0
  22. package/build/SatyaAttestModule.web.d.ts.map +1 -0
  23. package/build/SatyaAttestModule.web.js +28 -0
  24. package/build/SatyaAttestModule.web.js.map +1 -0
  25. package/build/SatyaMobileClient.d.ts +37 -0
  26. package/build/SatyaMobileClient.d.ts.map +1 -0
  27. package/build/SatyaMobileClient.js +82 -0
  28. package/build/SatyaMobileClient.js.map +1 -0
  29. package/build/SatyaProviderAttestButton.d.ts +99 -0
  30. package/build/SatyaProviderAttestButton.d.ts.map +1 -0
  31. package/build/SatyaProviderAttestButton.js +801 -0
  32. package/build/SatyaProviderAttestButton.js.map +1 -0
  33. package/build/index.d.ts +13 -0
  34. package/build/index.d.ts.map +1 -0
  35. package/build/index.js +16 -0
  36. package/build/index.js.map +1 -0
  37. package/build/providerPolicy.d.ts +10 -0
  38. package/build/providerPolicy.d.ts.map +1 -0
  39. package/build/providerPolicy.js +81 -0
  40. package/build/providerPolicy.js.map +1 -0
  41. package/build/providerRegistry.d.ts +17 -0
  42. package/build/providerRegistry.d.ts.map +1 -0
  43. package/build/providerRegistry.js +522 -0
  44. package/build/providerRegistry.js.map +1 -0
  45. package/build/providerWebview.d.ts +37 -0
  46. package/build/providerWebview.d.ts.map +1 -0
  47. package/build/providerWebview.js +524 -0
  48. package/build/providerWebview.js.map +1 -0
  49. package/build/providers.json +981 -0
  50. package/expo-module.config.json +9 -0
  51. package/ios/Frameworks/libsatya_ffi-rs.xcframework/Info.plist +29 -0
  52. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/Headers/module.modulemap +7 -0
  53. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/Headers/satya_ffi.swift +989 -0
  54. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/Headers/satya_ffiFFI.h +585 -0
  55. package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/libsatya_ffi.a +0 -0
  56. package/ios/Generated/satya_ffi.swift +989 -0
  57. package/ios/SatyaAttest.podspec +27 -0
  58. package/ios/SatyaAttestModule.swift +932 -0
  59. package/package.json +83 -0
  60. package/scripts/build-rust-android.sh +51 -0
  61. package/scripts/build-rust-ios.sh +129 -0
  62. package/src/SatyaAttest.types.ts +239 -0
  63. package/src/SatyaAttestModule.ts +36 -0
  64. package/src/SatyaAttestModule.web.ts +36 -0
  65. package/src/SatyaMobileClient.ts +122 -0
  66. package/src/SatyaProviderAttestButton.tsx +1142 -0
  67. package/src/index.ts +48 -0
  68. package/src/providerPolicy.ts +122 -0
  69. package/src/providerRegistry.ts +683 -0
  70. package/src/providerWebview.ts +548 -0
  71. package/src/providers.json +981 -0
package/README.md ADDED
@@ -0,0 +1,374 @@
1
+ # @burnt-labs/expo-satya-attest
2
+
3
+ Expo / React Native native module for SATYA mobile app attestation. The module uses
4
+ the Rust `satya-sdk` / `satya-ffi` pipeline for artifact construction and verifier
5
+ policy, while Swift and Kotlin provide platform-specific WebView capture, native HTTPS
6
+ replay, hardware-backed signing, and platform attestation evidence.
7
+
8
+ ## Current Flow
9
+
10
+ The supported Expo flow is **certificate-pinned native replay**. Third-party apps can
11
+ use the SDK-owned button component for the full managed flow:
12
+
13
+ 1. The app renders `SatyaProviderAttestButton` with a provider id and transport config.
14
+ 2. The SDK refreshes SPKI pin policy from the verifier, falling back to registry pins.
15
+ 3. The SDK opens an in-app `react-native-webview` modal for login and request discovery.
16
+ 4. The SDK waits for a provider request that matches template `requestMatchRules`.
17
+ 5. Swift/Kotlin replay the trusted template URL natively with shared WebView cookies.
18
+ 6. The native client uses system trust without user roots and requires SPKI pins.
19
+ 7. The Rust core emits a `native-pinned-observed` SATYA artifact.
20
+ 8. The SDK verifies locally, optionally posts the artifact to `satya-verifier-service`,
21
+ and returns the result through `onComplete`.
22
+
23
+ The earlier mock and plain `webview-observed` modes are no longer exposed through the
24
+ high-level Expo client because they do not prove a provider response fetched through a
25
+ pinned native TLS connection.
26
+
27
+ ## Native Trust Tiers
28
+
29
+ | Platform | Witness key | Platform evidence | Typical tier |
30
+ | ---------------- | ------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
31
+ | iOS device | Secure Enclave P-256 | App Attest assertion/attestation | `ios-app-attest-sep-bound` |
32
+ | iOS simulator | software P-256 | honest simulator fallback | `ios-simulator-dev` |
33
+ | Android device | Keystore EC, StrongBox when available | Play Integrity hook | `android-strongbox-play-integrity` / `android-keystore-play-integrity-no-strongbox` |
34
+ | Android emulator | software EC | honest emulator fallback | `android-emulator-dev` |
35
+
36
+ Artifacts are dev-notary signed by default. Production-grade verification requires
37
+ production notary keys, active templates, fresh revocation epochs, and pass-2 platform
38
+ verification where appropriate.
39
+
40
+ ## Build The Rust Core
41
+
42
+ The generated native artifacts are gitignored:
43
+
44
+ - `ios/Frameworks/`
45
+ - `ios/Generated/`
46
+ - `android/src/main/jniLibs/`
47
+ - generated uniffi Swift/Kotlin glue
48
+
49
+ Build them from the workspace root before opening Xcode or Gradle:
50
+
51
+ ```bash
52
+ # iOS XCFramework + Swift glue
53
+ platform_sdks/expo-satya-attest/scripts/build-rust-ios.sh
54
+
55
+ # Android .so files + Kotlin glue
56
+ # One-time prereqs: Android NDK, JDK 17, cargo-ndk, and Android Rust targets.
57
+ platform_sdks/expo-satya-attest/scripts/build-rust-android.sh
58
+ ```
59
+
60
+ From the module directory, the same commands are available as:
61
+
62
+ ```bash
63
+ npm run build:native:ios
64
+ npm run build:native:android
65
+ ```
66
+
67
+ `build:native:ios` strips local/debug symbols from the XCFramework and publishes a
68
+ physical-device `arm64` iOS slice by default because the real App Attest flow does not
69
+ work on simulator. See [`PUBLISHING.md`](./PUBLISHING.md) for optional simulator fallback
70
+ builds and other binary-size knobs.
71
+
72
+ Build TypeScript after editing `src/*.ts` or `src/*.tsx`:
73
+
74
+ ```bash
75
+ cd platform_sdks/expo-satya-attest
76
+ npm install
77
+ npm run build
78
+ npm run lint
79
+ ```
80
+
81
+ The example app consumes the compiled `build/` directory, so restart Metro with
82
+ `--clear` after rebuilding the module.
83
+
84
+ ## Provider Registry
85
+
86
+ Provider configs live in one SDK-owned JSON file: [`src/providers.json`](./src/providers.json).
87
+ The registry includes Kaggle, Deel, Gusto, Aadhaar, Uber, and Swiggy provider templates
88
+ ported from `mobile_tlsn_plugin`.
89
+
90
+ Useful exports:
91
+
92
+ ```ts
93
+ import {
94
+ loadProviderRegistry,
95
+ listProviderRefs,
96
+ requireProviderTemplate,
97
+ SatyaProviderAttestButton,
98
+ validateProviderRegistry,
99
+ withProviderSpkiPins,
100
+ SatyaMobileClient,
101
+ } from '@burnt-labs/expo-satya-attest';
102
+ ```
103
+
104
+ Native replay requires at least one SPKI SHA-256 base64 pin at runtime. By default,
105
+ `SatyaProviderAttestButton` fetches
106
+ `GET /v1/providers/{providerId}/policy` from `transportConfig.notaryUrl` before opening
107
+ the WebView, validates that the returned provider id, host, template hash, and parser
108
+ hash match the selected template, and freezes those pins for the attestation run. If the
109
+ verifier is unavailable, it falls back to the SDK registry pins. Set
110
+ `providerPolicyRefresh="verifier-required"` to fail closed whenever the verifier policy
111
+ cannot be fetched, or `providerPolicyRefresh="template-only"` for offline demos.
112
+
113
+ ## Installation Options
114
+
115
+ Every install path also needs the WebView peer dependency:
116
+
117
+ ```bash
118
+ npx expo install react-native-webview
119
+ ```
120
+
121
+ Use npm for released apps:
122
+
123
+ ```bash
124
+ npx expo install @burnt-labs/expo-satya-attest
125
+ ```
126
+
127
+ Use a local checkout while developing the SDK or testing unpublished changes:
128
+
129
+ ```bash
130
+ cd /path/to/tls-app-attest/platform_sdks/expo-satya-attest
131
+ npm install
132
+ npm run build
133
+ npm run check:native-artifacts
134
+
135
+ cd /path/to/your-expo-app
136
+ npm install /path/to/tls-app-attest/platform_sdks/expo-satya-attest
137
+ npx expo install react-native-webview
138
+ ```
139
+
140
+ Re-run `npm run build` in the SDK directory after TypeScript edits. Re-run the native
141
+ build scripts after Rust/UniFFI edits so the local package still contains
142
+ `ios/Generated`, `ios/Frameworks`, Android `jniLibs`, and generated Kotlin.
143
+
144
+ Use GitHub when you want a pinned unreleased version. The most reliable GitHub install
145
+ is a release tarball produced by `npm pack`, because it contains the same files npm
146
+ would publish:
147
+
148
+ ```bash
149
+ npm install https://github.com/<owner>/<repo>/releases/download/expo-satya-attest-v0.1.0/burnt-labs-expo-satya-attest-0.1.0.tgz
150
+ npx expo install react-native-webview
151
+ ```
152
+
153
+ Installing from a Git ref also works when that ref has the SDK package at the repository
154
+ root, such as a dedicated SDK repo or a subtree/split branch:
155
+
156
+ ```bash
157
+ npm install github:<owner>/<sdk-repo>#v0.1.0
158
+ npm install git+https://github.com/<owner>/<sdk-repo>.git#v0.1.0
159
+ npx expo install react-native-webview
160
+ ```
161
+
162
+ Do not point npm at the monorepo root unless that Git ref is prepared so
163
+ `package.json`, `build/`, native artifacts, and Expo module files are at the root of the
164
+ Git package. npm does not install this monorepo's `platform_sdks/expo-satya-attest`
165
+ subdirectory directly from a normal GitHub URL.
166
+
167
+ After changing the install source, rebuild the native app:
168
+
169
+ ```bash
170
+ npx expo prebuild --clean
171
+ npx expo run:ios --device
172
+ npx expo run:android
173
+ ```
174
+
175
+ ## Plug-And-Play API
176
+
177
+ Add the Expo config plugin as shown below, rebuild the native app, then render the
178
+ SDK-owned button:
179
+
180
+ ```tsx
181
+ import { SatyaProviderAttestButton } from '@burnt-labs/expo-satya-attest';
182
+
183
+ export function KaggleAttestation() {
184
+ return (
185
+ <SatyaProviderAttestButton
186
+ providerId="kaggle.current_user.v1"
187
+ providerPolicyRefresh="verifier-first"
188
+ transportConfig={{
189
+ notaryUrl: 'http://192.168.29.186:7047',
190
+ cloudProjectNumber: 0,
191
+ }}
192
+ verifyBackend
193
+ buttonLabel="Open Kaggle"
194
+ onComplete={(result) => {
195
+ console.log(result.raw);
196
+ console.log(result.verify);
197
+ console.log(result.backendVerify);
198
+ }}
199
+ onError={(error) => {
200
+ console.warn(error.message);
201
+ }}
202
+ />
203
+ );
204
+ }
205
+ ```
206
+
207
+ When pressed, the component opens the provider WebView inside the same app, captures the
208
+ matching request, performs native replay with cookie sharing, verifies locally, posts to
209
+ the backend verifier when `verifyBackend` is set, and returns a
210
+ `SatyaProviderAttestationResult`.
211
+
212
+ Apps can customize the button label, modal title, WebView props, and callbacks. Apps
213
+ that use a custom provider allow-list can pass `providerTemplate` instead of
214
+ `providerId`. Explicit `spkiPins` are still supported for controlled tests and override
215
+ verifier/template policy refresh. The managed WebView uses provider `userAgent` settings
216
+ when present, otherwise it falls back to a mobile Chrome/Safari user agent like
217
+ `mobile_tlsn_plugin`;
218
+ pass `webViewUserAgent` to override it explicitly.
219
+
220
+ ## Low-Level API
221
+
222
+ ```ts
223
+ import {
224
+ SatyaMobileClient,
225
+ requireProviderTemplate,
226
+ withProviderSpkiPins,
227
+ } from '@burnt-labs/expo-satya-attest';
228
+
229
+ const providerTemplate = withProviderSpkiPins(requireProviderTemplate('kaggle.current_user.v1'), [
230
+ 'KWQsjul7mJDk+i6fAisGLhWLd43FUs5ayVmkz5Y54DU=',
231
+ ]);
232
+
233
+ const client = new SatyaMobileClient();
234
+ const { artifact, raw, verify } = await client.attestNativeReplay({
235
+ providerTemplate,
236
+ capturedRequest,
237
+ transportConfig: {
238
+ notaryUrl: 'http://127.0.0.1:7047',
239
+ cloudProjectNumber: 0,
240
+ },
241
+ });
242
+ ```
243
+
244
+ `capturedRequest` should come from the WebView interceptor and must match the selected
245
+ provider template. The returned `raw` string is the artifact JSON to post to
246
+ `POST /v1/artifacts/verify` on `satya-verifier-service`.
247
+
248
+ ## Custom Provider Registries
249
+
250
+ Apps can keep provider templates outside the built-in SDK registry by passing
251
+ `providerRegistrySource` to the SDK-owned button:
252
+
253
+ ```tsx
254
+ <SatyaProviderAttestButton
255
+ providerId="acme.payroll.v1"
256
+ providerRegistrySource={{
257
+ url: 'https://example.com/.well-known/satya/providers.json',
258
+ timeoutMs: 10000,
259
+ maxBytes: 524288,
260
+ }}
261
+ providerPolicyRefresh="verifier-required"
262
+ transportConfig={{ notaryUrl: 'https://verifier.example.com' }}
263
+ />
264
+ ```
265
+
266
+ Or load it yourself for a custom selector:
267
+
268
+ ```ts
269
+ const registry = await loadProviderRegistry({
270
+ url: 'https://example.com/.well-known/satya/providers.json',
271
+ });
272
+ const providers = listProviderRefs(registry);
273
+ ```
274
+
275
+ Hosted registries are HTTPS-only by default, size/timeout bounded, and validated before
276
+ the WebView opens. See [`PROVIDER_REGISTRY.md`](./PROVIDER_REGISTRY.md) for the complete
277
+ JSON format, validation rules, and security review notes.
278
+
279
+ ## Config Plugin
280
+
281
+ Add the plugin to inject App Attest entitlements and platform pinning config:
282
+
283
+ ```json
284
+ {
285
+ "expo": {
286
+ "plugins": [
287
+ [
288
+ "@burnt-labs/expo-satya-attest",
289
+ {
290
+ "appAttestEnvironment": "development",
291
+ "pinnedProviders": [
292
+ {
293
+ "host": "www.kaggle.com",
294
+ "spkiSha256B64Pins": ["KWQsjul7mJDk+i6fAisGLhWLd43FUs5ayVmkz5Y54DU="]
295
+ }
296
+ ]
297
+ }
298
+ ]
299
+ ]
300
+ }
301
+ }
302
+ ```
303
+
304
+ Use `development` for debug/TestFlight and `production` for App Store builds. iOS App
305
+ Attest requires a real device plus an App ID with the DeviceCheck/App Attest capability.
306
+
307
+ ## Example App
308
+
309
+ ```bash
310
+ cd platform_sdks/expo-satya-attest/expo-satya-attest-example
311
+ npm install --ignore-scripts
312
+ mkdir -p node_modules/@burnt-labs
313
+ ln -sfn ../.. node_modules/@burnt-labs/expo-satya-attest
314
+ npx expo run:ios --device
315
+ npx expo run:android
316
+ ```
317
+
318
+ See [expo-satya-attest-example/README.md](./expo-satya-attest-example/README.md) for
319
+ device env setup and backend verifier commands.
320
+
321
+ ## Verification Backend
322
+
323
+ Run the SATYA verifier service from the workspace root:
324
+
325
+ ```bash
326
+ cargo run -p satya-verifier-service
327
+ ```
328
+
329
+ The service loads `crates/satya-verifier-service/.env`, which is prefilled locally with
330
+ provider-specific SPKI pins. For a physical device, set the Expo example's
331
+ `EXPO_PUBLIC_NOTARY_URL` to your laptop LAN URL, such as `http://192.168.29.186:7047`.
332
+
333
+ ## Checks
334
+
335
+ ```bash
336
+ cd platform_sdks/expo-satya-attest
337
+ npm run build
338
+ npm run lint
339
+ npm run release:check
340
+
341
+ cd expo-satya-attest-example
342
+ ./node_modules/.bin/tsc --pretty false --noEmit
343
+
344
+ cd ../../..
345
+ cargo test -p satya-ffi native_replay_observed_nested_json_paths_verify
346
+ cargo test -p satya-verifier-service
347
+ ```
348
+
349
+ ## Publishing
350
+
351
+ The Expo SDK should publish to npm. The npm package must include the generated Android
352
+ `.so` files, generated UniFFI Kotlin/Swift glue, and iOS XCFramework so third-party apps
353
+ can install it without building Rust locally. See [`PUBLISHING.md`](./PUBLISHING.md) for
354
+ the release checklist, dry-run commands, binary-size guidance, and scoped-package naming
355
+ recommendations.
356
+
357
+ ## Security Notes
358
+
359
+ - WebView response bytes are not attested. The WebView is only a login/request discovery
360
+ surface.
361
+ - Cookies are shared into native replay through platform cookie stores.
362
+ - Native replay blocks user roots and requires client-side SPKI pins so the replay
363
+ connection fails closed before producing an artifact.
364
+ - The SDK never learns production trust by fetching the provider's live certificate and
365
+ trusting it for the same run. Dynamic pins come from verifier policy, are validated
366
+ against the selected template, and are frozen before WebView capture starts.
367
+ - Backend verification independently re-derives trusted template hashes and pins from
368
+ backend config, not from the client artifact alone. Client pins protect the fetch;
369
+ backend pins protect acceptance.
370
+ - For production deployments, serve verifier policy over an authenticated channel
371
+ controlled by the verifier operator. Raw provider SPKI discovery is only suitable as a
372
+ manual/dev maintenance aid, not as automatic trust.
373
+ - Development artifacts remain dev-tier and should not be accepted by production
374
+ verifiers.
@@ -0,0 +1,37 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ }
5
+
6
+ group = 'expo.modules.satyaattest'
7
+ version = '0.1.0'
8
+
9
+ android {
10
+ namespace "expo.modules.satyaattest"
11
+ defaultConfig {
12
+ versionCode 1
13
+ versionName "0.1.0"
14
+ // StrongBox key attestation needs API 28+; basic Keystore attestation API 24+.
15
+ minSdkVersion 24
16
+ }
17
+ lintOptions {
18
+ abortOnError false
19
+ }
20
+ // Prebuilt per-ABI Rust libraries produced by scripts/build-rust-android.sh.
21
+ sourceSets {
22
+ main {
23
+ jniLibs.srcDirs += ['src/main/jniLibs']
24
+ }
25
+ }
26
+ }
27
+
28
+ dependencies {
29
+ // uniffi-generated Kotlin bindings load the native lib through JNA. The @aar
30
+ // classifier is mandatory on Android (the plain jar crashes at runtime).
31
+ implementation "net.java.dev.jna:jna:5.14.0@aar"
32
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
33
+ // Google Play Integrity (Android analog of iOS App Attest).
34
+ implementation "com.google.android.play:integrity:1.4.0"
35
+ // Native provider replay with certificate pinning and handshake capture.
36
+ implementation "com.squareup.okhttp3:okhttp:4.12.0"
37
+ }
@@ -0,0 +1,2 @@
1
+ <manifest>
2
+ </manifest>