Compare commits

...

39 Commits

Author SHA1 Message Date
9c60a0b7dd Merge branch 'master' of github.com:maxgoedjen/secretive 2020-04-02 00:54:14 -07:00
5b38ef00c1 Fix #84 2020-04-02 00:53:55 -07:00
ccbf92785d Fix padding bug (#83) 2020-04-02 00:43:45 -07:00
f1e8e43f62 Update FAQ.md 2020-04-01 22:11:45 -07:00
7b1563f167 Update FAQ.md 2020-03-31 23:11:04 -07:00
cca070dbe4 Update FAQ.md 2020-03-30 23:44:38 -07:00
31e51e45c1 Move types to common (#81) 2020-03-29 17:26:43 -07:00
4bfb3a0012 Rephrase access control. 2020-03-28 18:08:54 -07:00
a0ed880386 Fix #80. 2020-03-27 19:24:37 -07:00
92b9648e04 FAQ (#76) 2020-03-25 23:35:02 -07:00
4a2a342670 Create FUNDING.yml 2020-03-24 22:16:04 -07:00
1d147d8e34 Update README.md 2020-03-24 22:04:49 -07:00
4dcc9b113d Agent Tests (#74) 2020-03-23 23:22:22 -07:00
1f7ebcfe75 MIT notice on brief 2020-03-23 21:19:40 -07:00
b090a96975 Add option to ignore non-critical updates (#73) 2020-03-22 15:26:29 -07:00
594d855f19 Move status badges. 2020-03-21 23:40:43 -07:00
89f0798576 Add status badges 2020-03-21 23:40:10 -07:00
0d74f6f561 Really fix signing for brief 2020-03-21 23:36:52 -07:00
efeb7f09a3 Fix brief signing 2020-03-21 23:33:51 -07:00
104199cf53 Add test 2020-03-21 19:37:08 -07:00
f214da98a4 Remove test asset. 2020-03-21 19:37:03 -07:00
95cf88d3ca Add type eraser tests. 2020-03-21 19:36:11 -07:00
04835fa437 Fix weird spacing 2020-03-21 19:28:49 -07:00
31dfba8265 Add tests for reader. 2020-03-21 19:28:08 -07:00
df8eedebd0 Adding notifications for updater (#70) 2020-03-21 18:43:26 -07:00
0a9ecb039e Minimum frames (#69) 2020-03-21 18:10:37 -07:00
2e9a3eb90d Merge branch 'master' of github.com:maxgoedjen/secretive 2020-03-21 18:09:44 -07:00
df599fbe62 Update readme screenshot 2020-03-21 17:56:29 -07:00
7c60e9b04b Improve subtitle 2020-03-21 17:55:31 -07:00
f7f182ec77 Merge branch 'master' of github.com:maxgoedjen/secretive 2020-03-21 17:52:56 -07:00
a0052e8395 Richer notifications (#68) 2020-03-21 17:52:51 -07:00
10d3fa150c Better copy 2020-03-20 21:23:15 -07:00
c5abca4099 Indent. 2020-03-20 21:14:51 -07:00
f674d47507 Merge branch 'master' of github.com:maxgoedjen/secretive 2020-03-20 21:08:48 -07:00
1e3ece600d Fixes #51. 2020-03-20 21:07:58 -07:00
7481498c5b Basic no stores view (#66) 2020-03-20 20:20:20 -07:00
a6fbcdbe55 MIT licensing notices 2020-03-19 21:36:25 -07:00
36e655e527 Better links 2020-03-19 21:33:57 -07:00
dd3acf5ae1 Remove redundant import 2020-03-19 21:28:27 -07:00
42 changed files with 1039 additions and 166 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: maxgoedjen

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

19
Brief/Brief.h Normal file
View 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
View 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>

View File

@ -1,17 +1,17 @@
import Foundation
import Combine
protocol UpdaterProtocol: ObservableObject {
public protocol UpdaterProtocol: ObservableObject {
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()
let timer = Timer.scheduledTimer(withTimeInterval: 60*60*24, repeats: true) { _ in
self.checkForUpdates()
@ -19,7 +19,7 @@ class Updater: ObservableObject, UpdaterProtocol {
timer.tolerance = 60*60
}
func checkForUpdates() {
public func checkForUpdates() {
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in
guard let data = data else { return }
guard let release = try? JSONDecoder().decode(Release.self, from: data) else { return }
@ -27,7 +27,20 @@ class Updater: ObservableObject, UpdaterProtocol {
}.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) {
guard !userIgnored(release: release) else { return }
let latestVersion = semVer(from: release.name)
let currentVersion = semVer(from: Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String)
for (latest, current) in zip(latestVersion, currentVersion) {
@ -48,6 +61,14 @@ class Updater: ObservableObject, UpdaterProtocol {
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 {
@ -58,16 +79,24 @@ extension Updater {
}
struct Release: Codable {
let name: String
let html_url: URL
let body: String
public struct Release: Codable {
public let name: 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 {
var critical: Bool {
public var critical: Bool {
return body.contains(Constants.securityContent)
}

37
FAQ.md Normal file
View File

@ -0,0 +1,37 @@
# FAQ
### How do I import my current SSH keys, or export my Secretive Keys?
The secure enclave doesn't allow import or export of private keys. For any new computer, you should just create a new set of keys. If you're using a smart card, you _might_ be able to export your private key from the vendor's software.
### Secretive doesn't work with my git client
Secretive relies on the `SSH_AUTH_SOCK` environment variable being respected. The `git` and `ssh` command line tools natively respect this, but third party apps may require some configuration to work. A non-exhaustive list of clients is provided here:
Tower - [Instructions](https://www.git-tower.com/help/mac/integration/environment)
GitHub Desktop: Should just work, no configuration needed
### Secretive isn't working for me
Please run `ssh -Tv git@github.com` in your terminal and paste the output in a [new GitHub issue](https://github.com/maxgoedjen/secretive/issues/new) with a description of your issue.
### Why should I trust you?
You shouldn't, for a piece of software like this. Secretive, by design, has an auditable build process. Each build has a fully auditable build log, showing the source it was built from and a SHA of the build product. You can check the SHA of the zip you download against the SHA output in the build log (which is linked in the About window).
### I want to build Secretive from source
Awesome! Just bear in mind that because an app only has access to the keychain items that it created, if you have secrets that you created with the prebuilt version of Secretive, you'll be unable to access them using your own custom build (since you'll have changed the bundled ID).
### I have a security issue
Please contact [max.goedjen@gmail.com](mailto:max.goedjen@gmail.com) with a subject containing "SECRETIVE SECURITY" immediately with details, and I'll address the issue and credit you ASAP.
### I have a non-security related bug
Please file a [GitHub issue](https://github.com/maxgoedjen/secretive/issues/new) for it. I will not provide email support with the exception of the critical security issues mentioned above.
### I want to contribute to Secretive
Sweet! Please check out the [contributing guidelines](CONTRIBUTING.md) and go from there.

View File

@ -1,9 +1,11 @@
# Secretive
# Secretive ![Test](https://github.com/maxgoedjen/secretive/workflows/Test/badge.svg) ![Release](https://github.com/maxgoedjen/secretive/workflows/Release/badge.svg)
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">
## Why?
### Safer Storage
@ -12,7 +14,7 @@ The most common setup for SSH keys is just keeping them on disk, guarded by prop
### Access Control
If your Mac has a Secure Enclave, it also has support for strong biometric access controls like Touch ID. You can configure your key so that they require Touch ID (or Watch) authentication before they're accessed.
If your Mac has a Secure Enclave, it also has support for strong access controls like Touch ID, or authentication with Apple Watch. You can configure your key so that they require Touch ID (or Watch) authentication before they're accessed.
<img src="/.github/readme/touchid.png" alt="Screenshot of Secretive authenticating with Touch ID">
@ -28,15 +30,11 @@ For Macs without Secure Enclaves, you can configure a Smart Card (such as a Yubi
## Getting Started
### Setup for Third Party Apps
### FAQ
When you first launch Secretive, you'll be prompted to set up your command line environment. You can redisplay this prompt at any time by going to `Menu > Help -> Set Up Helper App`.
For non-command-line based apps, like GUI Git clients, you may need to go through app-specific setup.
There's a [FAQ here](FAQ.md).
[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.
@ -50,4 +48,4 @@ Beacuse secrets in the Secure Enclave are not exportable, they are not able to b
## Security
If you discover any vulnerabilities in this project, please notify max.goedjen@gmail.com
If you discover any vulnerabilities in this project, please notify [max.goedjen@gmail.com](mailto:max.goedjen@gmail.com) with the subject containing "SECRETIVE SECURITY."

View File

@ -1,7 +1,9 @@
import Cocoa
import OSLog
import Combine
import SecretKit
import SecretAgentKit
import OSLog
import Brief
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@ -12,6 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
list.add(store: SmartCard.Store())
return list
}()
let updater = Updater()
let notifier = Notifier()
lazy var agent: Agent = {
Agent(storeList: storeList, witness: notifier)
@ -20,17 +23,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
return SocketController(path: path)
}()
fileprivate var updateSink: AnyCancellable?
func applicationDidFinishLaunching(_ aNotification: Notification) {
os_log(.debug, "SecretAgent finished launching")
DispatchQueue.main.async {
self.socketController.handler = self.agent.handle(fileHandle:)
self.socketController.handler = self.agent.handle(reader:writer:)
}
notifier.prompt()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
updateSink = updater.$update.sink { update in
guard let update = update else { return }
self.notifier.notify(update: update, ignore: self.updater.ignore(release:))
}
}

View File

@ -25,7 +25,7 @@
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>

View File

@ -1,10 +1,23 @@
import Foundation
import UserNotifications
import AppKit
import SecretKit
import SecretAgentKit
import UserNotifications
import Brief
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() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.requestAuthorization(options: .alert) { _, _ in
@ -14,12 +27,49 @@ class Notifier {
func notify(accessTo secret: AnySecret, by provenance: SigningRequestProvenance) {
let notificationCenter = UNUserNotificationCenter.current()
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Signed Request"
notificationContent.body = "\(secret.name) was used to sign a request from \(provenance.origin.name)."
notificationContent.title = "Signed Request from \(provenance.origin.name)"
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)
notificationCenter.add(request, withCompletionHandler: nil)
}
}
extension Notifier {
func iconURL(for provenance: SigningRequestProvenance) -> URL? {
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 {
@ -32,3 +82,42 @@ extension Notifier: SigningWitness {
}
}
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)
}
}

View File

@ -4,6 +4,8 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.smartcard</key>
<true/>
<key>keychain-access-groups</key>

View File

@ -2,7 +2,6 @@ import Foundation
import CryptoKit
import OSLog
import SecretKit
import SecretAgentKit
import AppKit
public class Agent {
@ -22,28 +21,34 @@ public class Agent {
extension Agent {
public func handle(fileHandle: FileHandle) {
public func handle(reader: FileHandleReader, writer: FileHandleWriter) {
os_log(.debug, "Agent handling new data")
let data = fileHandle.availableData
let data = reader.availableData
guard !data.isEmpty else { return }
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)
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()
do {
switch requestType {
case .requestIdentities:
response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data)
response.append(try identities())
response.append(identities())
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)
case .signRequest:
let provenance = requestTracer.provenance(from: reader)
response.append(SSHAgent.ResponseType.agentSignResponse.data)
response.append(try sign(data: data, from: fileHandle.fileDescriptor))
response.append(try sign(data: data, provenance: provenance))
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentSignResponse.debugDescription)
}
} catch {
@ -52,14 +57,14 @@ extension Agent {
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription)
}
let full = OpenSSHKeyWriter().lengthAndData(of: response)
fileHandle.write(full)
return full
}
}
extension Agent {
func identities() throws -> Data {
func identities() -> Data {
// TODO: RESTORE ONCE XCODE 11.4 IS GM
let secrets = storeList.stores.flatMap { $0.secrets }
// let secrets = storeList.stores.flatMap(\.secrets)
@ -77,20 +82,19 @@ extension Agent {
return countData + keyData
}
func sign(data: Data, from pid: Int32) throws -> Data {
func sign(data: Data, provenance: SigningRequestProvenance) throws -> Data {
let reader = OpenSSHReader(data: data)
let hash = try reader.readNextChunk()
let hash = reader.readNextChunk()
guard let (store, secret) = secret(matching: hash) else {
os_log(.debug, "Agent did not have a key matching %@", hash as NSData)
throw AgentError.noMatchingKey
}
let provenance = requestTracer.provenance(from: pid)
if let witness = witness {
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 curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
@ -104,13 +108,22 @@ extension Agent {
case (.ellipticCurve, 384):
rawRepresentation = try CryptoKit.P384.Signing.ECDSASignature(derRepresentation: derSignature).rawRepresentation
default:
fatalError()
throw AgentError.unsupportedKeyType
}
let rawLength = rawRepresentation.count/2
let r = rawRepresentation[0..<rawLength]
let s = rawRepresentation[rawLength...]
// Check if we need to pad with 0x00 to prevent certain
// ssh servers from thinking r or s is negative
let paddingRange: ClosedRange<UInt8> = 0x80...0xFF
var r = Data(rawRepresentation[0..<rawLength])
if paddingRange ~= r.first! {
r.insert(0x00, at: 0)
}
var s = Data(rawRepresentation[rawLength...])
if paddingRange ~= s.first! {
s.insert(0x00, at: 0)
}
var signatureChunk = Data()
signatureChunk.append(writer.lengthAndData(of: r))
@ -155,6 +168,7 @@ extension Agent {
enum AgentError: Error {
case unhandledType
case noMatchingKey
case unsupportedKeyType
}
}

View 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)
}
}

View File

@ -19,6 +19,6 @@
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
</dict>
</plist>

View File

@ -12,7 +12,7 @@ extension SSHAgent {
switch self {
case .requestIdentities:
return "RequestIdentities"
default:
case .signRequest:
return "SignRequest"
}
}

View File

@ -1,11 +1,3 @@
//
// SecretAgentKit.h
// SecretAgentKit
//
// Created by Max Goedjen on 2/22/20.
// Copyright © 2020 Max Goedjen. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Security/Security.h>

View File

@ -1,7 +1,7 @@
import Foundation
import AppKit
public struct SigningRequestProvenance {
public struct SigningRequestProvenance: Equatable {
public var chain: [Process]
public init(root: Process) {
@ -24,7 +24,7 @@ extension SigningRequestProvenance {
extension SigningRequestProvenance {
public struct Process {
public struct Process: Equatable {
public let pid: Int32
public let name: String

View File

@ -4,12 +4,8 @@ import Security
struct SigningRequestTracer {
func provenance(from pid: Int32) -> SigningRequestProvenance {
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 1)
var len = socklen_t(MemoryLayout<Int32>.size)
getsockopt(pid, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len)
let pid = pidPointer.load(as: Int32.self)
let firstInfo = process(from: pid)
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 {

View File

@ -5,7 +5,7 @@ public class SocketController {
fileprivate var fileHandle: FileHandle?
fileprivate var port: SocketPort?
public var handler: ((FileHandle) -> Void)?
public var handler: ((FileHandleReader, FileHandleWriter) -> Void)?
public init(path: String) {
os_log(.debug, "Socket controller setting up at %@", path)
@ -52,7 +52,7 @@ public class SocketController {
@objc func handleConnectionAccept(notification: Notification) {
os_log(.debug, "Socket controller accepted connection")
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
handler?(new)
handler?(new, new)
new.waitForDataInBackgroundAndNotify()
fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!])
}
@ -61,7 +61,7 @@ public class SocketController {
os_log(.debug, "Socket controller has new data available")
guard let new = notification.object as? FileHandle else { return }
os_log(.debug, "Socket controller received new file handle")
handler?(new)
handler?(new, new)
}
}

View File

@ -0,0 +1,169 @@
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)
var r = rsData.readNextChunk()
var s = rsData.readNextChunk()
// This is fine IRL, but it freaks out CryptoKit
if r[0] == 0 {
r.removeFirst()
}
if s[0] == 0 {
s.removeFirst()
}
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==")!)
}
}
}

View File

@ -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.
}
}

View 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
}
}

View File

@ -0,0 +1,11 @@
import SecretAgentKit
class StubFileHandleWriter: FileHandleWriter {
var data = Data()
func write(_ data: Data) {
self.data.append(data)
}
}

View 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 {
}
}

View 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 {
}
}

View File

@ -8,7 +8,7 @@ public class OpenSSHReader {
remaining = Data(data)
}
public func readNextChunk() throws -> Data {
public func readNextChunk() -> Data {
let lengthRange = 0..<(UInt32.bitWidth/8)
let lengthChunk = remaining[lengthRange]
remaining.removeSubrange(lengthRange)

View File

@ -20,6 +20,10 @@ public class SecretStoreList: ObservableObject {
addInternal(store: modifiable)
}
public var anyAvailable: Bool {
stores.reduce(false, { $0 || $1.isAvailable })
}
}
extension SecretStoreList {

View File

@ -7,7 +7,7 @@ public protocol Secret: Identifiable, Hashable {
}
public enum Algorithm {
public enum Algorithm: Hashable {
case ellipticCurve
public init(secAttr: NSNumber) {
let secAttrString = secAttr.stringValue as CFString

View File

@ -19,6 +19,6 @@
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
</dict>
</plist>

View File

@ -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>
//! Project version number for SecretKit.

View 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)
}
}

View 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")!
}
}

View File

@ -27,17 +27,29 @@
50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.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 */; };
506838A12415EA5600F55094 /* AnySecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506838A02415EA5600F55094 /* AnySecret.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, ); }; };
50731666241DF8660023809E /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50731665241DF8660023809E /* Updater.swift */; };
50731669241E00C20023809E /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50731668241E00C20023809E /* NoticeView.swift */; };
507CE4ED2420A3C70029F750 /* Agent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79F24026B9900D209EA /* Agent.swift */; };
507CE4EE2420A3CA0029F750 /* SocketController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A3B79D24026B9900D209EA /* SocketController.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 */; };
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */; };
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */; };
@ -49,7 +61,7 @@
5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02A23FE352C0062B6F2 /* SmartCardSecret.swift */; };
5099A02E23FE56E10062B6F2 /* OpenSSHKeyWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */; };
5099A075240242BA0062B6F2 /* SecretAgentKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; };
5099A07C240242BA0062B6F2 /* SecretAgentKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */; };
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, ); }; };
5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */; };
50A3B79124026B7600D209EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79024026B7600D209EA /* Assets.xcassets */; };
@ -102,6 +114,20 @@
remoteGlobalIDString = 50617DA723FCE4AB0099B055;
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 */ = {
isa = PBXContainerItemProxy;
containerPortal = 50617D7723FCE48D0099B055 /* Project object */;
@ -126,6 +152,7 @@
dstSubfolderSpec = 10;
files = (
50617DBE23FCE4AB0099B055 /* SecretKit.framework in Embed Frameworks */,
506773032426F3F400034DED /* Brief.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -156,6 +183,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
5067730C2426F40E00034DED /* Brief.framework in Embed Frameworks */,
50A5C18D240E4B4B00E2996C /* SecretAgentKit.framework in Embed Frameworks */,
50A5C190240E4B4C00E2996C /* SecretKit.framework in Embed Frameworks */,
);
@ -201,14 +229,25 @@
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -223,7 +262,7 @@
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>"; };
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>"; };
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; };
@ -247,6 +286,7 @@
buildActionMask = 2147483647;
files = (
50617DBD23FCE4AB0099B055 /* SecretKit.framework in Frameworks */,
506773022426F3F400034DED /* Brief.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -272,6 +312,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
506772F82426F3F400034DED /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5099A069240242BA0062B6F2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -299,6 +346,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
504BA92D243171F20064740E /* Types */ = {
isa = PBXGroup;
children = (
50617DCA23FCECA10099B055 /* Secret.swift */,
50617DC623FCE4EA0099B055 /* SecretStore.swift */,
);
path = Types;
sourceTree = "<group>";
};
50617D7623FCE48D0099B055 = {
isa = PBXGroup;
children = (
@ -310,6 +366,7 @@
5099A06D240242BA0062B6F2 /* SecretAgentKit */,
5099A07A240242BA0062B6F2 /* SecretAgentKitTests */,
508A58AF241E144C0069DC07 /* Config */,
506772FC2426F3F400034DED /* Brief */,
50617D8023FCE48E0099B055 /* Products */,
5099A08B240243730062B6F2 /* Frameworks */,
);
@ -325,6 +382,7 @@
5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */,
5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */,
50A3B78A24026B7500D209EA /* SecretAgent.app */,
506772FB2426F3F400034DED /* Brief.framework */,
);
name = Products;
sourceTree = "<group>";
@ -369,8 +427,6 @@
isa = PBXGroup;
children = (
50617DAA23FCE4AB0099B055 /* SecretKit.h */,
50617DCA23FCECA10099B055 /* Secret.swift */,
50617DC623FCE4EA0099B055 /* SecretStore.swift */,
5099A02C23FE56D70062B6F2 /* Common */,
50617DCC23FCECEE0099B055 /* SecureEnclave */,
5099A02523FE34DE0062B6F2 /* SmartCard */,
@ -383,6 +439,8 @@
isa = PBXGroup;
children = (
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */,
5067730D242701BA00034DED /* OpenSSHReaderTests.swift */,
5067730F2427057600034DED /* AnySecretTests.swift */,
50617DB923FCE4AB0099B055 /* Info.plist */,
);
path = SecretKitTests;
@ -398,6 +456,16 @@
path = SecureEnclave;
sourceTree = "<group>";
};
506772FC2426F3F400034DED /* Brief */ = {
isa = PBXGroup;
children = (
506773082426F3FD00034DED /* Updater.swift */,
506772FD2426F3F400034DED /* Brief.h */,
506772FE2426F3F400034DED /* Info.plist */,
);
path = Brief;
sourceTree = "<group>";
};
5068389F2415EA4F00F55094 /* Erasers */ = {
isa = PBXGroup;
children = (
@ -434,6 +502,7 @@
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
506772C82425BB8500034DED /* NoStoresView.swift */,
50C385A8240B636500AF2719 /* SetupView.swift */,
);
path = Views;
@ -442,7 +511,6 @@
508A58B1241ED1EA0069DC07 /* Controllers */ = {
isa = PBXGroup;
children = (
50731665241DF8660023809E /* Updater.swift */,
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
);
path = Controllers;
@ -461,6 +529,7 @@
5099A02C23FE56D70062B6F2 /* Common */ = {
isa = PBXGroup;
children = (
504BA92D243171F20064740E /* Types */,
5068389F2415EA4F00F55094 /* Erasers */,
506838A42415EA6800F55094 /* OpenSSH */,
5068389D241471CD00F55094 /* SecretStoreList.swift */,
@ -478,6 +547,7 @@
507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */,
507CE4F52420A96F0029F750 /* SigningRequestTracer.swift */,
50A3B79F24026B9900D209EA /* Agent.swift */,
507EE34124281E12003C4FE3 /* FileHandleProtocols.swift */,
5099A06F240242BA0062B6F2 /* Info.plist */,
);
path = SecretAgentKit;
@ -486,7 +556,11 @@
5099A07A240242BA0062B6F2 /* SecretAgentKitTests */ = {
isa = PBXGroup;
children = (
5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */,
5099A07B240242BA0062B6F2 /* AgentTests.swift */,
507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */,
507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */,
507EE34D2428784F003C4FE3 /* StubWitness.swift */,
507EE3492428263B003C4FE3 /* StubStore.swift */,
5099A07D240242BA0062B6F2 /* Info.plist */,
);
path = SecretAgentKitTests;
@ -532,6 +606,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
506772F62426F3F400034DED /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
506772FF2426F3F400034DED /* Brief.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
5099A067240242BA0062B6F2 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
@ -557,6 +639,7 @@
);
dependencies = (
50617DBC23FCE4AB0099B055 /* PBXTargetDependency */,
506773012426F3F400034DED /* PBXTargetDependency */,
);
name = Secretive;
productName = Secretive;
@ -618,6 +701,24 @@
productReference = 50617DB023FCE4AB0099B055 /* SecretKitTests.xctest */;
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 */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5099A083240242BA0062B6F2 /* Build configuration list for PBXNativeTarget "SecretAgentKit" */;
@ -670,6 +771,7 @@
dependencies = (
5018F5492402736A002EB505 /* PBXTargetDependency */,
5018F54B2402736A002EB505 /* PBXTargetDependency */,
5067730B2426F40A00034DED /* PBXTargetDependency */,
);
name = SecretAgent;
productName = SecretAgent;
@ -700,6 +802,10 @@
50617DAF23FCE4AB0099B055 = {
CreatedOnToolsVersion = 11.3;
};
506772FA2426F3F400034DED = {
CreatedOnToolsVersion = 11.4;
LastSwiftMigration = 1140;
};
5099A06B240242BA0062B6F2 = {
CreatedOnToolsVersion = 11.4;
LastSwiftMigration = 1140;
@ -732,6 +838,7 @@
50617DAF23FCE4AB0099B055 /* SecretKitTests */,
5099A06B240242BA0062B6F2 /* SecretAgentKit */,
5099A073240242BA0062B6F2 /* SecretAgentKitTests */,
506772FA2426F3F400034DED /* Brief */,
);
};
/* End PBXProject section */
@ -769,6 +876,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
506772F92426F3F400034DED /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5099A06A240242BA0062B6F2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -806,11 +920,11 @@
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
50731666241DF8660023809E /* Updater.swift in Sources */,
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
50731669241E00C20023809E /* NoticeView.swift in Sources */,
50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */,
506772C92425BB8500034DED /* NoStoresView.swift in Sources */,
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
);
@ -849,6 +963,16 @@
buildActionMask = 2147483647;
files = (
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;
};
@ -856,6 +980,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
507EE34224281E12003C4FE3 /* FileHandleProtocols.swift in Sources */,
507CE4EE2420A3CA0029F750 /* SocketController.swift in Sources */,
5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */,
507CE4ED2420A3C70029F750 /* Agent.swift in Sources */,
@ -869,7 +994,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
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;
};
@ -910,6 +1039,16 @@
target = 50617DA723FCE4AB0099B055 /* SecretKit */;
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 */ = {
isa = PBXTargetDependency;
target = 50617DA723FCE4AB0099B055 /* SecretKit */;
@ -1254,6 +1393,95 @@
};
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 */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */;
@ -1427,8 +1655,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = Z72PRUAWF6;
INFOPLIST_FILE = SecretKitTests/Info.plist;
@ -1439,6 +1667,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretKitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Test;
@ -1477,7 +1706,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = Z72PRUAWF6;
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
@ -1488,6 +1718,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Test;
@ -1555,6 +1786,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = Z72PRUAWF6;
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
@ -1573,6 +1805,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = Z72PRUAWF6;
INFOPLIST_FILE = SecretAgentKitTests/Info.plist;
@ -1690,6 +1923,16 @@
defaultConfigurationIsVisible = 0;
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" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -1,6 +1,7 @@
import Cocoa
import SwiftUI
import SecretKit
import Brief
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@ -66,7 +67,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@IBAction func runSetup(sender: AnyObject?) {
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],
backing: .buffered, defer: false)
let setupView = SetupView() { success in

View File

@ -5,19 +5,19 @@
\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 https://github.com/maxgoedjen/secretive}}
\f0\fs24 \cf0 GitHub Repository}}
\f0\fs24 \
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
\cf0 \
GITHUB_BUILD_URL\
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
\
Special Thanks To:\
https://github.com/bdash\
https://github.com/danielctull\
https://github.com/davedelong\
https://github.com/esttorhe\
https://github.com/joeblau\
https://github.com/marksands\
https://github.com/mergesort\
https://github.com/phillco\
https://github.com/zackdotcomputer}
{\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}}}

View File

@ -23,7 +23,7 @@
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Max Goedjen. All rights reserved.</string>
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>

View File

@ -1,5 +1,6 @@
import Foundation
import Combine
import Brief
class PreviewUpdater: UpdaterProtocol {

View File

@ -1,5 +1,6 @@
import SwiftUI
import SecretKit
import Brief
struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentStatusCheckerProtocol>: View {
@ -20,6 +21,7 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
if !agentStatusChecker.running {
agentNotice()
}
if storeList.anyAvailable {
NavigationView {
List(selection: $active) {
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 {

View 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()
}
}

View File

@ -3,18 +3,18 @@ import SwiftUI
import ServiceManagement
struct SetupView: View {
var completion: ((Bool) -> Void)?
var body: some View {
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.",
index: 1,
nestedView: nil,
actionText: "Install") {
index: 1,
nestedView: nil,
actionText: "Install") {
self.installLaunchAgent()
}
SetupStepView(text: "You need to add a line to your shell config (.bashrc or .zshrc) telling SSH to talk to SecretAgent when it wants to authenticate. Drag this into your config file.",
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,
nestedView: SetupStepCommandView(text: Constants.socketPrompt),
actionText: "Added") {
@ -22,25 +22,25 @@ struct SetupView: View {
}
HStack {
Spacer()
Button(action: { self.completion?(true) }) {
Text("Finish")
}
Button(action: { self.completion?(true) }) {
Text("Finish")
}
.padding()
}
}
}.frame(minWidth: 640, minHeight: 400)
}
}
struct SetupStepView<NestedViewType: View>: View {
let text: String
let index: Int
let nestedView: NestedViewType?
@State var completed = false
let actionText: String
let action: (() -> Bool)
var body: some View {
Section {
HStack {
@ -64,10 +64,8 @@ struct SetupStepView<NestedViewType: View>: View {
Text(text)
.opacity(completed ? 0.5 : 1)
.lineLimit(nil)
.frame(idealHeight: 0, maxHeight: .infinity)
if nestedView != nil {
Spacer()
nestedView!
nestedView!.padding()
}
}
.padding()
@ -83,43 +81,58 @@ struct SetupStepView<NestedViewType: View>: View {
}
struct SetupStepCommandView: View {
let text: String
var body: some View {
Text(text)
.font(.system(.caption, design: .monospaced))
.lineLimit(nil)
.frame(idealHeight: 0, maxHeight: .infinity)
.padding()
.background(Color(white: 0, opacity: 0.10))
.cornerRadius(10)
.onDrag {
return NSItemProvider(item: NSData(data: self.text.data(using: .utf8)!), typeIdentifier: kUTTypeUTF8PlainText as String)
VStack(alignment: .leading) {
Text(text)
.lineLimit(nil)
.font(.system(.caption, design: .monospaced))
.multilineTextAlignment(.leading)
.frame(minHeight: 50)
HStack {
Spacer()
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 {
func installLaunchAgent() -> Bool {
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, true)
}
func markAsDone() -> Bool {
return true
}
}
extension SetupView {
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 socketPrompt = "export SSH_AUTH_SOCK=\(socketPath)"
}
}
#if DEBUG

View File

@ -1,11 +1,3 @@
//
// SecretiveTests.swift
// SecretiveTests
//
// Created by Max Goedjen on 2/18/20.
// Copyright © 2020 Max Goedjen. All rights reserved.
//
import XCTest
@testable import Secretive