Bug 1795020 - support WebAuthn large blob extension. r=webidl,keeler,smaug

Differential Revision: https://phabricator.services.mozilla.com/D244974
This commit is contained in:
John Schanck
2025-04-14 17:43:24 +00:00
parent 7fcedb3ad5
commit 3d5bb427bf
18 changed files with 608 additions and 61 deletions

View File

@@ -238,13 +238,16 @@ class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService final
void FinishMakeCredential(const nsTArray<uint8_t>& aRawAttestationObject, void FinishMakeCredential(const nsTArray<uint8_t>& aRawAttestationObject,
const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aCredentialId,
const nsTArray<nsString>& aTransports, const nsTArray<nsString>& aTransports,
const Maybe<nsString>& aAuthenticatorAttachment); const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<bool>& aLargeBlobSupported);
void FinishGetAssertion(const nsTArray<uint8_t>& aCredentialId, void FinishGetAssertion(const nsTArray<uint8_t>& aCredentialId,
const nsTArray<uint8_t>& aSignature, const nsTArray<uint8_t>& aSignature,
const nsTArray<uint8_t>& aAuthenticatorData, const nsTArray<uint8_t>& aAuthenticatorData,
const nsTArray<uint8_t>& aUserHandle, const nsTArray<uint8_t>& aUserHandle,
const Maybe<nsString>& aAuthenticatorAttachment); const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<nsTArray<uint8_t>>& aLargeBlobValue,
const Maybe<bool>& aLargeBlobWritten);
void ReleasePlatformResources(); void ReleasePlatformResources();
void AbortTransaction(nsresult aError); void AbortTransaction(nsresult aError);
@@ -314,18 +317,17 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID)); nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
nsTArray<nsString> transports; nsTArray<nsString> transports;
mozilla::Maybe<nsString> authenticatorAttachment; mozilla::Maybe<nsString> authenticatorAttachment;
mozilla::Maybe<bool> largeBlobSupported;
if ([credential isKindOfClass: if ([credential isKindOfClass:
[ASAuthorizationPlatformPublicKeyCredentialRegistration [ASAuthorizationPlatformPublicKeyCredentialRegistration
class]]) { class]]) {
transports.AppendElement(u"hybrid"_ns); transports.AppendElement(u"hybrid"_ns);
transports.AppendElement(u"internal"_ns); transports.AppendElement(u"internal"_ns);
#if defined(MAC_OS_VERSION_13_5) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
if (__builtin_available(macos 13.5, *)) {
ASAuthorizationPlatformPublicKeyCredentialRegistration* ASAuthorizationPlatformPublicKeyCredentialRegistration*
platformCredential = platformCredential =
(ASAuthorizationPlatformPublicKeyCredentialRegistration*) (ASAuthorizationPlatformPublicKeyCredentialRegistration*)
credential; credential;
if (__builtin_available(macos 13.5, *)) {
switch (platformCredential.attachment) { switch (platformCredential.attachment) {
case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform: case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
authenticatorAttachment.emplace(u"cross-platform"_ns); authenticatorAttachment.emplace(u"cross-platform"_ns);
@@ -337,7 +339,11 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
break; break;
} }
} }
#endif if (__builtin_available(macos 14.0, *)) {
if (platformCredential.largeBlob) {
largeBlobSupported.emplace(platformCredential.largeBlob.isSupported);
}
}
} else { } else {
// The platform didn't tell us what transport was used, but we know it // The platform didn't tell us what transport was used, but we know it
// wasn't the internal transport. The transport response is not signed by // wasn't the internal transport. The transport response is not signed by
@@ -348,7 +354,8 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
authenticatorAttachment.emplace(u"cross-platform"_ns); authenticatorAttachment.emplace(u"cross-platform"_ns);
} }
mCallback->FinishMakeCredential(rawAttestationObject, credentialId, mCallback->FinishMakeCredential(rawAttestationObject, credentialId,
transports, authenticatorAttachment); transports, authenticatorAttachment,
largeBlobSupported);
} else if ([authorization.credential } else if ([authorization.credential
conformsToProtocol: conformsToProtocol:
@protocol(ASAuthorizationPublicKeyCredentialAssertion)]) { @protocol(ASAuthorizationPublicKeyCredentialAssertion)]) {
@@ -364,16 +371,14 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
NSDataToArray(credential.rawAuthenticatorData)); NSDataToArray(credential.rawAuthenticatorData));
nsTArray<uint8_t> userHandle(NSDataToArray(credential.userID)); nsTArray<uint8_t> userHandle(NSDataToArray(credential.userID));
mozilla::Maybe<nsString> authenticatorAttachment; mozilla::Maybe<nsString> authenticatorAttachment;
mozilla::Maybe<nsTArray<uint8_t>> largeBlobValue;
mozilla::Maybe<bool> largeBlobWritten;
if ([credential if ([credential
isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion
class]]) { class]]) {
#if defined(MAC_OS_VERSION_13_5) && \ ASAuthorizationPlatformPublicKeyCredentialAssertion* platformCredential =
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5 (ASAuthorizationPlatformPublicKeyCredentialAssertion*)credential;
if (__builtin_available(macos 13.5, *)) { if (__builtin_available(macos 13.5, *)) {
ASAuthorizationPlatformPublicKeyCredentialAssertion*
platformCredential =
(ASAuthorizationPlatformPublicKeyCredentialAssertion*)
credential;
switch (platformCredential.attachment) { switch (platformCredential.attachment) {
case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform: case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
authenticatorAttachment.emplace(u"cross-platform"_ns); authenticatorAttachment.emplace(u"cross-platform"_ns);
@@ -385,12 +390,22 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
break; break;
} }
} }
#endif if (__builtin_available(macos 14.0, *)) {
if (platformCredential.largeBlob) {
if (platformCredential.largeBlob.readData) {
largeBlobValue.emplace(
NSDataToArray(platformCredential.largeBlob.readData));
} else {
largeBlobWritten.emplace(platformCredential.largeBlob.didWrite);
}
}
}
} else { } else {
authenticatorAttachment.emplace(u"cross-platform"_ns); authenticatorAttachment.emplace(u"cross-platform"_ns);
} }
mCallback->FinishGetAssertion(credentialId, signature, rawAuthenticatorData, mCallback->FinishGetAssertion(credentialId, signature, rawAuthenticatorData,
userHandle, authenticatorAttachment); userHandle, authenticatorAttachment,
largeBlobValue, largeBlobWritten);
} else { } else {
MOZ_LOG( MOZ_LOG(
gMacOSWebAuthnServiceLog, mozilla::LogLevel::Error, gMacOSWebAuthnServiceLog, mozilla::LogLevel::Error,
@@ -722,6 +737,27 @@ MacOSWebAuthnService::MakeCredential(uint64_t aTransactionId,
crossPlatformRegistrationRequest.userVerificationPreference = crossPlatformRegistrationRequest.userVerificationPreference =
*userVerificationPreference; *userVerificationPreference;
} }
if (__builtin_available(macos 14.0, *)) {
bool largeBlobSupportRequired;
nsresult rv =
aArgs->GetLargeBlobSupportRequired(&largeBlobSupportRequired);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
self->mRegisterPromise->Reject(rv);
return;
}
ASAuthorizationPublicKeyCredentialLargeBlobSupportRequirement
largeBlobRequirement =
largeBlobSupportRequired
? ASAuthorizationPublicKeyCredentialLargeBlobSupportRequirementRequired
: ASAuthorizationPublicKeyCredentialLargeBlobSupportRequirementPreferred;
platformRegistrationRequest.largeBlob =
[[ASAuthorizationPublicKeyCredentialLargeBlobRegistrationInput
alloc] initWithSupportRequirement:largeBlobRequirement];
}
}
nsTArray<uint8_t> clientDataHash; nsTArray<uint8_t> clientDataHash;
nsresult rv = aArgs->GetClientDataHash(clientDataHash); nsresult rv = aArgs->GetClientDataHash(clientDataHash);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
@@ -800,7 +836,8 @@ void MacOSWebAuthnService::FinishMakeCredential(
const nsTArray<uint8_t>& aRawAttestationObject, const nsTArray<uint8_t>& aRawAttestationObject,
const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aCredentialId,
const nsTArray<nsString>& aTransports, const nsTArray<nsString>& aTransports,
const Maybe<nsString>& aAuthenticatorAttachment) { const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<bool>& aLargeBlobSupported) {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
if (!mRegisterPromise) { if (!mRegisterPromise) {
return; return;
@@ -808,7 +845,7 @@ void MacOSWebAuthnService::FinishMakeCredential(
RefPtr<WebAuthnRegisterResult> result(new WebAuthnRegisterResult( RefPtr<WebAuthnRegisterResult> result(new WebAuthnRegisterResult(
aRawAttestationObject, Nothing(), aCredentialId, aTransports, aRawAttestationObject, Nothing(), aCredentialId, aTransports,
aAuthenticatorAttachment)); aAuthenticatorAttachment, aLargeBlobSupported));
Unused << mRegisterPromise->Resolve(result); Unused << mRegisterPromise->Resolve(result);
mRegisterPromise = nullptr; mRegisterPromise = nullptr;
} }
@@ -1037,6 +1074,47 @@ void MacOSWebAuthnService::DoGetAssertion(
crossPlatformAssertionRequest.userVerificationPreference = crossPlatformAssertionRequest.userVerificationPreference =
*userVerificationPreference; *userVerificationPreference;
} }
if (__builtin_available(macos 14.0, *)) {
nsTArray<uint8_t> largeBlobWrite;
bool largeBlobRead;
nsresult rv = aArgs->GetLargeBlobRead(&largeBlobRead);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
self->mSignPromise->Reject(rv);
return;
}
if (largeBlobRead) {
platformAssertionRequest
.largeBlob = [[ASAuthorizationPublicKeyCredentialLargeBlobAssertionInput
alloc]
initWithOperation:
ASAuthorizationPublicKeyCredentialLargeBlobAssertionOperationRead];
} else {
rv = aArgs->GetLargeBlobWrite(largeBlobWrite);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
self->mSignPromise->Reject(rv);
return;
}
ASAuthorizationPublicKeyCredentialLargeBlobAssertionInput*
largeBlobAssertionInput =
[[ASAuthorizationPublicKeyCredentialLargeBlobAssertionInput
alloc]
initWithOperation:
ASAuthorizationPublicKeyCredentialLargeBlobAssertionOperationWrite];
// We need to fully form the input before assigning it to
// platformAssertionRequest.largeBlob. See
// https://bugs.webkit.org/show_bug.cgi?id=276961
largeBlobAssertionInput.dataToWrite =
[NSData dataWithBytes:largeBlobWrite.Elements()
length:largeBlobWrite.Length()];
platformAssertionRequest.largeBlob = largeBlobAssertionInput;
}
}
}
}
nsTArray<uint8_t> clientDataHash; nsTArray<uint8_t> clientDataHash;
nsresult rv = aArgs->GetClientDataHash(clientDataHash); nsresult rv = aArgs->GetClientDataHash(clientDataHash);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
@@ -1057,7 +1135,9 @@ void MacOSWebAuthnService::FinishGetAssertion(
const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aSignature, const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aSignature,
const nsTArray<uint8_t>& aAuthenticatorData, const nsTArray<uint8_t>& aAuthenticatorData,
const nsTArray<uint8_t>& aUserHandle, const nsTArray<uint8_t>& aUserHandle,
const Maybe<nsString>& aAuthenticatorAttachment) { const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<nsTArray<uint8_t>>& aLargeBlobValue,
const Maybe<bool>& aLargeBlobWritten) {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
if (!mSignPromise) { if (!mSignPromise) {
return; return;
@@ -1065,7 +1145,7 @@ void MacOSWebAuthnService::FinishGetAssertion(
RefPtr<WebAuthnSignResult> result(new WebAuthnSignResult( RefPtr<WebAuthnSignResult> result(new WebAuthnSignResult(
aAuthenticatorData, Nothing(), aCredentialId, aSignature, aUserHandle, aAuthenticatorData, Nothing(), aCredentialId, aSignature, aUserHandle,
aAuthenticatorAttachment)); aAuthenticatorAttachment, aLargeBlobValue, aLargeBlobWritten));
Unused << mSignPromise->Resolve(result); Unused << mSignPromise->Resolve(result);
mSignPromise = nullptr; mSignPromise = nullptr;
} }

View File

@@ -40,6 +40,14 @@ struct WebAuthnExtensionHmacSecret {
bool hmacCreateSecret; bool hmacCreateSecret;
}; };
struct WebAuthnExtensionLargeBlob {
bool? flag; // In registrations this indicates whether large blob support is required.
// In authentications this indicates whether this is a request to read the
// a blob or whether it is a request to write one.
uint8_t[] write; // Authentication only. The value to be written when `flag` is
// present and false.
};
struct WebAuthnExtensionMinPinLength { struct WebAuthnExtensionMinPinLength {
bool minPinLength; bool minPinLength;
}; };
@@ -64,6 +72,7 @@ struct WebAuthnExtensionPrfEvalByCredentialEntry {
union WebAuthnExtension { union WebAuthnExtension {
WebAuthnExtensionCredProps; WebAuthnExtensionCredProps;
WebAuthnExtensionHmacSecret; WebAuthnExtensionHmacSecret;
WebAuthnExtensionLargeBlob;
WebAuthnExtensionMinPinLength; WebAuthnExtensionMinPinLength;
WebAuthnExtensionPrf; WebAuthnExtensionPrf;
}; };
@@ -80,6 +89,13 @@ struct WebAuthnExtensionResultHmacSecret {
bool hmacCreateSecret; bool hmacCreateSecret;
}; };
struct WebAuthnExtensionResultLargeBlob {
bool flag; // In registration this indicates support. In authentication
// it indicates whether this is a read return or a write return.
uint8_t[] blob; // Authentication only. Read return.
bool written; // Authentication only. Write return
};
struct WebAuthnExtensionResultPrf { struct WebAuthnExtensionResultPrf {
bool? enabled; bool? enabled;
WebAuthnExtensionPrfValues? results; WebAuthnExtensionPrfValues? results;
@@ -89,6 +105,7 @@ union WebAuthnExtensionResult {
WebAuthnExtensionResultAppId; WebAuthnExtensionResultAppId;
WebAuthnExtensionResultCredProps; WebAuthnExtensionResultCredProps;
WebAuthnExtensionResultHmacSecret; WebAuthnExtensionResultHmacSecret;
WebAuthnExtensionResultLargeBlob;
WebAuthnExtensionResultPrf; WebAuthnExtensionResultPrf;
}; };

View File

@@ -197,6 +197,14 @@ already_AddRefed<Promise> PublicKeyCredential::GetClientCapabilities(
entry->mKey = u"extension:hmacCreateSecret"_ns; entry->mKey = u"extension:hmacCreateSecret"_ns;
entry->mValue = true; entry->mValue = true;
entry = capabilities.Entries().AppendElement();
entry->mKey = u"extension:largeBlob"_ns;
#if defined(XP_MACOSX) || defined(XP_WIN)
entry->mValue = true;
#else
entry->mValue = false;
#endif
entry = capabilities.Entries().AppendElement(); entry = capabilities.Entries().AppendElement();
entry->mKey = u"extension:minPinLength"_ns; entry->mKey = u"extension:minPinLength"_ns;
entry->mValue = true; entry->mValue = true;
@@ -282,6 +290,26 @@ void PublicKeyCredential::GetClientExtensionResults(
mClientExtensionOutputs.mHmacCreateSecret.Value()); mClientExtensionOutputs.mHmacCreateSecret.Value());
} }
if (mClientExtensionOutputs.mLargeBlob.WasPassed()) {
const AuthenticationExtensionsLargeBlobOutputs& src =
mClientExtensionOutputs.mLargeBlob.Value();
AuthenticationExtensionsLargeBlobOutputs& dest =
aResult.mLargeBlob.Construct();
if (src.mSupported.WasPassed()) {
dest.mSupported.Construct(src.mSupported.Value());
}
if (src.mWritten.WasPassed()) {
dest.mWritten.Construct(src.mWritten.Value());
}
if (mLargeBlobValue.isSome()) {
dest.mBlob.Construct().Init(
TypedArrayCreator<ArrayBuffer>(mLargeBlobValue.ref()).Create(cx));
}
}
if (mClientExtensionOutputs.mPrf.WasPassed()) { if (mClientExtensionOutputs.mPrf.WasPassed()) {
AuthenticationExtensionsPRFOutputs& dest = aResult.mPrf.Construct(); AuthenticationExtensionsPRFOutputs& dest = aResult.mPrf.Construct();
@@ -336,6 +364,15 @@ void PublicKeyCredential::ToJSON(JSContext* aCx,
mClientExtensionOutputs.mPrf.Value().mEnabled.Value()); mClientExtensionOutputs.mPrf.Value().mEnabled.Value());
} }
} }
if (mClientExtensionOutputs.mLargeBlob.WasPassed()) {
const AuthenticationExtensionsLargeBlobOutputs& src =
mClientExtensionOutputs.mLargeBlob.Value();
AuthenticationExtensionsLargeBlobOutputsJSON& dest =
json.mClientExtensionResults.mLargeBlob.Construct();
if (src.mSupported.WasPassed()) {
dest.mSupported.Construct(src.mSupported.Value());
}
}
json.mType.Assign(u"public-key"_ns); json.mType.Assign(u"public-key"_ns);
if (!ToJSValue(aCx, json, &value)) { if (!ToJSValue(aCx, json, &value)) {
aError.StealExceptionFromJSContext(aCx); aError.StealExceptionFromJSContext(aCx);
@@ -360,6 +397,27 @@ void PublicKeyCredential::ToJSON(JSContext* aCx,
if (mClientExtensionOutputs.mPrf.WasPassed()) { if (mClientExtensionOutputs.mPrf.WasPassed()) {
json.mClientExtensionResults.mPrf.Construct(); json.mClientExtensionResults.mPrf.Construct();
} }
if (mClientExtensionOutputs.mLargeBlob.WasPassed()) {
const AuthenticationExtensionsLargeBlobOutputs& src =
mClientExtensionOutputs.mLargeBlob.Value();
AuthenticationExtensionsLargeBlobOutputsJSON& dest =
json.mClientExtensionResults.mLargeBlob.Construct();
if (src.mWritten.WasPassed()) {
dest.mWritten.Construct(src.mWritten.Value());
}
if (mLargeBlobValue.isSome()) {
nsCString largeBlobB64;
nsresult rv = mozilla::Base64URLEncode(
mLargeBlobValue->Length(), mLargeBlobValue->Elements(),
Base64URLEncodePaddingPolicy::Omit, largeBlobB64);
if (NS_FAILED(rv)) {
aError.ThrowEncodingError(
"could not encode large blob data as urlsafe base64");
return;
}
dest.mBlob.Construct(NS_ConvertUTF8toUTF16(largeBlobB64));
}
}
json.mType.Assign(u"public-key"_ns); json.mType.Assign(u"public-key"_ns);
if (!ToJSValue(aCx, json, &value)) { if (!ToJSValue(aCx, json, &value)) {
aError.StealExceptionFromJSContext(aCx); aError.StealExceptionFromJSContext(aCx);
@@ -390,6 +448,28 @@ void PublicKeyCredential::SetClientExtensionResultHmacSecret(
mClientExtensionOutputs.mHmacCreateSecret.Value() = aHmacCreateSecret; mClientExtensionOutputs.mHmacCreateSecret.Value() = aHmacCreateSecret;
} }
void PublicKeyCredential::InitClientExtensionResultLargeBlob() {
mClientExtensionOutputs.mLargeBlob.Construct();
}
void PublicKeyCredential::SetClientExtensionResultLargeBlobSupported(
bool aLargeBlobSupported) {
mClientExtensionOutputs.mLargeBlob.Value().mSupported.Construct(
aLargeBlobSupported);
}
void PublicKeyCredential::SetClientExtensionResultLargeBlobValue(
const nsTArray<uint8_t>& aLargeBlobValue) {
mLargeBlobValue.emplace(aLargeBlobValue.Length());
mLargeBlobValue->Assign(aLargeBlobValue);
}
void PublicKeyCredential::SetClientExtensionResultLargeBlobWritten(
bool aLargeBlobWritten) {
mClientExtensionOutputs.mLargeBlob.Value().mWritten.Construct(
aLargeBlobWritten);
}
void PublicKeyCredential::InitClientExtensionResultPrf() { void PublicKeyCredential::InitClientExtensionResultPrf() {
mClientExtensionOutputs.mPrf.Construct(); mClientExtensionOutputs.mPrf.Construct();
} }
@@ -474,6 +554,26 @@ bool DecodeAuthenticationExtensionsPRFInputsJSON(
return true; return true;
} }
bool DecodeAuthenticationExtensionsLargeBlobInputsJSON(
GlobalObject& aGlobal,
const AuthenticationExtensionsLargeBlobInputsJSON& aInputsJSON,
AuthenticationExtensionsLargeBlobInputs& aInputs, ErrorResult& aRv) {
if (aInputsJSON.mSupport.WasPassed()) {
aInputs.mSupport.Construct(aInputsJSON.mSupport.Value());
}
if (aInputsJSON.mRead.WasPassed()) {
aInputs.mRead.Construct(aInputsJSON.mRead.Value());
}
if (aInputsJSON.mWrite.WasPassed()) {
if (!Base64DecodeToArrayBuffer(
aGlobal, aInputsJSON.mWrite.Value(),
aInputs.mWrite.Construct().SetAsArrayBuffer(), aRv)) {
return false;
}
}
return true;
}
void PublicKeyCredential::ParseCreationOptionsFromJSON( void PublicKeyCredential::ParseCreationOptionsFromJSON(
GlobalObject& aGlobal, GlobalObject& aGlobal,
const PublicKeyCredentialCreationOptionsJSON& aOptions, const PublicKeyCredentialCreationOptionsJSON& aOptions,
@@ -547,6 +647,18 @@ void PublicKeyCredential::ParseCreationOptionsFromJSON(
aResult.mExtensions.mMinPinLength.Construct( aResult.mExtensions.mMinPinLength.Construct(
aOptions.mExtensions.Value().mMinPinLength.Value()); aOptions.mExtensions.Value().mMinPinLength.Value());
} }
if (aOptions.mExtensions.Value().mLargeBlob.WasPassed()) {
const AuthenticationExtensionsLargeBlobInputsJSON& largeBlobInputsJSON =
aOptions.mExtensions.Value().mLargeBlob.Value();
AuthenticationExtensionsLargeBlobInputs& largeBlobInputs =
aResult.mExtensions.mLargeBlob.Construct();
if (!DecodeAuthenticationExtensionsLargeBlobInputsJSON(
aGlobal, largeBlobInputsJSON, largeBlobInputs, aRv)) {
aRv.ThrowEncodingError(
"could not decode large blob inputs as urlsafe base64");
return;
}
}
if (aOptions.mExtensions.Value().mPrf.WasPassed()) { if (aOptions.mExtensions.Value().mPrf.WasPassed()) {
const AuthenticationExtensionsPRFInputsJSON& prfInputsJSON = const AuthenticationExtensionsPRFInputsJSON& prfInputsJSON =
aOptions.mExtensions.Value().mPrf.Value(); aOptions.mExtensions.Value().mPrf.Value();
@@ -615,6 +727,18 @@ void PublicKeyCredential::ParseRequestOptionsFromJSON(
aResult.mExtensions.mHmacCreateSecret.Construct( aResult.mExtensions.mHmacCreateSecret.Construct(
aOptions.mExtensions.Value().mHmacCreateSecret.Value()); aOptions.mExtensions.Value().mHmacCreateSecret.Value());
} }
if (aOptions.mExtensions.Value().mLargeBlob.WasPassed()) {
const AuthenticationExtensionsLargeBlobInputsJSON& largeBlobInputsJSON =
aOptions.mExtensions.Value().mLargeBlob.Value();
AuthenticationExtensionsLargeBlobInputs& largeBlobInputs =
aResult.mExtensions.mLargeBlob.Construct();
if (!DecodeAuthenticationExtensionsLargeBlobInputsJSON(
aGlobal, largeBlobInputsJSON, largeBlobInputs, aRv)) {
aRv.ThrowEncodingError(
"could not decode large blob inputs as urlsafe base64");
return;
}
}
if (aOptions.mExtensions.Value().mMinPinLength.WasPassed()) { if (aOptions.mExtensions.Value().mMinPinLength.WasPassed()) {
aResult.mExtensions.mMinPinLength.Construct( aResult.mExtensions.mMinPinLength.Construct(
aOptions.mExtensions.Value().mMinPinLength.Value()); aOptions.mExtensions.Value().mMinPinLength.Value());

View File

@@ -71,6 +71,13 @@ class PublicKeyCredential final : public Credential {
void SetClientExtensionResultCredPropsRk(bool aResult); void SetClientExtensionResultCredPropsRk(bool aResult);
void SetClientExtensionResultHmacSecret(bool aHmacCreateSecret); void SetClientExtensionResultHmacSecret(bool aHmacCreateSecret);
void InitClientExtensionResultLargeBlob();
void SetClientExtensionResultLargeBlobSupported(bool aSupported);
void SetClientExtensionResultLargeBlobValue(
const nsTArray<uint8_t>& aLargeBlobValue);
void SetClientExtensionResultLargeBlobWritten(bool aLargeBlobWritten);
void InitClientExtensionResultPrf(); void InitClientExtensionResultPrf();
void SetClientExtensionResultPrfEnabled(bool aPrfEnabled); void SetClientExtensionResultPrfEnabled(bool aPrfEnabled);
void SetClientExtensionResultPrfResultsFirst( void SetClientExtensionResultPrfResultsFirst(
@@ -99,6 +106,7 @@ class PublicKeyCredential final : public Credential {
// We need a reference to JSContext in order to convert nsTArray to // We need a reference to JSContext in order to convert nsTArray to
// BufferSource, so we need to store these outside mClientExtensionOutputs and // BufferSource, so we need to store these outside mClientExtensionOutputs and
// defer the conversion until the GetClientExtensionResults call. // defer the conversion until the GetClientExtensionResults call.
Maybe<nsTArray<uint8_t>> mLargeBlobValue;
Maybe<nsTArray<uint8_t>> mPrfResultsFirst; Maybe<nsTArray<uint8_t>> mPrfResultsFirst;
Maybe<nsTArray<uint8_t>> mPrfResultsSecond; Maybe<nsTArray<uint8_t>> mPrfResultsSecond;
}; };

View File

@@ -216,6 +216,16 @@ WebAuthnRegisterArgs::GetPrivateBrowsing(bool* aPrivateBrowsing) {
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
WebAuthnRegisterArgs::GetLargeBlobSupportRequired(
bool* aLargeBlobSupportRequired) {
if (mLargeBlobSupportRequired.isSome()) {
*aLargeBlobSupportRequired = mLargeBlobSupportRequired.ref();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMPL_ISUPPORTS(WebAuthnSignArgs, nsIWebAuthnSignArgs) NS_IMPL_ISUPPORTS(WebAuthnSignArgs, nsIWebAuthnSignArgs)
NS_IMETHODIMP NS_IMETHODIMP
@@ -437,4 +447,22 @@ WebAuthnSignArgs::GetPrivateBrowsing(bool* aPrivateBrowsing) {
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
WebAuthnSignArgs::GetLargeBlobRead(bool* aLargeBlobRead) {
if (mLargeBlobRead.isSome()) {
*aLargeBlobRead = mLargeBlobRead.ref();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
WebAuthnSignArgs::GetLargeBlobWrite(nsTArray<uint8_t>& aLargeBlobWrite) {
if (mLargeBlobRead.isSome() && mLargeBlobRead.ref() == false) {
aLargeBlobWrite.Assign(mLargeBlobWrite);
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
} // namespace mozilla::dom } // namespace mozilla::dom

View File

@@ -28,6 +28,7 @@ class WebAuthnRegisterArgs final : public nsIWebAuthnRegisterArgs {
mInfo(aInfo), mInfo(aInfo),
mCredProps(false), mCredProps(false),
mHmacCreateSecret(false), mHmacCreateSecret(false),
mLargeBlobSupportRequired(Nothing()),
mMinPinLength(false), mMinPinLength(false),
mPrf(false) { mPrf(false) {
for (const WebAuthnExtension& ext : mInfo.Extensions()) { for (const WebAuthnExtension& ext : mInfo.Extensions()) {
@@ -39,6 +40,10 @@ class WebAuthnRegisterArgs final : public nsIWebAuthnRegisterArgs {
mHmacCreateSecret = mHmacCreateSecret =
ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret(); ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret();
break; break;
case WebAuthnExtension::TWebAuthnExtensionLargeBlob:
mLargeBlobSupportRequired =
ext.get_WebAuthnExtensionLargeBlob().flag();
break;
case WebAuthnExtension::TWebAuthnExtensionMinPinLength: case WebAuthnExtension::TWebAuthnExtensionMinPinLength:
mMinPinLength = mMinPinLength =
ext.get_WebAuthnExtensionMinPinLength().minPinLength(); ext.get_WebAuthnExtensionMinPinLength().minPinLength();
@@ -63,6 +68,7 @@ class WebAuthnRegisterArgs final : public nsIWebAuthnRegisterArgs {
// Flags to indicate whether an extension is being requested. // Flags to indicate whether an extension is being requested.
bool mCredProps; bool mCredProps;
bool mHmacCreateSecret; bool mHmacCreateSecret;
Maybe<bool> mLargeBlobSupportRequired;
bool mMinPinLength; bool mMinPinLength;
bool mPrf; bool mPrf;
}; };
@@ -89,6 +95,16 @@ class WebAuthnSignArgs final : public nsIWebAuthnSignArgs {
break; break;
case WebAuthnExtension::TWebAuthnExtensionMinPinLength: case WebAuthnExtension::TWebAuthnExtensionMinPinLength:
break; break;
case WebAuthnExtension::TWebAuthnExtensionLargeBlob:
if (ext.get_WebAuthnExtensionLargeBlob().flag().isSome()) {
bool read = ext.get_WebAuthnExtensionLargeBlob().flag().ref();
mLargeBlobRead.emplace(read);
if (!read) {
mLargeBlobWrite.AppendElements(
ext.get_WebAuthnExtensionLargeBlob().write());
}
}
break;
case WebAuthnExtension::TWebAuthnExtensionPrf: case WebAuthnExtension::TWebAuthnExtensionPrf:
mPrf = ext.get_WebAuthnExtensionPrf().eval().isSome() || mPrf = ext.get_WebAuthnExtensionPrf().eval().isSome() ||
ext.get_WebAuthnExtensionPrf().evalByCredentialMaybe(); ext.get_WebAuthnExtensionPrf().evalByCredentialMaybe();
@@ -106,6 +122,8 @@ class WebAuthnSignArgs final : public nsIWebAuthnSignArgs {
const nsCString mClientDataJSON; const nsCString mClientDataJSON;
const bool mPrivateBrowsing; const bool mPrivateBrowsing;
const WebAuthnGetAssertionInfo mInfo; const WebAuthnGetAssertionInfo mInfo;
Maybe<bool> mLargeBlobRead;
nsTArray<uint8_t> mLargeBlobWrite;
bool mPrf; bool mPrf;
}; };

View File

@@ -271,6 +271,24 @@ already_AddRefed<Promise> WebAuthnHandler::MakeCredential(
} }
} }
// <https://w3c.github.io/webauthn/#sctn-large-blob-extension>
if (aOptions.mExtensions.mLargeBlob.WasPassed()) {
if (aOptions.mExtensions.mLargeBlob.Value().mRead.WasPassed() ||
aOptions.mExtensions.mLargeBlob.Value().mWrite.WasPassed()) {
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return promise.forget();
}
Maybe<bool> supportRequired;
const Optional<nsString>& largeBlobSupport =
aOptions.mExtensions.mLargeBlob.Value().mSupport;
if (largeBlobSupport.WasPassed()) {
supportRequired.emplace(largeBlobSupport.Value().Equals(u"required"_ns));
}
nsTArray<uint8_t> write; // unused
extensions.AppendElement(
WebAuthnExtensionLargeBlob(supportRequired, write));
}
// <https://w3c.github.io/webauthn/#prf-extension> // <https://w3c.github.io/webauthn/#prf-extension>
if (aOptions.mExtensions.mPrf.WasPassed()) { if (aOptions.mExtensions.mPrf.WasPassed()) {
const AuthenticationExtensionsPRFInputs& prf = const AuthenticationExtensionsPRFInputs& prf =
@@ -533,6 +551,30 @@ already_AddRefed<Promise> WebAuthnHandler::GetAssertion(
maybeAppId.emplace(std::move(appId)); maybeAppId.emplace(std::move(appId));
} }
// <https://w3c.github.io/webauthn/#sctn-large-blob-extension>
if (aOptions.mExtensions.mLargeBlob.WasPassed()) {
const AuthenticationExtensionsLargeBlobInputs& extLargeBlob =
aOptions.mExtensions.mLargeBlob.Value();
if (extLargeBlob.mSupport.WasPassed() ||
(extLargeBlob.mRead.WasPassed() && extLargeBlob.mWrite.WasPassed()) ||
(extLargeBlob.mWrite.WasPassed() &&
aOptions.mAllowCredentials.Length() != 1)) {
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return promise.forget();
}
Maybe<bool> read = Nothing();
if (extLargeBlob.mRead.WasPassed() && extLargeBlob.mRead.Value()) {
read.emplace(true);
}
CryptoBuffer write;
if (extLargeBlob.mWrite.WasPassed()) {
read.emplace(false);
write.Assign(extLargeBlob.mWrite.Value());
}
extensions.AppendElement(WebAuthnExtensionLargeBlob(read, write));
}
// <https://w3c.github.io/webauthn/#prf-extension> // <https://w3c.github.io/webauthn/#prf-extension>
if (aOptions.mExtensions.mPrf.WasPassed()) { if (aOptions.mExtensions.mPrf.WasPassed()) {
const AuthenticationExtensionsPRFInputs& prf = const AuthenticationExtensionsPRFInputs& prf =
@@ -753,6 +795,12 @@ void WebAuthnHandler::FinishMakeCredential(
ext.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret(); ext.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret();
credential->SetClientExtensionResultHmacSecret(hmacCreateSecret); credential->SetClientExtensionResultHmacSecret(hmacCreateSecret);
} }
if (ext.type() ==
WebAuthnExtensionResult::TWebAuthnExtensionResultLargeBlob) {
credential->InitClientExtensionResultLargeBlob();
credential->SetClientExtensionResultLargeBlobSupported(
ext.get_WebAuthnExtensionResultLargeBlob().flag());
}
if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultPrf) { if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultPrf) {
credential->InitClientExtensionResultPrf(); credential->InitClientExtensionResultPrf();
const Maybe<bool> prfEnabled = const Maybe<bool> prfEnabled =
@@ -823,6 +871,24 @@ void WebAuthnHandler::FinishGetAssertion(
bool appid = ext.get_WebAuthnExtensionResultAppId().AppId(); bool appid = ext.get_WebAuthnExtensionResultAppId().AppId();
credential->SetClientExtensionResultAppId(appid); credential->SetClientExtensionResultAppId(appid);
} }
if (ext.type() ==
WebAuthnExtensionResult::TWebAuthnExtensionResultLargeBlob) {
if (ext.get_WebAuthnExtensionResultLargeBlob().flag() &&
ext.get_WebAuthnExtensionResultLargeBlob().written()) {
// Signal a read failure by including an empty largeBlob extension.
credential->InitClientExtensionResultLargeBlob();
} else if (ext.get_WebAuthnExtensionResultLargeBlob().flag()) {
const nsTArray<uint8_t>& largeBlobValue =
ext.get_WebAuthnExtensionResultLargeBlob().blob();
credential->InitClientExtensionResultLargeBlob();
credential->SetClientExtensionResultLargeBlobValue(largeBlobValue);
} else {
bool largeBlobWritten =
ext.get_WebAuthnExtensionResultLargeBlob().written();
credential->InitClientExtensionResultLargeBlob();
credential->SetClientExtensionResultLargeBlobWritten(largeBlobWritten);
}
}
if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultPrf) { if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultPrf) {
credential->InitClientExtensionResultPrf(); credential->InitClientExtensionResultPrf();
Maybe<WebAuthnExtensionPrfValues> prfResults = Maybe<WebAuthnExtensionPrfValues> prfResults =

View File

@@ -90,6 +90,15 @@ WebAuthnRegisterResult::SetCredPropsRk(bool aCredPropsRk) {
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
WebAuthnRegisterResult::GetLargeBlobSupported(bool* aLargeBlobSupported) {
if (mLargeBlobSupported.isSome()) {
*aLargeBlobSupported = mLargeBlobSupported.ref();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP NS_IMETHODIMP
WebAuthnRegisterResult::GetPrfEnabled(bool* aPrfEnabled) { WebAuthnRegisterResult::GetPrfEnabled(bool* aPrfEnabled) {
if (mPrf.isSome()) { if (mPrf.isSome()) {
@@ -218,6 +227,24 @@ WebAuthnSignResult::SetUsedAppId(bool aUsedAppId) {
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
WebAuthnSignResult::GetLargeBlobValue(nsTArray<uint8_t>& aLargeBlobValue) {
if (mLargeBlobValue.isSome()) {
aLargeBlobValue.Assign(*mLargeBlobValue);
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
WebAuthnSignResult::GetLargeBlobWritten(bool* aLargeBlobWritten) {
if (mLargeBlobWritten.isSome()) {
*aLargeBlobWritten = mLargeBlobWritten.ref();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP NS_IMETHODIMP
WebAuthnSignResult::GetPrfMaybe(bool* aPrfMaybe) { WebAuthnSignResult::GetPrfMaybe(bool* aPrfMaybe) {
*aPrfMaybe = mPrfFirst.isSome(); *aPrfMaybe = mPrfFirst.isSome();

View File

@@ -33,10 +33,12 @@ class WebAuthnRegisterResult final : public nsIWebAuthnRegisterResult {
const Maybe<nsCString>& aClientDataJSON, const Maybe<nsCString>& aClientDataJSON,
const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aCredentialId,
const nsTArray<nsString>& aTransports, const nsTArray<nsString>& aTransports,
const Maybe<nsString>& aAuthenticatorAttachment) const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<bool>& aLargeBlobSupported)
: mClientDataJSON(aClientDataJSON), : mClientDataJSON(aClientDataJSON),
mCredPropsRk(Nothing()), mCredPropsRk(Nothing()),
mAuthenticatorAttachment(aAuthenticatorAttachment) { mAuthenticatorAttachment(aAuthenticatorAttachment),
mLargeBlobSupported(aLargeBlobSupported) {
mAttestationObject.AppendElements(aAttestationObject); mAttestationObject.AppendElements(aAttestationObject);
mCredentialId.AppendElements(aCredentialId); mCredentialId.AppendElements(aCredentialId);
mTransports.AppendElements(aTransports); mTransports.AppendElements(aTransports);
@@ -134,6 +136,12 @@ class WebAuthnRegisterResult final : public nsIWebAuthnRegisterResult {
} }
} }
if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4) {
if (aResponse->bLargeBlobSupported) {
mLargeBlobSupported = Some(true);
}
}
if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5) { if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5) {
if (aResponse->bPrfEnabled) { if (aResponse->bPrfEnabled) {
mPrf = Some(true); mPrf = Some(true);
@@ -151,8 +159,9 @@ class WebAuthnRegisterResult final : public nsIWebAuthnRegisterResult {
Maybe<nsCString> mClientDataJSON; Maybe<nsCString> mClientDataJSON;
Maybe<bool> mCredPropsRk; Maybe<bool> mCredPropsRk;
Maybe<bool> mHmacCreateSecret; Maybe<bool> mHmacCreateSecret;
Maybe<bool> mPrf;
Maybe<nsString> mAuthenticatorAttachment; Maybe<nsString> mAuthenticatorAttachment;
Maybe<bool> mLargeBlobSupported;
Maybe<bool> mPrf;
}; };
class WebAuthnSignResult final : public nsIWebAuthnSignResult { class WebAuthnSignResult final : public nsIWebAuthnSignResult {
@@ -165,13 +174,20 @@ class WebAuthnSignResult final : public nsIWebAuthnSignResult {
const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aCredentialId,
const nsTArray<uint8_t>& aSignature, const nsTArray<uint8_t>& aSignature,
const nsTArray<uint8_t>& aUserHandle, const nsTArray<uint8_t>& aUserHandle,
const Maybe<nsString>& aAuthenticatorAttachment) const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<nsTArray<uint8_t>>& aLargeBlobValue,
const Maybe<bool>& aLargeBlobWritten)
: mClientDataJSON(aClientDataJSON), : mClientDataJSON(aClientDataJSON),
mAuthenticatorAttachment(aAuthenticatorAttachment) { mAuthenticatorAttachment(aAuthenticatorAttachment),
mLargeBlobWritten(aLargeBlobWritten) {
mAuthenticatorData.AppendElements(aAuthenticatorData); mAuthenticatorData.AppendElements(aAuthenticatorData);
mCredentialId.AppendElements(aCredentialId); mCredentialId.AppendElements(aCredentialId);
mSignature.AppendElements(aSignature); mSignature.AppendElements(aSignature);
mUserHandle.AppendElements(aUserHandle); mUserHandle.AppendElements(aUserHandle);
if (aLargeBlobValue.isSome()) {
mLargeBlobValue.emplace(aLargeBlobValue->Length());
mLargeBlobValue->Assign(aLargeBlobValue.ref());
}
} }
#ifdef MOZ_WIDGET_ANDROID #ifdef MOZ_WIDGET_ANDROID
@@ -205,7 +221,8 @@ class WebAuthnSignResult final : public nsIWebAuthnSignResult {
#endif #endif
#ifdef XP_WIN #ifdef XP_WIN
WebAuthnSignResult(nsCString& aClientDataJSON, PCWEBAUTHN_ASSERTION aResponse) WebAuthnSignResult(nsCString& aClientDataJSON, DWORD aCredLargeBlobOperation,
PCWEBAUTHN_ASSERTION aResponse)
: mClientDataJSON(Some(aClientDataJSON)) { : mClientDataJSON(Some(aClientDataJSON)) {
mSignature.AppendElements(aResponse->pbSignature, aResponse->cbSignature); mSignature.AppendElements(aResponse->pbSignature, aResponse->cbSignature);
@@ -218,6 +235,26 @@ class WebAuthnSignResult final : public nsIWebAuthnSignResult {
aResponse->cbAuthenticatorData); aResponse->cbAuthenticatorData);
mAuthenticatorAttachment = Nothing(); // not available mAuthenticatorAttachment = Nothing(); // not available
if (aCredLargeBlobOperation == WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET) {
if (aResponse->dwVersion >= WEBAUTHN_ASSERTION_VERSION_2 &&
aResponse->dwCredLargeBlobStatus ==
WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS) {
mLargeBlobValue.emplace();
mLargeBlobValue->AppendElements(aResponse->pbCredLargeBlob,
aResponse->cbCredLargeBlob);
}
} else if (aCredLargeBlobOperation ==
WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET) {
if (aResponse->dwVersion >= WEBAUTHN_ASSERTION_VERSION_2 &&
aResponse->dwCredLargeBlobStatus ==
WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS) {
mLargeBlobWritten.emplace(true);
} else {
mLargeBlobWritten.emplace(false);
}
}
if (aResponse->dwVersion >= WEBAUTHN_ASSERTION_VERSION_3) { if (aResponse->dwVersion >= WEBAUTHN_ASSERTION_VERSION_3) {
if (aResponse->pHmacSecret) { if (aResponse->pHmacSecret) {
if (aResponse->pHmacSecret->cbFirst > 0) { if (aResponse->pHmacSecret->cbFirst > 0) {
@@ -245,6 +282,8 @@ class WebAuthnSignResult final : public nsIWebAuthnSignResult {
nsTArray<uint8_t> mUserHandle; nsTArray<uint8_t> mUserHandle;
Maybe<nsString> mAuthenticatorAttachment; Maybe<nsString> mAuthenticatorAttachment;
Maybe<bool> mUsedAppId; Maybe<bool> mUsedAppId;
Maybe<nsTArray<uint8_t>> mLargeBlobValue;
Maybe<bool> mLargeBlobWritten;
Maybe<nsTArray<uint8_t>> mPrfFirst; Maybe<nsTArray<uint8_t>> mPrfFirst;
Maybe<nsTArray<uint8_t>> mPrfSecond; Maybe<nsTArray<uint8_t>> mPrfSecond;
}; };

View File

@@ -73,6 +73,18 @@ nsresult AssembleClientData(WindowGlobalParent* aManager,
return NS_OK; return NS_OK;
} }
bool GetAssertionRequestIncludesLargeBlobRead(
const WebAuthnGetAssertionInfo& aInfo) {
for (const WebAuthnExtension& ext : aInfo.Extensions()) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionLargeBlob) {
if (ext.get_WebAuthnExtensionLargeBlob().flag().isSome()) {
return ext.get_WebAuthnExtensionLargeBlob().flag().ref();
}
}
}
return false;
}
void WebAuthnTransactionParent::CompleteTransaction() { void WebAuthnTransactionParent::CompleteTransaction() {
if (mTransactionId.isSome()) { if (mTransactionId.isSome()) {
if (mRegisterPromiseRequest.Exists()) { if (mRegisterPromiseRequest.Exists()) {
@@ -227,6 +239,17 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
WebAuthnExtensionResultHmacSecret(hmacCreateSecret)); WebAuthnExtensionResultHmacSecret(hmacCreateSecret));
} }
bool largeBlobSupported;
rv = registerResult->GetLargeBlobSupported(&largeBlobSupported);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
return;
}
nsTArray<uint8_t> blob; // unused
extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
largeBlobSupported, blob, false));
}
{ {
Maybe<bool> prfEnabledMaybe = Nothing(); Maybe<bool> prfEnabledMaybe = Nothing();
Maybe<WebAuthnExtensionPrfValues> prfResults = Nothing(); Maybe<WebAuthnExtensionPrfValues> prfResults = Nothing();
@@ -342,6 +365,9 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
return IPC_OK(); return IPC_OK();
} }
bool requestIncludesLargeBlobRead =
GetAssertionRequestIncludesLargeBlobRead(aTransactionInfo);
RefPtr<WebAuthnSignPromiseHolder> promiseHolder = RefPtr<WebAuthnSignPromiseHolder> promiseHolder =
new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget()); new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
@@ -350,7 +376,7 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
->Then( ->Then(
GetCurrentSerialEventTarget(), __func__, GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}, inputClientData = clientDataJSON, [self = RefPtr{this}, inputClientData = clientDataJSON,
resolver = std::move(aResolver)]( requestIncludesLargeBlobRead, resolver = std::move(aResolver)](
const WebAuthnSignPromise::ResolveOrRejectValue& aValue) { const WebAuthnSignPromise::ResolveOrRejectValue& aValue) {
self->CompleteTransaction(); self->CompleteTransaction();
@@ -414,6 +440,32 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId)); extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
} }
nsTArray<uint8_t> largeBlobValue;
rv = signResult->GetLargeBlobValue(largeBlobValue);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
return;
}
extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
true, largeBlobValue, false));
} else if (requestIncludesLargeBlobRead) {
// Signal a read error by setting both flags.
extensions.AppendElement(
WebAuthnExtensionResultLargeBlob(true, largeBlobValue, true));
} else {
// Read and write operations are mutually exclusive, so we only
// check for a write result if the read result is not available.
bool largeBlobWritten;
rv = signResult->GetLargeBlobWritten(&largeBlobWritten);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
return;
}
extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
false, largeBlobValue, largeBlobWritten));
}
}
{ {
Maybe<WebAuthnExtensionPrfValues> prfResults; Maybe<WebAuthnExtensionPrfValues> prfResults;
bool prfMaybe = false; bool prfMaybe = false;

View File

@@ -308,6 +308,23 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
// AttestationConveyance // AttestationConveyance
DWORD winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY; DWORD winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY;
// Large Blob
DWORD largeBlobSupport = WEBAUTHN_LARGE_BLOB_SUPPORT_NONE;
bool largeBlobSupportRequired;
nsresult rv =
aArgs->GetLargeBlobSupportRequired(&largeBlobSupportRequired);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
aPromise->Reject(rv);
return;
}
if (largeBlobSupportRequired) {
largeBlobSupport = WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED;
} else {
largeBlobSupport = WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED;
}
}
// Prf // Prf
BOOL winEnablePrf = FALSE; BOOL winEnablePrf = FALSE;
@@ -366,8 +383,7 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
// Attachment // Attachment
DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY; DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
nsString authenticatorAttachment; nsString authenticatorAttachment;
nsresult rv = rv = aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
if (rv != NS_ERROR_NOT_AVAILABLE) { if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aPromise->Reject(rv); aPromise->Reject(rv);
@@ -553,7 +569,7 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
&cancellationId, // CancellationId &cancellationId, // CancellationId
pExcludeCredentialList, pExcludeCredentialList,
WEBAUTHN_ENTERPRISE_ATTESTATION_NONE, WEBAUTHN_ENTERPRISE_ATTESTATION_NONE,
WEBAUTHN_LARGE_BLOB_SUPPORT_NONE, largeBlobSupport, // LargeBlobSupport
winPreferResidentKey, // PreferResidentKey winPreferResidentKey, // PreferResidentKey
winPrivateBrowsing, // BrowserInPrivateMode winPrivateBrowsing, // BrowserInPrivateMode
winEnablePrf, // EnablePrf winEnablePrf, // EnablePrf
@@ -739,6 +755,32 @@ void WinWebAuthnService::DoGetAssertion(
winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY; winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
} }
// Large Blob
DWORD credLargeBlobOperation = WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE;
DWORD credLargeBlobSize = 0;
PBYTE credLargeBlob = nullptr;
nsTArray<uint8_t> largeBlobWrite;
bool largeBlobRead;
rv = aArgs->GetLargeBlobRead(&largeBlobRead);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
aPromise->Reject(rv);
return;
}
if (largeBlobRead) {
credLargeBlobOperation = WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET;
} else {
rv = aArgs->GetLargeBlobWrite(largeBlobWrite);
if (rv != NS_ERROR_NOT_AVAILABLE && NS_FAILED(rv)) {
aPromise->Reject(rv);
return;
}
credLargeBlobOperation = WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET;
credLargeBlobSize = largeBlobWrite.Length();
credLargeBlob = largeBlobWrite.Elements();
}
}
// PRF inputs // PRF inputs
WEBAUTHN_HMAC_SECRET_SALT_VALUES* pPrfInputs = nullptr; WEBAUTHN_HMAC_SECRET_SALT_VALUES* pPrfInputs = nullptr;
WEBAUTHN_HMAC_SECRET_SALT_VALUES prfInputs = {0}; WEBAUTHN_HMAC_SECRET_SALT_VALUES prfInputs = {0};
@@ -906,9 +948,9 @@ void WinWebAuthnService::DoGetAssertion(
pbAppIdUsed, pbAppIdUsed,
&aCancellationId, // CancellationId &aCancellationId, // CancellationId
pAllowCredentialList, pAllowCredentialList,
WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE, credLargeBlobOperation, // CredLargeBlobOperation
0, // Size of CredLargeBlob credLargeBlobSize, // Size of CredLargeBlob
NULL, // CredLargeBlob credLargeBlob, // CredLargeBlob
pPrfInputs, // HmacSecretSaltValues pPrfInputs, // HmacSecretSaltValues
winPrivateBrowsing, // BrowserInPrivateMode winPrivateBrowsing, // BrowserInPrivateMode
NULL, // LinkedDevice NULL, // LinkedDevice
@@ -928,8 +970,8 @@ void WinWebAuthnService::DoGetAssertion(
&pWebAuthNAssertion); &pWebAuthNAssertion);
if (hr == S_OK) { if (hr == S_OK) {
RefPtr<WebAuthnSignResult> result = RefPtr<WebAuthnSignResult> result = new WebAuthnSignResult(
new WebAuthnSignResult(clientDataJSON, pWebAuthNAssertion); clientDataJSON, credLargeBlobOperation, pWebAuthNAssertion);
gWinWebauthnFreeAssertion(pWebAuthNAssertion); gWinWebauthnFreeAssertion(pWebAuthNAssertion);
if (winAppIdentifier != nullptr) { if (winAppIdentifier != nullptr) {
// The gWinWebauthnGetAssertion call modified bAppIdUsed through // The gWinWebauthnGetAssertion call modified bAppIdUsed through

View File

@@ -216,6 +216,11 @@ impl WebAuthnRegisterResult {
Ok(hmac_create_secret) Ok(hmac_create_secret)
} }
xpcom_method!(get_large_blob_supported => GetLargeBlobSupported() -> bool);
fn get_large_blob_supported(&self) -> Result<bool, nsresult> {
Err(NS_ERROR_NOT_AVAILABLE)
}
xpcom_method!(get_prf_enabled => GetPrfEnabled() -> bool); xpcom_method!(get_prf_enabled => GetPrfEnabled() -> bool);
fn get_prf_enabled(&self) -> Result<bool, nsresult> { fn get_prf_enabled(&self) -> Result<bool, nsresult> {
match self.result.borrow().extensions.prf { match self.result.borrow().extensions.prf {
@@ -412,6 +417,16 @@ impl WebAuthnSignResult {
Err(NS_ERROR_NOT_IMPLEMENTED) Err(NS_ERROR_NOT_IMPLEMENTED)
} }
xpcom_method!(get_large_blob_value => GetLargeBlobValue() -> ThinVec<u8>);
fn get_large_blob_value(&self) -> Result<ThinVec<u8>, nsresult> {
Err(NS_ERROR_NOT_AVAILABLE)
}
xpcom_method!(get_large_blob_written => GetLargeBlobWritten() -> bool);
fn get_large_blob_written(&self) -> Result<bool, nsresult> {
Err(NS_ERROR_NOT_AVAILABLE)
}
xpcom_method!(get_prf_maybe => GetPrfMaybe() -> bool); xpcom_method!(get_prf_maybe => GetPrfMaybe() -> bool);
/// Return true if a PRF output is present, even if all attributes are absent. /// Return true if a PRF output is present, even if all attributes are absent.
fn get_prf_maybe(&self) -> Result<bool, nsresult> { fn get_prf_maybe(&self) -> Result<bool, nsresult> {

View File

@@ -46,6 +46,7 @@ interface nsIWebAuthnRegisterArgs : nsISupports {
[must_use] readonly attribute boolean prf; [must_use] readonly attribute boolean prf;
[must_use] readonly attribute Array<octet> prfEvalFirst; [must_use] readonly attribute Array<octet> prfEvalFirst;
[must_use] readonly attribute Array<octet> prfEvalSecond; [must_use] readonly attribute Array<octet> prfEvalSecond;
[must_use] readonly attribute boolean largeBlobSupportRequired;
// Options. // Options.
readonly attribute AString residentKey; readonly attribute AString residentKey;
@@ -97,6 +98,8 @@ interface nsIWebAuthnSignArgs : nsISupports {
[must_use] readonly attribute Array<Array<octet> > prfEvalByCredentialEvalFirst; [must_use] readonly attribute Array<Array<octet> > prfEvalByCredentialEvalFirst;
[must_use] readonly attribute Array<boolean> prfEvalByCredentialEvalSecondMaybe; [must_use] readonly attribute Array<boolean> prfEvalByCredentialEvalSecondMaybe;
[must_use] readonly attribute Array<Array<octet> > prfEvalByCredentialEvalSecond; [must_use] readonly attribute Array<Array<octet> > prfEvalByCredentialEvalSecond;
[must_use] readonly attribute boolean largeBlobRead;
[must_use] readonly attribute Array<octet> largeBlobWrite;
// Options // Options
[must_use] readonly attribute AString userVerification; [must_use] readonly attribute AString userVerification;

View File

@@ -27,6 +27,8 @@ interface nsIWebAuthnRegisterResult : nsISupports {
[must_use] attribute boolean credPropsRk; [must_use] attribute boolean credPropsRk;
readonly attribute boolean largeBlobSupported;
readonly attribute boolean prfEnabled; readonly attribute boolean prfEnabled;
readonly attribute Array<octet> prfResultsFirst; readonly attribute Array<octet> prfResultsFirst;
readonly attribute Array<octet> prfResultsSecond; readonly attribute Array<octet> prfResultsSecond;
@@ -65,6 +67,9 @@ interface nsIWebAuthnSignResult : nsISupports {
// appId field of AuthenticationExtensionsClientOutputs (Optional) // appId field of AuthenticationExtensionsClientOutputs (Optional)
[must_use] attribute boolean usedAppId; [must_use] attribute boolean usedAppId;
readonly attribute Array<octet> largeBlobValue;
readonly attribute boolean largeBlobWritten;
readonly attribute boolean prfMaybe; // prf output can be present but empty readonly attribute boolean prfMaybe; // prf output can be present but empty
readonly attribute Array<octet> prfResultsFirst; readonly attribute Array<octet> prfResultsFirst;
readonly attribute Array<octet> prfResultsSecond; readonly attribute Array<octet> prfResultsSecond;

View File

@@ -319,6 +319,47 @@ partial dictionary AuthenticationExtensionsClientOutputsJSON {
boolean hmacCreateSecret; boolean hmacCreateSecret;
}; };
// largeBlob
// <https://w3c.github.io/webauthn/#sctn-large-blob-extension>
partial dictionary AuthenticationExtensionsClientInputs {
AuthenticationExtensionsLargeBlobInputs largeBlob;
};
partial dictionary AuthenticationExtensionsClientInputsJSON {
AuthenticationExtensionsLargeBlobInputsJSON largeBlob;
};
dictionary AuthenticationExtensionsLargeBlobInputs {
DOMString support;
boolean read;
BufferSource write;
};
dictionary AuthenticationExtensionsLargeBlobInputsJSON {
DOMString support;
boolean read;
Base64URLString write;
};
partial dictionary AuthenticationExtensionsClientOutputs {
AuthenticationExtensionsLargeBlobOutputs largeBlob;
};
partial dictionary AuthenticationExtensionsClientOutputsJSON {
AuthenticationExtensionsLargeBlobOutputsJSON largeBlob;
};
dictionary AuthenticationExtensionsLargeBlobOutputs {
boolean supported;
ArrayBuffer blob;
boolean written;
};
dictionary AuthenticationExtensionsLargeBlobOutputsJSON {
boolean supported;
Base64URLString blob;
boolean written;
};
// minPinLength // minPinLength
// <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-minpinlength-extension> // <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-minpinlength-extension>

View File

@@ -1,10 +1,4 @@
[createcredential-large-blob-not-supported.https.html] [createcredential-large-blob-not-supported.https.html]
[navigator.credentials.create() with largeBlob.write set]
expected: FAIL
[navigator.credentials.create() with largeBlob.read set]
expected: FAIL
[navigator.credentials.create() with largeBlob.support set to preferred and not supported by authenticator] [navigator.credentials.create() with largeBlob.support set to preferred and not supported by authenticator]
expected: FAIL expected: FAIL

View File

@@ -1,12 +1,3 @@
[getcredential-large-blob-not-supported.https.html] [getcredential-large-blob-not-supported.https.html]
[navigator.credentials.get() with largeBlob.support set]
expected: FAIL
[navigator.credentials.get() with largeBlob.read and largeBlob.write set]
expected: FAIL
[navigator.credentials.get() with largeBlob.read set without authenticator support]
expected: FAIL
[navigator.credentials.get() with largeBlob.write set without authenticator support] [navigator.credentials.get() with largeBlob.write set without authenticator support]
expected: FAIL expected: FAIL

View File

@@ -1,6 +1,3 @@
[getcredential-large-blob-supported.https.html] [getcredential-large-blob-supported.https.html]
[navigator.credentials.get() with largeBlob.read set with no blob on authenticator]
expected: FAIL
[navigator.credentials.get() read and write blob] [navigator.credentials.get() read and write blob]
expected: FAIL expected: FAIL