diff --git a/Sources/Panels/BrowserWebAuthnSupport.swift b/Sources/Panels/BrowserWebAuthnSupport.swift index 0353b630e03..899852b65b0 100644 --- a/Sources/Panels/BrowserWebAuthnSupport.swift +++ b/Sources/Panels/BrowserWebAuthnSupport.swift @@ -1179,6 +1179,39 @@ extension BrowserWebAuthnCoordinator: ASAuthorizationControllerDelegate, ASAutho } } +@MainActor +extension BrowserWebAuthnCoordinator { + func assertionReply( + credentialID: Data, + clientDataJSON: Data, + authenticatorData: Data, + signature: Data, + userHandle: Data?, + attachment: String, + clientExtensionResults: [String: Any] + ) -> [String: Any] { + var response: [String: Any] = [ + "clientDataJSON": clientDataJSON.base64URLEncodedString(), + "authenticatorData": authenticatorData.base64URLEncodedString(), + "signature": signature.base64URLEncodedString(), + ] + + if let userHandle, !userHandle.isEmpty { + response["userHandle"] = userHandle.base64URLEncodedString() + } + + return [ + "type": "public-key", + "id": credentialID.base64URLEncodedString(), + "rawId": credentialID.base64URLEncodedString(), + "authenticatorAttachment": attachment, + "responseKind": "assertion", + "response": response, + "clientExtensionResults": clientExtensionResults, + ] + } +} + @MainActor private extension BrowserWebAuthnCoordinator { enum BrowserWebAuthnAuthorizationErrorCode { @@ -1695,36 +1728,6 @@ private extension BrowserWebAuthnCoordinator { return credential } - func assertionReply( - credentialID: Data, - clientDataJSON: Data, - authenticatorData: Data, - signature: Data, - userHandle: Data, - attachment: String, - clientExtensionResults: [String: Any] - ) -> [String: Any] { - var response: [String: Any] = [ - "clientDataJSON": clientDataJSON.base64URLEncodedString(), - "authenticatorData": authenticatorData.base64URLEncodedString(), - "signature": signature.base64URLEncodedString(), - ] - - if !userHandle.isEmpty { - response["userHandle"] = userHandle.base64URLEncodedString() - } - - return [ - "type": "public-key", - "id": credentialID.base64URLEncodedString(), - "rawId": credentialID.base64URLEncodedString(), - "authenticatorAttachment": attachment, - "responseKind": "assertion", - "response": response, - "clientExtensionResults": clientExtensionResults, - ] - } - func securityKeyTransportValues( from registration: ASAuthorizationSecurityKeyPublicKeyCredentialRegistration ) -> [String] { diff --git a/cmuxTests/BrowserWebContentProcessTests.swift b/cmuxTests/BrowserWebContentProcessTests.swift index 3e01943af9b..8731c53a67d 100644 --- a/cmuxTests/BrowserWebContentProcessTests.swift +++ b/cmuxTests/BrowserWebContentProcessTests.swift @@ -1032,6 +1032,25 @@ struct BrowserWebContentProcessTests { #expect(probe.receivedKinds == ["getCredential"]) } + @Test(arguments: [nil, Data()] as [Data?]) + func webAuthnAssertionReplyOmitsAbsentUserHandle(userHandle: Data?) throws { + let coordinator = BrowserWebAuthnCoordinator() + let credential = coordinator.assertionReply( + credentialID: Data([1, 2, 3]), + clientDataJSON: Data([4, 5]), + authenticatorData: Data([6, 7]), + signature: Data([8, 9]), + userHandle: userHandle, + attachment: "cross-platform", + clientExtensionResults: [:] + ) + let response = try #require(credential["response"] as? [String: Any]) + + #expect(credential["id"] as? String == "AQID") + #expect(response["signature"] as? String == "CAk") + #expect(response["userHandle"] == nil) + } + @Test func webAuthnNativeBridgeScopesParentDomainRelyingPartyIDs() throws { let googleOrigin = try #require(