/src/mozilla-central/dom/u2f/U2F.cpp
Line | Count | Source (jump to first uncovered line) |
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/dom/U2F.h" |
8 | | #include "mozilla/dom/WebCryptoCommon.h" |
9 | | #include "mozilla/ipc/PBackgroundChild.h" |
10 | | #include "mozilla/ipc/BackgroundChild.h" |
11 | | #include "mozilla/dom/WebAuthnTransactionChild.h" |
12 | | #include "mozilla/dom/WebAuthnUtil.h" |
13 | | #include "nsContentUtils.h" |
14 | | #include "nsIEffectiveTLDService.h" |
15 | | #include "nsNetUtil.h" |
16 | | #include "nsURLParsers.h" |
17 | | |
18 | | using namespace mozilla::ipc; |
19 | | |
20 | | // Forward decl because of nsHTMLDocument.h's complex dependency on /layout/style |
21 | | class nsHTMLDocument { |
22 | | public: |
23 | | bool IsRegistrableDomainSuffixOfOrEqualTo(const nsAString& aHostSuffixString, |
24 | | const nsACString& aOrigHost); |
25 | | }; |
26 | | |
27 | | namespace mozilla { |
28 | | namespace dom { |
29 | | |
30 | | static mozilla::LazyLogModule gU2FLog("u2fmanager"); |
31 | | |
32 | | NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment"); |
33 | | NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion"); |
34 | | |
35 | | // Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023. |
36 | | NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId1, |
37 | | "https://www.gstatic.com/securitykey/origins.json"); |
38 | | NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId2, |
39 | | "https://www.gstatic.com/securitykey/a/google.com/origins.json"); |
40 | | |
41 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F) |
42 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
43 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
44 | 0 | NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) |
45 | 0 | NS_INTERFACE_MAP_END |
46 | | |
47 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F) |
48 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F) |
49 | | |
50 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent) |
51 | | |
52 | | /*********************************************************************** |
53 | | * Utility Functions |
54 | | **********************************************************************/ |
55 | | |
56 | | static ErrorCode |
57 | | ConvertNSResultToErrorCode(const nsresult& aError) |
58 | 0 | { |
59 | 0 | if (aError == NS_ERROR_DOM_TIMEOUT_ERR) { |
60 | 0 | return ErrorCode::TIMEOUT; |
61 | 0 | } |
62 | 0 | /* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */ |
63 | 0 | if (aError == NS_ERROR_DOM_INVALID_STATE_ERR) { |
64 | 0 | return ErrorCode::DEVICE_INELIGIBLE; |
65 | 0 | } |
66 | 0 | return ErrorCode::OTHER_ERROR; |
67 | 0 | } |
68 | | |
69 | | static uint32_t |
70 | | AdjustedTimeoutMillis(const Optional<Nullable<int32_t>>& opt_aSeconds) |
71 | 0 | { |
72 | 0 | uint32_t adjustedTimeoutMillis = 30000u; |
73 | 0 | if (opt_aSeconds.WasPassed() && !opt_aSeconds.Value().IsNull()) { |
74 | 0 | adjustedTimeoutMillis = opt_aSeconds.Value().Value() * 1000u; |
75 | 0 | adjustedTimeoutMillis = std::max(15000u, adjustedTimeoutMillis); |
76 | 0 | adjustedTimeoutMillis = std::min(120000u, adjustedTimeoutMillis); |
77 | 0 | } |
78 | 0 | return adjustedTimeoutMillis; |
79 | 0 | } |
80 | | |
81 | | static nsresult |
82 | | AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp, |
83 | | const nsAString& aChallenge, |
84 | | /* out */ nsString& aClientData) |
85 | 0 | { |
86 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
87 | 0 | U2FClientData clientDataObject; |
88 | 0 | clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification |
89 | 0 | clientDataObject.mChallenge.Construct(aChallenge); |
90 | 0 | clientDataObject.mOrigin.Construct(aOrigin); |
91 | 0 |
|
92 | 0 | if (NS_WARN_IF(!clientDataObject.ToJSON(aClientData))) { |
93 | 0 | return NS_ERROR_FAILURE; |
94 | 0 | } |
95 | 0 | |
96 | 0 | return NS_OK; |
97 | 0 | } |
98 | | |
99 | | static void |
100 | | RegisteredKeysToScopedCredentialList(const nsAString& aAppId, |
101 | | const nsTArray<RegisteredKey>& aKeys, |
102 | | nsTArray<WebAuthnScopedCredential>& aList) |
103 | 0 | { |
104 | 0 | for (const RegisteredKey& key : aKeys) { |
105 | 0 | // Check for required attributes |
106 | 0 | if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed() || |
107 | 0 | key.mVersion.Value() != kRequiredU2FVersion) { |
108 | 0 | continue; |
109 | 0 | } |
110 | 0 | |
111 | 0 | // If this key's mAppId doesn't match the invocation, we can't handle it. |
112 | 0 | if (key.mAppId.WasPassed() && !key.mAppId.Value().Equals(aAppId)) { |
113 | 0 | continue; |
114 | 0 | } |
115 | 0 | |
116 | 0 | CryptoBuffer keyHandle; |
117 | 0 | nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle.Value()); |
118 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
119 | 0 | continue; |
120 | 0 | } |
121 | 0 | |
122 | 0 | WebAuthnScopedCredential c; |
123 | 0 | c.id() = keyHandle; |
124 | 0 | aList.AppendElement(c); |
125 | 0 | } |
126 | 0 | } |
127 | | |
128 | | /*********************************************************************** |
129 | | * U2F JavaScript API Implementation |
130 | | **********************************************************************/ |
131 | | |
132 | | U2F::~U2F() |
133 | 0 | { |
134 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
135 | 0 |
|
136 | 0 | if (mTransaction.isSome()) { |
137 | 0 | RejectTransaction(NS_ERROR_ABORT); |
138 | 0 | } |
139 | 0 |
|
140 | 0 | if (mChild) { |
141 | 0 | RefPtr<WebAuthnTransactionChild> c; |
142 | 0 | mChild.swap(c); |
143 | 0 | c->Disconnect(); |
144 | 0 | } |
145 | 0 | } |
146 | | |
147 | | void |
148 | | U2F::Init(ErrorResult& aRv) |
149 | 0 | { |
150 | 0 | MOZ_ASSERT(mParent); |
151 | 0 |
|
152 | 0 | nsCOMPtr<nsIDocument> doc = mParent->GetDoc(); |
153 | 0 | MOZ_ASSERT(doc); |
154 | 0 | if (!doc) { |
155 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
156 | 0 | return; |
157 | 0 | } |
158 | 0 | |
159 | 0 | nsIPrincipal* principal = doc->NodePrincipal(); |
160 | 0 | aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin); |
161 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
162 | 0 | return; |
163 | 0 | } |
164 | 0 | |
165 | 0 | if (NS_WARN_IF(mOrigin.IsEmpty())) { |
166 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
167 | 0 | return; |
168 | 0 | } |
169 | 0 | } |
170 | | |
171 | | /* virtual */ JSObject* |
172 | | U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
173 | 0 | { |
174 | 0 | return U2F_Binding::Wrap(aCx, this, aGivenProto); |
175 | 0 | } |
176 | | |
177 | | template<typename T, typename C> |
178 | | void |
179 | | U2F::ExecuteCallback(T& aResp, nsMainThreadPtrHandle<C>& aCb) |
180 | 0 | { |
181 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
182 | 0 | MOZ_ASSERT(aCb); |
183 | 0 |
|
184 | 0 | // Assert that mTransaction was cleared before before we were called to allow |
185 | 0 | // reentrancy from microtask checkpoints. |
186 | 0 | MOZ_ASSERT(mTransaction.isNothing()); |
187 | 0 |
|
188 | 0 | ErrorResult error; |
189 | 0 | aCb->Call(aResp, error); |
190 | 0 | NS_WARNING_ASSERTION(!error.Failed(), "dom::U2F::Promise callback failed"); |
191 | 0 | error.SuppressException(); // Useful exceptions already emitted |
192 | 0 | } Unexecuted instantiation: void mozilla::dom::U2F::ExecuteCallback<mozilla::dom::RegisterResponse, mozilla::dom::U2FRegisterCallback>(mozilla::dom::RegisterResponse&, nsMainThreadPtrHandle<mozilla::dom::U2FRegisterCallback>&) Unexecuted instantiation: void mozilla::dom::U2F::ExecuteCallback<mozilla::dom::SignResponse, mozilla::dom::U2FSignCallback>(mozilla::dom::SignResponse&, nsMainThreadPtrHandle<mozilla::dom::U2FSignCallback>&) |
193 | | |
194 | | void |
195 | | U2F::Register(const nsAString& aAppId, |
196 | | const Sequence<RegisterRequest>& aRegisterRequests, |
197 | | const Sequence<RegisteredKey>& aRegisteredKeys, |
198 | | U2FRegisterCallback& aCallback, |
199 | | const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds, |
200 | | ErrorResult& aRv) |
201 | 0 | { |
202 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
203 | 0 |
|
204 | 0 | if (mTransaction.isSome()) { |
205 | 0 | CancelTransaction(NS_ERROR_ABORT); |
206 | 0 | } |
207 | 0 |
|
208 | 0 | nsMainThreadPtrHandle<U2FRegisterCallback> callback( |
209 | 0 | new nsMainThreadPtrHolder<U2FRegisterCallback>("U2F::Register::callback", |
210 | 0 | &aCallback)); |
211 | 0 |
|
212 | 0 | // Ensure we have a callback. |
213 | 0 | if (NS_WARN_IF(!callback)) { |
214 | 0 | return; |
215 | 0 | } |
216 | 0 | |
217 | 0 | // Evaluate the AppID |
218 | 0 | nsString adjustedAppId(aAppId); |
219 | 0 | if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Register, adjustedAppId)) { |
220 | 0 | RegisterResponse response; |
221 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST)); |
222 | 0 | ExecuteCallback(response, callback); |
223 | 0 | return; |
224 | 0 | } |
225 | 0 | |
226 | 0 | nsAutoString clientDataJSON; |
227 | 0 |
|
228 | 0 | // Pick the first valid RegisterRequest; we can only work with one. |
229 | 0 | CryptoBuffer challenge; |
230 | 0 | for (const RegisterRequest& req : aRegisterRequests) { |
231 | 0 | if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed() || |
232 | 0 | req.mVersion.Value() != kRequiredU2FVersion) { |
233 | 0 | continue; |
234 | 0 | } |
235 | 0 | if (!challenge.Assign(NS_ConvertUTF16toUTF8(req.mChallenge.Value()))) { |
236 | 0 | continue; |
237 | 0 | } |
238 | 0 | |
239 | 0 | nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment, |
240 | 0 | req.mChallenge.Value(), clientDataJSON); |
241 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
242 | 0 | continue; |
243 | 0 | } |
244 | 0 | } |
245 | 0 |
|
246 | 0 | // Did we not get a valid RegisterRequest? Abort. |
247 | 0 | if (clientDataJSON.IsEmpty()) { |
248 | 0 | RegisterResponse response; |
249 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST)); |
250 | 0 | ExecuteCallback(response, callback); |
251 | 0 | return; |
252 | 0 | } |
253 | 0 | |
254 | 0 | // Build the exclusion list, if any |
255 | 0 | nsTArray<WebAuthnScopedCredential> excludeList; |
256 | 0 | RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys, |
257 | 0 | excludeList); |
258 | 0 |
|
259 | 0 | if (!MaybeCreateBackgroundActor()) { |
260 | 0 | RegisterResponse response; |
261 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR)); |
262 | 0 | ExecuteCallback(response, callback); |
263 | 0 | return; |
264 | 0 | } |
265 | 0 | |
266 | 0 | ListenForVisibilityEvents(); |
267 | 0 |
|
268 | 0 | NS_ConvertUTF16toUTF8 clientData(clientDataJSON); |
269 | 0 | uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds); |
270 | 0 |
|
271 | 0 | WebAuthnMakeCredentialInfo info(mOrigin, |
272 | 0 | adjustedAppId, |
273 | 0 | challenge, |
274 | 0 | clientData, |
275 | 0 | adjustedTimeoutMillis, |
276 | 0 | excludeList, |
277 | 0 | null_t() /* no extra info for U2F */); |
278 | 0 |
|
279 | 0 | MOZ_ASSERT(mTransaction.isNothing()); |
280 | 0 | mTransaction = Some(U2FTransaction(AsVariant(callback))); |
281 | 0 | mChild->SendRequestRegister(mTransaction.ref().mId, info); |
282 | 0 | } |
283 | | |
284 | | void |
285 | | U2F::FinishMakeCredential(const uint64_t& aTransactionId, |
286 | | const WebAuthnMakeCredentialResult& aResult) |
287 | 0 | { |
288 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
289 | 0 |
|
290 | 0 | // Check for a valid transaction. |
291 | 0 | if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) { |
292 | 0 | return; |
293 | 0 | } |
294 | 0 | |
295 | 0 | if (NS_WARN_IF(!mTransaction.ref().HasRegisterCallback())) { |
296 | 0 | RejectTransaction(NS_ERROR_ABORT); |
297 | 0 | return; |
298 | 0 | } |
299 | 0 | |
300 | 0 | // A CTAP2 response. |
301 | 0 | if (aResult.RegistrationData().Length() == 0) { |
302 | 0 | RejectTransaction(NS_ERROR_ABORT); |
303 | 0 | return; |
304 | 0 | } |
305 | 0 | |
306 | 0 | CryptoBuffer clientDataBuf; |
307 | 0 | if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) { |
308 | 0 | RejectTransaction(NS_ERROR_ABORT); |
309 | 0 | return; |
310 | 0 | } |
311 | 0 | |
312 | 0 | CryptoBuffer regBuf; |
313 | 0 | if (NS_WARN_IF(!regBuf.Assign(aResult.RegistrationData()))) { |
314 | 0 | RejectTransaction(NS_ERROR_ABORT); |
315 | 0 | return; |
316 | 0 | } |
317 | 0 | |
318 | 0 | nsString clientDataBase64; |
319 | 0 | nsString registrationDataBase64; |
320 | 0 | nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64); |
321 | 0 | nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64); |
322 | 0 |
|
323 | 0 | if (NS_WARN_IF(NS_FAILED(rvClientData)) || |
324 | 0 | NS_WARN_IF(NS_FAILED(rvRegistrationData))) { |
325 | 0 | RejectTransaction(NS_ERROR_ABORT); |
326 | 0 | return; |
327 | 0 | } |
328 | 0 | |
329 | 0 | // Assemble a response object to return |
330 | 0 | RegisterResponse response; |
331 | 0 | response.mVersion.Construct(kRequiredU2FVersion); |
332 | 0 | response.mClientData.Construct(clientDataBase64); |
333 | 0 | response.mRegistrationData.Construct(registrationDataBase64); |
334 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK)); |
335 | 0 |
|
336 | 0 | // Keep the callback pointer alive. |
337 | 0 | nsMainThreadPtrHandle<U2FRegisterCallback> callback( |
338 | 0 | mTransaction.ref().GetRegisterCallback()); |
339 | 0 |
|
340 | 0 | ClearTransaction(); |
341 | 0 | ExecuteCallback(response, callback); |
342 | 0 | } |
343 | | |
344 | | void |
345 | | U2F::Sign(const nsAString& aAppId, |
346 | | const nsAString& aChallenge, |
347 | | const Sequence<RegisteredKey>& aRegisteredKeys, |
348 | | U2FSignCallback& aCallback, |
349 | | const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds, |
350 | | ErrorResult& aRv) |
351 | 0 | { |
352 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
353 | 0 |
|
354 | 0 | if (mTransaction.isSome()) { |
355 | 0 | CancelTransaction(NS_ERROR_ABORT); |
356 | 0 | } |
357 | 0 |
|
358 | 0 | nsMainThreadPtrHandle<U2FSignCallback> callback( |
359 | 0 | new nsMainThreadPtrHolder<U2FSignCallback>("U2F::Sign::callback", |
360 | 0 | &aCallback)); |
361 | 0 |
|
362 | 0 | // Ensure we have a callback. |
363 | 0 | if (NS_WARN_IF(!callback)) { |
364 | 0 | return; |
365 | 0 | } |
366 | 0 | |
367 | 0 | // Evaluate the AppID |
368 | 0 | nsString adjustedAppId(aAppId); |
369 | 0 | if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Sign, adjustedAppId)) { |
370 | 0 | SignResponse response; |
371 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST)); |
372 | 0 | ExecuteCallback(response, callback); |
373 | 0 | return; |
374 | 0 | } |
375 | 0 | |
376 | 0 | // Produce the AppParam from the current AppID |
377 | 0 | nsCString cAppId = NS_ConvertUTF16toUTF8(adjustedAppId); |
378 | 0 |
|
379 | 0 | nsAutoString clientDataJSON; |
380 | 0 | nsresult rv = AssembleClientData(mOrigin, kGetAssertion, aChallenge, |
381 | 0 | clientDataJSON); |
382 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
383 | 0 | SignResponse response; |
384 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST)); |
385 | 0 | ExecuteCallback(response, callback); |
386 | 0 | return; |
387 | 0 | } |
388 | 0 | |
389 | 0 | CryptoBuffer challenge; |
390 | 0 | if (!challenge.Assign(NS_ConvertUTF16toUTF8(aChallenge))) { |
391 | 0 | SignResponse response; |
392 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR)); |
393 | 0 | ExecuteCallback(response, callback); |
394 | 0 | return; |
395 | 0 | } |
396 | 0 | |
397 | 0 | // Build the key list, if any |
398 | 0 | nsTArray<WebAuthnScopedCredential> permittedList; |
399 | 0 | RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys, |
400 | 0 | permittedList); |
401 | 0 |
|
402 | 0 | if (!MaybeCreateBackgroundActor()) { |
403 | 0 | SignResponse response; |
404 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR)); |
405 | 0 | ExecuteCallback(response, callback); |
406 | 0 | return; |
407 | 0 | } |
408 | 0 | |
409 | 0 | ListenForVisibilityEvents(); |
410 | 0 |
|
411 | 0 | // Always blank for U2F |
412 | 0 | nsTArray<WebAuthnExtension> extensions; |
413 | 0 |
|
414 | 0 | NS_ConvertUTF16toUTF8 clientData(clientDataJSON); |
415 | 0 | uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds); |
416 | 0 |
|
417 | 0 | WebAuthnGetAssertionInfo info(mOrigin, |
418 | 0 | adjustedAppId, |
419 | 0 | challenge, |
420 | 0 | clientData, |
421 | 0 | adjustedTimeoutMillis, |
422 | 0 | permittedList, |
423 | 0 | null_t() /* no extra info for U2F */); |
424 | 0 |
|
425 | 0 | MOZ_ASSERT(mTransaction.isNothing()); |
426 | 0 | mTransaction = Some(U2FTransaction(AsVariant(callback))); |
427 | 0 | mChild->SendRequestSign(mTransaction.ref().mId, info); |
428 | 0 | } |
429 | | |
430 | | void |
431 | | U2F::FinishGetAssertion(const uint64_t& aTransactionId, |
432 | | const WebAuthnGetAssertionResult& aResult) |
433 | 0 | { |
434 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
435 | 0 |
|
436 | 0 | // Check for a valid transaction. |
437 | 0 | if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) { |
438 | 0 | return; |
439 | 0 | } |
440 | 0 | |
441 | 0 | if (NS_WARN_IF(!mTransaction.ref().HasSignCallback())) { |
442 | 0 | RejectTransaction(NS_ERROR_ABORT); |
443 | 0 | return; |
444 | 0 | } |
445 | 0 | |
446 | 0 | // A CTAP2 response. |
447 | 0 | if (aResult.SignatureData().Length() == 0) { |
448 | 0 | RejectTransaction(NS_ERROR_ABORT); |
449 | 0 | return; |
450 | 0 | } |
451 | 0 | |
452 | 0 | CryptoBuffer clientDataBuf; |
453 | 0 | if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) { |
454 | 0 | RejectTransaction(NS_ERROR_ABORT); |
455 | 0 | return; |
456 | 0 | } |
457 | 0 | |
458 | 0 | CryptoBuffer credBuf; |
459 | 0 | if (NS_WARN_IF(!credBuf.Assign(aResult.KeyHandle()))) { |
460 | 0 | RejectTransaction(NS_ERROR_ABORT); |
461 | 0 | return; |
462 | 0 | } |
463 | 0 | |
464 | 0 | CryptoBuffer sigBuf; |
465 | 0 | if (NS_WARN_IF(!sigBuf.Assign(aResult.SignatureData()))) { |
466 | 0 | RejectTransaction(NS_ERROR_ABORT); |
467 | 0 | return; |
468 | 0 | } |
469 | 0 | |
470 | 0 | // Assemble a response object to return |
471 | 0 | nsString clientDataBase64; |
472 | 0 | nsString signatureDataBase64; |
473 | 0 | nsString keyHandleBase64; |
474 | 0 | nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64); |
475 | 0 | nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64); |
476 | 0 | nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64); |
477 | 0 | if (NS_WARN_IF(NS_FAILED(rvClientData)) || |
478 | 0 | NS_WARN_IF(NS_FAILED(rvSignatureData) || |
479 | 0 | NS_WARN_IF(NS_FAILED(rvKeyHandle)))) { |
480 | 0 | RejectTransaction(NS_ERROR_ABORT); |
481 | 0 | return; |
482 | 0 | } |
483 | 0 | |
484 | 0 | SignResponse response; |
485 | 0 | response.mKeyHandle.Construct(keyHandleBase64); |
486 | 0 | response.mClientData.Construct(clientDataBase64); |
487 | 0 | response.mSignatureData.Construct(signatureDataBase64); |
488 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK)); |
489 | 0 |
|
490 | 0 | // Keep the callback pointer alive. |
491 | 0 | nsMainThreadPtrHandle<U2FSignCallback> callback( |
492 | 0 | mTransaction.ref().GetSignCallback()); |
493 | 0 |
|
494 | 0 | ClearTransaction(); |
495 | 0 | ExecuteCallback(response, callback); |
496 | 0 | } |
497 | | |
498 | | void |
499 | | U2F::ClearTransaction() |
500 | 0 | { |
501 | 0 | if (!NS_WARN_IF(mTransaction.isNothing())) { |
502 | 0 | StopListeningForVisibilityEvents(); |
503 | 0 | } |
504 | 0 |
|
505 | 0 | mTransaction.reset(); |
506 | 0 | } |
507 | | |
508 | | void |
509 | | U2F::RejectTransaction(const nsresult& aError) |
510 | 0 | { |
511 | 0 | if (NS_WARN_IF(mTransaction.isNothing())) { |
512 | 0 | return; |
513 | 0 | } |
514 | 0 | |
515 | 0 | StopListeningForVisibilityEvents(); |
516 | 0 |
|
517 | 0 | // Clear out mTransaction before calling ExecuteCallback() below to allow |
518 | 0 | // reentrancy from microtask checkpoints. |
519 | 0 | Maybe<U2FTransaction> maybeTransaction(std::move(mTransaction)); |
520 | 0 | MOZ_ASSERT(mTransaction.isNothing() && maybeTransaction.isSome()); |
521 | 0 |
|
522 | 0 | U2FTransaction& transaction = maybeTransaction.ref(); |
523 | 0 | ErrorCode code = ConvertNSResultToErrorCode(aError); |
524 | 0 |
|
525 | 0 | if (transaction.HasRegisterCallback()) { |
526 | 0 | RegisterResponse response; |
527 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(code)); |
528 | 0 | ExecuteCallback(response, transaction.GetRegisterCallback()); |
529 | 0 | } |
530 | 0 |
|
531 | 0 | if (transaction.HasSignCallback()) { |
532 | 0 | SignResponse response; |
533 | 0 | response.mErrorCode.Construct(static_cast<uint32_t>(code)); |
534 | 0 | ExecuteCallback(response, transaction.GetSignCallback()); |
535 | 0 | } |
536 | 0 | } |
537 | | |
538 | | void |
539 | | U2F::CancelTransaction(const nsresult& aError) |
540 | 0 | { |
541 | 0 | if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) { |
542 | 0 | mChild->SendRequestCancel(mTransaction.ref().mId); |
543 | 0 | } |
544 | 0 |
|
545 | 0 | RejectTransaction(aError); |
546 | 0 | } |
547 | | |
548 | | void |
549 | | U2F::RequestAborted(const uint64_t& aTransactionId, const nsresult& aError) |
550 | 0 | { |
551 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
552 | 0 |
|
553 | 0 | if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) { |
554 | 0 | RejectTransaction(aError); |
555 | 0 | } |
556 | 0 | } |
557 | | |
558 | | } // namespace dom |
559 | | } // namespace mozilla |