mirror of
https://github.com/ProtonMail/protoncore_ios.git
synced 2026-01-16 23:00:24 +00:00
feat: Add Passkey support to WebAuthn
Refs: https://gitlab.protontech.ch/apple/shared/protoncore/-/merge_requests/1829 Changelog: added
This commit is contained in:
commit
e0a9e2b9c1
6 changed files with 54 additions and 30 deletions
|
|
@ -1720,4 +1720,20 @@ extension AuthenticatorWithKeyGenerationMock {
|
|||
}
|
||||
}
|
||||
|
||||
extension LoginTestUser {
|
||||
func fido2SignatureWithAuthenticationOptions(_ options: AuthenticationOptions) -> Fido2Signature {
|
||||
.init(signature: Data(
|
||||
base64Encoded: "MEQCIACXYgPg+2eCHc72pFAan0JhYFaOIqQ++7E9AJwoW3evAiAqYv2S1VG4I4wU/rEeg9ppLx9FmnCfpcMVzqqGdlILkA=="
|
||||
)!,
|
||||
credentialID: Data(
|
||||
[214, 89, 242, 193, 240, 89, 89, 49, 22, 245, 29, 37, 39, 207, 145, 53, 240, 133, 121, 30, 193,
|
||||
196, 143, 230, 104, 21, 129, 81, 32, 172, 93, 34, 150, 176, 62, 233, 66, 142, 140, 171, 100, 11,
|
||||
72, 233, 203, 148, 132, 168, 88, 189, 25, 126, 20, 65, 35, 17, 42, 224, 110, 50, 203, 203, 166, 82]
|
||||
),
|
||||
authenticatorData: Data(base64Encoded: "+95oJ+45pI2RXXNJa4EVk4mJnlacXl6FI+/xqhhc7H4BAAABHQ==")!,
|
||||
clientData: Data(base64Encoded: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRm5NbEttV1lXSVhMUl9xZG5YSWtzTFF5Q293Tlg1N3dnSUdQQTQwcUJoRSIsIm9yaWdpbiI6Imh0dHBzOi8vYWNjb3VudC5wcm90b24uYmxhY2sifQ==")!,
|
||||
authenticationOptions: options)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ public enum LUITranslation: TranslationsExposing {
|
|||
case twofa_invalid_state_banner
|
||||
case twofa_invalid_2fa_key
|
||||
case twofa_unexpected_authorization_type
|
||||
case twofa_unexpected_signature
|
||||
case unavailable_authinfo
|
||||
|
||||
public var l10n: String {
|
||||
switch self {
|
||||
|
|
@ -387,6 +389,12 @@ public enum LUITranslation: TranslationsExposing {
|
|||
case .twofa_unexpected_authorization_type:
|
||||
return localized(key: "We received an authorization from a type we don't support yet.",
|
||||
comment: "Error shown when recieving a successful authorization of unkown type.")
|
||||
case .twofa_unexpected_signature:
|
||||
return localized(key: "Unexpected FIDO2 signature.",
|
||||
comment: "Error shown when receiving a FIDO2/Passkey signature that wasn't requested.")
|
||||
case .unavailable_authinfo:
|
||||
return localized(key: "We could not initiate the Secure Password update connection. Please try again.",
|
||||
comment: "Error shown when changing password on an account which has 2 Factor Auth configured, but can't retrieve the authentication challenge")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,6 +248,6 @@
|
|||
|
||||
"To manage, add, or remove security keys, please use the Proton %@ web application." = "To manage, add, or remove security keys, please use the Proton %@ web application.";
|
||||
|
||||
"Unexpected FIDO2 signature" = "Unexpected FIDO2 signature";
|
||||
"Unexpected FIDO2 signature." = "Unexpected FIDO2 signature.";
|
||||
|
||||
"We could not initiate the Secure Password update connection. Please try again." = "We could not initiate the Secure Password update connection. Please try again.";
|
||||
|
|
|
|||
|
|
@ -51,25 +51,32 @@ extension Fido2View {
|
|||
|
||||
let controller = makeAuthController(relyingPartyIdentifier: authenticationOptions.relyingPartyIdentifier,
|
||||
challenge: authenticationOptions.challenge,
|
||||
allowedCredentials: authenticationOptions.allowedCredentialIds.map {
|
||||
ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor(
|
||||
credentialID: $0,
|
||||
transports: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported
|
||||
)
|
||||
}
|
||||
allowedCredentials: authenticationOptions.allowedCredentialIds
|
||||
)
|
||||
controller.performRequests()
|
||||
}
|
||||
|
||||
private func makeAuthController(relyingPartyIdentifier: String,
|
||||
challenge: Data,
|
||||
allowedCredentials: [ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor]) -> ASAuthorizationController {
|
||||
let provider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: relyingPartyIdentifier)
|
||||
allowedCredentials: [Data]) -> ASAuthorizationController {
|
||||
let fido2Provider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: relyingPartyIdentifier)
|
||||
|
||||
let request = provider.createCredentialAssertionRequest(challenge: challenge)
|
||||
request.allowedCredentials = allowedCredentials
|
||||
let fido2Request = fido2Provider.createCredentialAssertionRequest(challenge: challenge)
|
||||
fido2Request.allowedCredentials = allowedCredentials.map {
|
||||
ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor(
|
||||
credentialID: $0,
|
||||
transports: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported
|
||||
)
|
||||
}
|
||||
|
||||
let controller = ASAuthorizationController(authorizationRequests: [request])
|
||||
let passkeyProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: relyingPartyIdentifier)
|
||||
|
||||
let passkeyRequest = passkeyProvider.createCredentialAssertionRequest(challenge: challenge)
|
||||
passkeyRequest.allowedCredentials = allowedCredentials.map {
|
||||
ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: $0)
|
||||
}
|
||||
|
||||
let controller = ASAuthorizationController(authorizationRequests: [fido2Request, passkeyRequest])
|
||||
controller.presentationContextProvider = self
|
||||
controller.delegate = self
|
||||
return controller
|
||||
|
|
@ -104,7 +111,14 @@ extension Fido2View.ViewModel: ASAuthorizationControllerDelegate {
|
|||
provideFido2Signature(Fido2Signature(credentialAssertion: credentialAssertion, authenticationOptions: authenticationOptions))
|
||||
} else {
|
||||
PMLog.error("Invalid state: received a signature for which we don't keep the challenge")
|
||||
bannerState = .error(content: .init(message: "Unexpected FIDO2 signature"))
|
||||
bannerState = .error(content: .init(message: LUITranslation.twofa_unexpected_signature.l10n))
|
||||
}
|
||||
case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
|
||||
if case .configured(let authenticationOptions) = state {
|
||||
provideFido2Signature(Fido2Signature(credentialAssertion: credentialAssertion, authenticationOptions: authenticationOptions))
|
||||
} else {
|
||||
PMLog.error("Invalid state: received a signature for which we don't keep the challenge")
|
||||
bannerState = .error(content: .init(message: LUITranslation.twofa_unexpected_signature.l10n))
|
||||
}
|
||||
default:
|
||||
PMLog.error("Received unknown authorization type: \(authorization.credential)", sendToExternal: true)
|
||||
|
|
@ -133,7 +147,7 @@ extension Fido2View.ViewModel: ASAuthorizationControllerPresentationContextProvi
|
|||
|
||||
@available(iOS 15.0, macOS 12.0, *)
|
||||
extension Fido2Signature {
|
||||
init(credentialAssertion: ASAuthorizationSecurityKeyPublicKeyCredentialAssertion, authenticationOptions: AuthenticationOptions) {
|
||||
init(credentialAssertion: ASAuthorizationPublicKeyCredentialAssertion, authenticationOptions: AuthenticationOptions) {
|
||||
self = .init(signature: credentialAssertion.signature,
|
||||
credentialID: credentialAssertion.credentialID,
|
||||
authenticatorData: credentialAssertion.rawAuthenticatorData,
|
||||
|
|
|
|||
|
|
@ -23,26 +23,12 @@ import Foundation
|
|||
|
||||
import ProtonCoreDataModel
|
||||
import ProtonCoreNetworking
|
||||
import ProtonCoreAuthentication
|
||||
import ProtonCoreServices
|
||||
|
||||
public class LoginTestUser {
|
||||
public let username: String
|
||||
public let password: String
|
||||
public let twoFactorCode: String = "123456"
|
||||
public func fido2SignatureWithAuthenticationOptions(_ options: AuthenticationOptions) -> Fido2Signature {
|
||||
.init(signature: Data(
|
||||
base64Encoded: "MEQCIACXYgPg+2eCHc72pFAan0JhYFaOIqQ++7E9AJwoW3evAiAqYv2S1VG4I4wU/rEeg9ppLx9FmnCfpcMVzqqGdlILkA=="
|
||||
)!,
|
||||
credentialID: Data(
|
||||
[214, 89, 242, 193, 240, 89, 89, 49, 22, 245, 29, 37, 39, 207, 145, 53, 240, 133, 121, 30, 193,
|
||||
196, 143, 230, 104, 21, 129, 81, 32, 172, 93, 34, 150, 176, 62, 233, 66, 142, 140, 171, 100, 11,
|
||||
72, 233, 203, 148, 132, 168, 88, 189, 25, 126, 20, 65, 35, 17, 42, 224, 110, 50, 203, 203, 166, 82]
|
||||
),
|
||||
authenticatorData: Data(base64Encoded: "+95oJ+45pI2RXXNJa4EVk4mJnlacXl6FI+/xqhhc7H4BAAABHQ==")!,
|
||||
clientData: Data(base64Encoded: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRm5NbEttV1lXSVhMUl9xZG5YSWtzTFF5Q293Tlg1N3dnSUdQQTQwcUJoRSIsIm9yaWdpbiI6Imh0dHBzOi8vYWNjb3VudC5wcm90b24uYmxhY2sifQ==")!,
|
||||
authenticationOptions: options)
|
||||
}
|
||||
|
||||
|
||||
public init(username: String, password: String) {
|
||||
self.username = username
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ extension PasswordChangeView {
|
|||
func savePasswordTapped() {
|
||||
Task { @MainActor in
|
||||
guard let authInfo = try? await self.passwordChangeService?.fetchAuthInfo() else {
|
||||
bannerState = .error(content: .init(message: "We could not initiate the Secure Password update connection. Please try again."))
|
||||
bannerState = .error(content: .init(message: LUITranslation.unavailable_authinfo.l10n))
|
||||
return
|
||||
}
|
||||
self.authInfo = authInfo
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue