/src/mozilla-central/dom/webauthn/U2FHIDTokenManager.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/U2FHIDTokenManager.h" |
8 | | #include "mozilla/dom/WebAuthnUtil.h" |
9 | | #include "mozilla/ipc/BackgroundParent.h" |
10 | | #include "mozilla/StaticMutex.h" |
11 | | |
12 | | namespace mozilla { |
13 | | namespace dom { |
14 | | |
15 | | static StaticMutex gInstanceMutex; |
16 | | static U2FHIDTokenManager* gInstance; |
17 | | static nsIThread* gPBackgroundThread; |
18 | | |
19 | | static void |
20 | | u2f_register_callback(uint64_t aTransactionId, rust_u2f_result* aResult) |
21 | 0 | { |
22 | 0 | UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult); |
23 | 0 |
|
24 | 0 | StaticMutexAutoLock lock(gInstanceMutex); |
25 | 0 | if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) { |
26 | 0 | return; |
27 | 0 | } |
28 | 0 | |
29 | 0 | nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>( |
30 | 0 | "U2FHIDTokenManager::HandleRegisterResult", gInstance, |
31 | 0 | &U2FHIDTokenManager::HandleRegisterResult, std::move(rv))); |
32 | 0 |
|
33 | 0 | MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(), |
34 | 0 | NS_DISPATCH_NORMAL)); |
35 | 0 | } |
36 | | |
37 | | static void |
38 | | u2f_sign_callback(uint64_t aTransactionId, rust_u2f_result* aResult) |
39 | 0 | { |
40 | 0 | UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult); |
41 | 0 |
|
42 | 0 | StaticMutexAutoLock lock(gInstanceMutex); |
43 | 0 | if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) { |
44 | 0 | return; |
45 | 0 | } |
46 | 0 | |
47 | 0 | nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>( |
48 | 0 | "U2FHIDTokenManager::HandleSignResult", gInstance, |
49 | 0 | &U2FHIDTokenManager::HandleSignResult, std::move(rv))); |
50 | 0 |
|
51 | 0 | MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(), |
52 | 0 | NS_DISPATCH_NORMAL)); |
53 | 0 | } |
54 | | |
55 | | U2FHIDTokenManager::U2FHIDTokenManager() |
56 | 0 | { |
57 | 0 | StaticMutexAutoLock lock(gInstanceMutex); |
58 | 0 | mozilla::ipc::AssertIsOnBackgroundThread(); |
59 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
60 | 0 | MOZ_ASSERT(!gInstance); |
61 | 0 |
|
62 | 0 | mU2FManager = rust_u2f_mgr_new(); |
63 | 0 | gPBackgroundThread = NS_GetCurrentThread(); |
64 | 0 | MOZ_ASSERT(gPBackgroundThread, "This should never be null!"); |
65 | 0 | gInstance = this; |
66 | 0 | } |
67 | | |
68 | | void |
69 | | U2FHIDTokenManager::Drop() |
70 | 0 | { |
71 | 0 | { |
72 | 0 | StaticMutexAutoLock lock(gInstanceMutex); |
73 | 0 | mozilla::ipc::AssertIsOnBackgroundThread(); |
74 | 0 |
|
75 | 0 | mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
76 | 0 | mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
77 | 0 |
|
78 | 0 | gInstance = nullptr; |
79 | 0 | } |
80 | 0 |
|
81 | 0 | // Release gInstanceMutex before we call U2FManager::drop(). It will wait |
82 | 0 | // for the work queue thread to join, and that requires the |
83 | 0 | // u2f_{register,sign}_callback to lock and return. |
84 | 0 | rust_u2f_mgr_free(mU2FManager); |
85 | 0 | mU2FManager = nullptr; |
86 | 0 |
|
87 | 0 | // Reset transaction ID so that queued runnables exit early. |
88 | 0 | mTransaction.reset(); |
89 | 0 | } |
90 | | |
91 | | // A U2F Register operation causes a new key pair to be generated by the token. |
92 | | // The token then returns the public key of the key pair, and a handle to the |
93 | | // private key, which is a fancy way of saying "key wrapped private key", as |
94 | | // well as the generated attestation certificate and a signature using that |
95 | | // certificate's private key. |
96 | | // |
97 | | // The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform |
98 | | // the actual key wrap/unwrap operations. |
99 | | // |
100 | | // The format of the return registration data is as follows: |
101 | | // |
102 | | // Bytes Value |
103 | | // 1 0x05 |
104 | | // 65 public key |
105 | | // 1 key handle length |
106 | | // * key handle |
107 | | // ASN.1 attestation certificate |
108 | | // * attestation signature |
109 | | // |
110 | | RefPtr<U2FRegisterPromise> |
111 | | U2FHIDTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo, |
112 | | bool aForceNoneAttestation) |
113 | 0 | { |
114 | 0 | mozilla::ipc::AssertIsOnBackgroundThread(); |
115 | 0 |
|
116 | 0 | uint64_t registerFlags = 0; |
117 | 0 |
|
118 | 0 | if (aInfo.Extra().type() != WebAuthnMaybeMakeCredentialExtraInfo::Tnull_t) { |
119 | 0 | const auto& extra = aInfo.Extra().get_WebAuthnMakeCredentialExtraInfo(); |
120 | 0 | const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection(); |
121 | 0 |
|
122 | 0 | // Set flags for credential creation. |
123 | 0 | if (sel.requireResidentKey()) { |
124 | 0 | registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY; |
125 | 0 | } |
126 | 0 | if (sel.requireUserVerification()) { |
127 | 0 | registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION; |
128 | 0 | } |
129 | 0 | if (sel.requirePlatformAttachment()) { |
130 | 0 | registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT; |
131 | 0 | } |
132 | 0 | } |
133 | 0 |
|
134 | 0 | CryptoBuffer rpIdHash, clientDataHash; |
135 | 0 | NS_ConvertUTF16toUTF8 rpId(aInfo.RpId()); |
136 | 0 | nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), |
137 | 0 | rpIdHash, clientDataHash); |
138 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
139 | 0 | return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
140 | 0 | } |
141 | 0 | |
142 | 0 | ClearPromises(); |
143 | 0 | mTransaction.reset(); |
144 | 0 | uint64_t tid = rust_u2f_mgr_register(mU2FManager, |
145 | 0 | registerFlags, |
146 | 0 | (uint64_t)aInfo.TimeoutMS(), |
147 | 0 | u2f_register_callback, |
148 | 0 | clientDataHash.Elements(), |
149 | 0 | clientDataHash.Length(), |
150 | 0 | rpIdHash.Elements(), |
151 | 0 | rpIdHash.Length(), |
152 | 0 | U2FKeyHandles(aInfo.ExcludeList()).Get()); |
153 | 0 |
|
154 | 0 | if (tid == 0) { |
155 | 0 | return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
156 | 0 | } |
157 | 0 | |
158 | 0 | mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON(), |
159 | 0 | aForceNoneAttestation)); |
160 | 0 |
|
161 | 0 | return mRegisterPromise.Ensure(__func__); |
162 | 0 | } |
163 | | |
164 | | // A U2F Sign operation creates a signature over the "param" arguments (plus |
165 | | // some other stuff) using the private key indicated in the key handle argument. |
166 | | // |
167 | | // The format of the signed data is as follows: |
168 | | // |
169 | | // 32 Application parameter |
170 | | // 1 User presence (0x01) |
171 | | // 4 Counter |
172 | | // 32 Challenge parameter |
173 | | // |
174 | | // The format of the signature data is as follows: |
175 | | // |
176 | | // 1 User presence |
177 | | // 4 Counter |
178 | | // * Signature |
179 | | // |
180 | | RefPtr<U2FSignPromise> |
181 | | U2FHIDTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo) |
182 | 0 | { |
183 | 0 | mozilla::ipc::AssertIsOnBackgroundThread(); |
184 | 0 |
|
185 | 0 | CryptoBuffer rpIdHash, clientDataHash; |
186 | 0 | NS_ConvertUTF16toUTF8 rpId(aInfo.RpId()); |
187 | 0 | nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), |
188 | 0 | rpIdHash, clientDataHash); |
189 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
190 | 0 | return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
191 | 0 | } |
192 | 0 | |
193 | 0 | uint64_t signFlags = 0; |
194 | 0 | nsTArray<nsTArray<uint8_t>> appIds; |
195 | 0 | appIds.AppendElement(rpIdHash); |
196 | 0 |
|
197 | 0 | if (aInfo.Extra().type() != WebAuthnMaybeGetAssertionExtraInfo::Tnull_t) { |
198 | 0 | const auto& extra = aInfo.Extra().get_WebAuthnGetAssertionExtraInfo(); |
199 | 0 |
|
200 | 0 | // Set flags for credential requests. |
201 | 0 | if (extra.RequireUserVerification()) { |
202 | 0 | signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION; |
203 | 0 | } |
204 | 0 |
|
205 | 0 | // Process extensions. |
206 | 0 | for (const WebAuthnExtension& ext: extra.Extensions()) { |
207 | 0 | if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) { |
208 | 0 | appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId()); |
209 | 0 | } |
210 | 0 | } |
211 | 0 | } |
212 | 0 |
|
213 | 0 | ClearPromises(); |
214 | 0 | mTransaction.reset(); |
215 | 0 | uint64_t tid = rust_u2f_mgr_sign(mU2FManager, |
216 | 0 | signFlags, |
217 | 0 | (uint64_t)aInfo.TimeoutMS(), |
218 | 0 | u2f_sign_callback, |
219 | 0 | clientDataHash.Elements(), |
220 | 0 | clientDataHash.Length(), |
221 | 0 | U2FAppIds(appIds).Get(), |
222 | 0 | U2FKeyHandles(aInfo.AllowList()).Get()); |
223 | 0 | if (tid == 0) { |
224 | 0 | return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
225 | 0 | } |
226 | 0 | |
227 | 0 | mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON())); |
228 | 0 |
|
229 | 0 | return mSignPromise.Ensure(__func__); |
230 | 0 | } |
231 | | |
232 | | void |
233 | | U2FHIDTokenManager::Cancel() |
234 | 0 | { |
235 | 0 | mozilla::ipc::AssertIsOnBackgroundThread(); |
236 | 0 |
|
237 | 0 | ClearPromises(); |
238 | 0 | rust_u2f_mgr_cancel(mU2FManager); |
239 | 0 | mTransaction.reset(); |
240 | 0 | } |
241 | | |
242 | | void |
243 | | U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult) |
244 | 0 | { |
245 | 0 | mozilla::ipc::AssertIsOnBackgroundThread(); |
246 | 0 |
|
247 | 0 | if (mTransaction.isNothing() || |
248 | 0 | aResult->GetTransactionId() != mTransaction.ref().mId) { |
249 | 0 | return; |
250 | 0 | } |
251 | 0 | |
252 | 0 | MOZ_ASSERT(!mRegisterPromise.IsEmpty()); |
253 | 0 |
|
254 | 0 | if (aResult->IsError()) { |
255 | 0 | mRegisterPromise.Reject(aResult->GetError(), __func__); |
256 | 0 | return; |
257 | 0 | } |
258 | 0 | |
259 | 0 | nsTArray<uint8_t> registration; |
260 | 0 | if (!aResult->CopyRegistration(registration)) { |
261 | 0 | mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
262 | 0 | return; |
263 | 0 | } |
264 | 0 | |
265 | 0 | // Decompose the U2F registration packet |
266 | 0 | CryptoBuffer pubKeyBuf; |
267 | 0 | CryptoBuffer keyHandle; |
268 | 0 | CryptoBuffer attestationCertBuf; |
269 | 0 | CryptoBuffer signatureBuf; |
270 | 0 |
|
271 | 0 | CryptoBuffer regData; |
272 | 0 | regData.Assign(registration); |
273 | 0 |
|
274 | 0 | // Only handles attestation cert chains of length=1. |
275 | 0 | nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandle, |
276 | 0 | attestationCertBuf, signatureBuf); |
277 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
278 | 0 | mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
279 | 0 | return; |
280 | 0 | } |
281 | 0 | |
282 | 0 | CryptoBuffer rpIdHashBuf; |
283 | 0 | if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) { |
284 | 0 | mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
285 | 0 | return; |
286 | 0 | } |
287 | 0 | |
288 | 0 | CryptoBuffer attObj; |
289 | 0 | rv = AssembleAttestationObject(rpIdHashBuf, pubKeyBuf, keyHandle, |
290 | 0 | attestationCertBuf, signatureBuf, |
291 | 0 | mTransaction.ref().mForceNoneAttestation, |
292 | 0 | attObj); |
293 | 0 | if (NS_FAILED(rv)) { |
294 | 0 | mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
295 | 0 | return; |
296 | 0 | } |
297 | 0 | |
298 | 0 | WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON, |
299 | 0 | attObj, keyHandle, regData); |
300 | 0 | mRegisterPromise.Resolve(std::move(result), __func__); |
301 | 0 | } |
302 | | |
303 | | void |
304 | | U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult) |
305 | 0 | { |
306 | 0 | mozilla::ipc::AssertIsOnBackgroundThread(); |
307 | 0 |
|
308 | 0 | if (mTransaction.isNothing() || |
309 | 0 | aResult->GetTransactionId() != mTransaction.ref().mId) { |
310 | 0 | return; |
311 | 0 | } |
312 | 0 | |
313 | 0 | MOZ_ASSERT(!mSignPromise.IsEmpty()); |
314 | 0 |
|
315 | 0 | if (aResult->IsError()) { |
316 | 0 | mSignPromise.Reject(aResult->GetError(), __func__); |
317 | 0 | return; |
318 | 0 | } |
319 | 0 | |
320 | 0 | nsTArray<uint8_t> appId; |
321 | 0 | if (!aResult->CopyAppId(appId)) { |
322 | 0 | mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
323 | 0 | return; |
324 | 0 | } |
325 | 0 | |
326 | 0 | nsTArray<uint8_t> keyHandle; |
327 | 0 | if (!aResult->CopyKeyHandle(keyHandle)) { |
328 | 0 | mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
329 | 0 | return; |
330 | 0 | } |
331 | 0 | |
332 | 0 | nsTArray<uint8_t> signature; |
333 | 0 | if (!aResult->CopySignature(signature)) { |
334 | 0 | mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
335 | 0 | return; |
336 | 0 | } |
337 | 0 | |
338 | 0 | CryptoBuffer rawSignatureBuf; |
339 | 0 | if (!rawSignatureBuf.Assign(signature)) { |
340 | 0 | mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
341 | 0 | return; |
342 | 0 | } |
343 | 0 | |
344 | 0 | nsTArray<WebAuthnExtensionResult> extensions; |
345 | 0 |
|
346 | 0 | if (appId != mTransaction.ref().mRpIdHash) { |
347 | 0 | // Indicate to the RP that we used the FIDO appId. |
348 | 0 | extensions.AppendElement(WebAuthnExtensionResultAppId(true)); |
349 | 0 | } |
350 | 0 |
|
351 | 0 | CryptoBuffer signatureBuf; |
352 | 0 | CryptoBuffer counterBuf; |
353 | 0 | uint8_t flags = 0; |
354 | 0 | nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf, |
355 | 0 | signatureBuf); |
356 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
357 | 0 | mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
358 | 0 | return; |
359 | 0 | } |
360 | 0 | |
361 | 0 | CryptoBuffer chosenAppIdBuf; |
362 | 0 | if (!chosenAppIdBuf.Assign(appId)) { |
363 | 0 | mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
364 | 0 | return; |
365 | 0 | } |
366 | 0 | |
367 | 0 | // Preserve the two LSBs of the flags byte, UP and RFU1. |
368 | 0 | // See <https://github.com/fido-alliance/fido-2-specs/pull/519> |
369 | 0 | flags &= 0b11; |
370 | 0 |
|
371 | 0 | CryptoBuffer emptyAttestationData; |
372 | 0 | CryptoBuffer authenticatorData; |
373 | 0 | rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf, |
374 | 0 | emptyAttestationData, authenticatorData); |
375 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
376 | 0 | mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); |
377 | 0 | return; |
378 | 0 | } |
379 | 0 | |
380 | 0 | WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON, |
381 | 0 | keyHandle, signatureBuf, authenticatorData, |
382 | 0 | extensions, rawSignatureBuf); |
383 | 0 | mSignPromise.Resolve(std::move(result), __func__); |
384 | 0 | } |
385 | | |
386 | | } |
387 | | } |