// NOTE: This method represents a theoretical way to use a U2F-compliant token // to produce the result of the WebAuthn GetAssertion method. The exact mapping // of U2F data fields to WebAuthn data fields is still a matter of ongoing // discussion, and this should not be taken as anything but a point-in- time // possibility. void WebAuthentication::U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest, const Authenticator& aToken, CryptoBuffer& aRpIdHash, const nsACString& aClientData, CryptoBuffer& aClientDataHash, nsTArray<CryptoBuffer>& aAllowList, const WebAuthnExtensions& aExtensions) { MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthGetAssertion")); // 4.1.2.7.e Add an entry to issuedRequests, corresponding to this request. aRequest->AddActiveToken(__func__); // 4.1.2.8 While issuedRequests is not empty, perform the following actions // depending upon the adjustedTimeout timer and responses from the // authenticators: // 4.1.2.8.a If the timer for adjustedTimeout expires, then for each entry // in issuedRequests invoke the authenticatorCancel operation on that // authenticator and remove its entry from the list. for (CryptoBuffer& allowedCredential : aAllowList) { bool isRegistered = false; nsresult rv = aToken->IsRegistered(allowedCredential.Elements(), allowedCredential.Length(), &isRegistered); // 4.1.2.8.b If any authenticator returns a status indicating that the user // cancelled the operation, delete that authenticator’s entry from // issuedRequests. For each remaining entry in issuedRequests invoke the // authenticatorCancel operation on that authenticator, and remove its entry // from the list. // 4.1.2.8.c If any authenticator returns an error status, delete the // corresponding entry from issuedRequests. if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(rv); return; } if (!isRegistered) { continue; } // Sign uint8_t* buffer; uint32_t bufferlen; rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(), aClientDataHash.Elements(), aClientDataHash.Length(), allowedCredential.Elements(), allowedCredential.Length(), &buffer, &bufferlen); if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(rv); return; } MOZ_ASSERT(buffer); CryptoBuffer signatureData; if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) { free(buffer); aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY); return; } free(buffer); // 4.1.2.8.d If any authenticator returns success: // 4.1.2.8.d.1 Remove this authenticator’s entry from issuedRequests. // 4.1.2.8.d.2 Create a new WebAuthnAssertion object named value and // populate its fields with the values returned from the authenticator as // well as the clientDataJSON computed earlier. CryptoBuffer clientDataBuf; if (!clientDataBuf.Assign(aClientData)) { aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY); return; } CryptoBuffer authenticatorDataBuf; rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash, signatureData); if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(rv); return; } RefPtr<ScopedCredential> credential = new ScopedCredential(this); credential->SetType(ScopedCredentialType::ScopedCred); credential->SetId(allowedCredential); AssertionPtr assertion = new WebAuthnAssertion(this); assertion->SetCredential(credential); assertion->SetClientData(clientDataBuf); assertion->SetAuthenticatorData(authenticatorDataBuf); assertion->SetSignature(signatureData); // 4.1.2.8.d.3 For each remaining entry in issuedRequests invoke the // authenticatorCancel operation on that authenticator and remove its entry // from the list. // 4.1.2.8.d.4 Resolve promise with value and terminate this algorithm. aRequest->SetSuccess(assertion); return; } // 4.1.2.9 Reject promise with a DOMException whose name is "NotAllowedError", // and terminate this algorithm. aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR); }
// NOTE: This method represents a theoretical way to use a U2F-compliant token // to produce the result of the WebAuthn MakeCredential method. The exact // mapping of U2F data fields to WebAuthn data fields is still a matter of // ongoing discussion, and this should not be taken as anything but a point-in- // time possibility. void WebAuthentication::U2FAuthMakeCredential( const RefPtr<CredentialRequest>& aRequest, const Authenticator& aToken, CryptoBuffer& aRpIdHash, const nsACString& aClientData, CryptoBuffer& aClientDataHash, const Account& aAccount, const nsTArray<ScopedCredentialParameters>& aNormalizedParams, const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList, const WebAuthnExtensions& aExtensions) { MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthMakeCredential")); aRequest->AddActiveToken(__func__); // 5.1.1 When this operation is invoked, the authenticator must perform the // following procedure: // 5.1.1.a Check if all the supplied parameters are syntactically well- // formed and of the correct length. If not, return an error code equivalent // to UnknownError and terminate the operation. if ((aRpIdHash.Length() != SHA256_LENGTH) || (aClientDataHash.Length() != SHA256_LENGTH)) { aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR); return; } // 5.1.1.b Check if at least one of the specified combinations of // ScopedCredentialType and cryptographic parameters is supported. If not, // return an error code equivalent to NotSupportedError and terminate the // operation. bool isValidCombination = false; for (size_t a = 0; a < aNormalizedParams.Length(); ++a) { if (aNormalizedParams[a].mType == ScopedCredentialType::ScopedCred && aNormalizedParams[a].mAlgorithm.IsString() && aNormalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral( WEBCRYPTO_NAMED_CURVE_P256)) { isValidCombination = true; break; } } if (!isValidCombination) { aRequest->SetFailure(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } // 5.1.1.c Check if a credential matching any of the supplied // ScopedCredential identifiers is present on this authenticator. If so, // return an error code equivalent to NotAllowedError and terminate the // operation. if (aExcludeList.WasPassed()) { const Sequence<ScopedCredentialDescriptor>& list = aExcludeList.Value(); for (const ScopedCredentialDescriptor& scd : list) { bool isRegistered = false; uint8_t *data; uint32_t len; // data is owned by the Descriptor, do don't free it here. if (NS_FAILED(ScopedCredentialGetData(scd, &data, &len))) { aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR); return; } nsresult rv = aToken->IsRegistered(data, len, &isRegistered); if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(rv); return; } if (isRegistered) { aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR); return; } } } // 5.1.1.d Prompt the user for consent to create a new credential. The // prompt for obtaining this consent is shown by the authenticator if it has // its own output capability, or by the user agent otherwise. If the user // denies consent, return an error code equivalent to NotAllowedError and // terminate the operation. // 5.1.1.d Once user consent has been obtained, generate a new credential // object // 5.1.1.e If any error occurred while creating the new credential object, // return an error code equivalent to UnknownError and terminate the // operation. // 5.1.1.f Process all the supported extensions requested by the client, and // generate an attestation statement. If no authority key is available to // sign such an attestation statement, then the authenticator performs self // attestation of the credential with its own private key. For more details // on attestation, see §5.3 Credential Attestation Statements. // No extensions are supported // 4.1.1.11 While issuedRequests is not empty, perform the following actions // depending upon the adjustedTimeout timer and responses from the // authenticators: // 4.1.1.11.a If the adjustedTimeout timer expires, then for each entry in // issuedRequests invoke the authenticatorCancel operation on that // authenticator and remove its entry from the list. uint8_t* buffer; uint32_t bufferlen; nsresult rv = aToken->Register(aRpIdHash.Elements(), aRpIdHash.Length(), aClientDataHash.Elements(), aClientDataHash.Length(), &buffer, &bufferlen); // 4.1.1.11.b If any authenticator returns a status indicating that the user // cancelled the operation, delete that authenticator’s entry from // issuedRequests. For each remaining entry in issuedRequests invoke the // authenticatorCancel operation on that authenticator and remove its entry // from the list. // 4.1.1.11.c If any authenticator returns an error status, delete the // corresponding entry from issuedRequests. if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR); return; } MOZ_ASSERT(buffer); CryptoBuffer regData; if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) { free(buffer); aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY); return; } free(buffer); // Decompose the U2F registration packet CryptoBuffer pubKeyBuf; CryptoBuffer keyHandleBuf; CryptoBuffer attestationCertBuf; CryptoBuffer signatureBuf; rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf, attestationCertBuf, signatureBuf); if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(rv); return; } // Sign the aClientDataHash explicitly to get the format needed for // the AuthenticatorData parameter of WebAuthnAttestation. This might // be temporary while the spec settles down how to incorporate U2F. rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(), aClientDataHash.Elements(), aClientDataHash.Length(), keyHandleBuf.Elements(), keyHandleBuf.Length(), &buffer, &bufferlen); if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(rv); return; } MOZ_ASSERT(buffer); CryptoBuffer signatureData; if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) { free(buffer); aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY); return; } free(buffer); CryptoBuffer clientDataBuf; if (!clientDataBuf.Assign(aClientData)) { aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY); return; } CryptoBuffer authenticatorDataBuf; rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash, signatureData); if (NS_WARN_IF(NS_FAILED(rv))) { aRequest->SetFailure(rv); return; } // 4.1.1.11.d If any authenticator indicates success: // 4.1.1.11.d.1 Remove this authenticator’s entry from issuedRequests. // 4.1.1.11.d.2 Create a new ScopedCredentialInfo object named value and // populate its fields with the values returned from the authenticator as well // as the clientDataJSON computed earlier. RefPtr<ScopedCredential> credential = new ScopedCredential(this); credential->SetType(ScopedCredentialType::ScopedCred); credential->SetId(keyHandleBuf); RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(this); attestation->SetFormat(NS_LITERAL_STRING("u2f")); attestation->SetClientData(clientDataBuf); attestation->SetAuthenticatorData(authenticatorDataBuf); attestation->SetAttestation(regData); CredentialPtr info = new ScopedCredentialInfo(this); info->SetCredential(credential); info->SetAttestation(attestation); // 4.1.1.11.d.3 For each remaining entry in issuedRequests invoke the // authenticatorCancel operation on that authenticator and remove its entry // from the list. // 4.1.1.11.d.4 Resolve promise with value and terminate this algorithm. aRequest->SetSuccess(info); }