@capgo/capacitor-device-integrity 8.0.2

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.
@@ -0,0 +1,260 @@
1
+ package app.capgo.deviceintegrity;
2
+
3
+ import app.capgo.deviceintegrity.DeviceIntegrity.DeviceIntegrityException;
4
+ import app.capgo.deviceintegrity.DeviceIntegrity.WidevineCapabilities;
5
+ import app.capgo.deviceintegrity.DeviceIntegrity.WidevineFingerprint;
6
+ import com.getcapacitor.JSObject;
7
+ import com.getcapacitor.Plugin;
8
+ import com.getcapacitor.PluginCall;
9
+ import com.getcapacitor.PluginMethod;
10
+ import com.getcapacitor.annotation.CapacitorPlugin;
11
+ import com.google.android.play.core.integrity.IntegrityManagerFactory;
12
+ import com.google.android.play.core.integrity.StandardIntegrityException;
13
+ import com.google.android.play.core.integrity.StandardIntegrityManager;
14
+ import java.security.NoSuchAlgorithmException;
15
+ import java.util.Map;
16
+ import java.util.concurrent.ConcurrentHashMap;
17
+
18
+ @CapacitorPlugin(name = "DeviceIntegrity")
19
+ public class DeviceIntegrityPlugin extends Plugin {
20
+
21
+ private static final String CONFIG_CLOUD_PROJECT_NUMBER = "cloudProjectNumber";
22
+ private static final String DEFAULT_ANDROID_KEY_ID = "android-standard-integrity";
23
+ private static final String FORMAT_PLAY_INTEGRITY = "google-play-integrity-standard";
24
+ private static final String INTEGRITY_ERROR = "INTEGRITY_ERROR";
25
+
26
+ private final DeviceIntegrity implementation = new DeviceIntegrity();
27
+ private final Map<String, StandardIntegrityManager.StandardIntegrityTokenProvider> tokenProviders = new ConcurrentHashMap<>();
28
+
29
+ @PluginMethod
30
+ public void getCapabilities(PluginCall call) {
31
+ WidevineCapabilities widevine = implementation.getWidevineCapabilities();
32
+
33
+ JSObject result = new JSObject();
34
+ result.put("platform", "android");
35
+ result.put("widevine", widevineCapabilitiesToJson(widevine));
36
+ result.put("appAttest", supportToJson(false));
37
+ result.put("deviceCheck", supportToJson(false));
38
+ result.put("playIntegrity", supportToJson(implementation.isPlayIntegritySupported(getContext())));
39
+ call.resolve(result);
40
+ }
41
+
42
+ @PluginMethod
43
+ public void getWidevineFingerprint(PluginCall call) {
44
+ boolean includeRawId = Boolean.TRUE.equals(call.getBoolean("includeRawId", false));
45
+ String hashSalt = call.getString("hashSalt");
46
+
47
+ try {
48
+ WidevineFingerprint result = implementation.getWidevineFingerprint(getContext().getPackageName(), includeRawId, hashSalt);
49
+ JSObject response = new JSObject();
50
+ response.put("platform", "android");
51
+ response.put("source", "widevine");
52
+ response.put("fingerprint", result.fingerprint);
53
+ response.put("widevineIdSha256", result.widevineIdSha256);
54
+ putOptional(response, "widevineIdBase64", result.widevineIdBase64);
55
+ putOptional(response, "securityLevel", result.securityLevel);
56
+ putOptional(response, "vendor", result.vendor);
57
+ putOptional(response, "version", result.version);
58
+ putOptional(response, "description", result.description);
59
+ call.resolve(response);
60
+ } catch (DeviceIntegrityException error) {
61
+ call.reject(error.getMessage(), "WIDEVINE_ERROR", error);
62
+ }
63
+ }
64
+
65
+ @PluginMethod
66
+ public void prepareAttestation(PluginCall call) {
67
+ if (!implementation.isPlayIntegritySupported(getContext())) {
68
+ call.reject("Play Integrity is not supported on this device");
69
+ return;
70
+ }
71
+
72
+ prepareProvider(call, DEFAULT_ANDROID_KEY_ID, (provider) -> {
73
+ JSObject response = new JSObject();
74
+ response.put("keyId", DEFAULT_ANDROID_KEY_ID);
75
+ response.put("format", FORMAT_PLAY_INTEGRITY);
76
+ call.resolve(response);
77
+ });
78
+ }
79
+
80
+ @PluginMethod
81
+ public void createAttestation(PluginCall call) {
82
+ String keyId = call.getString("keyId");
83
+ String challenge = call.getString("challenge");
84
+
85
+ if (isBlank(keyId)) {
86
+ call.reject("keyId is required");
87
+ return;
88
+ }
89
+
90
+ if (isBlank(challenge)) {
91
+ call.reject("challenge is required");
92
+ return;
93
+ }
94
+
95
+ requestStandardIntegrityToken(call, keyId, challenge);
96
+ }
97
+
98
+ @PluginMethod
99
+ public void createAssertion(PluginCall call) {
100
+ String keyId = call.getString("keyId");
101
+ String payload = call.getString("payload");
102
+
103
+ if (isBlank(keyId)) {
104
+ call.reject("keyId is required");
105
+ return;
106
+ }
107
+
108
+ if (isBlank(payload)) {
109
+ call.reject("payload is required");
110
+ return;
111
+ }
112
+
113
+ requestStandardIntegrityToken(call, keyId, payload);
114
+ }
115
+
116
+ @PluginMethod
117
+ public void getDeviceCheckToken(PluginCall call) {
118
+ call.reject("DeviceCheck is only available on iOS");
119
+ }
120
+
121
+ private JSObject widevineCapabilitiesToJson(WidevineCapabilities capabilities) {
122
+ JSObject result = new JSObject();
123
+ result.put("supported", capabilities.supported);
124
+ result.put("fingerprintAvailable", capabilities.fingerprintAvailable);
125
+ result.put("securityLevelScanSupported", capabilities.securityLevelScanSupported);
126
+ return result;
127
+ }
128
+
129
+ private JSObject supportToJson(boolean supported) {
130
+ JSObject result = new JSObject();
131
+ result.put("supported", supported);
132
+ return result;
133
+ }
134
+
135
+ private void putOptional(JSObject object, String key, String value) {
136
+ if (!isBlank(value)) {
137
+ object.put(key, value);
138
+ }
139
+ }
140
+
141
+ private void requestStandardIntegrityToken(PluginCall call, String keyId, String payload) {
142
+ if (!implementation.isPlayIntegritySupported(getContext())) {
143
+ call.reject("Play Integrity is not supported on this device");
144
+ return;
145
+ }
146
+
147
+ final String requestHash;
148
+ try {
149
+ requestHash = implementation.createRequestHash(payload);
150
+ } catch (NoSuchAlgorithmException error) {
151
+ call.reject("Failed to generate request hash", INTEGRITY_ERROR, error);
152
+ return;
153
+ }
154
+
155
+ withProvider(call, keyId, (provider) -> {
156
+ StandardIntegrityManager.StandardIntegrityTokenRequest tokenRequest =
157
+ StandardIntegrityManager.StandardIntegrityTokenRequest.builder().setRequestHash(requestHash).build();
158
+
159
+ provider
160
+ .request(tokenRequest)
161
+ .addOnSuccessListener((token) -> {
162
+ JSObject result = new JSObject();
163
+ result.put("token", token.token());
164
+ result.put("keyId", keyId);
165
+ result.put("format", FORMAT_PLAY_INTEGRITY);
166
+ call.resolve(result);
167
+ })
168
+ .addOnFailureListener((error) -> rejectPlayIntegrityError(call, "Play Integrity token request failed", error));
169
+ });
170
+ }
171
+
172
+ private void withProvider(PluginCall call, String keyId, ProviderReadyCallback callback) {
173
+ StandardIntegrityManager.StandardIntegrityTokenProvider existingProvider = tokenProviders.get(keyId);
174
+ if (existingProvider != null) {
175
+ callback.onReady(existingProvider);
176
+ return;
177
+ }
178
+
179
+ if (DEFAULT_ANDROID_KEY_ID.equals(keyId)) {
180
+ prepareProvider(call, keyId, callback);
181
+ return;
182
+ }
183
+
184
+ call.reject("Unknown Android keyId. Call prepareAttestation() before requesting an attestation token.");
185
+ }
186
+
187
+ private void prepareProvider(PluginCall call, String keyId, ProviderReadyCallback callback) {
188
+ final long cloudProjectNumber;
189
+ try {
190
+ cloudProjectNumber = resolveCloudProjectNumber(call);
191
+ } catch (IllegalArgumentException error) {
192
+ call.reject(error.getMessage());
193
+ return;
194
+ }
195
+
196
+ StandardIntegrityManager integrityManager = IntegrityManagerFactory.createStandard(getContext());
197
+ StandardIntegrityManager.PrepareIntegrityTokenRequest prepareRequest =
198
+ StandardIntegrityManager.PrepareIntegrityTokenRequest.builder().setCloudProjectNumber(cloudProjectNumber).build();
199
+
200
+ integrityManager
201
+ .prepareIntegrityToken(prepareRequest)
202
+ .addOnSuccessListener((provider) -> {
203
+ tokenProviders.put(keyId, provider);
204
+ callback.onReady(provider);
205
+ })
206
+ .addOnFailureListener((error) -> rejectPlayIntegrityError(call, "Failed to prepare Play Integrity token request", error));
207
+ }
208
+
209
+ private long resolveCloudProjectNumber(PluginCall call) {
210
+ Long callValueLong = call.getLong(CONFIG_CLOUD_PROJECT_NUMBER);
211
+ if (callValueLong != null) {
212
+ return callValueLong;
213
+ }
214
+
215
+ String callValueString = call.getString(CONFIG_CLOUD_PROJECT_NUMBER);
216
+ if (!isBlank(callValueString)) {
217
+ return parseCloudProjectNumber(callValueString);
218
+ }
219
+
220
+ String configValueString = getConfig().getString(CONFIG_CLOUD_PROJECT_NUMBER);
221
+ if (!isBlank(configValueString)) {
222
+ return parseCloudProjectNumber(configValueString);
223
+ }
224
+
225
+ throw new IllegalArgumentException(
226
+ "cloudProjectNumber is required on Android. Set DeviceIntegrity.cloudProjectNumber in capacitor config or pass it in options."
227
+ );
228
+ }
229
+
230
+ private long parseCloudProjectNumber(String value) {
231
+ try {
232
+ return Long.parseLong(value);
233
+ } catch (NumberFormatException error) {
234
+ throw new IllegalArgumentException("cloudProjectNumber must be a valid integer string");
235
+ }
236
+ }
237
+
238
+ private boolean isBlank(String value) {
239
+ return value == null || value.trim().isEmpty();
240
+ }
241
+
242
+ private void rejectPlayIntegrityError(PluginCall call, String message, Exception error) {
243
+ if (error instanceof StandardIntegrityException) {
244
+ StandardIntegrityException integrityError = (StandardIntegrityException) error;
245
+ call.reject(
246
+ message + " with code " + integrityError.getErrorCode() + ": " + integrityError.getMessage(),
247
+ INTEGRITY_ERROR,
248
+ error
249
+ );
250
+ return;
251
+ }
252
+
253
+ call.reject(message, INTEGRITY_ERROR, error);
254
+ }
255
+
256
+ @FunctionalInterface
257
+ private interface ProviderReadyCallback {
258
+ void onReady(StandardIntegrityManager.StandardIntegrityTokenProvider provider);
259
+ }
260
+ }
File without changes