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,
const nsTArray<uint8_t>& aCredentialId,
const nsTArray<nsString>& aTransports,
const Maybe<nsString>& aAuthenticatorAttachment);
const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<bool>& aLargeBlobSupported);
void FinishGetAssertion(const nsTArray<uint8_t>& aCredentialId,
const nsTArray<uint8_t>& aSignature,
const nsTArray<uint8_t>& aAuthenticatorData,
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 AbortTransaction(nsresult aError);
@@ -314,18 +317,17 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
nsTArray<nsString> transports;
mozilla::Maybe<nsString> authenticatorAttachment;
mozilla::Maybe<bool> largeBlobSupported;
if ([credential isKindOfClass:
[ASAuthorizationPlatformPublicKeyCredentialRegistration
class]]) {
transports.AppendElement(u"hybrid"_ns);
transports.AppendElement(u"internal"_ns);
#if defined(MAC_OS_VERSION_13_5) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
ASAuthorizationPlatformPublicKeyCredentialRegistration*
platformCredential =
(ASAuthorizationPlatformPublicKeyCredentialRegistration*)
credential;
if (__builtin_available(macos 13.5, *)) {
ASAuthorizationPlatformPublicKeyCredentialRegistration*
platformCredential =
(ASAuthorizationPlatformPublicKeyCredentialRegistration*)
credential;
switch (platformCredential.attachment) {
case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
authenticatorAttachment.emplace(u"cross-platform"_ns);
@@ -337,7 +339,11 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
break;
}
}
#endif
if (__builtin_available(macos 14.0, *)) {
if (platformCredential.largeBlob) {
largeBlobSupported.emplace(platformCredential.largeBlob.isSupported);
}
}
} else {
// 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
@@ -348,7 +354,8 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
authenticatorAttachment.emplace(u"cross-platform"_ns);
}
mCallback->FinishMakeCredential(rawAttestationObject, credentialId,
transports, authenticatorAttachment);
transports, authenticatorAttachment,
largeBlobSupported);
} else if ([authorization.credential
conformsToProtocol:
@protocol(ASAuthorizationPublicKeyCredentialAssertion)]) {
@@ -364,16 +371,14 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
NSDataToArray(credential.rawAuthenticatorData));
nsTArray<uint8_t> userHandle(NSDataToArray(credential.userID));
mozilla::Maybe<nsString> authenticatorAttachment;
mozilla::Maybe<nsTArray<uint8_t>> largeBlobValue;
mozilla::Maybe<bool> largeBlobWritten;
if ([credential
isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion
class]]) {
#if defined(MAC_OS_VERSION_13_5) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
ASAuthorizationPlatformPublicKeyCredentialAssertion* platformCredential =
(ASAuthorizationPlatformPublicKeyCredentialAssertion*)credential;
if (__builtin_available(macos 13.5, *)) {
ASAuthorizationPlatformPublicKeyCredentialAssertion*
platformCredential =
(ASAuthorizationPlatformPublicKeyCredentialAssertion*)
credential;
switch (platformCredential.attachment) {
case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
authenticatorAttachment.emplace(u"cross-platform"_ns);
@@ -385,12 +390,22 @@ nsTArray<uint8_t> NSDataToArray(NSData* data) {
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 {
authenticatorAttachment.emplace(u"cross-platform"_ns);
}
mCallback->FinishGetAssertion(credentialId, signature, rawAuthenticatorData,
userHandle, authenticatorAttachment);
userHandle, authenticatorAttachment,
largeBlobValue, largeBlobWritten);
} else {
MOZ_LOG(
gMacOSWebAuthnServiceLog, mozilla::LogLevel::Error,
@@ -722,6 +737,27 @@ MacOSWebAuthnService::MakeCredential(uint64_t aTransactionId,
crossPlatformRegistrationRequest.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;
nsresult rv = aArgs->GetClientDataHash(clientDataHash);
if (NS_FAILED(rv)) {
@@ -800,7 +836,8 @@ void MacOSWebAuthnService::FinishMakeCredential(
const nsTArray<uint8_t>& aRawAttestationObject,
const nsTArray<uint8_t>& aCredentialId,
const nsTArray<nsString>& aTransports,
const Maybe<nsString>& aAuthenticatorAttachment) {
const Maybe<nsString>& aAuthenticatorAttachment,
const Maybe<bool>& aLargeBlobSupported) {
MOZ_ASSERT(NS_IsMainThread());
if (!mRegisterPromise) {
return;
@@ -808,7 +845,7 @@ void MacOSWebAuthnService::FinishMakeCredential(
RefPtr<WebAuthnRegisterResult> result(new WebAuthnRegisterResult(
aRawAttestationObject, Nothing(), aCredentialId, aTransports,
aAuthenticatorAttachment));
aAuthenticatorAttachment, aLargeBlobSupported));
Unused << mRegisterPromise->Resolve(result);
mRegisterPromise = nullptr;
}
@@ -1037,6 +1074,47 @@ void MacOSWebAuthnService::DoGetAssertion(
crossPlatformAssertionRequest.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;
nsresult rv = aArgs->GetClientDataHash(clientDataHash);
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>& aAuthenticatorData,
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());
if (!mSignPromise) {
return;
@@ -1065,7 +1145,7 @@ void MacOSWebAuthnService::FinishGetAssertion(
RefPtr<WebAuthnSignResult> result(new WebAuthnSignResult(
aAuthenticatorData, Nothing(), aCredentialId, aSignature, aUserHandle,
aAuthenticatorAttachment));
aAuthenticatorAttachment, aLargeBlobValue, aLargeBlobWritten));
Unused << mSignPromise->Resolve(result);
mSignPromise = nullptr;
}

View File

@@ -40,6 +40,14 @@ struct WebAuthnExtensionHmacSecret {
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 {
bool minPinLength;
};
@@ -64,6 +72,7 @@ struct WebAuthnExtensionPrfEvalByCredentialEntry {
union WebAuthnExtension {
WebAuthnExtensionCredProps;
WebAuthnExtensionHmacSecret;
WebAuthnExtensionLargeBlob;
WebAuthnExtensionMinPinLength;
WebAuthnExtensionPrf;
};
@@ -80,6 +89,13 @@ struct WebAuthnExtensionResultHmacSecret {
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 {
bool? enabled;
WebAuthnExtensionPrfValues? results;
@@ -89,6 +105,7 @@ union WebAuthnExtensionResult {
WebAuthnExtensionResultAppId;
WebAuthnExtensionResultCredProps;
WebAuthnExtensionResultHmacSecret;
WebAuthnExtensionResultLargeBlob;
WebAuthnExtensionResultPrf;
};

View File

@@ -197,6 +197,14 @@ already_AddRefed<Promise> PublicKeyCredential::GetClientCapabilities(
entry->mKey = u"extension:hmacCreateSecret"_ns;
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->mKey = u"extension:minPinLength"_ns;
entry->mValue = true;
@@ -282,6 +290,26 @@ void PublicKeyCredential::GetClientExtensionResults(
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()) {
AuthenticationExtensionsPRFOutputs& dest = aResult.mPrf.Construct();
@@ -336,6 +364,15 @@ void PublicKeyCredential::ToJSON(JSContext* aCx,
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);
if (!ToJSValue(aCx, json, &value)) {
aError.StealExceptionFromJSContext(aCx);
@@ -360,6 +397,27 @@ void PublicKeyCredential::ToJSON(JSContext* aCx,
if (mClientExtensionOutputs.mPrf.WasPassed()) {
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);
if (!ToJSValue(aCx, json, &value)) {
aError.StealExceptionFromJSContext(aCx);
@@ -390,6 +448,28 @@ void PublicKeyCredential::SetClientExtensionResultHmacSecret(
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() {
mClientExtensionOutputs.mPrf.Construct();
}
@@ -474,6 +554,26 @@ bool DecodeAuthenticationExtensionsPRFInputsJSON(
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(
GlobalObject& aGlobal,
const PublicKeyCredentialCreationOptionsJSON& aOptions,
@@ -547,6 +647,18 @@ void PublicKeyCredential::ParseCreationOptionsFromJSON(
aResult.mExtensions.mMinPinLength.Construct(
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()) {
const AuthenticationExtensionsPRFInputsJSON& prfInputsJSON =
aOptions.mExtensions.Value().mPrf.Value();
@@ -615,6 +727,18 @@ void PublicKeyCredential::ParseRequestOptionsFromJSON(
aResult.mExtensions.mHmacCreateSecret.Construct(
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()) {
aResult.mExtensions.mMinPinLength.Construct(
aOptions.mExtensions.Value().mMinPinLength.Value());

View File

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

View File

@@ -216,6 +216,16 @@ WebAuthnRegisterArgs::GetPrivateBrowsing(bool* aPrivateBrowsing) {
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_IMETHODIMP
@@ -437,4 +447,22 @@ WebAuthnSignArgs::GetPrivateBrowsing(bool* aPrivateBrowsing) {
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

View File

@@ -28,6 +28,7 @@ class WebAuthnRegisterArgs final : public nsIWebAuthnRegisterArgs {
mInfo(aInfo),
mCredProps(false),
mHmacCreateSecret(false),
mLargeBlobSupportRequired(Nothing()),
mMinPinLength(false),
mPrf(false) {
for (const WebAuthnExtension& ext : mInfo.Extensions()) {
@@ -39,6 +40,10 @@ class WebAuthnRegisterArgs final : public nsIWebAuthnRegisterArgs {
mHmacCreateSecret =
ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret();
break;
case WebAuthnExtension::TWebAuthnExtensionLargeBlob:
mLargeBlobSupportRequired =
ext.get_WebAuthnExtensionLargeBlob().flag();
break;
case WebAuthnExtension::TWebAuthnExtensionMinPinLength:
mMinPinLength =
ext.get_WebAuthnExtensionMinPinLength().minPinLength();
@@ -63,6 +68,7 @@ class WebAuthnRegisterArgs final : public nsIWebAuthnRegisterArgs {
// Flags to indicate whether an extension is being requested.
bool mCredProps;
bool mHmacCreateSecret;
Maybe<bool> mLargeBlobSupportRequired;
bool mMinPinLength;
bool mPrf;
};
@@ -89,6 +95,16 @@ class WebAuthnSignArgs final : public nsIWebAuthnSignArgs {
break;
case WebAuthnExtension::TWebAuthnExtensionMinPinLength:
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:
mPrf = ext.get_WebAuthnExtensionPrf().eval().isSome() ||
ext.get_WebAuthnExtensionPrf().evalByCredentialMaybe();
@@ -106,6 +122,8 @@ class WebAuthnSignArgs final : public nsIWebAuthnSignArgs {
const nsCString mClientDataJSON;
const bool mPrivateBrowsing;
const WebAuthnGetAssertionInfo mInfo;
Maybe<bool> mLargeBlobRead;
nsTArray<uint8_t> mLargeBlobWrite;
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>
if (aOptions.mExtensions.mPrf.WasPassed()) {
const AuthenticationExtensionsPRFInputs& prf =
@@ -533,6 +551,30 @@ already_AddRefed<Promise> WebAuthnHandler::GetAssertion(
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>
if (aOptions.mExtensions.mPrf.WasPassed()) {
const AuthenticationExtensionsPRFInputs& prf =
@@ -753,6 +795,12 @@ void WebAuthnHandler::FinishMakeCredential(
ext.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret();
credential->SetClientExtensionResultHmacSecret(hmacCreateSecret);
}
if (ext.type() ==
WebAuthnExtensionResult::TWebAuthnExtensionResultLargeBlob) {
credential->InitClientExtensionResultLargeBlob();
credential->SetClientExtensionResultLargeBlobSupported(
ext.get_WebAuthnExtensionResultLargeBlob().flag());
}
if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultPrf) {
credential->InitClientExtensionResultPrf();
const Maybe<bool> prfEnabled =
@@ -823,6 +871,24 @@ void WebAuthnHandler::FinishGetAssertion(
bool appid = ext.get_WebAuthnExtensionResultAppId().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) {
credential->InitClientExtensionResultPrf();
Maybe<WebAuthnExtensionPrfValues> prfResults =

View File

@@ -90,6 +90,15 @@ WebAuthnRegisterResult::SetCredPropsRk(bool aCredPropsRk) {
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
WebAuthnRegisterResult::GetPrfEnabled(bool* aPrfEnabled) {
if (mPrf.isSome()) {
@@ -218,6 +227,24 @@ WebAuthnSignResult::SetUsedAppId(bool aUsedAppId) {
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
WebAuthnSignResult::GetPrfMaybe(bool* aPrfMaybe) {
*aPrfMaybe = mPrfFirst.isSome();

View File

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

View File

@@ -73,6 +73,18 @@ nsresult AssembleClientData(WindowGlobalParent* aManager,
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() {
if (mTransactionId.isSome()) {
if (mRegisterPromiseRequest.Exists()) {
@@ -227,6 +239,17 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
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<WebAuthnExtensionPrfValues> prfResults = Nothing();
@@ -342,6 +365,9 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
return IPC_OK();
}
bool requestIncludesLargeBlobRead =
GetAssertionRequestIncludesLargeBlobRead(aTransactionInfo);
RefPtr<WebAuthnSignPromiseHolder> promiseHolder =
new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
@@ -350,7 +376,7 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}, inputClientData = clientDataJSON,
resolver = std::move(aResolver)](
requestIncludesLargeBlobRead, resolver = std::move(aResolver)](
const WebAuthnSignPromise::ResolveOrRejectValue& aValue) {
self->CompleteTransaction();
@@ -414,6 +440,32 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
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;
bool prfMaybe = false;

View File

@@ -308,6 +308,23 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
// AttestationConveyance
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
BOOL winEnablePrf = FALSE;
@@ -366,8 +383,7 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
// Attachment
DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
nsString authenticatorAttachment;
nsresult rv =
aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
rv = aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
aPromise->Reject(rv);
@@ -553,7 +569,7 @@ WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
&cancellationId, // CancellationId
pExcludeCredentialList,
WEBAUTHN_ENTERPRISE_ATTESTATION_NONE,
WEBAUTHN_LARGE_BLOB_SUPPORT_NONE,
largeBlobSupport, // LargeBlobSupport
winPreferResidentKey, // PreferResidentKey
winPrivateBrowsing, // BrowserInPrivateMode
winEnablePrf, // EnablePrf
@@ -739,6 +755,32 @@ void WinWebAuthnService::DoGetAssertion(
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
WEBAUTHN_HMAC_SECRET_SALT_VALUES* pPrfInputs = nullptr;
WEBAUTHN_HMAC_SECRET_SALT_VALUES prfInputs = {0};
@@ -906,15 +948,15 @@ void WinWebAuthnService::DoGetAssertion(
pbAppIdUsed,
&aCancellationId, // CancellationId
pAllowCredentialList,
WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE,
0, // Size of CredLargeBlob
NULL, // CredLargeBlob
pPrfInputs, // HmacSecretSaltValues
winPrivateBrowsing, // BrowserInPrivateMode
NULL, // LinkedDevice
FALSE, // AutoFill
0, // Size of JsonExt
NULL, // JsonExt
credLargeBlobOperation, // CredLargeBlobOperation
credLargeBlobSize, // Size of CredLargeBlob
credLargeBlob, // CredLargeBlob
pPrfInputs, // HmacSecretSaltValues
winPrivateBrowsing, // BrowserInPrivateMode
NULL, // LinkedDevice
FALSE, // AutoFill
0, // Size of JsonExt
NULL, // JsonExt
};
PWEBAUTHN_ASSERTION pWebAuthNAssertion = nullptr;
@@ -928,8 +970,8 @@ void WinWebAuthnService::DoGetAssertion(
&pWebAuthNAssertion);
if (hr == S_OK) {
RefPtr<WebAuthnSignResult> result =
new WebAuthnSignResult(clientDataJSON, pWebAuthNAssertion);
RefPtr<WebAuthnSignResult> result = new WebAuthnSignResult(
clientDataJSON, credLargeBlobOperation, pWebAuthNAssertion);
gWinWebauthnFreeAssertion(pWebAuthNAssertion);
if (winAppIdentifier != nullptr) {
// The gWinWebauthnGetAssertion call modified bAppIdUsed through

View File

@@ -216,6 +216,11 @@ impl WebAuthnRegisterResult {
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);
fn get_prf_enabled(&self) -> Result<bool, nsresult> {
match self.result.borrow().extensions.prf {
@@ -412,6 +417,16 @@ impl WebAuthnSignResult {
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);
/// Return true if a PRF output is present, even if all attributes are absent.
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 Array<octet> prfEvalFirst;
[must_use] readonly attribute Array<octet> prfEvalSecond;
[must_use] readonly attribute boolean largeBlobSupportRequired;
// Options.
readonly attribute AString residentKey;
@@ -97,6 +98,8 @@ interface nsIWebAuthnSignArgs : nsISupports {
[must_use] readonly attribute Array<Array<octet> > prfEvalByCredentialEvalFirst;
[must_use] readonly attribute Array<boolean> prfEvalByCredentialEvalSecondMaybe;
[must_use] readonly attribute Array<Array<octet> > prfEvalByCredentialEvalSecond;
[must_use] readonly attribute boolean largeBlobRead;
[must_use] readonly attribute Array<octet> largeBlobWrite;
// Options
[must_use] readonly attribute AString userVerification;

View File

@@ -27,6 +27,8 @@ interface nsIWebAuthnRegisterResult : nsISupports {
[must_use] attribute boolean credPropsRk;
readonly attribute boolean largeBlobSupported;
readonly attribute boolean prfEnabled;
readonly attribute Array<octet> prfResultsFirst;
readonly attribute Array<octet> prfResultsSecond;
@@ -65,6 +67,9 @@ interface nsIWebAuthnSignResult : nsISupports {
// appId field of AuthenticationExtensionsClientOutputs (Optional)
[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 Array<octet> prfResultsFirst;
readonly attribute Array<octet> prfResultsSecond;

View File

@@ -319,6 +319,47 @@ partial dictionary AuthenticationExtensionsClientOutputsJSON {
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
// <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]
[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]
expected: FAIL

View File

@@ -1,12 +1,3 @@
[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]
expected: FAIL

View File

@@ -1,6 +1,3 @@
[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]
expected: FAIL