bool RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader, const nsNSSShutDownPreventionLock& /*proof*/) { CryptoBuffer cert; if (!ReadBuffer(aReader, cert) || cert.Length() == 0) { return false; } SECItem der = { siBuffer, cert.Elements(), static_cast<unsigned int>(cert.Length()) }; mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der, nullptr, true, true)); return !!mCertificate; }
static nsresult U2FAssembleAuthenticatorData(/* out */ CryptoBuffer& aAuthenticatorData, const CryptoBuffer& aRpIdHash, const CryptoBuffer& aSignatureData) { // The AuthenticatorData for U2F devices is the concatenation of the // RP ID with the output of the U2F Sign operation. if (aRpIdHash.Length() != 32) { return NS_ERROR_INVALID_ARG; } if (!aAuthenticatorData.AppendElements(aRpIdHash, mozilla::fallible)) { return NS_ERROR_OUT_OF_MEMORY; } if (!aAuthenticatorData.AppendElements(aSignatureData, mozilla::fallible)) { return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; }
NS_IMETHODIMP U2FSignTask::Run() { nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } // Search the requests for one a token can fulfill for (size_t i = 0; i < mRegisteredKeys.Length(); i += 1) { RegisteredKey request(mRegisteredKeys[i]); // Check for required attributes if (!(request.mVersion.WasPassed() && request.mKeyHandle.WasPassed())) { continue; } // Do not permit an individual RegisteredKey to assert a different AppID if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) { continue; } // Assemble a clientData object CryptoBuffer clientData; nsresult rv = AssembleClientData(mOrigin, kGetAssertion, mChallenge, clientData); if (NS_WARN_IF(NS_FAILED(rv))) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } // Hash the AppID and the ClientData into the AppParam and ChallengeParam SECStatus srv; nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId); CryptoBuffer appParam; CryptoBuffer challengeParam; if (!appParam.SetLength(SHA256_LENGTH, fallible) || !challengeParam.SetLength(SHA256_LENGTH, fallible)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(), reinterpret_cast<const uint8_t*>(cAppId.BeginReading()), cAppId.Length()); if (srv != SECSuccess) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(), clientData.Elements(), clientData.Length()); if (srv != SECSuccess) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } // Decode the key handle CryptoBuffer keyHandle; rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value()); if (NS_WARN_IF(NS_FAILED(rv))) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } // Get the signature from the token CryptoBuffer signatureData; bool signSuccess = false; // We ignore mTransports, as it is intended to be used for sorting the // available devices by preference, but is not an exclusion factor. for (size_t a = 0; a < mAuthenticators.Length() && !signSuccess; ++a) { Authenticator token(mAuthenticators[a]); bool isCompatible = false; bool isRegistered = false; rv = token->IsCompatibleVersion(request.mVersion.Value(), &isCompatible); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } if (!isCompatible) { continue; } rv = token->IsRegistered(keyHandle.Elements(), keyHandle.Length(), &isRegistered); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } if (isCompatible && isRegistered) { uint8_t* buffer; uint32_t bufferlen; nsresult rv = token->Sign(appParam.Elements(), appParam.Length(), challengeParam.Elements(), challengeParam.Length(), keyHandle.Elements(), keyHandle.Length(), &buffer, &bufferlen); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } MOZ_ASSERT(buffer); signatureData.Assign(buffer, bufferlen); free(buffer); signSuccess = true; } } if (!signSuccess) { // Try another request continue; } // Assemble a response object to return nsString clientDataBase64, signatureDataBase64; nsresult rvClientData = clientData.ToJwkBase64(clientDataBase64); nsresult rvSignatureData = signatureData.ToJwkBase64(signatureDataBase64); if (NS_WARN_IF(NS_FAILED(rvClientData)) || NS_WARN_IF(NS_FAILED(rvSignatureData))) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } SignResponse response; response.mKeyHandle.Construct(request.mKeyHandle.Value()); response.mClientData.Construct(clientDataBase64); response.mSignatureData.Construct(signatureDataBase64); response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK)); ErrorResult result; mCallback->Call(response, result); NS_WARN_IF(result.Failed()); // Useful exceptions already got reported. result.SuppressException(); return NS_OK; } // Nothing could satisfy ReturnError(ErrorCode::DEVICE_INELIGIBLE); return NS_ERROR_FAILURE; }
// 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); }
static nsresult U2FDecomposeRegistrationResponse(const CryptoBuffer& aResponse, /* out */ CryptoBuffer& aPubKeyBuf, /* out */ CryptoBuffer& aKeyHandleBuf, /* out */ CryptoBuffer& aAttestationCertBuf, /* out */ CryptoBuffer& aSignatureBuf) { // U2F v1.1 Format via // http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html // // Bytes Value // 1 0x05 // 65 public key // 1 key handle length // * key handle // ASN.1 attestation certificate // * attestation signature pkix::Input u2fResponse; u2fResponse.Init(aResponse.Elements(), aResponse.Length()); pkix::Reader input(u2fResponse); uint8_t b; if (input.Read(b) != pkix::Success) { return NS_ERROR_DOM_UNKNOWN_ERR; } if (b != 0x05) { return NS_ERROR_DOM_UNKNOWN_ERR; } nsresult rv = ReadToCryptoBuffer(input, aPubKeyBuf, 65); if (NS_FAILED(rv)) { return rv; } uint8_t handleLen; if (input.Read(handleLen) != pkix::Success) { return NS_ERROR_DOM_UNKNOWN_ERR; } rv = ReadToCryptoBuffer(input, aKeyHandleBuf, handleLen); if (NS_FAILED(rv)) { return rv; } // We have to parse the ASN.1 SEQUENCE on the outside to determine the cert's // length. pkix::Input cert; if (pkix::der::ExpectTagAndGetValue(input, pkix::der::SEQUENCE, cert) != pkix::Success) { return NS_ERROR_DOM_UNKNOWN_ERR; } pkix::Reader certInput(cert); rv = ReadToCryptoBuffer(certInput, aAttestationCertBuf, cert.GetLength()); if (NS_FAILED(rv)) { return rv; } // The remainder of u2fResponse is the signature pkix::Input u2fSig; input.SkipToEnd(u2fSig); pkix::Reader sigInput(u2fSig); rv = ReadToCryptoBuffer(sigInput, aSignatureBuf, u2fSig.GetLength()); if (NS_FAILED(rv)) { return rv; } return NS_OK; }