mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-07-01 17:53:36 +00:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
4a2a342670 | |||
1d147d8e34 | |||
4dcc9b113d | |||
1f7ebcfe75 | |||
b090a96975 | |||
594d855f19 | |||
89f0798576 | |||
0d74f6f561 | |||
efeb7f09a3 | |||
104199cf53 | |||
f214da98a4 | |||
95cf88d3ca | |||
04835fa437 | |||
31dfba8265 | |||
df8eedebd0 | |||
0a9ecb039e | |||
2e9a3eb90d | |||
df599fbe62 | |||
7c60e9b04b | |||
f7f182ec77 | |||
a0052e8395 | |||
10d3fa150c | |||
c5abca4099 | |||
f674d47507 | |||
1e3ece600d | |||
7481498c5b | |||
a6fbcdbe55 | |||
36e655e527 | |||
dd3acf5ae1 | |||
2257e1e190 | |||
fe16e4d9c4 | |||
1caf65c68d | |||
85eb4983bc | |||
5a2d3ecc2e | |||
32f0ed88f4 | |||
d35c58509b | |||
f810b185f0 | |||
5ef1fe996b | |||
2b5fdf541d |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: maxgoedjen
|
BIN
.github/readme/notification.png
vendored
BIN
.github/readme/notification.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.6 MiB |
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@ -45,9 +45,12 @@ jobs:
|
|||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
run: |
|
run: |
|
||||||
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//')
|
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//')
|
||||||
sed -i '' -e "s/CI_VERSION = 0.0.0/CI_VERSION = $CLEAN_TAG/g" Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Config/Config.xcconfig
|
||||||
|
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Config/Config.xcconfig
|
||||||
|
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Secretive/Credits.rtf
|
||||||
- name: Build
|
- name: Build
|
||||||
run: xcrun xcodebuild -project Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
run: xcrun xcodebuild -project Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
||||||
- name: Create ZIPs
|
- name: Create ZIPs
|
||||||
|
19
Brief/Brief.h
Normal file
19
Brief/Brief.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Brief.h
|
||||||
|
// Brief
|
||||||
|
//
|
||||||
|
// Created by Max Goedjen on 3/21/20.
|
||||||
|
// Copyright © 2020 Max Goedjen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
//! Project version number for Brief.
|
||||||
|
FOUNDATION_EXPORT double BriefVersionNumber;
|
||||||
|
|
||||||
|
//! Project version string for Brief.
|
||||||
|
FOUNDATION_EXPORT const unsigned char BriefVersionString[];
|
||||||
|
|
||||||
|
// In this header, you should import all the public headers of your framework using statements like #import <Brief/PublicHeader.h>
|
||||||
|
|
||||||
|
|
24
Brief/Info.plist
Normal file
24
Brief/Info.plist
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -1,17 +1,17 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
protocol UpdaterProtocol: ObservableObject {
|
public protocol UpdaterProtocol: ObservableObject {
|
||||||
|
|
||||||
var update: Release? { get }
|
var update: Release? { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Updater: ObservableObject, UpdaterProtocol {
|
public class Updater: ObservableObject, UpdaterProtocol {
|
||||||
|
|
||||||
@Published var update: Release?
|
@Published public var update: Release?
|
||||||
|
|
||||||
init() {
|
public init() {
|
||||||
checkForUpdates()
|
checkForUpdates()
|
||||||
let timer = Timer.scheduledTimer(withTimeInterval: 60*60*24, repeats: true) { _ in
|
let timer = Timer.scheduledTimer(withTimeInterval: 60*60*24, repeats: true) { _ in
|
||||||
self.checkForUpdates()
|
self.checkForUpdates()
|
||||||
@ -19,7 +19,7 @@ class Updater: ObservableObject, UpdaterProtocol {
|
|||||||
timer.tolerance = 60*60
|
timer.tolerance = 60*60
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkForUpdates() {
|
public func checkForUpdates() {
|
||||||
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in
|
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in
|
||||||
guard let data = data else { return }
|
guard let data = data else { return }
|
||||||
guard let release = try? JSONDecoder().decode(Release.self, from: data) else { return }
|
guard let release = try? JSONDecoder().decode(Release.self, from: data) else { return }
|
||||||
@ -27,7 +27,20 @@ class Updater: ObservableObject, UpdaterProtocol {
|
|||||||
}.resume()
|
}.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func ignore(release: Release) {
|
||||||
|
guard !release.critical else { return }
|
||||||
|
defaults.set(true, forKey: release.name)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.update = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Updater {
|
||||||
|
|
||||||
func evaluate(release: Release) {
|
func evaluate(release: Release) {
|
||||||
|
guard !userIgnored(release: release) else { return }
|
||||||
let latestVersion = semVer(from: release.name)
|
let latestVersion = semVer(from: release.name)
|
||||||
let currentVersion = semVer(from: Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String)
|
let currentVersion = semVer(from: Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String)
|
||||||
for (latest, current) in zip(latestVersion, currentVersion) {
|
for (latest, current) in zip(latestVersion, currentVersion) {
|
||||||
@ -48,6 +61,14 @@ class Updater: ObservableObject, UpdaterProtocol {
|
|||||||
return split
|
return split
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userIgnored(release: Release) -> Bool {
|
||||||
|
guard !release.critical else { return false }
|
||||||
|
return defaults.bool(forKey: release.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults: UserDefaults {
|
||||||
|
UserDefaults(suiteName: "com.maxgoedjen.Secretive.updater.ignorelist")!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Updater {
|
extension Updater {
|
||||||
@ -58,16 +79,24 @@ extension Updater {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Release: Codable {
|
public struct Release: Codable {
|
||||||
let name: String
|
|
||||||
let html_url: URL
|
public let name: String
|
||||||
let body: String
|
public let html_url: URL
|
||||||
|
public let body: String
|
||||||
|
|
||||||
|
public init(name: String, html_url: URL, body: String) {
|
||||||
|
self.name = name
|
||||||
|
self.html_url = html_url
|
||||||
|
self.body = body
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extension Release {
|
extension Release {
|
||||||
|
|
||||||
var critical: Bool {
|
public var critical: Bool {
|
||||||
return body.contains(Constants.securityContent)
|
return body.contains(Constants.securityContent)
|
||||||
}
|
}
|
||||||
|
|
@ -1 +1,2 @@
|
|||||||
CI_VERSION = 0.0.0
|
CI_VERSION = GITHUB_CI_VERSION
|
||||||
|
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
# Secretive
|
# Secretive  
|
||||||
|
|
||||||
|
|
||||||
Secretive is an app for storing and managing SSH keys in the Secure Enclave. It is inspired by the [sekey project](https://github.com/sekey/sekey), but rewritten in Swift with no external dependencies and with a handy native management app.
|
Secretive is an app for storing and managing SSH keys in the Secure Enclave. It is inspired by the [sekey project](https://github.com/sekey/sekey), but rewritten in Swift with no external dependencies and with a handy native management app.
|
||||||
|
|
||||||
<img src="/.github/readme/app.png" alt="Screenshot of Secretive" width="600">
|
<img src="/.github/readme/app.png" alt="Screenshot of Secretive" width="600">
|
||||||
|
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
### Safer Storage
|
### Safer Storage
|
||||||
@ -36,7 +38,7 @@ For non-command-line based apps, like GUI Git clients, you may need to go throug
|
|||||||
[Tower](https://www.git-tower.com/help/mac/integration/environment)
|
[Tower](https://www.git-tower.com/help/mac/integration/environment)
|
||||||
|
|
||||||
|
|
||||||
### Security Considerations
|
### Auditable Build Process
|
||||||
|
|
||||||
Builds are produced by GitHub Actions with an auditable build and release generation process. Each build has a "Document SHAs" step, which will output SHA checksums for the build produced by the GitHub Action, so you can verify that the source code for a given build corresponds to any given release.
|
Builds are produced by GitHub Actions with an auditable build and release generation process. Each build has a "Document SHAs" step, which will output SHA checksums for the build produced by the GitHub Action, so you can verify that the source code for a given build corresponds to any given release.
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
import OSLog
|
||||||
|
import Combine
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import OSLog
|
import Brief
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
@ -12,25 +14,27 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
list.add(store: SmartCard.Store())
|
list.add(store: SmartCard.Store())
|
||||||
return list
|
return list
|
||||||
}()
|
}()
|
||||||
|
let updater = Updater()
|
||||||
let notifier = Notifier()
|
let notifier = Notifier()
|
||||||
lazy var agent: Agent = {
|
lazy var agent: Agent = {
|
||||||
Agent(storeList: storeList/*, notifier: notifier*/)
|
Agent(storeList: storeList, witness: notifier)
|
||||||
}()
|
}()
|
||||||
lazy var socketController: SocketController = {
|
lazy var socketController: SocketController = {
|
||||||
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
|
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
|
||||||
return SocketController(path: path)
|
return SocketController(path: path)
|
||||||
}()
|
}()
|
||||||
|
fileprivate var updateSink: AnyCancellable?
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
os_log(.debug, "SecretAgent finished launching")
|
os_log(.debug, "SecretAgent finished launching")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.socketController.handler = self.agent.handle(fileHandle:)
|
self.socketController.handler = self.agent.handle(reader:writer:)
|
||||||
}
|
}
|
||||||
notifier.prompt()
|
notifier.prompt()
|
||||||
}
|
updateSink = updater.$update.sink { update in
|
||||||
|
guard let update = update else { return }
|
||||||
func applicationWillTerminate(_ aNotification: Notification) {
|
self.notifier.notify(update: update, ignore: self.updater.ignore(release:))
|
||||||
// Insert code here to tear down your application
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
|
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
|
||||||
<key>NSMainStoryboardFile</key>
|
<key>NSMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
@ -1,31 +1,123 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import UserNotifications
|
||||||
|
import AppKit
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import UserNotifications
|
import Brief
|
||||||
|
|
||||||
class Notifier {
|
class Notifier {
|
||||||
|
|
||||||
|
fileprivate let notificationDelegate = NotificationDelegate()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let updateAction = UNNotificationAction(identifier: Constants.updateActionIdentitifier, title: "Update", options: [])
|
||||||
|
let ignoreAction = UNNotificationAction(identifier: Constants.ignoreActionIdentitifier, title: "Ignore", options: [])
|
||||||
|
let updateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction, ignoreAction], intentIdentifiers: [], options: [])
|
||||||
|
let criticalUpdateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction], intentIdentifiers: [], options: [])
|
||||||
|
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory])
|
||||||
|
UNUserNotificationCenter.current().delegate = notificationDelegate
|
||||||
|
}
|
||||||
|
|
||||||
func prompt() {
|
func prompt() {
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
notificationCenter.requestAuthorization(options: .alert) { _, _ in
|
notificationCenter.requestAuthorization(options: .alert) { _, _ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(accessTo secret: AnySecret) {
|
func notify(accessTo secret: AnySecret, by provenance: SigningRequestProvenance) {
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
notificationContent.title = "Signed Request"
|
notificationContent.title = "Signed Request from \(provenance.origin.name)"
|
||||||
notificationContent.body = "\(secret.name) was used to sign a request."
|
notificationContent.subtitle = "Using secret \"\(secret.name)\""
|
||||||
|
if let iconURL = iconURL(for: provenance), let attachment = try? UNNotificationAttachment(identifier: "icon", url: iconURL, options: nil) {
|
||||||
|
notificationContent.attachments = [attachment]
|
||||||
|
}
|
||||||
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil)
|
||||||
|
notificationCenter.add(request, withCompletionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notify(update: Release, ignore: ((Release) -> Void)?) {
|
||||||
|
notificationDelegate.release = update
|
||||||
|
notificationDelegate.ignore = ignore
|
||||||
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
|
let notificationContent = UNMutableNotificationContent()
|
||||||
|
if update.critical {
|
||||||
|
notificationContent.title = "Critical Security Update - \(update.name)"
|
||||||
|
} else {
|
||||||
|
notificationContent.title = "Update Available - \(update.name)"
|
||||||
|
}
|
||||||
|
notificationContent.subtitle = "Click to Update"
|
||||||
|
notificationContent.body = update.body
|
||||||
|
notificationContent.categoryIdentifier = update.critical ? Constants.criticalUpdateCategoryIdentitifier : Constants.updateCategoryIdentitifier
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil)
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil)
|
||||||
notificationCenter.add(request, withCompletionHandler: nil)
|
notificationCenter.add(request, withCompletionHandler: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notifier: SigningWitness {
|
extension Notifier {
|
||||||
|
|
||||||
func witness(accessTo secret: AnySecret) throws {
|
func iconURL(for provenance: SigningRequestProvenance) -> URL? {
|
||||||
notify(accessTo: secret)
|
do {
|
||||||
|
if let app = NSRunningApplication(processIdentifier: provenance.origin.pid), let icon = app.icon?.tiffRepresentation {
|
||||||
|
let temporaryURL = URL(fileURLWithPath: (NSTemporaryDirectory() as NSString).appendingPathComponent("\(UUID().uuidString).png"))
|
||||||
|
let bitmap = NSBitmapImageRep(data: icon)
|
||||||
|
try bitmap?.representation(using: .png, properties: [:])?.write(to: temporaryURL)
|
||||||
|
return temporaryURL
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Notifier: SigningWitness {
|
||||||
|
|
||||||
|
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws {
|
||||||
|
}
|
||||||
|
|
||||||
|
func witness(accessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws {
|
||||||
|
notify(accessTo: secret, by: provenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Notifier {
|
||||||
|
|
||||||
|
enum Constants {
|
||||||
|
static let updateCategoryIdentitifier = "com.maxgoedjen.Secretive.SecretAgent.update"
|
||||||
|
static let criticalUpdateCategoryIdentitifier = "com.maxgoedjen.Secretive.SecretAgent.update.critical"
|
||||||
|
static let updateActionIdentitifier = "com.maxgoedjen.Secretive.SecretAgent.update.updateaction"
|
||||||
|
static let ignoreActionIdentitifier = "com.maxgoedjen.Secretive.SecretAgent.update.ignoreaction"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
||||||
|
|
||||||
|
fileprivate var release: Release?
|
||||||
|
fileprivate var ignore: ((Release) -> Void)?
|
||||||
|
|
||||||
|
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||||
|
guard let update = release else { return }
|
||||||
|
switch response.actionIdentifier {
|
||||||
|
case Notifier.Constants.updateActionIdentitifier, UNNotificationDefaultActionIdentifier:
|
||||||
|
NSWorkspace.shared.open(update.html_url)
|
||||||
|
case Notifier.Constants.ignoreActionIdentitifier:
|
||||||
|
ignore?(update)
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||||
|
completionHandler(.alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.smartcard</key>
|
<key>com.apple.security.smartcard</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
|
@ -2,12 +2,14 @@ import Foundation
|
|||||||
import CryptoKit
|
import CryptoKit
|
||||||
import OSLog
|
import OSLog
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import AppKit
|
||||||
|
|
||||||
public class Agent {
|
public class Agent {
|
||||||
|
|
||||||
fileprivate let storeList: SecretStoreList
|
fileprivate let storeList: SecretStoreList
|
||||||
fileprivate let witness: SigningWitness?
|
fileprivate let witness: SigningWitness?
|
||||||
fileprivate let writer = OpenSSHKeyWriter()
|
fileprivate let writer = OpenSSHKeyWriter()
|
||||||
|
fileprivate let requestTracer = SigningRequestTracer()
|
||||||
|
|
||||||
public init(storeList: SecretStoreList, witness: SigningWitness? = nil) {
|
public init(storeList: SecretStoreList, witness: SigningWitness? = nil) {
|
||||||
os_log(.debug, "Agent is running")
|
os_log(.debug, "Agent is running")
|
||||||
@ -19,28 +21,34 @@ public class Agent {
|
|||||||
|
|
||||||
extension Agent {
|
extension Agent {
|
||||||
|
|
||||||
public func handle(fileHandle: FileHandle) {
|
public func handle(reader: FileHandleReader, writer: FileHandleWriter) {
|
||||||
os_log(.debug, "Agent handling new data")
|
os_log(.debug, "Agent handling new data")
|
||||||
let data = fileHandle.availableData
|
let data = reader.availableData
|
||||||
guard !data.isEmpty else { return }
|
guard !data.isEmpty else { return }
|
||||||
let requestTypeInt = data[4]
|
let requestTypeInt = data[4]
|
||||||
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else { return }
|
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else {
|
||||||
|
writer.write(OpenSSHKeyWriter().lengthAndData(of: SSHAgent.ResponseType.agentFailure.data))
|
||||||
|
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription)
|
||||||
|
return
|
||||||
|
}
|
||||||
os_log(.debug, "Agent handling request of type %@", requestType.debugDescription)
|
os_log(.debug, "Agent handling request of type %@", requestType.debugDescription)
|
||||||
let subData = Data(data[5...])
|
let subData = Data(data[5...])
|
||||||
handle(requestType: requestType, data: subData, fileHandle: fileHandle)
|
let response = handle(requestType: requestType, data: subData, reader: reader)
|
||||||
|
writer.write(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle(requestType: SSHAgent.RequestType, data: Data, fileHandle: FileHandle) {
|
func handle(requestType: SSHAgent.RequestType, data: Data, reader: FileHandleReader) -> Data {
|
||||||
var response = Data()
|
var response = Data()
|
||||||
do {
|
do {
|
||||||
switch requestType {
|
switch requestType {
|
||||||
case .requestIdentities:
|
case .requestIdentities:
|
||||||
response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data)
|
response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data)
|
||||||
response.append(try identities())
|
response.append(identities())
|
||||||
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)
|
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)
|
||||||
case .signRequest:
|
case .signRequest:
|
||||||
|
let provenance = requestTracer.provenance(from: reader)
|
||||||
response.append(SSHAgent.ResponseType.agentSignResponse.data)
|
response.append(SSHAgent.ResponseType.agentSignResponse.data)
|
||||||
response.append(try sign(data: data))
|
response.append(try sign(data: data, provenance: provenance))
|
||||||
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentSignResponse.debugDescription)
|
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentSignResponse.debugDescription)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@ -49,14 +57,14 @@ extension Agent {
|
|||||||
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription)
|
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription)
|
||||||
}
|
}
|
||||||
let full = OpenSSHKeyWriter().lengthAndData(of: response)
|
let full = OpenSSHKeyWriter().lengthAndData(of: response)
|
||||||
fileHandle.write(full)
|
return full
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Agent {
|
extension Agent {
|
||||||
|
|
||||||
func identities() throws -> Data {
|
func identities() -> Data {
|
||||||
// TODO: RESTORE ONCE XCODE 11.4 IS GM
|
// TODO: RESTORE ONCE XCODE 11.4 IS GM
|
||||||
let secrets = storeList.stores.flatMap { $0.secrets }
|
let secrets = storeList.stores.flatMap { $0.secrets }
|
||||||
// let secrets = storeList.stores.flatMap(\.secrets)
|
// let secrets = storeList.stores.flatMap(\.secrets)
|
||||||
@ -74,19 +82,19 @@ extension Agent {
|
|||||||
return countData + keyData
|
return countData + keyData
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(data: Data) throws -> Data {
|
func sign(data: Data, provenance: SigningRequestProvenance) throws -> Data {
|
||||||
let reader = OpenSSHReader(data: data)
|
let reader = OpenSSHReader(data: data)
|
||||||
let hash = try reader.readNextChunk()
|
let hash = reader.readNextChunk()
|
||||||
guard let (store, secret) = secret(matching: hash) else {
|
guard let (store, secret) = secret(matching: hash) else {
|
||||||
os_log(.debug, "Agent did not have a key matching %@", hash as NSData)
|
os_log(.debug, "Agent did not have a key matching %@", hash as NSData)
|
||||||
throw AgentError.noMatchingKey
|
throw AgentError.noMatchingKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if let witness = witness {
|
if let witness = witness {
|
||||||
try witness.witness(accessTo: secret)
|
try witness.speakNowOrForeverHoldYourPeace(forAccessTo: secret, by: provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataToSign = try reader.readNextChunk()
|
let dataToSign = reader.readNextChunk()
|
||||||
let derSignature = try store.sign(data: dataToSign, with: secret)
|
let derSignature = try store.sign(data: dataToSign, with: secret)
|
||||||
|
|
||||||
let curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
|
let curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
|
||||||
@ -100,7 +108,7 @@ extension Agent {
|
|||||||
case (.ellipticCurve, 384):
|
case (.ellipticCurve, 384):
|
||||||
rawRepresentation = try CryptoKit.P384.Signing.ECDSASignature(derRepresentation: derSignature).rawRepresentation
|
rawRepresentation = try CryptoKit.P384.Signing.ECDSASignature(derRepresentation: derSignature).rawRepresentation
|
||||||
default:
|
default:
|
||||||
fatalError()
|
throw AgentError.unsupportedKeyType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -118,6 +126,10 @@ extension Agent {
|
|||||||
sub.append(writer.lengthAndData(of: signatureChunk))
|
sub.append(writer.lengthAndData(of: signatureChunk))
|
||||||
signedData.append(writer.lengthAndData(of: sub))
|
signedData.append(writer.lengthAndData(of: sub))
|
||||||
|
|
||||||
|
if let witness = witness {
|
||||||
|
try witness.witness(accessTo: secret, by: provenance)
|
||||||
|
}
|
||||||
|
|
||||||
os_log(.debug, "Agent signed request")
|
os_log(.debug, "Agent signed request")
|
||||||
|
|
||||||
return signedData
|
return signedData
|
||||||
@ -147,6 +159,7 @@ extension Agent {
|
|||||||
enum AgentError: Error {
|
enum AgentError: Error {
|
||||||
case unhandledType
|
case unhandledType
|
||||||
case noMatchingKey
|
case noMatchingKey
|
||||||
|
case unsupportedKeyType
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
26
SecretAgentKit/FileHandleProtocols.swift
Normal file
26
SecretAgentKit/FileHandleProtocols.swift
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol FileHandleReader {
|
||||||
|
|
||||||
|
var availableData: Data { get }
|
||||||
|
var fileDescriptor: Int32 { get }
|
||||||
|
var pidOfConnectedProcess: Int32 { get }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol FileHandleWriter {
|
||||||
|
|
||||||
|
func write(_ data: Data)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FileHandle: FileHandleReader, FileHandleWriter {
|
||||||
|
|
||||||
|
public var pidOfConnectedProcess: Int32 {
|
||||||
|
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 1)
|
||||||
|
var len = socklen_t(MemoryLayout<Int32>.size)
|
||||||
|
getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len)
|
||||||
|
return pidPointer.load(as: Int32.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,6 +19,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
|
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -12,7 +12,7 @@ extension SSHAgent {
|
|||||||
switch self {
|
switch self {
|
||||||
case .requestIdentities:
|
case .requestIdentities:
|
||||||
return "RequestIdentities"
|
return "RequestIdentities"
|
||||||
default:
|
case .signRequest:
|
||||||
return "SignRequest"
|
return "SignRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
//
|
|
||||||
// SecretAgentKit.h
|
|
||||||
// SecretAgentKit
|
|
||||||
//
|
|
||||||
// Created by Max Goedjen on 2/22/20.
|
|
||||||
// Copyright © 2020 Max Goedjen. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <Security/Security.h>
|
||||||
|
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
|
||||||
|
// from libproc.h
|
||||||
|
int proc_pidpath(int pid, void * buffer, uint32_t buffersize);
|
||||||
|
|
||||||
|
// from SecTask.h
|
||||||
|
OSStatus SecCodeCreateWithPID(int32_t, SecCSFlags, SecCodeRef *);
|
||||||
|
|
||||||
//! Project version number for SecretAgentKit.
|
//! Project version number for SecretAgentKit.
|
||||||
FOUNDATION_EXPORT double SecretAgentKitVersionNumber;
|
FOUNDATION_EXPORT double SecretAgentKitVersionNumber;
|
||||||
@ -14,6 +16,4 @@ FOUNDATION_EXPORT double SecretAgentKitVersionNumber;
|
|||||||
//! Project version string for SecretAgentKit.
|
//! Project version string for SecretAgentKit.
|
||||||
FOUNDATION_EXPORT const unsigned char SecretAgentKitVersionString[];
|
FOUNDATION_EXPORT const unsigned char SecretAgentKitVersionString[];
|
||||||
|
|
||||||
// In this header, you should import all the public headers of your framework using statements like #import <SecretAgentKit/PublicHeader.h>
|
|
||||||
|
|
||||||
|
|
||||||
|
45
SecretAgentKit/SigningRequestProvenance.swift
Normal file
45
SecretAgentKit/SigningRequestProvenance.swift
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import Foundation
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
public struct SigningRequestProvenance: Equatable {
|
||||||
|
|
||||||
|
public var chain: [Process]
|
||||||
|
public init(root: Process) {
|
||||||
|
self.chain = [root]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SigningRequestProvenance {
|
||||||
|
|
||||||
|
public var origin: Process {
|
||||||
|
chain.last!
|
||||||
|
}
|
||||||
|
|
||||||
|
public var intact: Bool {
|
||||||
|
return chain.reduce(true) { $0 && $1.validSignature }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SigningRequestProvenance {
|
||||||
|
|
||||||
|
public struct Process: Equatable {
|
||||||
|
|
||||||
|
public let pid: Int32
|
||||||
|
public let name: String
|
||||||
|
public let path: String
|
||||||
|
public let validSignature: Bool
|
||||||
|
let parentPID: Int32?
|
||||||
|
|
||||||
|
init(pid: Int32, name: String, path: String, validSignature: Bool, parentPID: Int32?) {
|
||||||
|
self.pid = pid
|
||||||
|
self.name = name
|
||||||
|
self.path = path
|
||||||
|
self.validSignature = validSignature
|
||||||
|
self.parentPID = parentPID
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
SecretAgentKit/SigningRequestTracer.swift
Normal file
39
SecretAgentKit/SigningRequestTracer.swift
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import Foundation
|
||||||
|
import AppKit
|
||||||
|
import Security
|
||||||
|
|
||||||
|
struct SigningRequestTracer {
|
||||||
|
|
||||||
|
func provenance(from fileHandleReader: FileHandleReader) -> SigningRequestProvenance {
|
||||||
|
let firstInfo = process(from: fileHandleReader.pidOfConnectedProcess)
|
||||||
|
|
||||||
|
var provenance = SigningRequestProvenance(root: firstInfo)
|
||||||
|
while NSRunningApplication(processIdentifier: provenance.origin.pid) == nil && provenance.origin.parentPID != nil {
|
||||||
|
provenance.chain.append(process(from: provenance.origin.parentPID!))
|
||||||
|
}
|
||||||
|
return provenance
|
||||||
|
}
|
||||||
|
|
||||||
|
func pidAndNameInfo(from pid: Int32) -> kinfo_proc {
|
||||||
|
var len = MemoryLayout<kinfo_proc>.size
|
||||||
|
let infoPointer = UnsafeMutableRawPointer.allocate(byteCount: len, alignment: 1)
|
||||||
|
var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]
|
||||||
|
sysctl(&name, UInt32(name.count), infoPointer, &len, nil, 0)
|
||||||
|
return infoPointer.load(as: kinfo_proc.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(from pid: Int32) -> SigningRequestProvenance.Process {
|
||||||
|
var pidAndNameInfo = self.pidAndNameInfo(from: pid)
|
||||||
|
let ppid = pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
|
||||||
|
let procName = String(cString: &pidAndNameInfo.kp_proc.p_comm.0)
|
||||||
|
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
|
||||||
|
_ = proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
|
||||||
|
let path = String(cString: pathPointer)
|
||||||
|
var secCode: Unmanaged<SecCode>!
|
||||||
|
let flags: SecCSFlags = [.considerExpiration, .enforceRevocationChecks]
|
||||||
|
SecCodeCreateWithPID(pid, SecCSFlags(), &secCode)
|
||||||
|
let valid = SecCodeCheckValidity(secCode.takeRetainedValue(), flags, nil) == errSecSuccess
|
||||||
|
return SigningRequestProvenance.Process(pid: pid, name: procName, path: path, validSignature: valid, parentPID: ppid)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ import SecretKit
|
|||||||
|
|
||||||
public protocol SigningWitness {
|
public protocol SigningWitness {
|
||||||
|
|
||||||
func witness(accessTo secret: AnySecret) throws
|
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws
|
||||||
|
func witness(accessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ public class SocketController {
|
|||||||
|
|
||||||
fileprivate var fileHandle: FileHandle?
|
fileprivate var fileHandle: FileHandle?
|
||||||
fileprivate var port: SocketPort?
|
fileprivate var port: SocketPort?
|
||||||
public var handler: ((FileHandle) -> Void)?
|
public var handler: ((FileHandleReader, FileHandleWriter) -> Void)?
|
||||||
|
|
||||||
public init(path: String) {
|
public init(path: String) {
|
||||||
os_log(.debug, "Socket controller setting up at %@", path)
|
os_log(.debug, "Socket controller setting up at %@", path)
|
||||||
@ -52,7 +52,7 @@ public class SocketController {
|
|||||||
@objc func handleConnectionAccept(notification: Notification) {
|
@objc func handleConnectionAccept(notification: Notification) {
|
||||||
os_log(.debug, "Socket controller accepted connection")
|
os_log(.debug, "Socket controller accepted connection")
|
||||||
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
|
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
|
||||||
handler?(new)
|
handler?(new, new)
|
||||||
new.waitForDataInBackgroundAndNotify()
|
new.waitForDataInBackgroundAndNotify()
|
||||||
fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!])
|
fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!])
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ public class SocketController {
|
|||||||
os_log(.debug, "Socket controller has new data available")
|
os_log(.debug, "Socket controller has new data available")
|
||||||
guard let new = notification.object as? FileHandle else { return }
|
guard let new = notification.object as? FileHandle else { return }
|
||||||
os_log(.debug, "Socket controller received new file handle")
|
os_log(.debug, "Socket controller received new file handle")
|
||||||
handler?(new)
|
handler?(new, new)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
162
SecretAgentKitTests/AgentTests.swift
Normal file
162
SecretAgentKitTests/AgentTests.swift
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
import CryptoKit
|
||||||
|
@testable import SecretKit
|
||||||
|
@testable import SecretAgentKit
|
||||||
|
|
||||||
|
class AgentTests: XCTestCase {
|
||||||
|
|
||||||
|
let stubWriter = StubFileHandleWriter()
|
||||||
|
|
||||||
|
// MARK: Identity Listing
|
||||||
|
|
||||||
|
func testEmptyStores() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestIdentities)
|
||||||
|
let agent = Agent(storeList: SecretStoreList())
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
XCTAssertEqual(stubWriter.data, Constants.Responses.requestIdentitiesEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIdentitiesList() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestIdentities)
|
||||||
|
let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
|
let agent = Agent(storeList: list)
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
XCTAssertEqual(stubWriter.data, Constants.Responses.requestIdentitiesMultiple)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Signatures
|
||||||
|
|
||||||
|
func testNoMatchingIdentities() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignatureWithNoneMatching)
|
||||||
|
let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
|
let agent = Agent(storeList: list)
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
// XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSignature() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
|
||||||
|
let requestReader = OpenSSHReader(data: Constants.Requests.requestSignature[5...])
|
||||||
|
_ = requestReader.readNextChunk()
|
||||||
|
let dataToSign = requestReader.readNextChunk()
|
||||||
|
let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
|
let agent = Agent(storeList: list)
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
let outer = OpenSSHReader(data: stubWriter.data[5...])
|
||||||
|
let payload = outer.readNextChunk()
|
||||||
|
let inner = OpenSSHReader(data: payload)
|
||||||
|
_ = inner.readNextChunk()
|
||||||
|
let signedData = inner.readNextChunk()
|
||||||
|
let rsData = OpenSSHReader(data: signedData)
|
||||||
|
let r = rsData.readNextChunk()
|
||||||
|
let s = rsData.readNextChunk()
|
||||||
|
var rs = r
|
||||||
|
rs.append(s)
|
||||||
|
let signature = try! P256.Signing.ECDSASignature(rawRepresentation: rs)
|
||||||
|
let valid = try! P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey).isValidSignature(signature, for: dataToSign)
|
||||||
|
XCTAssertTrue(valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Witness protocol
|
||||||
|
|
||||||
|
func testWitnessObjectionStopsRequest() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
|
||||||
|
let list = storeList(with: [Constants.Secrets.ecdsa256Secret])
|
||||||
|
let witness = StubWitness(speakNow: { _,_ in
|
||||||
|
return true
|
||||||
|
}, witness: { _, _ in })
|
||||||
|
let agent = Agent(storeList: list, witness: witness)
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWitnessSignature() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
|
||||||
|
let list = storeList(with: [Constants.Secrets.ecdsa256Secret])
|
||||||
|
var witnessed = false
|
||||||
|
let witness = StubWitness(speakNow: { _, trace in
|
||||||
|
return false
|
||||||
|
}, witness: { _, trace in
|
||||||
|
witnessed = true
|
||||||
|
})
|
||||||
|
let agent = Agent(storeList: list, witness: witness)
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
XCTAssertTrue(witnessed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRequestTracing() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
|
||||||
|
let list = storeList(with: [Constants.Secrets.ecdsa256Secret])
|
||||||
|
var speakNowTrace: SigningRequestProvenance! = nil
|
||||||
|
var witnessTrace: SigningRequestProvenance! = nil
|
||||||
|
let witness = StubWitness(speakNow: { _, trace in
|
||||||
|
speakNowTrace = trace
|
||||||
|
return false
|
||||||
|
}, witness: { _, trace in
|
||||||
|
witnessTrace = trace
|
||||||
|
})
|
||||||
|
let agent = Agent(storeList: list, witness: witness)
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
XCTAssertEqual(witnessTrace, speakNowTrace)
|
||||||
|
XCTAssertEqual(witnessTrace.origin.name, "Finder")
|
||||||
|
XCTAssertEqual(witnessTrace.origin.validSignature, true)
|
||||||
|
XCTAssertEqual(witnessTrace.origin.parentPID, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Exception Handling
|
||||||
|
|
||||||
|
func testSignatureException() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
|
||||||
|
let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
|
let store = list.stores.first?.base as! Stub.Store
|
||||||
|
store.shouldThrow = true
|
||||||
|
let agent = Agent(storeList: list)
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Unsupported
|
||||||
|
|
||||||
|
func testUnhandledAdd() {
|
||||||
|
let stubReader = StubFileHandleReader(availableData: Constants.Requests.addIdentity)
|
||||||
|
let agent = Agent(storeList: SecretStoreList())
|
||||||
|
agent.handle(reader: stubReader, writer: stubWriter)
|
||||||
|
XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AgentTests {
|
||||||
|
|
||||||
|
func storeList(with secrets: [Stub.Secret]) -> SecretStoreList {
|
||||||
|
let store = Stub.Store()
|
||||||
|
store.secrets.append(contentsOf: secrets)
|
||||||
|
let storeList = SecretStoreList()
|
||||||
|
storeList.add(store: store)
|
||||||
|
return storeList
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Constants {
|
||||||
|
|
||||||
|
enum Requests {
|
||||||
|
static let requestIdentities = Data(base64Encoded: "AAAAAQs=")!
|
||||||
|
static let addIdentity = Data(base64Encoded: "AAAAARE=")!
|
||||||
|
static let requestSignatureWithNoneMatching = Data(base64Encoded: "AAABhA0AAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAO8AAAAgbqmrqPUtJ8mmrtaSVexjMYyXWNqjHSnoto7zgv86xvcyAAAAA2dpdAAAAA5zc2gtY29ubmVjdGlvbgAAAAlwdWJsaWNrZXkBAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAAA=")!
|
||||||
|
static let requestSignature = Data(base64Encoded: "AAABRA0AAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08AAADPAAAAIBIFsbCZ4/dhBmLNGHm0GKj7EJ4N8k/jXRxlyg+LFIYzMgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAAA==")!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Responses {
|
||||||
|
static let requestIdentitiesEmpty = Data(base64Encoded: "AAAABQwAAAAA")!
|
||||||
|
static let requestIdentitiesMultiple = Data(base64Encoded: "AAABKwwAAAACAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0")!
|
||||||
|
static let requestFailure = Data(base64Encoded: "AAAAAQU=")!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Secrets {
|
||||||
|
static let ecdsa256Secret = Stub.Secret(keySize: 256, publicKey: Data(base64Encoded: "BKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08=")!, privateKey: Data(base64Encoded: "BKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy09nw780wy/TSfUmzj15iJkV234AaCLNl+H8qFL6qK8VIg==")!)
|
||||||
|
static let ecdsa384Secret = Stub.Secret(keySize: 384, publicKey: Data(base64Encoded: "BLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDA==")!, privateKey: Data(base64Encoded: "BLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDHNapAOzrt9E+9QC4/KYoXS7Uw4pmdAz53uIj02tttiq3c0ZyIQ7XoscWWRqRrz8Kw==")!)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 {
|
|
||||||
|
|
||||||
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
14
SecretAgentKitTests/StubFileHandleReader.swift
Normal file
14
SecretAgentKitTests/StubFileHandleReader.swift
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import SecretAgentKit
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
struct StubFileHandleReader: FileHandleReader {
|
||||||
|
|
||||||
|
let availableData: Data
|
||||||
|
var fileDescriptor: Int32 {
|
||||||
|
NSWorkspace.shared.runningApplications.filter({ $0.localizedName == "Finder" }).first!.processIdentifier
|
||||||
|
}
|
||||||
|
var pidOfConnectedProcess: Int32 {
|
||||||
|
fileDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
SecretAgentKitTests/StubFileHandleWriter.swift
Normal file
11
SecretAgentKitTests/StubFileHandleWriter.swift
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import SecretAgentKit
|
||||||
|
|
||||||
|
class StubFileHandleWriter: FileHandleWriter {
|
||||||
|
|
||||||
|
var data = Data()
|
||||||
|
|
||||||
|
func write(_ data: Data) {
|
||||||
|
self.data.append(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
113
SecretAgentKitTests/StubStore.swift
Normal file
113
SecretAgentKitTests/StubStore.swift
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import SecretKit
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
struct Stub {}
|
||||||
|
|
||||||
|
extension Stub {
|
||||||
|
|
||||||
|
public class Store: SecretStore {
|
||||||
|
|
||||||
|
public let isAvailable = true
|
||||||
|
public let id = UUID()
|
||||||
|
public let name = "Stub"
|
||||||
|
public var secrets: [Secret] = []
|
||||||
|
public var shouldThrow = false
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
// try! create(size: 256)
|
||||||
|
// try! create(size: 384)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func create(size: Int) throws {
|
||||||
|
let flags: SecAccessControlCreateFlags = []
|
||||||
|
let access =
|
||||||
|
SecAccessControlCreateWithFlags(kCFAllocatorDefault,
|
||||||
|
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||||
|
flags,
|
||||||
|
nil) as Any
|
||||||
|
|
||||||
|
let attributes = [
|
||||||
|
kSecAttrLabel: name,
|
||||||
|
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
||||||
|
kSecAttrKeySizeInBits: size,
|
||||||
|
kSecPrivateKeyAttrs: [
|
||||||
|
kSecAttrIsPermanent: true,
|
||||||
|
kSecAttrAccessControl: access
|
||||||
|
]
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
var privateKey: SecKey! = nil
|
||||||
|
var publicKey: SecKey! = nil
|
||||||
|
SecKeyGeneratePair(attributes, &publicKey, &privateKey)
|
||||||
|
let publicAttributes = SecKeyCopyAttributes(publicKey) as! [CFString: Any]
|
||||||
|
let privateAttributes = SecKeyCopyAttributes(privateKey) as! [CFString: Any]
|
||||||
|
let publicData = (publicAttributes[kSecValueData] as! Data)
|
||||||
|
let privateData = (privateAttributes[kSecValueData] as! Data)
|
||||||
|
let secret = Secret(keySize: size, publicKey: publicData, privateKey: privateData)
|
||||||
|
print(secret)
|
||||||
|
print("Public Key OpenSSH: \(OpenSSHKeyWriter().openSSHString(secret: secret))")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sign(data: Data, with secret: Secret) throws -> Data {
|
||||||
|
guard !shouldThrow else {
|
||||||
|
throw NSError()
|
||||||
|
}
|
||||||
|
let privateKey = SecKeyCreateWithData(secret.privateKey as CFData, [
|
||||||
|
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
||||||
|
kSecAttrKeySizeInBits: secret.keySize,
|
||||||
|
kSecAttrKeyClass: kSecAttrKeyClassPrivate
|
||||||
|
] as CFDictionary
|
||||||
|
, nil)!
|
||||||
|
let signatureAlgorithm: SecKeyAlgorithm
|
||||||
|
switch secret.keySize {
|
||||||
|
case 256:
|
||||||
|
signatureAlgorithm = .ecdsaSignatureMessageX962SHA256
|
||||||
|
case 384:
|
||||||
|
signatureAlgorithm = .ecdsaSignatureMessageX962SHA384
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
return SecKeyCreateSignature(privateKey, signatureAlgorithm, data as CFData, nil)! as Data
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Stub {
|
||||||
|
|
||||||
|
struct Secret: SecretKit.Secret, CustomDebugStringConvertible {
|
||||||
|
|
||||||
|
let id = UUID().uuidString.data(using: .utf8)!
|
||||||
|
let name = UUID().uuidString
|
||||||
|
let algorithm = Algorithm.ellipticCurve
|
||||||
|
|
||||||
|
let keySize: Int
|
||||||
|
let publicKey: Data
|
||||||
|
let privateKey: Data
|
||||||
|
|
||||||
|
init(keySize: Int, publicKey: Data, privateKey: Data) {
|
||||||
|
self.keySize = keySize
|
||||||
|
self.publicKey = publicKey
|
||||||
|
self.privateKey = privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
var debugDescription: String {
|
||||||
|
"""
|
||||||
|
Key Size \(keySize)
|
||||||
|
Private: \(privateKey.base64EncodedString())
|
||||||
|
Public: \(publicKey.base64EncodedString())
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension Stub.Store {
|
||||||
|
|
||||||
|
struct StubError: Error {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
SecretAgentKitTests/StubWitness.swift
Normal file
32
SecretAgentKitTests/StubWitness.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import SecretKit
|
||||||
|
import SecretAgentKit
|
||||||
|
|
||||||
|
struct StubWitness {
|
||||||
|
|
||||||
|
let speakNow: (AnySecret, SigningRequestProvenance) -> Bool
|
||||||
|
let witness: (AnySecret, SigningRequestProvenance) -> ()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StubWitness: SigningWitness {
|
||||||
|
|
||||||
|
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws {
|
||||||
|
let objection = speakNow(secret, provenance)
|
||||||
|
if objection {
|
||||||
|
throw TheresMyChance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func witness(accessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws {
|
||||||
|
witness(secret, provenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StubWitness {
|
||||||
|
|
||||||
|
struct TheresMyChance: Error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -8,7 +8,7 @@ public class OpenSSHReader {
|
|||||||
remaining = Data(data)
|
remaining = Data(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func readNextChunk() throws -> Data {
|
public func readNextChunk() -> Data {
|
||||||
let lengthRange = 0..<(UInt32.bitWidth/8)
|
let lengthRange = 0..<(UInt32.bitWidth/8)
|
||||||
let lengthChunk = remaining[lengthRange]
|
let lengthChunk = remaining[lengthRange]
|
||||||
remaining.removeSubrange(lengthRange)
|
remaining.removeSubrange(lengthRange)
|
||||||
|
@ -20,6 +20,10 @@ public class SecretStoreList: ObservableObject {
|
|||||||
addInternal(store: modifiable)
|
addInternal(store: modifiable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var anyAvailable: Bool {
|
||||||
|
stores.reduce(false, { $0 || $1.isAvailable })
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SecretStoreList {
|
extension SecretStoreList {
|
||||||
|
@ -19,6 +19,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
|
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -7,7 +7,7 @@ public protocol Secret: Identifiable, Hashable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Algorithm {
|
public enum Algorithm: Hashable {
|
||||||
case ellipticCurve
|
case ellipticCurve
|
||||||
public init(secAttr: NSNumber) {
|
public init(secAttr: NSNumber) {
|
||||||
let secAttrString = secAttr.stringValue as CFString
|
let secAttrString = secAttr.stringValue as CFString
|
||||||
|
@ -1,11 +1,3 @@
|
|||||||
//
|
|
||||||
// SecretKit.h
|
|
||||||
// SecretKit
|
|
||||||
//
|
|
||||||
// Created by Max Goedjen on 2/18/20.
|
|
||||||
// Copyright © 2020 Max Goedjen. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
//! Project version number for SecretKit.
|
//! Project version number for SecretKit.
|
||||||
|
@ -11,16 +11,17 @@ extension SmartCard {
|
|||||||
// TODO: Read actual smart card name, eg "YubiKey 5c"
|
// TODO: Read actual smart card name, eg "YubiKey 5c"
|
||||||
@Published public var isAvailable: Bool = false
|
@Published public var isAvailable: Bool = false
|
||||||
public let id = UUID()
|
public let id = UUID()
|
||||||
public let name = NSLocalizedString("Smart Card", comment: "Smart Card")
|
public fileprivate(set) var name = NSLocalizedString("Smart Card", comment: "Smart Card")
|
||||||
@Published public fileprivate(set) var secrets: [Secret] = []
|
@Published public fileprivate(set) var secrets: [Secret] = []
|
||||||
fileprivate let watcher = TKTokenWatcher()
|
fileprivate let watcher = TKTokenWatcher()
|
||||||
fileprivate var tokenID: String?
|
fileprivate var tokenID: String?
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
tokenID = watcher.tokenIDs.filter { !$0.contains("setoken") }.first
|
tokenID = watcher.nonSecureEnclaveTokens.first
|
||||||
watcher.setInsertionHandler { string in
|
watcher.setInsertionHandler { string in
|
||||||
guard self.tokenID == nil else { return }
|
guard self.tokenID == nil else { return }
|
||||||
guard !string.contains("setoken") else { return }
|
guard !string.contains("setoken") else { return }
|
||||||
|
|
||||||
self.tokenID = string
|
self.tokenID = string
|
||||||
self.reloadSecrets()
|
self.reloadSecrets()
|
||||||
self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string)
|
self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string)
|
||||||
@ -97,6 +98,14 @@ extension SmartCard.Store {
|
|||||||
|
|
||||||
fileprivate func loadSecrets() {
|
fileprivate func loadSecrets() {
|
||||||
guard let tokenID = tokenID else { return }
|
guard let tokenID = tokenID else { return }
|
||||||
|
// Hack to read name if there's only one smart card
|
||||||
|
let slotNames = TKSmartCardSlotManager().slotNames
|
||||||
|
if watcher.nonSecureEnclaveTokens.count == 1 && slotNames.count == 1 {
|
||||||
|
name = slotNames.first!
|
||||||
|
} else {
|
||||||
|
name = NSLocalizedString("Smart Card", comment: "Smart Card")
|
||||||
|
}
|
||||||
|
|
||||||
let attributes = [
|
let attributes = [
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrTokenID: tokenID,
|
kSecAttrTokenID: tokenID,
|
||||||
@ -124,6 +133,14 @@ extension SmartCard.Store {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TKTokenWatcher {
|
||||||
|
|
||||||
|
fileprivate var nonSecureEnclaveTokens: [String] {
|
||||||
|
tokenIDs.filter { !$0.contains("setoken") }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extension SmartCard {
|
extension SmartCard {
|
||||||
|
|
||||||
public struct KeychainError: Error {
|
public struct KeychainError: Error {
|
||||||
|
17
SecretKitTests/AnySecretTests.swift
Normal file
17
SecretKitTests/AnySecretTests.swift
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
@testable import SecretKit
|
||||||
|
|
||||||
|
class AnySecretTests: XCTestCase {
|
||||||
|
|
||||||
|
func testEraser() {
|
||||||
|
let secret = SmartCard.Secret(id: UUID().uuidString.data(using: .utf8)!, name: "Name", algorithm: .ellipticCurve, keySize: 256, publicKey: UUID().uuidString.data(using: .utf8)!)
|
||||||
|
let erased = AnySecret(secret)
|
||||||
|
XCTAssert(erased.id == secret.id as AnyHashable)
|
||||||
|
XCTAssert(erased.name == secret.name)
|
||||||
|
XCTAssert(erased.algorithm == secret.algorithm)
|
||||||
|
XCTAssert(erased.keySize == secret.keySize)
|
||||||
|
XCTAssert(erased.publicKey == secret.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
SecretKitTests/OpenSSHReaderTests.swift
Normal file
25
SecretKitTests/OpenSSHReaderTests.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
@testable import SecretKit
|
||||||
|
|
||||||
|
class OpenSSHReaderTests: XCTestCase {
|
||||||
|
|
||||||
|
func testSignatureRequest() {
|
||||||
|
let reader = OpenSSHReader(data: Constants.signatureRequest)
|
||||||
|
let hash = reader.readNextChunk()
|
||||||
|
XCTAssert(hash == Data(base64Encoded: "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQ=="))
|
||||||
|
let dataToSign = reader.readNextChunk()
|
||||||
|
XCTAssert(dataToSign == Data(base64Encoded: "AAAAICi5xf1ixOestUlxdjvt/BDcM+rzhwy7Vo8cW5YcxA8+MgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRKgm5CWzh8uUtcFQmiaCj5qUtHoDOH6zKpZYJ5UUUP1L55+hFUvKFdN3DwNgc9BHREskSgP7G5p9GgbYIyMBhC8buOVFxFtUxM+k8cktLt77iKkd1qNQq0FkQ55Kxv6QU="))
|
||||||
|
let empty = reader.readNextChunk()
|
||||||
|
XCTAssert(empty.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OpenSSHReaderTests {
|
||||||
|
|
||||||
|
enum Constants {
|
||||||
|
static let signatureRequest = Data(base64Encoded: "AAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRKgm5CWzh8uUtcFQmiaCj5qUtHoDOH6zKpZYJ5UUUP1L55+hFUvKFdN3DwNgc9BHREskSgP7G5p9GgbYIyMBhC8buOVFxFtUxM+k8cktLt77iKkd1qNQq0FkQ55Kxv6QUAAADvAAAAICi5xf1ixOestUlxdjvt/BDcM+rzhwy7Vo8cW5YcxA8+MgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRKgm5CWzh8uUtcFQmiaCj5qUtHoDOH6zKpZYJ5UUUP1L55+hFUvKFdN3DwNgc9BHREskSgP7G5p9GgbYIyMBhC8buOVFxFtUxM+k8cktLt77iKkd1qNQq0FkQ55Kxv6QUAAAAA")!
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,15 +26,30 @@
|
|||||||
50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */; };
|
50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */; };
|
||||||
50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */; };
|
50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */; };
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
|
||||||
|
506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; };
|
||||||
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
||||||
|
506772FF2426F3F400034DED /* Brief.h in Headers */ = {isa = PBXBuildFile; fileRef = 506772FD2426F3F400034DED /* Brief.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
506773022426F3F400034DED /* Brief.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 506772FB2426F3F400034DED /* Brief.framework */; };
|
||||||
|
506773032426F3F400034DED /* Brief.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 506772FB2426F3F400034DED /* Brief.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
506773092426F3FD00034DED /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506773082426F3FD00034DED /* Updater.swift */; };
|
||||||
|
5067730C2426F40E00034DED /* Brief.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 506772FB2426F3F400034DED /* Brief.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
5067730E242701BA00034DED /* OpenSSHReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5067730D242701BA00034DED /* OpenSSHReaderTests.swift */; };
|
||||||
|
506773102427057600034DED /* AnySecretTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5067730F2427057600034DED /* AnySecretTests.swift */; };
|
||||||
5068389E241471CD00F55094 /* SecretStoreList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5068389D241471CD00F55094 /* SecretStoreList.swift */; };
|
5068389E241471CD00F55094 /* SecretStoreList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5068389D241471CD00F55094 /* SecretStoreList.swift */; };
|
||||||
506838A12415EA5600F55094 /* AnySecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506838A02415EA5600F55094 /* AnySecret.swift */; };
|
506838A12415EA5600F55094 /* AnySecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506838A02415EA5600F55094 /* AnySecret.swift */; };
|
||||||
506838A32415EA5D00F55094 /* AnySecretStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506838A22415EA5D00F55094 /* AnySecretStore.swift */; };
|
506838A32415EA5D00F55094 /* AnySecretStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506838A22415EA5D00F55094 /* AnySecretStore.swift */; };
|
||||||
506AB87E2412334700335D91 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
506AB87E2412334700335D91 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
50731666241DF8660023809E /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50731665241DF8660023809E /* Updater.swift */; };
|
|
||||||
50731669241E00C20023809E /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50731668241E00C20023809E /* NoticeView.swift */; };
|
50731669241E00C20023809E /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50731668241E00C20023809E /* NoticeView.swift */; };
|
||||||
507CE4ED2420A3C70029F750 /* Agent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79F24026B9900D209EA /* Agent.swift */; };
|
507CE4ED2420A3C70029F750 /* Agent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79F24026B9900D209EA /* Agent.swift */; };
|
||||||
507CE4EE2420A3CA0029F750 /* SocketController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79D24026B9900D209EA /* SocketController.swift */; };
|
507CE4EE2420A3CA0029F750 /* SocketController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79D24026B9900D209EA /* SocketController.swift */; };
|
||||||
507CE4F02420A4C50029F750 /* SigningWitness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4EF2420A4C50029F750 /* SigningWitness.swift */; };
|
507CE4F02420A4C50029F750 /* SigningWitness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4EF2420A4C50029F750 /* SigningWitness.swift */; };
|
||||||
|
507CE4F42420A8C10029F750 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; };
|
||||||
|
507CE4F62420A96F0029F750 /* SigningRequestTracer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F52420A96F0029F750 /* SigningRequestTracer.swift */; };
|
||||||
|
507EE34224281E12003C4FE3 /* FileHandleProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34124281E12003C4FE3 /* FileHandleProtocols.swift */; };
|
||||||
|
507EE34624281F89003C4FE3 /* StubFileHandleReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */; };
|
||||||
|
507EE34824281FB8003C4FE3 /* StubFileHandleWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */; };
|
||||||
|
507EE34A2428263B003C4FE3 /* StubStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE3492428263B003C4FE3 /* StubStore.swift */; };
|
||||||
|
507EE34E2428784F003C4FE3 /* StubWitness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34D2428784F003C4FE3 /* StubWitness.swift */; };
|
||||||
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58A9241E06B40069DC07 /* PreviewUpdater.swift */; };
|
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58A9241E06B40069DC07 /* PreviewUpdater.swift */; };
|
||||||
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */; };
|
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */; };
|
||||||
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */; };
|
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */; };
|
||||||
@ -46,7 +61,7 @@
|
|||||||
5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02A23FE352C0062B6F2 /* SmartCardSecret.swift */; };
|
5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02A23FE352C0062B6F2 /* SmartCardSecret.swift */; };
|
||||||
5099A02E23FE56E10062B6F2 /* OpenSSHKeyWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */; };
|
5099A02E23FE56E10062B6F2 /* OpenSSHKeyWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */; };
|
||||||
5099A075240242BA0062B6F2 /* SecretAgentKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; };
|
5099A075240242BA0062B6F2 /* SecretAgentKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; };
|
||||||
5099A07C240242BA0062B6F2 /* SecretAgentKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */; };
|
5099A07C240242BA0062B6F2 /* AgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A07B240242BA0062B6F2 /* AgentTests.swift */; };
|
||||||
5099A07E240242BA0062B6F2 /* SecretAgentKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 5099A06E240242BA0062B6F2 /* SecretAgentKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
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 */; };
|
5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */; };
|
||||||
50A3B79124026B7600D209EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79024026B7600D209EA /* Assets.xcassets */; };
|
50A3B79124026B7600D209EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79024026B7600D209EA /* Assets.xcassets */; };
|
||||||
@ -99,6 +114,20 @@
|
|||||||
remoteGlobalIDString = 50617DA723FCE4AB0099B055;
|
remoteGlobalIDString = 50617DA723FCE4AB0099B055;
|
||||||
remoteInfo = SecretKit;
|
remoteInfo = SecretKit;
|
||||||
};
|
};
|
||||||
|
506773002426F3F400034DED /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 506772FA2426F3F400034DED;
|
||||||
|
remoteInfo = Brief;
|
||||||
|
};
|
||||||
|
5067730A2426F40A00034DED /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 506772FA2426F3F400034DED;
|
||||||
|
remoteInfo = Brief;
|
||||||
|
};
|
||||||
507CE4F12420A6B50029F750 /* PBXContainerItemProxy */ = {
|
507CE4F12420A6B50029F750 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
@ -123,6 +152,7 @@
|
|||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
50617DBE23FCE4AB0099B055 /* SecretKit.framework in Embed Frameworks */,
|
50617DBE23FCE4AB0099B055 /* SecretKit.framework in Embed Frameworks */,
|
||||||
|
506773032426F3F400034DED /* Brief.framework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -153,6 +183,7 @@
|
|||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
|
5067730C2426F40E00034DED /* Brief.framework in Embed Frameworks */,
|
||||||
50A5C18D240E4B4B00E2996C /* SecretAgentKit.framework in Embed Frameworks */,
|
50A5C18D240E4B4B00E2996C /* SecretAgentKit.framework in Embed Frameworks */,
|
||||||
50A5C190240E4B4C00E2996C /* SecretKit.framework in Embed Frameworks */,
|
50A5C190240E4B4C00E2996C /* SecretKit.framework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
@ -197,12 +228,26 @@
|
|||||||
50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclaveSecret.swift; sourceTree = "<group>"; };
|
50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclaveSecret.swift; sourceTree = "<group>"; };
|
||||||
50617DCF23FCED2C0099B055 /* SecureEnclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclave.swift; sourceTree = "<group>"; };
|
50617DCF23FCED2C0099B055 /* SecureEnclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclave.swift; sourceTree = "<group>"; };
|
||||||
50617DD123FCEFA90099B055 /* PreviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStore.swift; sourceTree = "<group>"; };
|
50617DD123FCEFA90099B055 /* PreviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStore.swift; sourceTree = "<group>"; };
|
||||||
|
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
||||||
|
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
||||||
|
506772FB2426F3F400034DED /* Brief.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Brief.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
506772FD2426F3F400034DED /* Brief.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Brief.h; sourceTree = "<group>"; };
|
||||||
|
506772FE2426F3F400034DED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
506773082426F3FD00034DED /* Updater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
|
||||||
|
5067730D242701BA00034DED /* OpenSSHReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHReaderTests.swift; sourceTree = "<group>"; };
|
||||||
|
5067730F2427057600034DED /* AnySecretTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnySecretTests.swift; sourceTree = "<group>"; };
|
||||||
5068389D241471CD00F55094 /* SecretStoreList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretStoreList.swift; sourceTree = "<group>"; };
|
5068389D241471CD00F55094 /* SecretStoreList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretStoreList.swift; sourceTree = "<group>"; };
|
||||||
506838A02415EA5600F55094 /* AnySecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnySecret.swift; sourceTree = "<group>"; };
|
506838A02415EA5600F55094 /* AnySecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnySecret.swift; sourceTree = "<group>"; };
|
||||||
506838A22415EA5D00F55094 /* AnySecretStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnySecretStore.swift; sourceTree = "<group>"; };
|
506838A22415EA5D00F55094 /* AnySecretStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnySecretStore.swift; sourceTree = "<group>"; };
|
||||||
50731665241DF8660023809E /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
|
|
||||||
50731668241E00C20023809E /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = "<group>"; };
|
50731668241E00C20023809E /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = "<group>"; };
|
||||||
507CE4EF2420A4C50029F750 /* SigningWitness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningWitness.swift; sourceTree = "<group>"; };
|
507CE4EF2420A4C50029F750 /* SigningWitness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningWitness.swift; sourceTree = "<group>"; };
|
||||||
|
507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningRequestProvenance.swift; sourceTree = "<group>"; };
|
||||||
|
507CE4F52420A96F0029F750 /* SigningRequestTracer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningRequestTracer.swift; sourceTree = "<group>"; };
|
||||||
|
507EE34124281E12003C4FE3 /* FileHandleProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHandleProtocols.swift; sourceTree = "<group>"; };
|
||||||
|
507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubFileHandleReader.swift; sourceTree = "<group>"; };
|
||||||
|
507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubFileHandleWriter.swift; sourceTree = "<group>"; };
|
||||||
|
507EE3492428263B003C4FE3 /* StubStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubStore.swift; sourceTree = "<group>"; };
|
||||||
|
507EE34D2428784F003C4FE3 /* StubWitness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubWitness.swift; sourceTree = "<group>"; };
|
||||||
508A58A9241E06B40069DC07 /* PreviewUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewUpdater.swift; sourceTree = "<group>"; };
|
508A58A9241E06B40069DC07 /* PreviewUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewUpdater.swift; sourceTree = "<group>"; };
|
||||||
508A58AB241E121B0069DC07 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
508A58AB241E121B0069DC07 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentStatusChecker.swift; sourceTree = "<group>"; };
|
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentStatusChecker.swift; sourceTree = "<group>"; };
|
||||||
@ -217,7 +262,7 @@
|
|||||||
5099A06E240242BA0062B6F2 /* SecretAgentKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecretAgentKit.h; sourceTree = "<group>"; };
|
5099A06E240242BA0062B6F2 /* SecretAgentKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecretAgentKit.h; sourceTree = "<group>"; };
|
||||||
5099A06F240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
5099A06F240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecretAgentKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
5099A07B240242BA0062B6F2 /* AgentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentTests.swift; sourceTree = "<group>"; };
|
||||||
5099A07D240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
5099A07D240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHAgentProtocol.swift; sourceTree = "<group>"; };
|
5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHAgentProtocol.swift; sourceTree = "<group>"; };
|
||||||
50A3B78A24026B7500D209EA /* SecretAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecretAgent.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
50A3B78A24026B7500D209EA /* SecretAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecretAgent.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -241,6 +286,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50617DBD23FCE4AB0099B055 /* SecretKit.framework in Frameworks */,
|
50617DBD23FCE4AB0099B055 /* SecretKit.framework in Frameworks */,
|
||||||
|
506773022426F3F400034DED /* Brief.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -266,6 +312,13 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
506772F82426F3F400034DED /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
5099A069240242BA0062B6F2 /* Frameworks */ = {
|
5099A069240242BA0062B6F2 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -304,6 +357,7 @@
|
|||||||
5099A06D240242BA0062B6F2 /* SecretAgentKit */,
|
5099A06D240242BA0062B6F2 /* SecretAgentKit */,
|
||||||
5099A07A240242BA0062B6F2 /* SecretAgentKitTests */,
|
5099A07A240242BA0062B6F2 /* SecretAgentKitTests */,
|
||||||
508A58AF241E144C0069DC07 /* Config */,
|
508A58AF241E144C0069DC07 /* Config */,
|
||||||
|
506772FC2426F3F400034DED /* Brief */,
|
||||||
50617D8023FCE48E0099B055 /* Products */,
|
50617D8023FCE48E0099B055 /* Products */,
|
||||||
5099A08B240243730062B6F2 /* Frameworks */,
|
5099A08B240243730062B6F2 /* Frameworks */,
|
||||||
);
|
);
|
||||||
@ -319,6 +373,7 @@
|
|||||||
5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */,
|
5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */,
|
||||||
5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */,
|
5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */,
|
||||||
50A3B78A24026B7500D209EA /* SecretAgent.app */,
|
50A3B78A24026B7500D209EA /* SecretAgent.app */,
|
||||||
|
506772FB2426F3F400034DED /* Brief.framework */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -333,6 +388,7 @@
|
|||||||
50617D8B23FCE48E0099B055 /* Main.storyboard */,
|
50617D8B23FCE48E0099B055 /* Main.storyboard */,
|
||||||
50617D8E23FCE48E0099B055 /* Info.plist */,
|
50617D8E23FCE48E0099B055 /* Info.plist */,
|
||||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
||||||
|
506772C62424784600034DED /* Credits.rtf */,
|
||||||
50617D8823FCE48E0099B055 /* Preview Content */,
|
50617D8823FCE48E0099B055 /* Preview Content */,
|
||||||
);
|
);
|
||||||
path = Secretive;
|
path = Secretive;
|
||||||
@ -376,6 +432,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */,
|
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */,
|
||||||
|
5067730D242701BA00034DED /* OpenSSHReaderTests.swift */,
|
||||||
|
5067730F2427057600034DED /* AnySecretTests.swift */,
|
||||||
50617DB923FCE4AB0099B055 /* Info.plist */,
|
50617DB923FCE4AB0099B055 /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = SecretKitTests;
|
path = SecretKitTests;
|
||||||
@ -391,6 +449,16 @@
|
|||||||
path = SecureEnclave;
|
path = SecureEnclave;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
506772FC2426F3F400034DED /* Brief */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
506773082426F3FD00034DED /* Updater.swift */,
|
||||||
|
506772FD2426F3F400034DED /* Brief.h */,
|
||||||
|
506772FE2426F3F400034DED /* Info.plist */,
|
||||||
|
);
|
||||||
|
path = Brief;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
5068389F2415EA4F00F55094 /* Erasers */ = {
|
5068389F2415EA4F00F55094 /* Erasers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -427,6 +495,7 @@
|
|||||||
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
||||||
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
||||||
|
506772C82425BB8500034DED /* NoStoresView.swift */,
|
||||||
50C385A8240B636500AF2719 /* SetupView.swift */,
|
50C385A8240B636500AF2719 /* SetupView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@ -435,7 +504,6 @@
|
|||||||
508A58B1241ED1EA0069DC07 /* Controllers */ = {
|
508A58B1241ED1EA0069DC07 /* Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50731665241DF8660023809E /* Updater.swift */,
|
|
||||||
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
||||||
);
|
);
|
||||||
path = Controllers;
|
path = Controllers;
|
||||||
@ -468,7 +536,10 @@
|
|||||||
5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */,
|
5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */,
|
||||||
50A3B79D24026B9900D209EA /* SocketController.swift */,
|
50A3B79D24026B9900D209EA /* SocketController.swift */,
|
||||||
507CE4EF2420A4C50029F750 /* SigningWitness.swift */,
|
507CE4EF2420A4C50029F750 /* SigningWitness.swift */,
|
||||||
|
507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */,
|
||||||
|
507CE4F52420A96F0029F750 /* SigningRequestTracer.swift */,
|
||||||
50A3B79F24026B9900D209EA /* Agent.swift */,
|
50A3B79F24026B9900D209EA /* Agent.swift */,
|
||||||
|
507EE34124281E12003C4FE3 /* FileHandleProtocols.swift */,
|
||||||
5099A06F240242BA0062B6F2 /* Info.plist */,
|
5099A06F240242BA0062B6F2 /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = SecretAgentKit;
|
path = SecretAgentKit;
|
||||||
@ -477,7 +548,11 @@
|
|||||||
5099A07A240242BA0062B6F2 /* SecretAgentKitTests */ = {
|
5099A07A240242BA0062B6F2 /* SecretAgentKitTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */,
|
5099A07B240242BA0062B6F2 /* AgentTests.swift */,
|
||||||
|
507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */,
|
||||||
|
507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */,
|
||||||
|
507EE34D2428784F003C4FE3 /* StubWitness.swift */,
|
||||||
|
507EE3492428263B003C4FE3 /* StubStore.swift */,
|
||||||
5099A07D240242BA0062B6F2 /* Info.plist */,
|
5099A07D240242BA0062B6F2 /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = SecretAgentKitTests;
|
path = SecretAgentKitTests;
|
||||||
@ -523,6 +598,14 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
506772F62426F3F400034DED /* Headers */ = {
|
||||||
|
isa = PBXHeadersBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
506772FF2426F3F400034DED /* Brief.h in Headers */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
5099A067240242BA0062B6F2 /* Headers */ = {
|
5099A067240242BA0062B6F2 /* Headers */ = {
|
||||||
isa = PBXHeadersBuildPhase;
|
isa = PBXHeadersBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -548,6 +631,7 @@
|
|||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
50617DBC23FCE4AB0099B055 /* PBXTargetDependency */,
|
50617DBC23FCE4AB0099B055 /* PBXTargetDependency */,
|
||||||
|
506773012426F3F400034DED /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Secretive;
|
name = Secretive;
|
||||||
productName = Secretive;
|
productName = Secretive;
|
||||||
@ -609,6 +693,24 @@
|
|||||||
productReference = 50617DB023FCE4AB0099B055 /* SecretKitTests.xctest */;
|
productReference = 50617DB023FCE4AB0099B055 /* SecretKitTests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
|
506772FA2426F3F400034DED /* Brief */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 506773042426F3F400034DED /* Build configuration list for PBXNativeTarget "Brief" */;
|
||||||
|
buildPhases = (
|
||||||
|
506772F62426F3F400034DED /* Headers */,
|
||||||
|
506772F72426F3F400034DED /* Sources */,
|
||||||
|
506772F82426F3F400034DED /* Frameworks */,
|
||||||
|
506772F92426F3F400034DED /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Brief;
|
||||||
|
productName = Brief;
|
||||||
|
productReference = 506772FB2426F3F400034DED /* Brief.framework */;
|
||||||
|
productType = "com.apple.product-type.framework";
|
||||||
|
};
|
||||||
5099A06B240242BA0062B6F2 /* SecretAgentKit */ = {
|
5099A06B240242BA0062B6F2 /* SecretAgentKit */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 5099A083240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKit" */;
|
buildConfigurationList = 5099A083240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKit" */;
|
||||||
@ -661,6 +763,7 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
5018F5492402736A002EB505 /* PBXTargetDependency */,
|
5018F5492402736A002EB505 /* PBXTargetDependency */,
|
||||||
5018F54B2402736A002EB505 /* PBXTargetDependency */,
|
5018F54B2402736A002EB505 /* PBXTargetDependency */,
|
||||||
|
5067730B2426F40A00034DED /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = SecretAgent;
|
name = SecretAgent;
|
||||||
productName = SecretAgent;
|
productName = SecretAgent;
|
||||||
@ -691,6 +794,10 @@
|
|||||||
50617DAF23FCE4AB0099B055 = {
|
50617DAF23FCE4AB0099B055 = {
|
||||||
CreatedOnToolsVersion = 11.3;
|
CreatedOnToolsVersion = 11.3;
|
||||||
};
|
};
|
||||||
|
506772FA2426F3F400034DED = {
|
||||||
|
CreatedOnToolsVersion = 11.4;
|
||||||
|
LastSwiftMigration = 1140;
|
||||||
|
};
|
||||||
5099A06B240242BA0062B6F2 = {
|
5099A06B240242BA0062B6F2 = {
|
||||||
CreatedOnToolsVersion = 11.4;
|
CreatedOnToolsVersion = 11.4;
|
||||||
LastSwiftMigration = 1140;
|
LastSwiftMigration = 1140;
|
||||||
@ -723,6 +830,7 @@
|
|||||||
50617DAF23FCE4AB0099B055 /* SecretKitTests */,
|
50617DAF23FCE4AB0099B055 /* SecretKitTests */,
|
||||||
5099A06B240242BA0062B6F2 /* SecretAgentKit */,
|
5099A06B240242BA0062B6F2 /* SecretAgentKit */,
|
||||||
5099A073240242BA0062B6F2 /* SecretAgentKitTests */,
|
5099A073240242BA0062B6F2 /* SecretAgentKitTests */,
|
||||||
|
506772FA2426F3F400034DED /* Brief */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@ -735,6 +843,7 @@
|
|||||||
50617D8D23FCE48E0099B055 /* Main.storyboard in Resources */,
|
50617D8D23FCE48E0099B055 /* Main.storyboard in Resources */,
|
||||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
|
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
|
||||||
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */,
|
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */,
|
||||||
|
506772C72424784600034DED /* Credits.rtf in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -759,6 +868,13 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
506772F92426F3F400034DED /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
5099A06A240242BA0062B6F2 /* Resources */ = {
|
5099A06A240242BA0062B6F2 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -796,11 +912,11 @@
|
|||||||
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
||||||
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
||||||
50731666241DF8660023809E /* Updater.swift in Sources */,
|
|
||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
||||||
50731669241E00C20023809E /* NoticeView.swift in Sources */,
|
50731669241E00C20023809E /* NoticeView.swift in Sources */,
|
||||||
50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */,
|
50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */,
|
||||||
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */,
|
||||||
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
|
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
|
||||||
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
|
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
|
||||||
);
|
);
|
||||||
@ -839,6 +955,16 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */,
|
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */,
|
||||||
|
506773102427057600034DED /* AnySecretTests.swift in Sources */,
|
||||||
|
5067730E242701BA00034DED /* OpenSSHReaderTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
506772F72426F3F400034DED /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
506773092426F3FD00034DED /* Updater.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -846,10 +972,13 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
507EE34224281E12003C4FE3 /* FileHandleProtocols.swift in Sources */,
|
||||||
507CE4EE2420A3CA0029F750 /* SocketController.swift in Sources */,
|
507CE4EE2420A3CA0029F750 /* SocketController.swift in Sources */,
|
||||||
5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */,
|
5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */,
|
||||||
507CE4ED2420A3C70029F750 /* Agent.swift in Sources */,
|
507CE4ED2420A3C70029F750 /* Agent.swift in Sources */,
|
||||||
507CE4F02420A4C50029F750 /* SigningWitness.swift in Sources */,
|
507CE4F02420A4C50029F750 /* SigningWitness.swift in Sources */,
|
||||||
|
507CE4F62420A96F0029F750 /* SigningRequestTracer.swift in Sources */,
|
||||||
|
507CE4F42420A8C10029F750 /* SigningRequestProvenance.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -857,7 +986,11 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
5099A07C240242BA0062B6F2 /* SecretAgentKitTests.swift in Sources */,
|
507EE34E2428784F003C4FE3 /* StubWitness.swift in Sources */,
|
||||||
|
507EE34624281F89003C4FE3 /* StubFileHandleReader.swift in Sources */,
|
||||||
|
507EE34A2428263B003C4FE3 /* StubStore.swift in Sources */,
|
||||||
|
5099A07C240242BA0062B6F2 /* AgentTests.swift in Sources */,
|
||||||
|
507EE34824281FB8003C4FE3 /* StubFileHandleWriter.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -898,6 +1031,16 @@
|
|||||||
target = 50617DA723FCE4AB0099B055 /* SecretKit */;
|
target = 50617DA723FCE4AB0099B055 /* SecretKit */;
|
||||||
targetProxy = 50617DBB23FCE4AB0099B055 /* PBXContainerItemProxy */;
|
targetProxy = 50617DBB23FCE4AB0099B055 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
506773012426F3F400034DED /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 506772FA2426F3F400034DED /* Brief */;
|
||||||
|
targetProxy = 506773002426F3F400034DED /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
5067730B2426F40A00034DED /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 506772FA2426F3F400034DED /* Brief */;
|
||||||
|
targetProxy = 5067730A2426F40A00034DED /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
507CE4F22420A6B50029F750 /* PBXTargetDependency */ = {
|
507CE4F22420A6B50029F750 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 50617DA723FCE4AB0099B055 /* SecretKit */;
|
target = 50617DA723FCE4AB0099B055 /* SecretKit */;
|
||||||
@ -1242,6 +1385,95 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
506773052426F3F400034DED /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
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 = Brief/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Brief;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
506773062426F3F400034DED /* Test */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
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 = Brief/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Brief;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
VERSION_INFO_PREFIX = "";
|
||||||
|
};
|
||||||
|
name = Test;
|
||||||
|
};
|
||||||
|
506773072426F3F400034DED /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
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 = Brief/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Brief;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
VERSION_INFO_PREFIX = "";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
508A5914241EF1A00069DC07 /* Test */ = {
|
508A5914241EF1A00069DC07 /* Test */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
|
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
|
||||||
@ -1415,8 +1647,8 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
INFOPLIST_FILE = SecretKitTests/Info.plist;
|
INFOPLIST_FILE = SecretKitTests/Info.plist;
|
||||||
@ -1427,6 +1659,7 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretKitTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretKitTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@ -1465,7 +1698,8 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
|
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
|
||||||
@ -1476,6 +1710,7 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKitTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKitTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@ -1543,6 +1778,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
|
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
|
||||||
@ -1561,6 +1797,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
|
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
|
||||||
@ -1678,6 +1915,16 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
506773042426F3F400034DED /* Build configuration list for PBXNativeTarget "Brief" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
506773052426F3F400034DED /* Debug */,
|
||||||
|
506773062426F3F400034DED /* Test */,
|
||||||
|
506773072426F3F400034DED /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
5099A083240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKit" */ = {
|
5099A083240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKit" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Test"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import Brief
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
@ -66,7 +67,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
@IBAction func runSetup(sender: AnyObject?) {
|
@IBAction func runSetup(sender: AnyObject?) {
|
||||||
let setupWindow = NSWindow(
|
let setupWindow = NSWindow(
|
||||||
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
|
contentRect: NSRect(x: 0, y: 0, width: 0, height: 0),
|
||||||
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
|
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
|
||||||
backing: .buffered, defer: false)
|
backing: .buffered, defer: false)
|
||||||
let setupView = SetupView() { success in
|
let setupView = SetupView() { success in
|
||||||
|
23
Secretive/Credits.rtf
Normal file
23
Secretive/Credits.rtf
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{\rtf1\ansi\ansicpg1252\cocoartf2511
|
||||||
|
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
|
{\colortbl;\red255\green255\blue255;}
|
||||||
|
{\*\expandedcolortbl;;}
|
||||||
|
\margl1440\margr1440\vieww9000\viewh8400\viewkind0
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6119\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive"}}{\fldrslt
|
||||||
|
\f0\fs24 \cf0 GitHub Repository}}
|
||||||
|
\f0\fs24 \
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
\cf0 \
|
||||||
|
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
||||||
|
\
|
||||||
|
Special Thanks To:\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/bdash"}}{\fldrslt Mark Rowe}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/danielctull"}}{\fldrslt Daniel Tull}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/esttorhe"}}{\fldrslt Esteban Torres}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/joeblau"}}{\fldrslt Joe Blau}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/mergesort"}}{\fldrslt Joe Fabisevich}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
@ -19,11 +19,11 @@
|
|||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(CI_VERSION)</string>
|
<string>$(CI_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CI_VERSION)</string>
|
<string>$(CI_BUILD_NUMBER)</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
|
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
|
||||||
<key>NSMainStoryboardFile</key>
|
<key>NSMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
import Brief
|
||||||
|
|
||||||
class PreviewUpdater: UpdaterProtocol {
|
class PreviewUpdater: UpdaterProtocol {
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import Brief
|
||||||
|
|
||||||
struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentStatusCheckerProtocol>: View {
|
struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentStatusCheckerProtocol>: View {
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
|
|||||||
if !agentStatusChecker.running {
|
if !agentStatusChecker.running {
|
||||||
agentNotice()
|
agentNotice()
|
||||||
}
|
}
|
||||||
|
if storeList.anyAvailable {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(selection: $active) {
|
List(selection: $active) {
|
||||||
ForEach(storeList.stores) { store in
|
ForEach(storeList.stores) { store in
|
||||||
@ -68,7 +70,10 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
NoStoresView()
|
||||||
|
}
|
||||||
|
}.frame(minWidth: 640, minHeight: 320)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateNotice() -> some View {
|
func updateNotice() -> some View {
|
||||||
|
29
Secretive/Views/NoStoresView.swift
Normal file
29
Secretive/Views/NoStoresView.swift
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// NoStoresView.swift
|
||||||
|
// Secretive
|
||||||
|
//
|
||||||
|
// Created by Max Goedjen on 3/20/20.
|
||||||
|
// Copyright © 2020 Max Goedjen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NoStoresView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("No Secure Storage Available").bold()
|
||||||
|
Text("Your Mac doesn't have a Secure Enclave, and there's not a compatible Smart Card inserted.")
|
||||||
|
Button(action: {
|
||||||
|
NSWorkspace.shared.open(URL(string: "https://www.yubico.com/products/compare-yubikey-5-series/")!)
|
||||||
|
}) {
|
||||||
|
Text("If you're looking to add one to your Mac, the YubiKey 5 Series are great.")
|
||||||
|
}
|
||||||
|
}.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoStoresView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
NoStoresView()
|
||||||
|
}
|
||||||
|
}
|
@ -3,18 +3,18 @@ import SwiftUI
|
|||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
|
|
||||||
struct SetupView: View {
|
struct SetupView: View {
|
||||||
|
|
||||||
var completion: ((Bool) -> Void)?
|
var completion: ((Bool) -> Void)?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
SetupStepView<Spacer>(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.",
|
SetupStepView<Spacer>(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,
|
index: 1,
|
||||||
nestedView: nil,
|
nestedView: nil,
|
||||||
actionText: "Install") {
|
actionText: "Install") {
|
||||||
self.installLaunchAgent()
|
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.",
|
SetupStepView(text: "Add this 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,
|
index: 2,
|
||||||
nestedView: SetupStepCommandView(text: Constants.socketPrompt),
|
nestedView: SetupStepCommandView(text: Constants.socketPrompt),
|
||||||
actionText: "Added") {
|
actionText: "Added") {
|
||||||
@ -22,25 +22,25 @@ struct SetupView: View {
|
|||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(action: { self.completion?(true) }) {
|
Button(action: { self.completion?(true) }) {
|
||||||
Text("Finish")
|
Text("Finish")
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}.frame(minWidth: 640, minHeight: 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SetupStepView<NestedViewType: View>: View {
|
struct SetupStepView<NestedViewType: View>: View {
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
let index: Int
|
let index: Int
|
||||||
let nestedView: NestedViewType?
|
let nestedView: NestedViewType?
|
||||||
@State var completed = false
|
@State var completed = false
|
||||||
let actionText: String
|
let actionText: String
|
||||||
let action: (() -> Bool)
|
let action: (() -> Bool)
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section {
|
Section {
|
||||||
HStack {
|
HStack {
|
||||||
@ -64,10 +64,8 @@ struct SetupStepView<NestedViewType: View>: View {
|
|||||||
Text(text)
|
Text(text)
|
||||||
.opacity(completed ? 0.5 : 1)
|
.opacity(completed ? 0.5 : 1)
|
||||||
.lineLimit(nil)
|
.lineLimit(nil)
|
||||||
.frame(idealHeight: 0, maxHeight: .infinity)
|
|
||||||
if nestedView != nil {
|
if nestedView != nil {
|
||||||
Spacer()
|
nestedView!.padding()
|
||||||
nestedView!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
@ -83,43 +81,58 @@ struct SetupStepView<NestedViewType: View>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct SetupStepCommandView: View {
|
struct SetupStepCommandView: View {
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text(text)
|
VStack(alignment: .leading) {
|
||||||
.font(.system(.caption, design: .monospaced))
|
Text(text)
|
||||||
.lineLimit(nil)
|
.lineLimit(nil)
|
||||||
.frame(idealHeight: 0, maxHeight: .infinity)
|
.font(.system(.caption, design: .monospaced))
|
||||||
.padding()
|
.multilineTextAlignment(.leading)
|
||||||
.background(Color(white: 0, opacity: 0.10))
|
.frame(minHeight: 50)
|
||||||
.cornerRadius(10)
|
HStack {
|
||||||
.onDrag {
|
Spacer()
|
||||||
return NSItemProvider(item: NSData(data: self.text.data(using: .utf8)!), typeIdentifier: kUTTypeUTF8PlainText as String)
|
Button(action: copy) {
|
||||||
|
Text("Copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copy() {
|
||||||
|
NSPasteboard.general.declareTypes([.string], owner: nil)
|
||||||
|
NSPasteboard.general.setString(text, forType: .string)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SetupView {
|
extension SetupView {
|
||||||
|
|
||||||
func installLaunchAgent() -> Bool {
|
func installLaunchAgent() -> Bool {
|
||||||
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, true)
|
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAsDone() -> Bool {
|
func markAsDone() -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SetupView {
|
extension SetupView {
|
||||||
|
|
||||||
enum Constants {
|
enum Constants {
|
||||||
static let socketPath = (NSHomeDirectory().replacingOccurrences(of: "com.maxgoedjen.Secretive.Host", with: "com.maxgoedjen.Secretive.SecretAgent") as NSString).appendingPathComponent("socket.ssh") as String
|
static let socketPath = (NSHomeDirectory().replacingOccurrences(of: "com.maxgoedjen.Secretive.Host", with: "com.maxgoedjen.Secretive.SecretAgent") as NSString).appendingPathComponent("socket.ssh") as String
|
||||||
static let socketPrompt = "export SSH_AUTH_SOCK=\(socketPath)"
|
static let socketPrompt = "export SSH_AUTH_SOCK=\(socketPath)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
@ -1,11 +1,3 @@
|
|||||||
//
|
|
||||||
// SecretiveTests.swift
|
|
||||||
// SecretiveTests
|
|
||||||
//
|
|
||||||
// Created by Max Goedjen on 2/18/20.
|
|
||||||
// Copyright © 2020 Max Goedjen. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@testable import Secretive
|
@testable import Secretive
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user