Skip to content

Commit

Permalink
WebAuthn with logging and special tracking for #1988
Browse files Browse the repository at this point in the history
  • Loading branch information
Carlos Cabanero committed Mar 11, 2024
1 parent 2a76c6d commit 3cec085
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 11 deletions.
5 changes: 2 additions & 3 deletions Blink/Commands/ssh/SSHConfigProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class SSHClientConfigProvider {
// Return HostName, SSHClientConfig for the server
static func config(host: BKSSHHost, using device: TermDevice) throws -> SSHClientConfig {
let prov = try SSHClientConfigProvider(using: device)

let agent = prov.agent(for: host)

let availableAuthMethods: [AuthMethod] = [AuthAgent(agent)] + prov.passwordAuthMethods(for: host)
Expand Down Expand Up @@ -134,10 +134,9 @@ extension SSHClientConfigProvider {

signers.forEach { (signer, name) in
// NOTE We could also keep the reference and just read the key at the proper time.
// TODO Errors. Either pass or log here, or if we create a different
// type of key, then let the Agent fail.
if let signer = signer as? BlinkConfig.InputPrompter {
signer.setPromptOnView(device.view)
signer.setLogger(self.logger, verbosity: host.logLevel ?? .none)
}
agent.loadKey(signer, aka: name, constraints: consts)
}
Expand Down
24 changes: 18 additions & 6 deletions BlinkConfig/WebAuthnKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ import SwiftCBOR

public protocol InputPrompter {
func setPromptOnView(_ view: UIView)
func setLogger(_ logger: SSHLogPublisher, verbosity: SSHLogLevel)
}

public class WebAuthnKey: NSObject {
let rpId: String
let rawAttestationObject: Data

var termView: UIView? = nil
var log: SSHLogger? = nil
//var authAnchor: ASPresentationAnchor? = nil
var signaturePub: PassthroughSubject<Data, Error>!

Expand Down Expand Up @@ -74,6 +76,10 @@ extension WebAuthnKey: InputPrompter {
public func setPromptOnView(_ view: UIView) {
self.termView = view
}

public func setLogger(_ logger: SSHLogPublisher, verbosity: SSHLogLevel) {
self.log = SSHLogger(verbosity: verbosity, logger: logger)
}
}


Expand All @@ -86,24 +92,26 @@ extension WebAuthnKey: Signer {
// an async interface to the Signers and Constraints.
public func sign(_ message: Data, algorithm: String?) throws -> Data {
guard self.termView != nil else {
throw WebAuthnError.clientError("Prompt not configured for request")
throw WebAuthnError.clientError("Prompt not configured for request.")
}

self.log?.message("WebAuthn signature requested.", .info)

let authController = ASAuthorizationController(authorizationRequests: [self.signAuthorizationRequest(message)])
authController.delegate = self
authController.presentationContextProvider = self


if #available(iOS 16.0, *) {
let semaphore = DispatchSemaphore(value: 0)
var signature: Data? = nil
var error: Error? = nil
self.signaturePub = PassthroughSubject<Data, Error>()
// TODO Send it on main for now
// Controller needs to be displayed on main.
let cancel = Just(authController)
.receive(on: DispatchQueue.main)
.flatMap { authController in
authController.performRequests(options: .preferImmediatelyAvailableCredentials)
authController.performRequests() // options: .preferImmediatelyAvailableCredentials may suppress the UI, and it doesn't make sense in our scenario
self.log?.message("WebAuthn Controller called to perform request.", .debug)
return self.signaturePub!
}
.sink(receiveCompletion: { completion in
Expand All @@ -115,13 +123,15 @@ extension WebAuthnKey: Signer {
}
semaphore.signal()
}, receiveValue: { signature = $0 })


self.log?.message("WebAuthn Controller awaiting response.", .debug)
semaphore.wait()

guard let signature = signature else {
throw error!
}

self.log?.message("WebAuthn signature completed.", .info)
return signature
} else {
// Fallback on earlier versions
Expand All @@ -135,7 +145,8 @@ extension WebAuthnKey: ASAuthorizationControllerDelegate, ASAuthorizationControl
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {

self.log?.message("WebAuthn Controller received response.", .debug)

guard
let credentialAssertion = authorization.credential as? ASAuthorizationPublicKeyCredentialAssertion,
let rawSignature = credentialAssertion.signature
Expand All @@ -159,6 +170,7 @@ extension WebAuthnKey: ASAuthorizationControllerDelegate, ASAuthorizationControl

public func authorizationController(controller: ASAuthorizationController,
didCompleteWithError error: Error) {
self.log?.message("WebAuthn signature failed or cancelled - \(error)", .warn)
signaturePub.send(completion: .failure(error))
}

Expand Down
8 changes: 6 additions & 2 deletions SSH/SSHClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -915,15 +915,19 @@ public enum SSHLogLevel: Int {
}
}

class SSHLogger {
public class SSHLogger {
let verbosity: SSHLogLevel
let logger: SSHLogPublisher?

init(verbosity level: SSHLogLevel, logger: SSHLogPublisher?) {
public init(verbosity level: SSHLogLevel, logger: SSHLogPublisher?) {
self.verbosity = level
self.logger = logger
}

public func message(_ message: String, _ level: SSHLogLevel) {
self.message(message, Int32(level.rawValue))
}

func message(_ message: String, _ level: Int32) {
if verbosity.rawValue >= level {
print(message)
Expand Down

0 comments on commit 3cec085

Please sign in to comment.