-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
407 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/config/registries.json | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.netrc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"pins" : [ | ||
{ | ||
"identity" : "swiftrs", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/devsnek/SwiftRs", | ||
"state" : { | ||
"revision" : "a1578d5808b22b5a3461d36a6c8add5cd83a2ddf", | ||
"version" : "1.0.5" | ||
} | ||
} | ||
], | ||
"version" : 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// swift-tools-version: 5.8 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "MacAuthn", | ||
platforms: [ | ||
.macOS(.v12) | ||
], | ||
products: [ | ||
.library( | ||
name: "MacAuthn", | ||
type: .static, | ||
targets: ["MacAuthn"]), | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/devsnek/SwiftRs", from: "1.0.5") | ||
], | ||
targets: [ | ||
.target( | ||
name: "MacAuthn", | ||
dependencies: ["SwiftRs"]), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# MacAuthn | ||
|
||
A description of this package. |
193 changes: 193 additions & 0 deletions
193
webauthn-authenticator-rs/src/MacAuthn/Sources/MacAuthn/MacAuthn.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import AuthenticationServices | ||
import SwiftRs | ||
import Cocoa | ||
|
||
enum Result { | ||
case ok([String: Any]) | ||
case error(String) | ||
} | ||
|
||
class ApplicationDelegate: NSObject, NSApplicationDelegate, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { | ||
let window: NSWindow | ||
let authController: ASAuthorizationController | ||
var result: Result = .error("task did not finish") | ||
|
||
init(window: NSWindow, authController: ASAuthorizationController) { | ||
self.window = window | ||
self.authController = authController | ||
} | ||
|
||
func applicationDidFinishLaunching(_ notification: Notification) { | ||
authController.delegate = self | ||
authController.presentationContextProvider = self | ||
authController.performRequests() | ||
} | ||
|
||
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { | ||
return window | ||
} | ||
|
||
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { | ||
if let credential = authorization.credential as? ASAuthorizationSecurityKeyPublicKeyCredentialRegistration { | ||
let rawId = credential.credentialID.toBase64Url() | ||
let clientDataJSON = credential.rawClientDataJSON.toBase64Url() | ||
let attestationObject = credential.rawAttestationObject!.toBase64Url() | ||
self.result = .ok([ | ||
"id": rawId, | ||
"rawId": rawId, | ||
"type": "public-key", | ||
"response": [ | ||
"clientDataJSON": clientDataJSON, | ||
"attestationObject": attestationObject | ||
] | ||
]) | ||
} else if let credential = authorization.credential as? ASAuthorizationSecurityKeyPublicKeyCredentialAssertion { | ||
let signature = credential.signature.toBase64Url() | ||
let clientDataJSON = credential.rawClientDataJSON.toBase64Url() | ||
let authenticatorData = credential.rawAuthenticatorData.toBase64Url() | ||
let rawId = credential.credentialID.toBase64Url() | ||
self.result = .ok([ | ||
"id": rawId, | ||
"rawId": rawId, | ||
"type": "public-key", | ||
"response": [ | ||
"clientDataJSON": clientDataJSON, | ||
"authenticatorData": authenticatorData, | ||
"signature": signature | ||
] | ||
]) | ||
} else if let credential = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialRegistration { | ||
let rawId = credential.credentialID.toBase64Url() | ||
let clientDataJSON = credential.rawClientDataJSON.toBase64Url() | ||
let attestationObject = credential.rawAttestationObject!.toBase64Url() | ||
self.result = .ok([ | ||
"id": rawId, | ||
"rawId": rawId, | ||
"type": "public-key", | ||
"response": [ | ||
"clientDataJSON": clientDataJSON, | ||
"attestationObject": attestationObject | ||
] | ||
]) | ||
} else if let credential = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion { | ||
let signature = credential.signature.toBase64Url() | ||
let clientDataJSON = credential.rawClientDataJSON.toBase64Url() | ||
let authenticatorData = credential.rawAuthenticatorData.toBase64Url() | ||
let rawId = credential.credentialID.toBase64Url() | ||
self.result = .ok([ | ||
"id": rawId, | ||
"rawId": rawId, | ||
"type": "public-key", | ||
"response": [ | ||
"clientDataJSON": clientDataJSON, | ||
"authenticatorData": authenticatorData, | ||
"signature": signature | ||
] | ||
]) | ||
} else { | ||
self.result = .error("unhandled credential") | ||
} | ||
NSApplication.shared.stop(0) | ||
} | ||
|
||
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { | ||
self.result = .error(error.localizedDescription) | ||
NSApplication.shared.stop(0) | ||
} | ||
} | ||
|
||
// Spawn a mini application to | ||
func run(authController: ASAuthorizationController) -> String { | ||
NSApplication.shared.setActivationPolicy(.regular) | ||
let window = NSWindow(contentRect: NSMakeRect(0, 0, 1, 1), styleMask: .borderless, backing: .buffered, defer: false) | ||
window.center() | ||
window.makeKeyAndOrderFront(window) | ||
|
||
let applicationDelegate = ApplicationDelegate(window: window, authController: authController) | ||
NSApplication.shared.delegate = applicationDelegate | ||
|
||
NSApplication.shared.activate(ignoringOtherApps: true) | ||
// run NSApp event loop. NSApplication.shared.stop(0) above will cause this to return | ||
NSApplication.shared.run() | ||
|
||
// Rust expects one of either {"data": ...} or {"error": ...} | ||
switch applicationDelegate.result { | ||
case let .ok(data): | ||
return String(data: try! JSONSerialization.data(withJSONObject: ["data": data]), encoding: .utf8)! | ||
case let .error(message): | ||
return String(data: try! JSONSerialization.data(withJSONObject: ["error": message]), encoding: .utf8)! | ||
} | ||
} | ||
|
||
@_cdecl("perform_register") | ||
public func performRegister(options: SRString) -> SRString { | ||
let options = try! JSONDecoder().decode(PublicKeyCredentialCreationOptions.self, from: Data(options.toArray())) | ||
|
||
let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: options.rp.id) | ||
let platformKeyRequest = platformProvider.createCredentialRegistrationRequest(challenge: options.challenge.decodeBase64Url()!, name: options.user.name, userID: options.user.id.decodeBase64Url()!) | ||
platformKeyRequest.displayName = options.user.displayName | ||
platformKeyRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.init(rawValue: options.authenticatorSelection.userVerification ?? "preferred") | ||
|
||
let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: options.rp.id) | ||
|
||
let securityKeyRequest = securityKeyProvider.createCredentialRegistrationRequest(challenge: options.challenge.decodeBase64Url()!, displayName: options.user.displayName, name: options.user.name, userID: options.user.id.decodeBase64Url()!) | ||
|
||
securityKeyRequest.credentialParameters = [] | ||
for publicKeyParam in options.pubKeyCredParams { | ||
let algorithm = ASCOSEAlgorithmIdentifier(rawValue: publicKeyParam.alg) | ||
let parameters = ASAuthorizationPublicKeyCredentialParameters(algorithm: algorithm) | ||
securityKeyRequest.credentialParameters.append(parameters) | ||
} | ||
|
||
securityKeyRequest.excludedCredentials = [] | ||
for credential in (options.excludeCredentials ?? []) { | ||
let id = credential.id.decodeBase64Url()! | ||
let transports = credential.transports?.map { | ||
ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.init(rawValue: $0) | ||
} ?? ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported | ||
let credential = ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor(credentialID: id, transports: transports) | ||
securityKeyRequest.excludedCredentials.append(credential) | ||
} | ||
|
||
securityKeyRequest.attestationPreference = ASAuthorizationPublicKeyCredentialAttestationKind(rawValue: options.attestation ?? "none") | ||
securityKeyRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.init(rawValue: options.authenticatorSelection.userVerification ?? "preferred") | ||
|
||
if options.authenticatorSelection.requireResidentKey == true { | ||
securityKeyRequest.residentKeyPreference = .required | ||
} else { | ||
securityKeyRequest.residentKeyPreference = .preferred | ||
} | ||
|
||
let authController = ASAuthorizationController(authorizationRequests: [platformKeyRequest, securityKeyRequest]) | ||
|
||
return SRString(run(authController: authController)) | ||
} | ||
|
||
@_cdecl("perform_auth") | ||
public func performAuth(options: SRString) -> SRString { | ||
let options = try! JSONDecoder().decode(PublicKeyCredentialRequestOptions.self, from: Data(options.toArray())) | ||
|
||
let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: options.rpId) | ||
let securityKeyRequest = securityKeyProvider.createCredentialAssertionRequest(challenge: options.challenge.decodeBase64Url()!) | ||
|
||
let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: options.rpId) | ||
let platformKeyRequest = platformProvider.createCredentialAssertionRequest(challenge: options.challenge.decodeBase64Url()!) | ||
|
||
securityKeyRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.init(rawValue: options.userVerification ?? "preferred") | ||
|
||
securityKeyRequest.allowedCredentials = [] | ||
for credential in (options.allowCredentials ?? []) { | ||
let id = credential.id.decodeBase64Url()! | ||
let transports = credential.transports?.map { | ||
ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.init(rawValue: $0) | ||
} ?? ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported | ||
let descriptor = ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor(credentialID: id, transports: transports) | ||
securityKeyRequest.allowedCredentials.append(descriptor) | ||
} | ||
// Setting allowedCredentials can hang for some reason: https://developer.apple.com/forums/thread/727267 | ||
securityKeyRequest.allowedCredentials = [] | ||
|
||
let authController = ASAuthorizationController(authorizationRequests: [platformKeyRequest, securityKeyRequest]) | ||
|
||
return SRString(run(authController: authController)) | ||
} |
72 changes: 72 additions & 0 deletions
72
webauthn-authenticator-rs/src/MacAuthn/Sources/MacAuthn/Protocol.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by snek on 2023-08-05. | ||
// | ||
|
||
import Foundation | ||
|
||
struct PublicKeyCredentialDescriptor: Decodable { | ||
let id: String | ||
let transports: [String]? | ||
} | ||
|
||
struct PublicKeyCredentialUserEntity: Decodable { | ||
let id: String | ||
let name: String | ||
let displayName: String | ||
} | ||
|
||
struct PublicKeyCredentialRpEntity: Decodable { | ||
let id: String | ||
let name: String | ||
} | ||
|
||
struct PublicKeyCredentialParameters: Decodable { | ||
let type: String | ||
let alg: Int | ||
} | ||
|
||
struct AuthenticatorSelectionCriteria: Decodable { | ||
let userVerification: String? | ||
let requireResidentKey: Bool? | ||
} | ||
|
||
struct PublicKeyCredentialCreationOptions: Decodable { | ||
let rp: PublicKeyCredentialRpEntity | ||
let user: PublicKeyCredentialUserEntity | ||
let challenge: String | ||
let pubKeyCredParams: [PublicKeyCredentialParameters] | ||
let timeout: Double | ||
let excludeCredentials: [PublicKeyCredentialDescriptor]? | ||
let authenticatorSelection: AuthenticatorSelectionCriteria | ||
let attestation: String? | ||
|
||
} | ||
|
||
struct PublicKeyCredentialRequestOptions: Decodable { | ||
let challenge: String | ||
let timeout: Double | ||
let rpId: String | ||
let allowCredentials: [PublicKeyCredentialDescriptor]? | ||
let userVerification: String? | ||
} | ||
|
||
extension String { | ||
func decodeBase64Url() -> Data? { | ||
var base64 = self | ||
.replacingOccurrences(of: "-", with: "+") | ||
.replacingOccurrences(of: "_", with: "/") | ||
if base64.count % 4 != 0 { | ||
base64.append(String(repeating: "=", count: 4 - base64.count % 4)) | ||
} | ||
return Data(base64Encoded: base64) | ||
} | ||
} | ||
|
||
extension Data { | ||
func toBase64Url() -> String { | ||
return self.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") | ||
} | ||
} |
Oops, something went wrong.