Bug 1536155 - implement getTransports() for AuthenticatorAttestationResponse. r=geckoview-reviewers,webidl,keeler,smaug,owlish

Differential Revision: https://phabricator.services.mozilla.com/D185225
This commit is contained in:
John Schanck 2023-09-06 21:50:39 +00:00
parent 6773f8457a
commit 1a32e9ea0f
13 changed files with 100 additions and 26 deletions

View File

@ -261,9 +261,9 @@ void AndroidWebAuthnTokenManager::HandleRegisterResult(
[self = RefPtr<AndroidWebAuthnTokenManager>(this),
aResult = std::move(aResult)]() {
nsTArray<WebAuthnExtensionResult> extensions;
WebAuthnMakeCredentialResult result(aResult.mClientDataJSON,
aResult.mAttObj,
aResult.mKeyHandle, extensions);
WebAuthnMakeCredentialResult result(
aResult.mClientDataJSON, aResult.mAttObj, aResult.mKeyHandle,
aResult.mTransports, extensions);
self->mRegisterPromise.Resolve(std::move(result), __func__);
}));
}
@ -411,6 +411,11 @@ AndroidWebAuthnResult::AndroidWebAuthnResult(
reinterpret_cast<uint8_t*>(
aResponse->AttestationObject()->GetElements().Elements()),
aResponse->AttestationObject()->Length());
auto transports = aResponse->Transports();
for (size_t i = 0; i < transports->Length(); i++) {
mTransports.AppendElement(
jni::String::LocalRef(transports->GetElement(i))->ToString());
}
}
AndroidWebAuthnResult::AndroidWebAuthnResult(

View File

@ -84,6 +84,7 @@ class AndroidWebAuthnResult {
// Attestation-only
nsTArray<uint8_t> mAttObj;
nsTArray<nsString> mTransports;
// Attestations and assertions
nsTArray<uint8_t> mKeyHandle;

View File

@ -64,13 +64,19 @@ void AuthenticatorAttestationResponse::GetAttestationObject(
aValue.set(mAttestationObjectCachedObj);
}
nsresult AuthenticatorAttestationResponse::SetAttestationObject(
void AuthenticatorAttestationResponse::SetAttestationObject(
const nsTArray<uint8_t>& aBuffer) {
if (!mAttestationObject.Assign(aBuffer, mozilla::fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
mAttestationObject.Assign(aBuffer);
}
return NS_OK;
void AuthenticatorAttestationResponse::GetTransports(
nsTArray<nsString>& aTransports) {
aTransports.Assign(mTransports);
}
void AuthenticatorAttestationResponse::SetTransports(
const nsTArray<nsString>& aTransports) {
mTransports.Assign(aTransports);
}
void AuthenticatorAttestationResponse::GetAuthenticatorData(

View File

@ -35,7 +35,11 @@ class AuthenticatorAttestationResponse final : public AuthenticatorResponse {
void GetAttestationObject(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
ErrorResult& aRv);
nsresult SetAttestationObject(const nsTArray<uint8_t>& aBuffer);
void SetAttestationObject(const nsTArray<uint8_t>& aBuffer);
void GetTransports(nsTArray<nsString>& aTransports);
void SetTransports(const nsTArray<nsString>& aTransports);
void GetAuthenticatorData(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
ErrorResult& aRv);
@ -49,6 +53,7 @@ class AuthenticatorAttestationResponse final : public AuthenticatorResponse {
nsTArray<uint8_t> mAttestationObject;
nsCOMPtr<nsIWebAuthnAttObj> mAttestationObjectParsed;
JS::Heap<JSObject*> mAttestationObjectCachedObj;
nsTArray<nsString> mTransports;
};
} // namespace mozilla::dom

View File

@ -92,6 +92,7 @@ struct WebAuthnMakeCredentialResult {
nsCString ClientDataJSON;
uint8_t[] AttestationObject;
uint8_t[] KeyHandle;
nsString[] Transports;
WebAuthnExtensionResult[] Extensions;
};

View File

@ -409,9 +409,16 @@ void WebAuthnController::RunFinishRegister(
return;
}
nsTArray<nsString> transports;
rv = aResult->GetTransports(transports);
if (NS_WARN_IF(NS_FAILED(rv))) {
AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR, true);
return;
}
nsTArray<WebAuthnExtensionResult> extensions;
WebAuthnMakeCredentialResult result(clientDataJson, attObj, credentialId,
extensions);
transports, extensions);
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
u"CTAPRegisterFinish"_ns, 1);

View File

@ -711,6 +711,7 @@ void WebAuthnManager::FinishMakeCredential(
new AuthenticatorAttestationResponse(mParent);
attestation->SetClientDataJSON(aResult.ClientDataJSON());
attestation->SetAttestationObject(aResult.AttestationObject());
attestation->SetTransports(aResult.Transports());
RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mParent);
credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));

View File

@ -453,7 +453,6 @@ void WinWebAuthnManager::Register(
}
nsTArray<WebAuthnExtensionResult> extensions;
if (pWebAuthNCredentialAttestation->dwVersion >=
WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2) {
PCWEBAUTHN_EXTENSIONS pExtensionList =
@ -478,8 +477,29 @@ void WinWebAuthnManager::Register(
}
}
nsTArray<nsString> transports;
if (pWebAuthNCredentialAttestation->dwVersion >=
WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3) {
if (pWebAuthNCredentialAttestation->dwUsedTransport &
WEBAUTHN_CTAP_TRANSPORT_USB) {
transports.AppendElement(u"usb"_ns);
}
if (pWebAuthNCredentialAttestation->dwUsedTransport &
WEBAUTHN_CTAP_TRANSPORT_NFC) {
transports.AppendElement(u"nfc"_ns);
}
if (pWebAuthNCredentialAttestation->dwUsedTransport &
WEBAUTHN_CTAP_TRANSPORT_BLE) {
transports.AppendElement(u"ble"_ns);
}
if (pWebAuthNCredentialAttestation->dwUsedTransport &
WEBAUTHN_CTAP_TRANSPORT_INTERNAL) {
transports.AppendElement(u"internal"_ns);
}
}
WebAuthnMakeCredentialResult result(aInfo.ClientDataJSON(), attObject,
credentialId, extensions);
credentialId, transports, extensions);
Unused << mTransactionParent->SendConfirmRegister(aTransactionId, result);
ClearTransaction();

View File

@ -34,7 +34,7 @@ use serde_cbor;
use std::cell::RefCell;
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
use std::sync::{Arc, Mutex};
use thin_vec::ThinVec;
use thin_vec::{thin_vec, ThinVec};
use xpcom::interfaces::{
nsICredentialParameters, nsICtapRegisterArgs, nsICtapRegisterResult, nsICtapSignArgs,
nsICtapSignResult, nsIWebAuthnAttObj, nsIWebAuthnController, nsIWebAuthnTransport,
@ -114,6 +114,18 @@ impl CtapRegisterResult {
Err(NS_ERROR_FAILURE)
}
xpcom_method!(get_transports => GetTransports() -> ThinVec<nsString>);
fn get_transports(&self) -> Result<ThinVec<nsString>, nsresult> {
if self.result.is_err() {
return Err(NS_ERROR_FAILURE);
}
// The list that we return here might be included in a future GetAssertion request as a
// hint as to which transports to try. We currently only support the USB transport. If
// that changes, we will need a mechanism to track which transport was used for a
// request.
Ok(thin_vec![nsString::from("usb")])
}
xpcom_method!(get_status => GetStatus() -> nsresult);
fn get_status(&self) -> Result<nsresult, nsresult> {
match &self.result {

View File

@ -119,12 +119,7 @@ interface nsICtapRegisterResult : nsISupports {
// attestationObject.
readonly attribute Array<octet> credentialId;
// Bug 1536155
// readonly attribute Array<AString> transports;
// Bug 1816520
// readonly attribute Array<octet> publicKey
// readonly attribute COSEAlgorithmIdentifier publicKeyAlgorithm;
readonly attribute Array<AString> transports;
// bug 1593571
// readonly attribute bool hmacCreateSecret;

View File

@ -34,9 +34,10 @@
ok(aResult.toString().startsWith("InvalidStateError"), "Expecting a InvalidStateError, got " + aResult);
}
// Store the credential of the first successful make credential
// operation so we can use it to get assertions later.
// Store the credential and transports of the first successful make credential
// operation so we can use them to get assertions later.
let gCredential;
let gTransports;
// Start a new MakeCredential() request.
function requestMakeCredential(excludeCredentials) {
@ -69,15 +70,25 @@
// Make a credential.
await requestMakeCredential([])
// Save the credential for later.
.then(res => gCredential = res.rawId)
.then(res => {
gCredential = res.rawId;
gTransports = res.response.getTransports();
})
.then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
// gTransports should be "a sequence of zero or more unique DOMStrings in lexicographical order."
for (let i = 0; i < gTransports.length - 1; i++) {
if (gTransports[i] >= gTransports[i+1]) {
ok(false, "getTransports() should return a sorted list");
}
}
// Pass a random credential to exclude.
await requestMakeCredential([{
type: "public-key",
id: crypto.getRandomValues(new Uint8Array(16)),
transports: ["usb"],
transports: gTransports,
}]).then(arrivingHereIsGood)
.catch(arrivingHereIsBad);
@ -117,7 +128,7 @@
await requestGetAssertion([{
type: "public-key",
id: gCredential,
transports: ["usb"],
transports: gTransports,
}]).then(arrivingHereIsGood)
.catch(arrivingHereIsBad);

View File

@ -34,6 +34,7 @@ interface AuthenticatorResponse {
Exposed=Window]
interface AuthenticatorAttestationResponse : AuthenticatorResponse {
[SameObject, Throws] readonly attribute ArrayBuffer attestationObject;
sequence<DOMString> getTransports();
[Throws] ArrayBuffer getAuthenticatorData();
[Throws] ArrayBuffer? getPublicKey();
[Throws] COSEAlgorithmIdentifier getPublicKeyAlgorithm();

View File

@ -124,12 +124,17 @@ import org.mozilla.gecko.util.GeckoBundle;
public final byte[] clientDataJson;
public final byte[] keyHandle;
public final byte[] attestationObject;
public final String[] transports;
public MakeCredentialResponse(
final byte[] clientDataJson, final byte[] keyHandle, final byte[] attestationObject) {
final byte[] clientDataJson,
final byte[] keyHandle,
final byte[] attestationObject,
final String[] transports) {
this.clientDataJson = clientDataJson;
this.keyHandle = keyHandle;
this.attestationObject = attestationObject;
this.transports = transports;
}
}
@ -308,11 +313,15 @@ import org.mozilla.gecko.util.GeckoBundle;
+ Base64.encodeToString(
responseData.getAttestationObject(), Base64.DEFAULT));
Log.d(
LOGTAG, "transports: " + String.join(", ", responseData.getTransports()));
result.complete(
new WebAuthnTokenManager.MakeCredentialResponse(
responseData.getClientDataJSON(),
responseData.getKeyHandle(),
responseData.getAttestationObject()));
responseData.getAttestationObject(),
responseData.getTransports()));
}
},
e -> {