Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
5
* You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "mozilla/ipc/BackgroundParent.h"
8
#include "mozilla/jni/GeckoBundleUtils.h"
9
#include "mozilla/StaticPtr.h"
10
11
#include "AndroidWebAuthnTokenManager.h"
12
#include "GeneratedJNIWrappers.h"
13
#include "JavaBuiltins.h"
14
15
namespace mozilla {
16
namespace dom {
17
18
static nsIThread* gAndroidPBackgroundThread;
19
20
StaticRefPtr<AndroidWebAuthnTokenManager> gAndroidWebAuthnManager;
21
22
/* static */ AndroidWebAuthnTokenManager*
23
AndroidWebAuthnTokenManager::GetInstance() {
24
if (!gAndroidWebAuthnManager) {
25
mozilla::ipc::AssertIsOnBackgroundThread();
26
gAndroidWebAuthnManager = new AndroidWebAuthnTokenManager();
27
}
28
return gAndroidWebAuthnManager;
29
}
30
31
AndroidWebAuthnTokenManager::AndroidWebAuthnTokenManager() {
32
mozilla::ipc::AssertIsOnBackgroundThread();
33
MOZ_ASSERT(XRE_IsParentProcess());
34
MOZ_ASSERT(!gAndroidWebAuthnManager);
35
36
gAndroidPBackgroundThread = NS_GetCurrentThread();
37
MOZ_ASSERT(gAndroidPBackgroundThread, "This should never be null!");
38
gAndroidWebAuthnManager = this;
39
}
40
41
void AndroidWebAuthnTokenManager::AssertIsOnOwningThread() const {
42
mozilla::ipc::AssertIsOnBackgroundThread();
43
MOZ_ASSERT(gAndroidPBackgroundThread);
44
#ifdef DEBUG
45
bool current;
46
MOZ_ASSERT(
47
NS_SUCCEEDED(gAndroidPBackgroundThread->IsOnCurrentThread(&current)));
48
MOZ_ASSERT(current);
49
#endif
50
}
51
52
void AndroidWebAuthnTokenManager::Drop() {
53
AssertIsOnOwningThread();
54
55
ClearPromises();
56
gAndroidWebAuthnManager = nullptr;
57
gAndroidPBackgroundThread = nullptr;
58
}
59
60
RefPtr<U2FRegisterPromise> AndroidWebAuthnTokenManager::Register(
61
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
62
AssertIsOnOwningThread();
63
64
ClearPromises();
65
66
GetMainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
67
"java::WebAuthnTokenManager::WebAuthnMakeCredential",
68
[aInfo, aForceNoneAttestation]() {
69
AssertIsOnMainThread();
70
71
// Produce the credential exclusion list
72
jni::ObjectArray::LocalRef idList =
73
jni::ObjectArray::New(aInfo.ExcludeList().Length());
74
75
nsTArray<uint8_t> transportBuf;
76
int ix = 0;
77
78
for (const WebAuthnScopedCredential& cred : aInfo.ExcludeList()) {
79
jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
80
const_cast<void*>(static_cast<const void*>(cred.id().Elements())),
81
cred.id().Length());
82
83
idList->SetElement(ix, id);
84
transportBuf.AppendElement(cred.transports());
85
86
ix += 1;
87
}
88
89
jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
90
const_cast<void*>(
91
static_cast<const void*>(transportBuf.Elements())),
92
transportBuf.Length());
93
94
const nsTArray<uint8_t>& challBuf = aInfo.Challenge();
95
jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
96
const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
97
challBuf.Length());
98
99
nsTArray<uint8_t> uidBuf;
100
101
// Get authenticator selection criteria
102
GECKOBUNDLE_START(authSelBundle);
103
GECKOBUNDLE_START(extensionsBundle);
104
GECKOBUNDLE_START(credentialBundle);
105
106
if (aInfo.Extra().isSome()) {
107
const auto& extra = aInfo.Extra().ref();
108
const auto& rp = extra.Rp();
109
const auto& user = extra.User();
110
111
// If we have extra data, then this is WebAuthn, not U2F
112
GECKOBUNDLE_PUT(credentialBundle, "isWebAuthn",
113
java::sdk::Integer::ValueOf(1));
114
115
// Get the attestation preference and override if the user asked
116
AttestationConveyancePreference attestation =
117
extra.attestationConveyancePreference();
118
119
if (aForceNoneAttestation) {
120
// Add UI support to trigger this, bug 1550164
121
attestation = AttestationConveyancePreference::None;
122
}
123
124
nsString attestPref;
125
attestPref.AssignASCII(
126
AttestationConveyancePreferenceValues::GetString(attestation));
127
GECKOBUNDLE_PUT(authSelBundle, "attestationPreference",
128
jni::StringParam(attestPref));
129
130
const WebAuthnAuthenticatorSelection& sel =
131
extra.AuthenticatorSelection();
132
if (sel.requireResidentKey()) {
133
GECKOBUNDLE_PUT(authSelBundle, "requireResidentKey",
134
java::sdk::Integer::ValueOf(1));
135
}
136
137
if (sel.userVerificationRequirement() ==
138
UserVerificationRequirement::Required) {
139
GECKOBUNDLE_PUT(authSelBundle, "requireUserVerification",
140
java::sdk::Integer::ValueOf(1));
141
}
142
143
if (sel.authenticatorAttachment().isSome()) {
144
const AuthenticatorAttachment authenticatorAttachment =
145
sel.authenticatorAttachment().value();
146
if (authenticatorAttachment == AuthenticatorAttachment::Platform) {
147
GECKOBUNDLE_PUT(authSelBundle, "requirePlatformAttachment",
148
java::sdk::Integer::ValueOf(1));
149
}
150
}
151
152
// Get extensions
153
for (const WebAuthnExtension& ext : extra.Extensions()) {
154
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
155
GECKOBUNDLE_PUT(
156
extensionsBundle, "fidoAppId",
157
jni::StringParam(
158
ext.get_WebAuthnExtensionAppId().appIdentifier()));
159
}
160
}
161
162
uidBuf.Assign(user.Id());
163
164
GECKOBUNDLE_PUT(credentialBundle, "rpName",
165
jni::StringParam(rp.Name()));
166
GECKOBUNDLE_PUT(credentialBundle, "rpIcon",
167
jni::StringParam(rp.Icon()));
168
GECKOBUNDLE_PUT(credentialBundle, "userName",
169
jni::StringParam(user.Name()));
170
GECKOBUNDLE_PUT(credentialBundle, "userIcon",
171
jni::StringParam(user.Icon()));
172
GECKOBUNDLE_PUT(credentialBundle, "userDisplayName",
173
jni::StringParam(user.DisplayName()));
174
}
175
176
GECKOBUNDLE_PUT(credentialBundle, "rpId",
177
jni::StringParam(aInfo.RpId()));
178
GECKOBUNDLE_PUT(credentialBundle, "origin",
179
jni::StringParam(aInfo.Origin()));
180
GECKOBUNDLE_PUT(credentialBundle, "timeoutMS",
181
java::sdk::Double::New(aInfo.TimeoutMS()));
182
183
GECKOBUNDLE_FINISH(authSelBundle);
184
GECKOBUNDLE_FINISH(extensionsBundle);
185
GECKOBUNDLE_FINISH(credentialBundle);
186
187
// For non-WebAuthn cases, uidBuf is empty (and unused)
188
jni::ByteBuffer::LocalRef uid = jni::ByteBuffer::New(
189
const_cast<void*>(static_cast<const void*>(uidBuf.Elements())),
190
uidBuf.Length());
191
192
java::WebAuthnTokenManager::WebAuthnMakeCredential(
193
credentialBundle, uid, challenge, idList, transportList,
194
authSelBundle, extensionsBundle);
195
}));
196
197
return mRegisterPromise.Ensure(__func__);
198
}
199
200
void AndroidWebAuthnTokenManager::HandleRegisterResult(
201
const AndroidWebAuthnResult& aResult) {
202
// This is likely running on the main thread, so we'll always dispatch to the
203
// background for state updates.
204
if (aResult.IsError()) {
205
nsresult aError = aResult.GetError();
206
207
gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
208
"AndroidWebAuthnTokenManager::RegisterAbort",
209
[self = RefPtr<AndroidWebAuthnTokenManager>(this), aError]() {
210
self->mRegisterPromise.RejectIfExists(aError, __func__);
211
}));
212
} else {
213
gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
214
"AndroidWebAuthnTokenManager::RegisterComplete",
215
[self = RefPtr<AndroidWebAuthnTokenManager>(this), aResult]() {
216
CryptoBuffer emptyBuffer;
217
nsTArray<WebAuthnExtensionResult> extensions;
218
WebAuthnMakeCredentialResult result(
219
aResult.mClientDataJSON, aResult.mAttObj, aResult.mKeyHandle,
220
emptyBuffer, extensions);
221
self->mRegisterPromise.Resolve(std::move(result), __func__);
222
}));
223
}
224
}
225
226
RefPtr<U2FSignPromise> AndroidWebAuthnTokenManager::Sign(
227
const WebAuthnGetAssertionInfo& aInfo) {
228
AssertIsOnOwningThread();
229
230
ClearPromises();
231
232
GetMainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
233
"java::WebAuthnTokenManager::WebAuthnGetAssertion", [aInfo]() {
234
AssertIsOnMainThread();
235
236
jni::ObjectArray::LocalRef idList =
237
jni::ObjectArray::New(aInfo.AllowList().Length());
238
239
nsTArray<uint8_t> transportBuf;
240
241
int ix = 0;
242
for (const WebAuthnScopedCredential& cred : aInfo.AllowList()) {
243
jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
244
const_cast<void*>(static_cast<const void*>(cred.id().Elements())),
245
cred.id().Length());
246
247
idList->SetElement(ix, id);
248
transportBuf.AppendElement(cred.transports());
249
250
ix += 1;
251
}
252
253
jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
254
const_cast<void*>(
255
static_cast<const void*>(transportBuf.Elements())),
256
transportBuf.Length());
257
258
const nsTArray<uint8_t>& challBuf = aInfo.Challenge();
259
jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
260
const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
261
challBuf.Length());
262
263
// Get extensions
264
GECKOBUNDLE_START(assertionBundle);
265
GECKOBUNDLE_START(extensionsBundle);
266
if (aInfo.Extra().isSome()) {
267
const auto& extra = aInfo.Extra().ref();
268
269
// If we have extra data, then this is WebAuthn, not U2F
270
GECKOBUNDLE_PUT(assertionBundle, "isWebAuthn",
271
java::sdk::Integer::ValueOf(1));
272
273
// User Verification Requirement is not currently used in the
274
// Android FIDO API. Adding it should look like
275
// AttestationConveyancePreference
276
277
for (const WebAuthnExtension& ext : extra.Extensions()) {
278
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
279
GECKOBUNDLE_PUT(
280
extensionsBundle, "fidoAppId",
281
jni::StringParam(
282
ext.get_WebAuthnExtensionAppId().appIdentifier()));
283
}
284
}
285
}
286
287
GECKOBUNDLE_PUT(assertionBundle, "rpId",
288
jni::StringParam(aInfo.RpId()));
289
GECKOBUNDLE_PUT(assertionBundle, "origin",
290
jni::StringParam(aInfo.Origin()));
291
GECKOBUNDLE_PUT(assertionBundle, "timeoutMS",
292
java::sdk::Double::New(aInfo.TimeoutMS()));
293
294
GECKOBUNDLE_FINISH(assertionBundle);
295
GECKOBUNDLE_FINISH(extensionsBundle);
296
297
java::WebAuthnTokenManager::WebAuthnGetAssertion(
298
challenge, idList, transportList, assertionBundle,
299
extensionsBundle);
300
}));
301
302
return mSignPromise.Ensure(__func__);
303
}
304
305
void AndroidWebAuthnTokenManager::HandleSignResult(
306
const AndroidWebAuthnResult& aResult) {
307
// This is likely running on the main thread, so we'll always dispatch to the
308
// background for state updates.
309
if (aResult.IsError()) {
310
nsresult aError = aResult.GetError();
311
312
gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
313
"AndroidWebAuthnTokenManager::SignAbort",
314
[self = RefPtr<AndroidWebAuthnTokenManager>(this), aError]() {
315
self->mSignPromise.RejectIfExists(aError, __func__);
316
}));
317
} else {
318
gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
319
"AndroidWebAuthnTokenManager::SignComplete",
320
[self = RefPtr<AndroidWebAuthnTokenManager>(this), aResult]() {
321
CryptoBuffer emptyBuffer;
322
323
nsTArray<WebAuthnExtensionResult> emptyExtensions;
324
WebAuthnGetAssertionResult result(
325
aResult.mClientDataJSON, aResult.mKeyHandle, aResult.mSignature,
326
aResult.mAuthData, emptyExtensions, emptyBuffer,
327
aResult.mUserHandle);
328
self->mSignPromise.Resolve(std::move(result), __func__);
329
}));
330
}
331
}
332
333
void AndroidWebAuthnTokenManager::Cancel() {
334
AssertIsOnOwningThread();
335
336
ClearPromises();
337
}
338
339
} // namespace dom
340
} // namespace mozilla