298 lines
8.5 KiB
Plaintext
298 lines
8.5 KiB
Plaintext
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
|
|
#include "platform/platform_webauthn.h"
|
|
|
|
#if 0
|
|
|
|
#include "data/data_passkey_deserialize.h"
|
|
#include "base/options.h"
|
|
|
|
#import <AuthenticationServices/AuthenticationServices.h>
|
|
#import <Foundation/Foundation.h>
|
|
|
|
@interface WebAuthnDelegate : NSObject<
|
|
ASAuthorizationControllerDelegate,
|
|
ASAuthorizationControllerPresentationContextProviding>
|
|
@property (nonatomic, copy) void (^completionRegister)(
|
|
const Platform::WebAuthn::RegisterResult&);
|
|
@property (nonatomic, copy) void (^completionLogin)(
|
|
const Platform::WebAuthn::LoginResult&);
|
|
@property (nonatomic, strong) ASAuthorizationController *controller API_AVAILABLE(macos(10.15));
|
|
@property (nonatomic, strong)
|
|
ASAuthorizationPlatformPublicKeyCredentialProvider *provider API_AVAILABLE(macos(12.0));
|
|
@end
|
|
|
|
@implementation WebAuthnDelegate
|
|
|
|
- (void)authorizationController:(ASAuthorizationController *)controller
|
|
didCompleteWithAuthorization:(ASAuthorization *)authorization
|
|
API_AVAILABLE(macos(12.0)) {
|
|
if (self.completionRegister) {
|
|
if ([authorization.credential conformsToProtocol:
|
|
@protocol(ASAuthorizationPublicKeyCredentialRegistration)]) {
|
|
auto credential
|
|
= (id<ASAuthorizationPublicKeyCredentialRegistration>)
|
|
authorization.credential;
|
|
auto result = Platform::WebAuthn::RegisterResult();
|
|
result.success = true;
|
|
result.credentialId = QByteArray::fromNSData(
|
|
credential.credentialID);
|
|
result.attestationObject = QByteArray::fromNSData(
|
|
credential.rawAttestationObject);
|
|
result.clientDataJSON = QByteArray::fromNSData(
|
|
credential.rawClientDataJSON);
|
|
self.completionRegister(result);
|
|
self.completionRegister = nil;
|
|
}
|
|
self.controller = nil;
|
|
if (@available(macOS 12.0, *)) {
|
|
self.provider = nil;
|
|
}
|
|
} else if (self.completionLogin) {
|
|
if ([authorization.credential conformsToProtocol:
|
|
@protocol(ASAuthorizationPublicKeyCredentialAssertion)]) {
|
|
auto credential
|
|
= (id<ASAuthorizationPublicKeyCredentialAssertion>)
|
|
authorization.credential;
|
|
auto result = Platform::WebAuthn::LoginResult();
|
|
result.credentialId = QByteArray::fromNSData(
|
|
credential.credentialID);
|
|
result.authenticatorData = QByteArray::fromNSData(
|
|
credential.rawAuthenticatorData);
|
|
result.signature = QByteArray::fromNSData(
|
|
credential.signature);
|
|
result.clientDataJSON = QByteArray::fromNSData(
|
|
credential.rawClientDataJSON);
|
|
result.userHandle = QByteArray::fromNSData(credential.userID);
|
|
self.completionLogin(result);
|
|
self.completionLogin = nil;
|
|
}
|
|
self.controller = nil;
|
|
if (@available(macOS 12.0, *)) {
|
|
self.provider = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)authorizationController:(ASAuthorizationController *)controller
|
|
didCompleteWithError:(NSError *)error
|
|
API_AVAILABLE(macos(10.15)) {
|
|
const auto isCancelled = (error.code == ASAuthorizationErrorCanceled);
|
|
const auto isUnsigned = (error.code == 1004 || error.code == 1009);
|
|
if (!isCancelled) {
|
|
NSLog(@"WebAuthn error: %@ (code: %ld)",
|
|
error.localizedDescription, (long)error.code);
|
|
}
|
|
if (self.completionRegister) {
|
|
auto result = Platform::WebAuthn::RegisterResult();
|
|
result.success = false;
|
|
result.error = isUnsigned
|
|
? Platform::WebAuthn::Error::UnsignedBuild
|
|
: (isCancelled
|
|
? Platform::WebAuthn::Error::Cancelled
|
|
: Platform::WebAuthn::Error::Other);
|
|
self.completionRegister(result);
|
|
self.completionRegister = nil;
|
|
} else if (self.completionLogin) {
|
|
auto result = Platform::WebAuthn::LoginResult();
|
|
result.error = isUnsigned
|
|
? Platform::WebAuthn::Error::UnsignedBuild
|
|
: (isCancelled
|
|
? Platform::WebAuthn::Error::Cancelled
|
|
: Platform::WebAuthn::Error::Other);
|
|
self.completionLogin(result);
|
|
self.completionLogin = nil;
|
|
}
|
|
self.controller = nil;
|
|
if (@available(macOS 12.0, *)) {
|
|
self.provider = nil;
|
|
}
|
|
}
|
|
|
|
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:
|
|
(ASAuthorizationController *)controller
|
|
API_AVAILABLE(macos(10.15)) {
|
|
return [NSApp mainWindow];
|
|
}
|
|
|
|
@end
|
|
|
|
namespace {
|
|
|
|
base::options::toggle WebAuthnMacOption({
|
|
.id = "webauthn-mac",
|
|
.name = "Enable Passkey on macOS",
|
|
.description = "Enable Passkey support on macOS 12.0+. Experimental feature that may cause crash.",
|
|
.defaultValue = false,
|
|
.scope = base::options::macos,
|
|
});
|
|
|
|
} // namespace
|
|
|
|
namespace Platform::WebAuthn {
|
|
|
|
bool IsSupported() {
|
|
if (!WebAuthnMacOption.value()) {
|
|
return false;
|
|
}
|
|
if (@available(macOS 12.0, *)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RegisterKey(
|
|
const Data::Passkey::RegisterData &data,
|
|
Fn<void(RegisterResult result)> callback) {
|
|
if (@available(macOS 12.0, *)) {
|
|
auto rpId = data.rp.id.toNSString();
|
|
auto userName = data.user.name.toNSString();
|
|
auto userDisplayName = data.user.displayName.toNSString();
|
|
auto userId = [NSData dataWithBytes:data.user.id.constData()
|
|
length:data.user.id.size()];
|
|
auto challenge = [NSData dataWithBytes:data.challenge.constData()
|
|
length:data.challenge.size()];
|
|
|
|
if (!userId || !challenge) {
|
|
auto result = RegisterResult();
|
|
result.success = false;
|
|
callback(result);
|
|
return;
|
|
}
|
|
|
|
auto provider = [[ASAuthorizationPlatformPublicKeyCredentialProvider
|
|
alloc] initWithRelyingPartyIdentifier:rpId];
|
|
|
|
auto request = [provider
|
|
createCredentialRegistrationRequestWithChallenge:challenge
|
|
name:userName
|
|
userID:userId];
|
|
|
|
if (userDisplayName && userDisplayName.length > 0) {
|
|
request.displayName = userDisplayName;
|
|
}
|
|
|
|
if (@available(macOS 13.5, *)) {
|
|
request.attestationPreference =
|
|
ASAuthorizationPublicKeyCredentialAttestationKindNone;
|
|
}
|
|
|
|
auto controller = [[ASAuthorizationController alloc]
|
|
initWithAuthorizationRequests:@[request]];
|
|
|
|
auto delegate = [[WebAuthnDelegate alloc] init];
|
|
if (@available(macOS 12.0, *)) {
|
|
delegate.provider = provider;
|
|
}
|
|
delegate.controller = controller;
|
|
delegate.completionRegister = ^(const RegisterResult &result) {
|
|
callback(result);
|
|
[delegate release];
|
|
};
|
|
|
|
controller.delegate = delegate;
|
|
controller.presentationContextProvider = delegate;
|
|
[controller performRequests];
|
|
} else {
|
|
auto result = RegisterResult();
|
|
result.success = false;
|
|
callback(result);
|
|
}
|
|
}
|
|
|
|
void Login(
|
|
const Data::Passkey::LoginData &data,
|
|
Fn<void(LoginResult result)> callback) {
|
|
if (@available(macOS 12.0, *)) {
|
|
auto challenge = [NSData dataWithBytes:data.challenge.constData()
|
|
length:data.challenge.size()];
|
|
|
|
if (!challenge) {
|
|
auto result = LoginResult();
|
|
callback(result);
|
|
return;
|
|
}
|
|
|
|
auto provider = [[ASAuthorizationPlatformPublicKeyCredentialProvider
|
|
alloc] initWithRelyingPartyIdentifier:data.rpId.toNSString()];
|
|
|
|
auto request = [provider
|
|
createCredentialAssertionRequestWithChallenge:challenge];
|
|
|
|
if (!data.allowCredentials.empty()) {
|
|
NSMutableArray *credentialIds = [NSMutableArray array];
|
|
for (const auto &cred : data.allowCredentials) {
|
|
auto credId = [NSData dataWithBytes:cred.id.constData()
|
|
length:cred.id.size()];
|
|
if (credId) {
|
|
[credentialIds addObject:credId];
|
|
}
|
|
}
|
|
if (credentialIds.count > 0) {
|
|
request.allowedCredentials = credentialIds;
|
|
}
|
|
}
|
|
|
|
if (data.userVerification == "required") {
|
|
request.userVerificationPreference =
|
|
ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired;
|
|
} else if (data.userVerification == "preferred") {
|
|
request.userVerificationPreference =
|
|
ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred;
|
|
} else if (data.userVerification == "discouraged") {
|
|
request.userVerificationPreference =
|
|
ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged;
|
|
}
|
|
|
|
auto controller = [[ASAuthorizationController alloc]
|
|
initWithAuthorizationRequests:@[request]];
|
|
|
|
auto delegate = [[WebAuthnDelegate alloc] init];
|
|
if (@available(macOS 12.0, *)) {
|
|
delegate.provider = provider;
|
|
}
|
|
delegate.controller = controller;
|
|
delegate.completionLogin = ^(const LoginResult &result) {
|
|
callback(result);
|
|
[delegate release];
|
|
};
|
|
|
|
controller.delegate = delegate;
|
|
controller.presentationContextProvider = delegate;
|
|
[controller performRequests];
|
|
} else {
|
|
auto result = LoginResult();
|
|
callback(result);
|
|
}
|
|
}
|
|
|
|
} // namespace Platform::WebAuthn
|
|
|
|
#endif
|
|
|
|
namespace Platform::WebAuthn {
|
|
|
|
|
|
bool IsSupported() {
|
|
return false;
|
|
}
|
|
|
|
|
|
void RegisterKey(
|
|
const Data::Passkey::RegisterData &data,
|
|
Fn<void(RegisterResult result)> callback) {
|
|
}
|
|
|
|
void Login(
|
|
const Data::Passkey::LoginData &data,
|
|
Fn<void(LoginResult result)> callback) {
|
|
}
|
|
|
|
} // namespace Platform::WebAuthn
|