diff --git a/SecretAgent/Agent.swift b/SecretAgent/Agent.swift new file mode 100644 index 0000000..c00317a --- /dev/null +++ b/SecretAgent/Agent.swift @@ -0,0 +1,130 @@ +import Foundation +import CryptoKit +import OSLog +import SecretKit +import SecretAgentKit + +class Agent { + + fileprivate let store: StoreType + fileprivate let notifier: Notifier + + public init(store: StoreType, notifier: Notifier) { + os_log(.debug, "Agent is running") + self.store = store + self.notifier = notifier + } + +} + +extension Agent { + + func handle(fileHandle: FileHandle) { + os_log(.debug, "Agent handling new data") + let data = fileHandle.availableData + guard !data.isEmpty else { return } + let requestTypeInt = data[4] + guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else { return } + os_log(.debug, "Agent handling request of type %@", requestType.debugDescription) + let subData = Data(data[5...]) + handle(requestType: requestType, data: subData, fileHandle: fileHandle) + } + + func handle(requestType: SSHAgent.RequestType, data: Data, fileHandle: FileHandle) { + var response = Data() + do { + switch requestType { + case .requestIdentities: + response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data) + response.append(try identities()) + os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription) + case .signRequest: + response.append(SSHAgent.ResponseType.agentSignResponse.data) + response.append(try sign(data: data)) + os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentSignResponse.debugDescription) + } + } catch { + response.removeAll() + response.append(SSHAgent.ResponseType.agentFailure.data) + os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription) + } + let full = OpenSSHKeyWriter().lengthAndData(of: response) + fileHandle.write(full) + } + +} + +extension Agent { + + func identities() throws -> Data { + var count = UInt32(store.secrets.count).bigEndian + let countData = Data(bytes: &count, count: UInt32.bitWidth/8) + var keyData = Data() + let writer = OpenSSHKeyWriter() + for secret in store.secrets { + let keyBlob = writer.data(secret: secret) + keyData.append(writer.lengthAndData(of: keyBlob)) + let curveData = OpenSSHKeyWriter.Constants.curveType.data(using: .utf8)! + keyData.append(writer.lengthAndData(of: curveData)) + } + os_log(.debug, "Agent enumerated %@ identities", store.secrets.count as NSNumber) + return countData + keyData + } + + func sign(data: Data) throws -> Data { + let reader = OpenSSHReader(data: data) + let writer = OpenSSHKeyWriter() + let hash = try reader.readNextChunk() + let matching = store.secrets.filter { secret in + hash == writer.data(secret: secret) + } + guard let secret = matching.first else { + throw AgentError.noMatchingKey + } + let dataToSign = try reader.readNextChunk() + let derSignature = try store.sign(data: dataToSign, with: secret) + // TODO: Move this + notifier.notify(accessTo: secret) + let curveData = OpenSSHKeyWriter.Constants.curveType.data(using: .utf8)! + + // Convert from DER formatted rep to raw (r||s) + let signature = try CryptoKit.P256.Signing.ECDSASignature(derRepresentation: derSignature) + let rawLength = signature.rawRepresentation.count/2 + let r = signature.rawRepresentation[0.. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SecretAgent/Info.plist b/SecretAgent/Info.plist new file mode 100644 index 0000000..57be50e --- /dev/null +++ b/SecretAgent/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + + NSHumanReadableCopyright + Copyright © 2020 Max Goedjen. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + NSSupportsAutomaticTermination + + NSSupportsSuddenTermination + + + diff --git a/SecretAgent/Notifier.swift b/SecretAgent/Notifier.swift new file mode 100644 index 0000000..19d93ea --- /dev/null +++ b/SecretAgent/Notifier.swift @@ -0,0 +1,22 @@ +import Foundation +import SecretKit +import UserNotifications + +class Notifier { + + func prompt() { + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.requestAuthorization(options: .alert) { _, _ in + } + } + + func notify(accessTo secret: SecretType) { + let notificationCenter = UNUserNotificationCenter.current() + let notificationContent = UNMutableNotificationContent() + notificationContent.title = "Signed Request" + notificationContent.body = "\(secret.name) was used to sign a request." + let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil) + notificationCenter.add(request, withCompletionHandler: nil) + } + +} diff --git a/SecretAgent/Preview Content/Preview Assets.xcassets/Contents.json b/SecretAgent/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SecretAgent/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SecretAgent/SecretAgent.entitlements b/SecretAgent/SecretAgent.entitlements new file mode 100644 index 0000000..7f8f6ca --- /dev/null +++ b/SecretAgent/SecretAgent.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + keychain-access-groups + + $(AppIdentifierPrefix)com.maxgoedjen.Secretive + + + diff --git a/SecretAgent/SocketController.swift b/SecretAgent/SocketController.swift new file mode 100644 index 0000000..9d811fb --- /dev/null +++ b/SecretAgent/SocketController.swift @@ -0,0 +1,67 @@ +import Foundation +import OSLog + +class SocketController { + + fileprivate var fileHandle: FileHandle? + fileprivate var port: SocketPort? + var handler: ((FileHandle) -> Void)? + + init(path: String) { + os_log(.debug, "Socket controller setting up at %@", path) + if let _ = try? FileManager.default.removeItem(atPath: path) { + os_log(.debug, "Socket controller removed existing socket") + } + let exists = FileManager.default.fileExists(atPath: path) + assert(!exists) + os_log(.debug, "Socket controller path is clear") + port = socketPort(at: path) + configureSocket(at: path) + os_log(.debug, "Socket listening at %@", path) + } + + func configureSocket(at path: String) { + guard let port = port else { return } + fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true) + NotificationCenter.default.addObserver(self, selector: #selector(handleConnectionAccept(notification:)), name: .NSFileHandleConnectionAccepted, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleConnectionDataAvailable(notification:)), name: .NSFileHandleDataAvailable, object: nil) + fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!]) + } + + func socketPort(at path: String) -> SocketPort { + var addr = sockaddr_un() + addr.sun_family = sa_family_t(AF_UNIX) + + var len: Int = 0 + _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in + path.withCString { cstring in + len = strlen(cstring) + strncpy(pointer, cstring, len) + } + } + addr.sun_len = UInt8(len+2) + + var data: Data! + _ = withUnsafePointer(to: &addr) { pointer in + data = Data(bytes: pointer, count: MemoryLayout.size) + } + + return SocketPort(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)! + } + + @objc func handleConnectionAccept(notification: Notification) { + os_log(.debug, "Socket controller accepted connection") + guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return } + handler?(new) + new.waitForDataInBackgroundAndNotify() + fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!]) + } + + @objc func handleConnectionDataAvailable(notification: Notification) { + os_log(.debug, "Socket controller has new data available") + guard let new = notification.object as? FileHandle else { return } + os_log(.debug, "Socket controller received new file handle") + handler?(new) + } + +} diff --git a/SecretAgentKit/Info.plist b/SecretAgentKit/Info.plist new file mode 100644 index 0000000..d5ff04c --- /dev/null +++ b/SecretAgentKit/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Max Goedjen. All rights reserved. + + diff --git a/SecretAgentKit/OpenSSHReader.swift b/SecretAgentKit/OpenSSHReader.swift new file mode 100644 index 0000000..90f878c --- /dev/null +++ b/SecretAgentKit/OpenSSHReader.swift @@ -0,0 +1,25 @@ +import Foundation + +public class OpenSSHReader { + + var remaining: Data + + public init(data: Data) { + remaining = Data(data) + } + + public func readNextChunk() throws -> Data { + let lengthRange = 0..<(UInt32.bitWidth/8) + let lengthChunk = remaining[lengthRange] + remaining.removeSubrange(lengthRange) + let littleEndianLength = lengthChunk.withUnsafeBytes { pointer in + return pointer.load(as: UInt32.self) + } + let length = Int(littleEndianLength.bigEndian) + let dataRange = 0.. + +//! Project version number for SecretAgentKit. +FOUNDATION_EXPORT double SecretAgentKitVersionNumber; + +//! Project version string for SecretAgentKit. +FOUNDATION_EXPORT const unsigned char SecretAgentKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/SecretAgentKitTests/Info.plist b/SecretAgentKitTests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/SecretAgentKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/SecretAgentKitTests/SecretAgentKitTests.swift b/SecretAgentKitTests/SecretAgentKitTests.swift new file mode 100644 index 0000000..99711e7 --- /dev/null +++ b/SecretAgentKitTests/SecretAgentKitTests.swift @@ -0,0 +1,34 @@ +// +// SecretAgentKitTests.swift +// SecretAgentKitTests +// +// Created by Max Goedjen on 2/22/20. +// Copyright © 2020 Max Goedjen. All rights reserved. +// + +import XCTest +@testable import SecretAgentKit + +class SecretAgentKitTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/SecretKit/Common/OpenSSHKeyWriter.swift b/SecretKit/Common/OpenSSHKeyWriter.swift new file mode 100644 index 0000000..ebd25b2 --- /dev/null +++ b/SecretKit/Common/OpenSSHKeyWriter.swift @@ -0,0 +1,46 @@ +import Foundation +import CryptoKit + +// For the moment, only supports ecdsa-sha2-nistp256 keys +public struct OpenSSHKeyWriter { + + public init() { + } + + public func data(secret: SecretType) -> Data { + lengthAndData(of: Constants.curveType.data(using: .utf8)!) + + lengthAndData(of: Constants.curveIdentifier.data(using: .utf8)!) + + lengthAndData(of: secret.publicKey) + } + + public func openSSHString(secret: SecretType) -> String { + "\(Constants.curveType) \(data(secret: secret).base64EncodedString())" + } + + public func openSSHFingerprint(secret: SecretType) -> String { + Insecure.MD5.hash(data: data(secret: secret)) + .compactMap { String($0, radix: 16, uppercase: false) } + .joined(separator: ":") + } + +} + +extension OpenSSHKeyWriter { + + public func lengthAndData(of data: Data) -> Data { + let rawLength = UInt32(data.count) + var endian = rawLength.bigEndian + return Data(bytes: &endian, count: UInt32.bitWidth/8) + data + } + + public func readData() {} +} + +extension OpenSSHKeyWriter { + + public enum Constants { + public static let curveIdentifier = "nistp256" + public static let curveType = "ecdsa-sha2-nistp256" + } + +} diff --git a/SecretKit/Secret.swift b/SecretKit/Secret.swift index 2a84f5b..a4c40b5 100644 --- a/SecretKit/Secret.swift +++ b/SecretKit/Secret.swift @@ -1,5 +1,6 @@ -import Foundation - public protocol Secret: Identifiable, Hashable { - var id: String { get } + + var name: String { get } + var publicKey: Data { get } + } diff --git a/SecretKit/SecretStore.swift b/SecretKit/SecretStore.swift index 88f2eab..d944836 100644 --- a/SecretKit/SecretStore.swift +++ b/SecretKit/SecretStore.swift @@ -1,9 +1,18 @@ -import Foundation import Combine public protocol SecretStore: ObservableObject { associatedtype SecretType: Secret + var name: String { get } var secrets: [SecretType] { get } + func sign(data: Data, with secret: SecretType) throws -> Data + func delete(secret: SecretType) throws + +} + +extension NSNotification.Name { + + static let secretStoreUpdated = NSNotification.Name("com.maxgoedjen.Secretive.secretStore.updated") + } diff --git a/SecretKit/SecureEnclave/SecureEnclave.swift b/SecretKit/SecureEnclave/SecureEnclave.swift index 2536bc9..5955575 100644 --- a/SecretKit/SecureEnclave/SecureEnclave.swift +++ b/SecretKit/SecureEnclave/SecureEnclave.swift @@ -1,3 +1 @@ -import Foundation - public enum SecureEnclave {} diff --git a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift index 5d60ff8..3ab9f5f 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift @@ -5,7 +5,9 @@ extension SecureEnclave { public struct Secret: SecretKit.Secret { - public let id: String + public let id: Data + public let name: String + public let publicKey: Data } diff --git a/SecretKit/SecureEnclave/SecureEnclaveStore.swift b/SecretKit/SecureEnclave/SecureEnclaveStore.swift index b340d6c..8e9ac2e 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveStore.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveStore.swift @@ -5,17 +5,173 @@ extension SecureEnclave { public class Store: SecretStore { + public let name = NSLocalizedString("Secure Enclave", comment: "Secure Enclave") @Published public fileprivate(set) var secrets: [Secret] = [] public init() { + DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { _ in + self.reloadSecrets(notify: false) + } loadSecrets() } - fileprivate func loadSecrets() { - let secret = Secret(id: "Test") - secrets.append(secret) + // MARK: Public API + + public func create(name: String, requiresAuthentication: Bool) throws { + var accessError: SecurityError? + let flags: SecAccessControlCreateFlags + if requiresAuthentication { + flags = [.privateKeyUsage, .userPresence] + } else { + flags = .privateKeyUsage + } + let access = + SecAccessControlCreateWithFlags(kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + flags, + &accessError) as Any + if let error = accessError { + throw error.takeRetainedValue() as Error + } + + let attributes = [ + kSecAttrLabel: name, + kSecAttrKeyType: Constants.keyType, + kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, + kSecAttrApplicationTag: Constants.keyTag, + kSecPrivateKeyAttrs: [ + kSecAttrIsPermanent: true, + kSecAttrAccessControl: access + ] + ] as CFDictionary + + var privateKey: SecKey? = nil + var publicKey: SecKey? = nil + let status = SecKeyGeneratePair(attributes, &publicKey, &privateKey) + guard privateKey != nil, let pk = publicKey else { + throw KeychainError(statusCode: status) + } + try savePublicKey(pk, name: name) + reloadSecrets() + } + + public func delete(secret: Secret) throws { + let deleteAttributes = [ + kSecClass: kSecClassKey, + kSecAttrApplicationLabel: secret.id as CFData + ] as CFDictionary + let status = SecItemDelete(deleteAttributes) + if status != errSecSuccess { + throw KeychainError(statusCode: status) + } + reloadSecrets() + } + + public func sign(data: Data, with secret: SecretType) throws -> Data { + let attributes = [ + kSecClass: kSecClassKey, + kSecAttrKeyClass: kSecAttrKeyClassPrivate, + kSecAttrApplicationLabel: secret.id as CFData, + kSecAttrKeyType: Constants.keyType, + kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, + kSecAttrApplicationTag: Constants.keyTag, + kSecReturnRef: true + ] as CFDictionary + var untyped: CFTypeRef? + let status = SecItemCopyMatching(attributes, &untyped) + if status != errSecSuccess { + throw KeychainError(statusCode: status) + } + guard let untypedSafe = untyped else { + throw KeychainError(statusCode: errSecSuccess) + } + let key = untypedSafe as! SecKey + var signError: SecurityError? + guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &signError) else { + throw SigningError(error: signError) + } + return signature as Data } } } + +extension SecureEnclave.Store { + + fileprivate func reloadSecrets(notify: Bool = true) { + secrets.removeAll() + loadSecrets() + if notify { + DistributedNotificationCenter.default().post(name: .secretStoreUpdated, object: nil) + } + } + + fileprivate func loadSecrets() { + let attributes = [ + kSecClass: kSecClassKey, + kSecAttrKeyType: SecureEnclave.Constants.keyType, + kSecAttrApplicationTag: SecureEnclave.Constants.keyTag, + kSecAttrKeyClass: kSecAttrKeyClassPublic, + kSecReturnRef: true, + kSecMatchLimit: kSecMatchLimitAll, + kSecReturnAttributes: true + ] as CFDictionary + var untyped: CFTypeRef? + SecItemCopyMatching(attributes, &untyped) + guard let typed = untyped as? [[CFString: Any]] else { return } + let wrapped: [SecureEnclave.Secret] = typed.map { + let name = $0[kSecAttrLabel] as? String ?? "Unnamed" + let id = $0[kSecAttrApplicationLabel] as! Data + let publicKeyRef = $0[kSecValueRef] as! SecKey + let publicKeyAttributes = SecKeyCopyAttributes(publicKeyRef) as! [CFString: Any] + let publicKey = publicKeyAttributes[kSecValueData] as! Data + return SecureEnclave.Secret(id: id, name: name, publicKey: publicKey) + } + secrets.append(contentsOf: wrapped) + } + + fileprivate func savePublicKey(_ publicKey: SecKey, name: String) throws { + let attributes = [ + kSecClass: kSecClassKey, + kSecAttrKeyType: SecureEnclave.Constants.keyType, + kSecAttrKeyClass: kSecAttrKeyClassPublic, + kSecAttrApplicationTag: SecureEnclave.Constants.keyTag, + kSecValueRef: publicKey, + kSecAttrIsPermanent: true, + kSecReturnData: true, + kSecAttrLabel: name + ] as CFDictionary + let status = SecItemAdd(attributes, nil) + if status != errSecSuccess { + throw SecureEnclave.KeychainError(statusCode: status) + } + } +} + +extension SecureEnclave { + + public struct KeychainError: Error { + public let statusCode: OSStatus + } + + public struct SigningError: Error { + public let error: SecurityError? + } + +} + +extension SecureEnclave { + + public typealias SecurityError = Unmanaged + +} + +extension SecureEnclave { + + enum Constants { + fileprivate static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)! as CFData + fileprivate static let keyType = kSecAttrKeyTypeECSECPrimeRandom + } + +} diff --git a/SecretKit/SmartCard/SmartCard.swift b/SecretKit/SmartCard/SmartCard.swift new file mode 100644 index 0000000..db0eced --- /dev/null +++ b/SecretKit/SmartCard/SmartCard.swift @@ -0,0 +1 @@ +public enum SmartCard {} diff --git a/SecretKit/SmartCard/SmartCardSecret.swift b/SecretKit/SmartCard/SmartCardSecret.swift new file mode 100644 index 0000000..6444470 --- /dev/null +++ b/SecretKit/SmartCard/SmartCardSecret.swift @@ -0,0 +1,14 @@ +import Foundation +import Combine + +extension SmartCard { + + public struct Secret: SecretKit.Secret { + + public let id: Data + public let name: String + public let publicKey: Data + + } + +} diff --git a/SecretKit/SmartCard/SmartCardStore.swift b/SecretKit/SmartCard/SmartCardStore.swift new file mode 100644 index 0000000..da243c6 --- /dev/null +++ b/SecretKit/SmartCard/SmartCardStore.swift @@ -0,0 +1,29 @@ +import Foundation +import Security +import CryptoTokenKit + +extension SmartCard { + + public class Store: SecretStore { + + // TODO: Read actual smart card name, eg "YubiKey 5c" + public let name = NSLocalizedString("Smart Card", comment: "Smart Card") + @Published public fileprivate(set) var secrets: [Secret] = [] + fileprivate let watcher = TKTokenWatcher() + + public init() { + watcher.setInsertionHandler { (string) in + print(string) + } + print(watcher.tokenIDs) + } + + public func sign(data: Data, with secret: SmartCard.Secret) throws -> Data { + fatalError() + } + + public func delete(secret: SmartCard.Secret) throws { + } + + } +} diff --git a/Secretive.xcodeproj/project.pbxproj b/Secretive.xcodeproj/project.pbxproj index 57b1965..bca60b9 100644 --- a/Secretive.xcodeproj/project.pbxproj +++ b/Secretive.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; }; + 5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; }; 50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* AppDelegate.swift */; }; 50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; }; 50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; }; @@ -24,9 +26,46 @@ 50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */; }; 50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */; }; 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; }; + 5099A02423FD2AAA0062B6F2 /* CreateSecureEnclaveSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02323FD2AAA0062B6F2 /* CreateSecureEnclaveSecretView.swift */; }; + 5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02623FE34FA0062B6F2 /* SmartCard.swift */; }; + 5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02823FE35240062B6F2 /* SmartCardStore.swift */; }; + 5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02A23FE352C0062B6F2 /* SmartCardSecret.swift */; }; + 5099A02E23FE56E10062B6F2 /* OpenSSHKeyWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */; }; + 5099A075240242BA0062B6F2 /* SecretAgentKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; }; + 5099A07C240242BA0062B6F2 /* SecretAgentKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */; }; + 5099A07E240242BA0062B6F2 /* SecretAgentKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 5099A06E240242BA0062B6F2 /* SecretAgentKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */; }; + 50A3B79124026B7600D209EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79024026B7600D209EA /* Assets.xcassets */; }; + 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79324026B7600D209EA /* Preview Assets.xcassets */; }; + 50A3B79724026B7600D209EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79524026B7600D209EA /* Main.storyboard */; }; + 50A3B7A024026B9900D209EA /* SocketController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79D24026B9900D209EA /* SocketController.swift */; }; + 50A3B7A224026B9900D209EA /* Agent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79F24026B9900D209EA /* Agent.swift */; }; + 50A5C18C240E4B4B00E2996C /* SecretAgentKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; }; + 50A5C18D240E4B4B00E2996C /* SecretAgentKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 50A5C18F240E4B4C00E2996C /* SecretKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50617DA823FCE4AB0099B055 /* SecretKit.framework */; }; + 50A5C190240E4B4C00E2996C /* SecretKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 50617DA823FCE4AB0099B055 /* SecretKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A2240789E600AF2719 /* OpenSSHReader.swift */; }; + 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; }; + 50C385A9240B636500AF2719 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A8240B636500AF2719 /* SetupView.swift */; }; + 50C385AA240E434400AF2719 /* SecretAgent.app in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; }; + 50C385B0240E439A00AF2719 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 5018F5482402736A002EB505 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5099A06B240242BA0062B6F2; + remoteInfo = SecretAgentKit; + }; + 5018F54A2402736A002EB505 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 50617DA723FCE4AB0099B055; + remoteInfo = SecretKit; + }; 50617D9523FCE48E0099B055 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 50617D7723FCE48D0099B055 /* Project object */; @@ -55,6 +94,27 @@ remoteGlobalIDString = 50617DA723FCE4AB0099B055; remoteInfo = SecretKit; }; + 5099A076240242BA0062B6F2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5099A06B240242BA0062B6F2; + remoteInfo = SecretAgentKit; + }; + 5099A078240242BA0062B6F2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 50617D7E23FCE48D0099B055; + remoteInfo = Secretive; + }; + 50C385AB240E434400AF2719 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 50A3B78924026B7500D209EA; + remoteInfo = SecretAgent; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -69,9 +129,33 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + 50A5C18E240E4B4B00E2996C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 50A5C18D240E4B4B00E2996C /* SecretAgentKit.framework in Embed Frameworks */, + 50A5C190240E4B4C00E2996C /* SecretKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 50C385AF240E438B00AF2719 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Contents/Library/LoginItems; + dstSubfolderSpec = 1; + files = ( + 50C385B0240E439A00AF2719 /* SecretAgent.app in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = ""; }; 50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50617D8223FCE48E0099B055 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -95,6 +179,29 @@ 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclaveSecret.swift; sourceTree = ""; }; 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclave.swift; sourceTree = ""; }; 50617DD123FCEFA90099B055 /* PreviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStore.swift; sourceTree = ""; }; + 5099A02323FD2AAA0062B6F2 /* CreateSecureEnclaveSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecureEnclaveSecretView.swift; sourceTree = ""; }; + 5099A02623FE34FA0062B6F2 /* SmartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCard.swift; sourceTree = ""; }; + 5099A02823FE35240062B6F2 /* SmartCardStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCardStore.swift; sourceTree = ""; }; + 5099A02A23FE352C0062B6F2 /* SmartCardSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCardSecret.swift; sourceTree = ""; }; + 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHKeyWriter.swift; sourceTree = ""; }; + 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SecretAgentKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5099A06E240242BA0062B6F2 /* SecretAgentKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecretAgentKit.h; sourceTree = ""; }; + 5099A06F240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecretAgentKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretAgentKitTests.swift; sourceTree = ""; }; + 5099A07D240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHAgentProtocol.swift; sourceTree = ""; }; + 50A3B78A24026B7500D209EA /* SecretAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecretAgent.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 50A3B79024026B7600D209EA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 50A3B79324026B7600D209EA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 50A3B79624026B7600D209EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 50A3B79824026B7600D209EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50A3B79924026B7600D209EA /* SecretAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgent.entitlements; sourceTree = ""; }; + 50A3B79D24026B9900D209EA /* SocketController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketController.swift; sourceTree = ""; }; + 50A3B79F24026B9900D209EA /* Agent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Agent.swift; sourceTree = ""; }; + 50C385A2240789E600AF2719 /* OpenSSHReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenSSHReader.swift; path = SecretAgentKit/OpenSSHReader.swift; sourceTree = SOURCE_ROOT; }; + 50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = ""; }; + 50C385A8240B636500AF2719 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -128,6 +235,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5099A069240242BA0062B6F2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5099A071240242BA0062B6F2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5099A075240242BA0062B6F2 /* SecretAgentKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 50A3B78724026B7500D209EA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 50A5C18C240E4B4B00E2996C /* SecretAgentKit.framework in Frameworks */, + 50A5C18F240E4B4C00E2996C /* SecretKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -138,7 +269,11 @@ 50617D9723FCE48E0099B055 /* SecretiveTests */, 50617DA923FCE4AB0099B055 /* SecretKit */, 50617DB623FCE4AB0099B055 /* SecretKitTests */, + 50A3B78B24026B7500D209EA /* SecretAgent */, + 5099A06D240242BA0062B6F2 /* SecretAgentKit */, + 5099A07A240242BA0062B6F2 /* SecretAgentKitTests */, 50617D8023FCE48E0099B055 /* Products */, + 5099A08B240243730062B6F2 /* Frameworks */, ); sourceTree = ""; }; @@ -149,6 +284,9 @@ 50617D9423FCE48E0099B055 /* SecretiveTests.xctest */, 50617DA823FCE4AB0099B055 /* SecretKit.framework */, 50617DB023FCE4AB0099B055 /* SecretKitTests.xctest */, + 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */, + 5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */, + 50A3B78A24026B7500D209EA /* SecretAgent.app */, ); name = Products; sourceTree = ""; @@ -158,6 +296,9 @@ children = ( 50617D8223FCE48E0099B055 /* AppDelegate.swift */, 50617D8423FCE48E0099B055 /* ContentView.swift */, + 50C385A42407A76D00AF2719 /* SecretDetailView.swift */, + 5099A02323FD2AAA0062B6F2 /* CreateSecureEnclaveSecretView.swift */, + 50C385A8240B636500AF2719 /* SetupView.swift */, 50617D8623FCE48E0099B055 /* Assets.xcassets */, 50617D8B23FCE48E0099B055 /* Main.storyboard */, 50617D8E23FCE48E0099B055 /* Info.plist */, @@ -191,7 +332,9 @@ 50617DAA23FCE4AB0099B055 /* SecretKit.h */, 50617DCA23FCECA10099B055 /* Secret.swift */, 50617DC623FCE4EA0099B055 /* SecretStore.swift */, + 5099A02C23FE56D70062B6F2 /* Common */, 50617DCC23FCECEE0099B055 /* SecureEnclave */, + 5099A02523FE34DE0062B6F2 /* SmartCard */, 50617DAB23FCE4AB0099B055 /* Info.plist */, ); path = SecretKit; @@ -216,6 +359,75 @@ path = SecureEnclave; sourceTree = ""; }; + 5099A02523FE34DE0062B6F2 /* SmartCard */ = { + isa = PBXGroup; + children = ( + 5099A02623FE34FA0062B6F2 /* SmartCard.swift */, + 5099A02A23FE352C0062B6F2 /* SmartCardSecret.swift */, + 5099A02823FE35240062B6F2 /* SmartCardStore.swift */, + ); + path = SmartCard; + sourceTree = ""; + }; + 5099A02C23FE56D70062B6F2 /* Common */ = { + isa = PBXGroup; + children = ( + 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */, + 50C385A2240789E600AF2719 /* OpenSSHReader.swift */, + ); + path = Common; + sourceTree = ""; + }; + 5099A06D240242BA0062B6F2 /* SecretAgentKit */ = { + isa = PBXGroup; + children = ( + 5099A06E240242BA0062B6F2 /* SecretAgentKit.h */, + 5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */, + 5099A06F240242BA0062B6F2 /* Info.plist */, + ); + path = SecretAgentKit; + sourceTree = ""; + }; + 5099A07A240242BA0062B6F2 /* SecretAgentKitTests */ = { + isa = PBXGroup; + children = ( + 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */, + 5099A07D240242BA0062B6F2 /* Info.plist */, + ); + path = SecretAgentKitTests; + sourceTree = ""; + }; + 5099A08B240243730062B6F2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 50A3B78B24026B7500D209EA /* SecretAgent */ = { + isa = PBXGroup; + children = ( + 50020BAF24064869003D4025 /* AppDelegate.swift */, + 50A3B79F24026B9900D209EA /* Agent.swift */, + 5018F54E24064786002EB505 /* Notifier.swift */, + 50A3B79D24026B9900D209EA /* SocketController.swift */, + 50A3B79024026B7600D209EA /* Assets.xcassets */, + 50A3B79524026B7600D209EA /* Main.storyboard */, + 50A3B79824026B7600D209EA /* Info.plist */, + 50A3B79924026B7600D209EA /* SecretAgent.entitlements */, + 50A3B79224026B7600D209EA /* Preview Content */, + ); + path = SecretAgent; + sourceTree = ""; + }; + 50A3B79224026B7600D209EA /* Preview Content */ = { + isa = PBXGroup; + children = ( + 50A3B79324026B7600D209EA /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -227,6 +439,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5099A067240242BA0062B6F2 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5099A07E240242BA0062B6F2 /* SecretAgentKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -238,11 +458,13 @@ 50617D7C23FCE48D0099B055 /* Frameworks */, 50617D7D23FCE48D0099B055 /* Resources */, 50617DBF23FCE4AB0099B055 /* Embed Frameworks */, + 50C385AF240E438B00AF2719 /* CopyFiles */, ); buildRules = ( ); dependencies = ( 50617DBC23FCE4AB0099B055 /* PBXTargetDependency */, + 50C385AC240E434400AF2719 /* PBXTargetDependency */, ); name = Secretive; productName = Secretive; @@ -304,13 +526,70 @@ productReference = 50617DB023FCE4AB0099B055 /* SecretKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 5099A06B240242BA0062B6F2 /* SecretAgentKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5099A083240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKit" */; + buildPhases = ( + 5099A067240242BA0062B6F2 /* Headers */, + 5099A068240242BA0062B6F2 /* Sources */, + 5099A069240242BA0062B6F2 /* Frameworks */, + 5099A06A240242BA0062B6F2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SecretAgentKit; + productName = SecretAgentKit; + productReference = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 5099A073240242BA0062B6F2 /* SecretAgentKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5099A086240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKitTests" */; + buildPhases = ( + 5099A070240242BA0062B6F2 /* Sources */, + 5099A071240242BA0062B6F2 /* Frameworks */, + 5099A072240242BA0062B6F2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5099A077240242BA0062B6F2 /* PBXTargetDependency */, + 5099A079240242BA0062B6F2 /* PBXTargetDependency */, + ); + name = SecretAgentKitTests; + productName = SecretAgentKitTests; + productReference = 5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 50A3B78924026B7500D209EA /* SecretAgent */ = { + isa = PBXNativeTarget; + buildConfigurationList = 50A3B79A24026B7600D209EA /* Build configuration list for PBXNativeTarget "SecretAgent" */; + buildPhases = ( + 50A3B78624026B7500D209EA /* Sources */, + 50A3B78724026B7500D209EA /* Frameworks */, + 50A3B78824026B7500D209EA /* Resources */, + 50A5C18E240E4B4B00E2996C /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 5018F5492402736A002EB505 /* PBXTargetDependency */, + 5018F54B2402736A002EB505 /* PBXTargetDependency */, + ); + name = SecretAgent; + productName = SecretAgent; + productReference = 50A3B78A24026B7500D209EA /* SecretAgent.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 50617D7723FCE48D0099B055 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1130; + LastSwiftUpdateCheck = 1140; LastUpgradeCheck = 1130; ORGANIZATIONNAME = "Max Goedjen"; TargetAttributes = { @@ -329,6 +608,17 @@ CreatedOnToolsVersion = 11.3; TestTargetID = 50617D7E23FCE48D0099B055; }; + 5099A06B240242BA0062B6F2 = { + CreatedOnToolsVersion = 11.4; + LastSwiftMigration = 1140; + }; + 5099A073240242BA0062B6F2 = { + CreatedOnToolsVersion = 11.4; + TestTargetID = 50617D7E23FCE48D0099B055; + }; + 50A3B78924026B7500D209EA = { + CreatedOnToolsVersion = 11.4; + }; }; }; buildConfigurationList = 50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */; @@ -346,8 +636,11 @@ targets = ( 50617D7E23FCE48D0099B055 /* Secretive */, 50617D9323FCE48E0099B055 /* SecretiveTests */, + 50A3B78924026B7500D209EA /* SecretAgent */, 50617DA723FCE4AB0099B055 /* SecretKit */, 50617DAF23FCE4AB0099B055 /* SecretKitTests */, + 5099A06B240242BA0062B6F2 /* SecretAgentKit */, + 5099A073240242BA0062B6F2 /* SecretAgentKitTests */, ); }; /* End PBXProject section */ @@ -360,6 +653,7 @@ 50617D8D23FCE48E0099B055 /* Main.storyboard in Resources */, 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */, 50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */, + 50C385AA240E434400AF2719 /* SecretAgent.app in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -384,6 +678,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5099A06A240242BA0062B6F2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5099A072240242BA0062B6F2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 50A3B78824026B7500D209EA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 50A3B79724026B7600D209EA /* Main.storyboard in Resources */, + 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */, + 50A3B79124026B7600D209EA /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -391,8 +709,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 50C385A9240B636500AF2719 /* SetupView.swift in Sources */, 50617D8523FCE48E0099B055 /* ContentView.swift in Sources */, 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */, + 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */, + 5099A02423FD2AAA0062B6F2 /* CreateSecureEnclaveSecretView.swift in Sources */, 50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -410,10 +731,15 @@ buildActionMask = 2147483647; files = ( 50617DC723FCE4EA0099B055 /* SecretStore.swift in Sources */, + 5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */, 50617DCB23FCECA10099B055 /* Secret.swift in Sources */, + 5099A02E23FE56E10062B6F2 /* OpenSSHKeyWriter.swift in Sources */, 50617DC923FCE50E0099B055 /* SecureEnclaveStore.swift in Sources */, 50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */, 50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */, + 5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */, + 5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */, + 50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -425,9 +751,46 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5099A068240242BA0062B6F2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5099A070240242BA0062B6F2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5099A07C240242BA0062B6F2 /* SecretAgentKitTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 50A3B78624026B7500D209EA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 50020BB024064869003D4025 /* AppDelegate.swift in Sources */, + 5018F54F24064786002EB505 /* Notifier.swift in Sources */, + 50A3B7A224026B9900D209EA /* Agent.swift in Sources */, + 50A3B7A024026B9900D209EA /* SocketController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 5018F5492402736A002EB505 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5099A06B240242BA0062B6F2 /* SecretAgentKit */; + targetProxy = 5018F5482402736A002EB505 /* PBXContainerItemProxy */; + }; + 5018F54B2402736A002EB505 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 50617DA723FCE4AB0099B055 /* SecretKit */; + targetProxy = 5018F54A2402736A002EB505 /* PBXContainerItemProxy */; + }; 50617D9623FCE48E0099B055 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 50617D7E23FCE48D0099B055 /* Secretive */; @@ -448,6 +811,21 @@ target = 50617DA723FCE4AB0099B055 /* SecretKit */; targetProxy = 50617DBB23FCE4AB0099B055 /* PBXContainerItemProxy */; }; + 5099A077240242BA0062B6F2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5099A06B240242BA0062B6F2 /* SecretAgentKit */; + targetProxy = 5099A076240242BA0062B6F2 /* PBXContainerItemProxy */; + }; + 5099A079240242BA0062B6F2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 50617D7E23FCE48D0099B055 /* Secretive */; + targetProxy = 5099A078240242BA0062B6F2 /* PBXContainerItemProxy */; + }; + 50C385AC240E434400AF2719 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 50A3B78924026B7500D209EA /* SecretAgent */; + targetProxy = 50C385AB240E434400AF2719 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -459,6 +837,14 @@ name = Main.storyboard; sourceTree = ""; }; + 50A3B79524026B7600D209EA /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 50A3B79624026B7600D209EA /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -581,6 +967,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Secretive/Secretive.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\""; @@ -605,6 +992,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Secretive/Secretive.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\""; @@ -686,7 +1074,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretKit; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -715,7 +1103,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretKit; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; @@ -764,6 +1152,151 @@ }; name = Release; }; + 5099A084240242BA0062B6F2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = Z72PRUAWF6; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SecretAgentKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 5099A085240242BA0062B6F2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = Z72PRUAWF6; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SecretAgentKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 5099A087240242BA0062B6F2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = Z72PRUAWF6; + INFOPLIST_FILE = SecretAgentKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive"; + }; + name = Debug; + }; + 5099A088240242BA0062B6F2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = Z72PRUAWF6; + INFOPLIST_FILE = SecretAgentKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Secretive.app/Contents/MacOS/Secretive"; + }; + name = Release; + }; + 50A3B79B24026B7600D209EA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\""; + DEVELOPMENT_TEAM = Z72PRUAWF6; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = SecretAgent/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 50A3B79C24026B7600D209EA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\""; + DEVELOPMENT_TEAM = Z72PRUAWF6; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = SecretAgent/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -812,6 +1345,33 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 5099A083240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5099A084240242BA0062B6F2 /* Debug */, + 5099A085240242BA0062B6F2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5099A086240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5099A087240242BA0062B6F2 /* Debug */, + 5099A088240242BA0062B6F2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 50A3B79A24026B7600D209EA /* Build configuration list for PBXNativeTarget "SecretAgent" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 50A3B79B24026B7600D209EA /* Debug */, + 50A3B79C24026B7600D209EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 50617D7723FCE48D0099B055 /* Project object */; diff --git a/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgent.xcscheme b/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgent.xcscheme new file mode 100644 index 0000000..aac72c9 --- /dev/null +++ b/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgent.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgentKit.xcscheme b/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgentKit.xcscheme new file mode 100644 index 0000000..ed81ba6 --- /dev/null +++ b/Secretive.xcodeproj/xcshareddata/xcschemes/SecretAgentKit.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretive.xcodeproj/xcshareddata/xcschemes/SecretKit.xcscheme b/Secretive.xcodeproj/xcshareddata/xcschemes/SecretKit.xcscheme new file mode 100644 index 0000000..039c03b --- /dev/null +++ b/Secretive.xcodeproj/xcshareddata/xcschemes/SecretKit.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme b/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme new file mode 100644 index 0000000..f75ced2 --- /dev/null +++ b/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretive/AppDelegate.swift b/Secretive/AppDelegate.swift index f540eea..c6092ab 100644 --- a/Secretive/AppDelegate.swift +++ b/Secretive/AppDelegate.swift @@ -6,13 +6,14 @@ import SecretKit class AppDelegate: NSObject, NSApplicationDelegate { var window: NSWindow! - + @IBOutlet var toolbar: NSToolbar! + let secureEnclave = SecureEnclave.Store() + let yubikey = SmartCard.Store() func applicationDidFinishLaunching(_ aNotification: Notification) { - let secureEnclave = SecureEnclave.Store() let contentView = ContentView(store: secureEnclave) - +// try! secureEnclave.create(name: "SecretiveTest") // Create the window and set the content view. window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), @@ -22,12 +23,57 @@ class AppDelegate: NSObject, NSApplicationDelegate { window.setFrameAutosaveName("Main Window") window.contentView = NSHostingView(rootView: contentView) window.makeKeyAndOrderFront(nil) + window.titleVisibility = .hidden + window.toolbar = toolbar + let plus = NSTitlebarAccessoryViewController() + plus.view = NSButton(image: NSImage(named: NSImage.addTemplateName)!, target: self, action: #selector(add(sender:))) + plus.layoutAttribute = .right + window.addTitlebarAccessoryViewController(plus) + runSetupIfNeeded() } - func applicationWillTerminate(_ aNotification: Notification) { - // Insert code here to tear down your application + @IBAction func add(sender: AnyObject?) { + var addWindow: NSWindow! + let addView = CreateSecureEnclaveSecretView(store: secureEnclave) { + self.window.endSheet(addWindow) + } + addWindow = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false) + addWindow.contentView = NSHostingView(rootView: addView) + window.beginSheet(addWindow, completionHandler: nil) } + @IBAction func runSetup(sender: AnyObject?) { + let setupWindow = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false) + let setupView = SetupView() { success in + self.window.endSheet(setupWindow) + } + setupWindow.contentView = NSHostingView(rootView: setupView) + window.beginSheet(setupWindow, completionHandler: nil) + } } +extension AppDelegate { + + func runSetupIfNeeded() { + if !UserDefaults.standard.bool(forKey: Constants.defaultsHasRunSetup) { + UserDefaults.standard.set(true, forKey: Constants.defaultsHasRunSetup) + runSetup(sender: nil) + } + } + +} + +extension AppDelegate { + + enum Constants { + static let defaultsHasRunSetup = "defaultsHasRunSetup" + } + +} diff --git a/Secretive/Base.lproj/Main.storyboard b/Secretive/Base.lproj/Main.storyboard index 1aed238..9055c1c 100644 --- a/Secretive/Base.lproj/Main.storyboard +++ b/Secretive/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -60,573 +61,15 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -659,6 +102,13 @@ + + + + + + + @@ -673,7 +123,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretive/ContentView.swift b/Secretive/ContentView.swift index 51d49bf..ae2edaa 100644 --- a/Secretive/ContentView.swift +++ b/Secretive/ContentView.swift @@ -1,11 +1,3 @@ -// -// ContentView.swift -// Secretive -// -// Created by Max Goedjen on 2/18/20. -// Copyright © 2020 Max Goedjen. All rights reserved. -// - import SwiftUI import SecretKit @@ -13,27 +5,36 @@ struct ContentView: View { @ObservedObject var store: StoreType - @State var pk: String = "" - var body: some View { - HSplitView { + NavigationView { List { - ForEach(store.secrets) { secret in - Text(secret.id) + Section(header: Text(store.name)) { + ForEach(store.secrets) { secret in + NavigationLink(destination: SecretDetailView(secret: secret)) { + Text(secret.name) + }.contextMenu { + Button(action: { self.delete(secret: secret) }) { + Text("Delete") + } + } + } } - }.listStyle(SidebarListStyle()) - Form { - Text("Public Key") } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) + .listStyle(SidebarListStyle()) + .frame(minWidth: 100, idealWidth: 240) + }.navigationViewStyle(DoubleColumnNavigationViewStyle()) } -} + func delete(secret: StoreType.SecretType) { + // TODO: Add "type the name of the key to delete" dialogue + try! store.delete(secret: secret) + } + +} + struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(store: Preview.Store(numberOfRandomSecrets: 10)) } } - diff --git a/Secretive/CreateSecureEnclaveSecretView.swift b/Secretive/CreateSecureEnclaveSecretView.swift new file mode 100644 index 0000000..2179e4a --- /dev/null +++ b/Secretive/CreateSecureEnclaveSecretView.swift @@ -0,0 +1,43 @@ +import SwiftUI +import SecretKit + +struct CreateSecureEnclaveSecretView: View { + + @ObservedObject var store: SecureEnclave.Store + + @State var name = "" + @State var requiresAuthentication = true + + var dismissalBlock: () -> () + + var body: some View { + Form { + Section(header: Text("Secret Name")) { + TextField("Name", text: $name) + } + Section(header: Text("Authentication")) { + Toggle(isOn: $requiresAuthentication) { + Text("Requires Authentication (Biometrics or Password)") + } + } + Section { + HStack { + Spacer() + Button(action: dismissalBlock) { + Text("Cancel") + } + Button(action: save) { + Text("Save") + }.disabled(name.isEmpty) + } + } + } + .padding() + .onExitCommand(perform: dismissalBlock) + } + + func save() { + try! store.create(name: name, requiresAuthentication: requiresAuthentication) + dismissalBlock() + } +} diff --git a/Secretive/Preview Content/PreviewStore.swift b/Secretive/Preview Content/PreviewStore.swift index 7173bbd..cfc41d0 100644 --- a/Secretive/Preview Content/PreviewStore.swift +++ b/Secretive/Preview Content/PreviewStore.swift @@ -8,9 +8,8 @@ extension Preview { struct Secret: SecretKit.Secret { let id = UUID().uuidString - var name: String { - return id - } + let name: String + let publicKey = UUID().uuidString.data(using: .utf8)! } @@ -20,6 +19,7 @@ extension Preview { class Store: SecretStore, ObservableObject { + let name = "Preview Store" @Published var secrets: [Secret] = [] init(secrets: [Secret]) { @@ -27,10 +27,17 @@ extension Preview { } init(numberOfRandomSecrets: Int) { - let new = (0...numberOfRandomSecrets).map { _ in Secret() } + let new = (0...numberOfRandomSecrets).map { Secret(name: String(describing: $0)) } self.secrets.append(contentsOf: new) } + func sign(data: Data, with secret: Preview.Secret) throws -> Data { + return data + } + + func delete(secret: Preview.Secret) throws { + } + } } diff --git a/Secretive/SecretDetailView.swift b/Secretive/SecretDetailView.swift new file mode 100644 index 0000000..f2cc709 --- /dev/null +++ b/Secretive/SecretDetailView.swift @@ -0,0 +1,54 @@ +import SwiftUI +import SecretKit + +struct SecretDetailView: View { + + @State var secret: SecretType + let keyWriter = OpenSSHKeyWriter() + + var body: some View { + Form { + Section { + GroupBox(label: Text("Fingerprint")) { + HStack { + Text(keyWriter.openSSHFingerprint(secret: secret)) + Spacer() + } + .frame(minWidth: 150, maxWidth: .infinity) + .padding() + }.onDrag { + return NSItemProvider(item: NSData(data: self.keyWriter.openSSHFingerprint(secret: self.secret).data(using: .utf8)!), typeIdentifier: kUTTypeUTF8PlainText as String) + } + Spacer() + GroupBox(label: Text("Public Key")) { + Text(keyWriter.openSSHString(secret: secret)) + .multilineTextAlignment(.leading) + .frame(minWidth: 150, maxWidth: .infinity, minHeight: 150) + .padding() + } + .onDrag { + return NSItemProvider(item: NSData(data: self.keyString.data(using: .utf8)!), typeIdentifier: kUTTypeUTF8PlainText as String) + } + .overlay( + Button(action: copy) { + Text("Copy") + }.padding(), + alignment: .bottomTrailing) + + } + }.padding() + .frame(minHeight: 150, maxHeight: .infinity) + + } + + var keyString: String { + keyWriter.openSSHString(secret: secret) + } + + func copy() { + NSPasteboard.general.declareTypes([.string], owner: nil) + NSPasteboard.general.setString(keyString, forType: .string) + } + + +} diff --git a/Secretive/Secretive.entitlements b/Secretive/Secretive.entitlements index f2ef3ae..7f8f6ca 100644 --- a/Secretive/Secretive.entitlements +++ b/Secretive/Secretive.entitlements @@ -2,9 +2,11 @@ - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - + com.apple.security.app-sandbox + + keychain-access-groups + + $(AppIdentifierPrefix)com.maxgoedjen.Secretive + diff --git a/Secretive/SetupView.swift b/Secretive/SetupView.swift new file mode 100644 index 0000000..a0bff63 --- /dev/null +++ b/Secretive/SetupView.swift @@ -0,0 +1,129 @@ +import Foundation +import SwiftUI +import ServiceManagement + +struct SetupView: View { + + var completion: ((Bool) -> Void)? + + var body: some View { + Form { + SetupStepView(text: "Secretive needs to install a helper app to sign requests when the main app isn't running. This app is called \"SecretAgent\" and you might see it in Activity Manager from time to time.", + index: 1, + nestedView: nil, + actionText: "Install") { + self.installLaunchAgent() + } + SetupStepView(text: "You need to add a line to your shell config (.bashrc or .zshrc) telling SSH to talk to SecretAgent when it wants to authenticate. Drag this into your config file.", + index: 2, + nestedView: SetupStepCommandView(text: Constants.socketPrompt), + actionText: "Added") { + self.markAsDone() + } + HStack { + Spacer() + Button(action: { self.completion?(true) }) { + Text("Finish") + } + .padding() + } + } + } + +} + +struct SetupStepView: View { + + let text: String + let index: Int + let nestedView: NestedViewType? + @State var completed = false + let actionText: String + let action: (() -> Bool) + + var body: some View { + Section { + HStack { + ZStack { + if completed { + Circle().foregroundColor(.green) + .frame(width: 30, height: 30) + Text("✓") + .foregroundColor(.white) + .bold() + } else { + Circle().foregroundColor(.blue) + .frame(width: 30, height: 30) + Text(String(describing: index)) + .foregroundColor(.white) + .bold() + } + } + .padding() + VStack { + Text(text) + .opacity(completed ? 0.5 : 1) + .lineLimit(nil) + .frame(idealHeight: 0, maxHeight: .infinity) + if nestedView != nil { + Spacer() + nestedView! + } + } + .padding() + Button(action: { + self.completed = self.action() + }) { + Text(actionText) + }.disabled(completed) + .padding() + } + } + } +} + +struct SetupStepCommandView: View { + + let text: String + + var body: some View { + Text(text) + .font(.system(.caption, design: .monospaced)) + .lineLimit(nil) + .frame(idealHeight: 0, maxHeight: .infinity) + .padding() + .background(Color(white: 0, opacity: 0.10)) + .cornerRadius(10) + .onDrag { + return NSItemProvider(item: NSData(data: self.text.data(using: .utf8)!), typeIdentifier: kUTTypeUTF8PlainText as String) + } + } + +} + +extension SetupView { + + func installLaunchAgent() -> Bool { + SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, true) + } + + func markAsDone() -> Bool { + return true + } + +} + +extension SetupView { + + enum Constants { + static let socketPath = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String + static let socketPrompt = "export SSH_AUTH_SOCK=\(socketPath)" + } + +} + +struct SetupView_Previews: PreviewProvider { + static var previews: some View { + SetupView() + } +}