Compare commits

...

9 Commits

Author SHA1 Message Date
9c28efab5c Improve release template (#206)
* Improve release template

* Update release.yml

* Update test.yml
2021-01-18 15:14:23 -08:00
698a69a034 Improve updater (#203) 2021-01-18 00:49:26 -08:00
4105c1d6f6 Fix arrow position (#200)
* Fix arrow position

* Restore that
2021-01-17 19:35:22 -08:00
4de805dd37 Add support for SHA256 fingerprints (#198)
* Add SHA256

* Update tests

* Fix padding
2021-01-17 16:16:38 -08:00
0544287141 Add internet access policy (#199) 2021-01-17 15:59:35 -08:00
8ec19d1fb7 brew cask install is deprecated (#192)
Use `brew install` instead of `brew cask install`
2021-01-17 13:37:30 -08:00
6c3748b6bf Comment (#181) 2020-11-29 13:15:14 -08:00
dc91f61dba Update ApplicationDirectoryController.swift (#178) 2020-11-25 18:13:46 -08:00
702388fdf8 Update FAQ.md (#176) 2020-11-25 16:00:04 -08:00
15 changed files with 318 additions and 44 deletions

View File

@ -18,7 +18,7 @@ jobs:
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }} AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
run: ./.github/scripts/signing.sh run: ./.github/scripts/signing.sh
- name: Set Environment - name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_12.2.app run: sudo xcrun xcode-select -s /Applications/Xcode_12.3.app
- name: Test - name: Test
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive
build: build:
@ -26,17 +26,6 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: "## Build\nhttps://github.com/maxgoedjen/secretive/actions/runs/${{ github.run_id }}"
draft: true
prerelease: false
- name: Setup Signing - name: Setup Signing
env: env:
SIGNING_DATA: ${{ secrets.SIGNING_DATA }} SIGNING_DATA: ${{ secrets.SIGNING_DATA }}
@ -70,6 +59,30 @@ jobs:
run: | run: |
shasum -a 512 Secretive.zip shasum -a 512 Secretive.zip
shasum -a 512 Archive.zip shasum -a 512 Archive.zip
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: |
Update description
## Features
## Fixes
## Minimum macOS Version
## Build
https://github.com/maxgoedjen/secretive/actions/runs/${{ github.run_id }}
draft: true
prerelease: false
- name: Upload App to Release - name: Upload App to Release
id: upload-release-asset id: upload-release-asset
uses: actions/upload-release-asset@v1.0.1 uses: actions/upload-release-asset@v1.0.1

View File

@ -8,6 +8,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set Environment - name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_12.2.app run: sudo xcrun xcode-select -s /Applications/Xcode_12.3.app
- name: Test - name: Test
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive

View File

@ -11,7 +11,10 @@ public class Updater: ObservableObject, UpdaterProtocol {
@Published public var update: Release? @Published public var update: Release?
public init(checkOnLaunch: Bool) { private let osVersion: SemVer
public init(checkOnLaunch: Bool, osVersion: SemVer = SemVer(ProcessInfo.processInfo.operatingSystemVersion)) {
self.osVersion = osVersion
if checkOnLaunch { if checkOnLaunch {
// Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet. // Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet.
checkForUpdates() checkForUpdates()
@ -25,8 +28,8 @@ public class Updater: ObservableObject, UpdaterProtocol {
public 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 releases = try? JSONDecoder().decode([Release].self, from: data) else { return }
self.evaluate(release: release) self.evaluate(releases: releases)
}.resume() }.resume()
} }
@ -42,11 +45,16 @@ public class Updater: ObservableObject, UpdaterProtocol {
extension Updater { extension Updater {
func evaluate(release: Release) { func evaluate(releases: [Release]) {
guard let release = releases
.sorted()
.reversed()
.filter({ !$0.prerelease })
.first(where: { $0.minimumOSVersion <= osVersion }) else { return }
guard !userIgnored(release: release) else { return } guard !userIgnored(release: release) else { return }
guard !release.prerelease else { return } guard !release.prerelease else { return }
let latestVersion = SemVer(release.name) let latestVersion = SemVer(release.name)
let currentVersion = SemVer(Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String) let currentVersion = SemVer(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0")
if latestVersion > currentVersion { if latestVersion > currentVersion {
DispatchQueue.main.async { DispatchQueue.main.async {
self.update = release self.update = release
@ -64,11 +72,11 @@ extension Updater {
} }
} }
struct SemVer { public struct SemVer {
let versionNumbers: [Int] let versionNumbers: [Int]
init(_ version: String) { public init(_ version: String) {
// Betas have the format 1.2.3_beta1 // Betas have the format 1.2.3_beta1
let strippedBeta = version.split(separator: "_").first! let strippedBeta = version.split(separator: "_").first!
var split = strippedBeta.split(separator: ".").compactMap { Int($0) } var split = strippedBeta.split(separator: ".").compactMap { Int($0) }
@ -78,11 +86,15 @@ struct SemVer {
versionNumbers = split versionNumbers = split
} }
public init(_ version: OperatingSystemVersion) {
versionNumbers = [version.majorVersion, version.minorVersion, version.patchVersion]
}
} }
extension SemVer: Comparable { extension SemVer: Comparable {
static func < (lhs: SemVer, rhs: SemVer) -> Bool { public static func < (lhs: SemVer, rhs: SemVer) -> Bool {
for (latest, current) in zip(lhs.versionNumbers, rhs.versionNumbers) { for (latest, current) in zip(lhs.versionNumbers, rhs.versionNumbers) {
if latest < current { if latest < current {
return true return true
@ -99,7 +111,7 @@ extension SemVer: Comparable {
extension Updater { extension Updater {
enum Constants { enum Constants {
static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases/latest")! static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")!
} }
} }
@ -128,12 +140,32 @@ extension Release: Identifiable {
} }
extension Release: Comparable {
public static func < (lhs: Release, rhs: Release) -> Bool {
lhs.version < rhs.version
}
}
extension Release { extension Release {
public var critical: Bool { public var critical: Bool {
body.contains(Constants.securityContent) body.contains(Constants.securityContent)
} }
public var version: SemVer {
SemVer(name)
}
public var minimumOSVersion: SemVer {
guard let range = body.range(of: "Minimum macOS Version"),
let numberStart = body.rangeOfCharacter(from: CharacterSet.decimalDigits, options: [], range: range.upperBound..<body.endIndex) else { return SemVer("11.0.0") }
let numbersEnd = body.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines, options: [], range: numberStart.upperBound..<body.endIndex)?.lowerBound ?? body.endIndex
let version = numberStart.lowerBound..<numbersEnd
return SemVer(String(body[version]))
}
} }
extension Release { extension Release {

View File

@ -0,0 +1,104 @@
import XCTest
@testable import Brief
class ReleaseParsingTests: XCTestCase {
func testNonCritical() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Initial release")
XCTAssert(release.critical == false)
}
func testCritical() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update")
XCTAssert(release.critical == true)
}
func testOSMissing() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update")
XCTAssert(release.minimumOSVersion == SemVer("11.0.0"))
}
func testOSPresentWithContentBelow() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update ##Minimum macOS Version\n1.2.3\nBuild info")
XCTAssert(release.minimumOSVersion == SemVer("1.2.3"))
}
func testOSPresentAtEnd() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update Minimum macOS Version: 1.2.3")
XCTAssert(release.minimumOSVersion == SemVer("1.2.3"))
}
func testOSWithMacOSPrefix() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update Minimum macOS Version: macOS 1.2.3")
XCTAssert(release.minimumOSVersion == SemVer("1.2.3"))
}
func testOSGreaterThanMinimum() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update Minimum macOS Version: 1.2.3")
XCTAssert(release.minimumOSVersion < SemVer("11.0.0"))
}
func testOSEqualToMinimum() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update Minimum macOS Version: 11.2.3")
XCTAssert(release.minimumOSVersion <= SemVer("11.2.3"))
}
func testOSLessThanMinimum() {
let release = Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Critical Security Update Minimum macOS Version: 1.2.3")
XCTAssert(release.minimumOSVersion > SemVer("1.0.0"))
}
func testGreatestSelectedIfOldPatchIsPublishedLater() {
// If 2.x.x series has been published, and a patch for 1.x.x is issued
// 2.x.x should still be selected if user can run it.
let updater = Updater(checkOnLaunch: false, osVersion: SemVer("2.2.3"))
let two = Release(name: "2.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "2.0 available! Minimum macOS Version: 2.2.3")
let releases = [
Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Initial release Minimum macOS Version: 1.2.3"),
Release(name: "1.0.1", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Bug fixes Minimum macOS Version: 1.2.3"),
two,
Release(name: "1.0.2", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Emergency patch! Minimum macOS Version: 1.2.3"),
]
let expectation = XCTestExpectation()
updater.evaluate(releases: releases)
DispatchQueue.main.async {
XCTAssert(updater.update == two)
expectation.fulfill()
}
wait(for: [expectation], timeout: 1)
}
func testLatestVersionIsRunnable() {
// If the 2.x.x series has been published but the user can't run it
// the last version the user can run should be selected.
let updater = Updater(checkOnLaunch: false, osVersion: SemVer("1.2.3"))
let oneOhTwo = Release(name: "1.0.2", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Emergency patch! Minimum macOS Version: 1.2.3")
let releases = [
Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Initial release Minimum macOS Version: 1.2.3"),
Release(name: "1.0.1", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Bug fixes Minimum macOS Version: 1.2.3"),
Release(name: "2.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "2.0 available! Minimum macOS Version: 2.2.3"),
Release(name: "1.0.2", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Emergency patch! Minimum macOS Version: 1.2.3"),
]
let expectation = XCTestExpectation()
updater.evaluate(releases: releases)
DispatchQueue.main.async {
XCTAssert(updater.update == oneOhTwo)
expectation.fulfill()
}
wait(for: [expectation], timeout: 1)
}
func testSorting() {
let two = Release(name: "2.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "2.0 available!")
let releases = [
Release(name: "1.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Initial release"),
Release(name: "1.0.1", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Bug fixes"),
two,
Release(name: "1.0.2", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Emergency patch!"),
]
let sorted = releases.sorted().reversed().first
XCTAssert(sorted == two)
}
}

View File

@ -27,6 +27,21 @@ class SemVerTests: XCTestCase {
XCTAssert(current < new) XCTAssert(current < new)
} }
func testRegularParsing() {
let current = SemVer("1.0.2")
XCTAssert(current.versionNumbers == [1, 0, 2])
}
func testNoPatch() {
let current = SemVer("1.1")
XCTAssert(current.versionNumbers == [1, 1, 0])
}
func testGarbage() {
let current = SemVer("Test")
XCTAssert(current.versionNumbers == [0, 0, 0])
}
func testBeta() { func testBeta() {
let current = SemVer("1.0.2") let current = SemVer("1.0.2")
let new = SemVer("1.1.0_beta1") let new = SemVer("1.1.0_beta1")

4
FAQ.md
View File

@ -24,6 +24,10 @@ Please run `ssh -Tv git@github.com` in your terminal and paste the output in a [
![Apple Watch Prompt](.github/readme/apple_watch_auth_mac.png) ![Apple Watch Prompt](.github/readme/apple_watch_auth_mac.png)
![Apple Watch Prompt](.github/readme/apple_watch_auth_watch.png) ![Apple Watch Prompt](.github/readme/apple_watch_auth_watch.png)
### How do I tell SSH to use a specific key?
You can create a `mykey.pub` (where `mykey` is the name of your key) in your `~/.ssh/` directory with the contents of your public key, and specify that you want to use that key in your `~/.ssh/config`. [This ServerFault answer](https://serverfault.com/a/295771) has more details on setting that up
### Why should I trust you? ### 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). 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).

View File

@ -38,7 +38,7 @@ You can download the latest release over on the [Releases Page](https://github.c
#### Using Homebrew #### Using Homebrew
brew cask install secretive brew install secretive
### FAQ ### FAQ

View File

@ -0,0 +1,31 @@
<?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>ApplicationDescription</key>
<string>Secretive is an app for storing and managing SSH keys in the Secure Enclave. SecretAgent is a helper process that runs in the background to sign requests, so that you don&apos;t always have to keep the main Secretive app open.</string>
<key>DeveloperName</key>
<string>Max Goedjen</string>
<key>Website</key>
<string>https://github.com/maxgoedjen/secretive</string>
<key>Connections</key>
<array>
<dict>
<key>IsIncoming</key>
<false/>
<key>Host</key>
<string>api.github.com</string>
<key>NetworkProtocol</key>
<string>TCP</string>
<key>Port</key>
<string>443</string>
<key>Purpose</key>
<string>Secretive checks GitHub for new versions and security updates.</string>
<key>DenyConsequences</key>
<string>If you deny these connections, you will not be notified about new versions and critical security updates.</string>
</dict>
</array>
<key>Services</key>
<array/>
</dict>
</plist>

View File

@ -13,11 +13,21 @@ public struct OpenSSHKeyWriter {
lengthAndData(of: secret.publicKey) lengthAndData(of: secret.publicKey)
} }
public func openSSHString<SecretType: Secret>(secret: SecretType) -> String { public func openSSHString<SecretType: Secret>(secret: SecretType, comment: String? = nil) -> String {
"\(curveType(for: secret.algorithm, length: secret.keySize)) \(data(secret: secret).base64EncodedString())" [curveType(for: secret.algorithm, length: secret.keySize), data(secret: secret).base64EncodedString(), comment]
.compactMap { $0 }
.joined(separator: " ")
} }
public func openSSHFingerprint<SecretType: Secret>(secret: SecretType) -> String { public func openSSHSHA256Fingerprint<SecretType: Secret>(secret: SecretType) -> String {
// OpenSSL format seems to strip the padding at the end.
let base64 = Data(SHA256.hash(data: data(secret: secret))).base64EncodedString()
let paddingRange = base64.index(base64.endIndex, offsetBy: -2)..<base64.endIndex
let cleaned = base64.replacingOccurrences(of: "=", with: "", range: paddingRange)
return "SHA256:\(cleaned)"
}
public func openSSHMD5Fingerprint<SecretType: Secret>(secret: SecretType) -> String {
Insecure.MD5.hash(data: data(secret: secret)) Insecure.MD5.hash(data: data(secret: secret))
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) } .compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }
.joined(separator: ":") .joined(separator: ":")

View File

@ -6,8 +6,12 @@ class OpenSSHWriterTests: XCTestCase {
let writer = OpenSSHKeyWriter() let writer = OpenSSHKeyWriter()
func testECDSA256Fingerprint() { func testECDSA256MD5Fingerprint() {
XCTAssertEqual(writer.openSSHFingerprint(secret: Constants.ecdsa256Secret), "dc:60:4d:ff:c2:d9:18:8b:2f:24:40:b5:7f:43:47:e5") XCTAssertEqual(writer.openSSHMD5Fingerprint(secret: Constants.ecdsa256Secret), "dc:60:4d:ff:c2:d9:18:8b:2f:24:40:b5:7f:43:47:e5")
}
func testECDSA256SHA256Fingerprint() {
XCTAssertEqual(writer.openSSHSHA256Fingerprint(secret: Constants.ecdsa256Secret), "SHA256:/VQFeGyM8qKA8rB6WGMuZZxZLJln2UgXLk3F0uTF650")
} }
func testECDSA256PublicKey() { func testECDSA256PublicKey() {
@ -19,8 +23,12 @@ class OpenSSHWriterTests: XCTestCase {
XCTAssertEqual(writer.data(secret: Constants.ecdsa256Secret), Data(base64Encoded: "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")) XCTAssertEqual(writer.data(secret: Constants.ecdsa256Secret), Data(base64Encoded: "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo="))
} }
func testECDSA384Fingerprint() { func testECDSA384MD5Fingerprint() {
XCTAssertEqual(writer.openSSHFingerprint(secret: Constants.ecdsa384Secret), "66:e0:66:d7:41:ed:19:8e:e2:20:df:ce:ac:7e:2b:6e") XCTAssertEqual(writer.openSSHMD5Fingerprint(secret: Constants.ecdsa384Secret), "66:e0:66:d7:41:ed:19:8e:e2:20:df:ce:ac:7e:2b:6e")
}
func testECDSA384SHA256Fingerprint() {
XCTAssertEqual(writer.openSSHSHA256Fingerprint(secret: Constants.ecdsa384Secret), "SHA256:GJUEymQNL9ymaMRRJCMGY4rWIJHu/Lm8Yhao/PAiz1I")
} }
func testECDSA384PublicKey() { func testECDSA384PublicKey() {

View File

@ -61,8 +61,10 @@
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */; }; 508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */; };
508A5911241EF09C0069DC07 /* SecretAgentKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 508A5911241EF09C0069DC07 /* SecretAgentKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
508A5913241EF0B20069DC07 /* SecretKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50617DA823FCE4AB0099B055 /* SecretKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 508A5913241EF0B20069DC07 /* SecretKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50617DA823FCE4AB0099B055 /* SecretKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = 508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */; };
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = 508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */; };
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */; }; 5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */; };
5091D3222519D56D0049FD9B /* BriefTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5091D3212519D56D0049FD9B /* BriefTests.swift */; }; 5091D3222519D56D0049FD9B /* SemVerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5091D3212519D56D0049FD9B /* SemVerTests.swift */; };
5091D3242519D56D0049FD9B /* Brief.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 506772FB2426F3F400034DED /* Brief.framework */; }; 5091D3242519D56D0049FD9B /* Brief.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 506772FB2426F3F400034DED /* Brief.framework */; };
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */; }; 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */; };
5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02623FE34FA0062B6F2 /* SmartCard.swift */; }; 5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02623FE34FA0062B6F2 /* SmartCard.swift */; };
@ -73,6 +75,7 @@
5099A07C240242BA0062B6F2 /* AgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A07B240242BA0062B6F2 /* AgentTests.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 */; };
509FA3B625B53C49005E2535 /* ReleaseParsingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509FA3B525B53C49005E2535 /* ReleaseParsingTests.swift */; };
50A3B79124026B7600D209EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79024026B7600D209EA /* Assets.xcassets */; }; 50A3B79124026B7600D209EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79024026B7600D209EA /* Assets.xcassets */; };
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79324026B7600D209EA /* Preview Assets.xcassets */; }; 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79324026B7600D209EA /* Preview Assets.xcassets */; };
50A3B79724026B7600D209EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79524026B7600D209EA /* Main.storyboard */; }; 50A3B79724026B7600D209EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79524026B7600D209EA /* Main.storyboard */; };
@ -274,9 +277,11 @@
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>"; };
508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewAgentStatusChecker.swift; sourceTree = "<group>"; }; 508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewAgentStatusChecker.swift; sourceTree = "<group>"; };
508A590F241EEF6D0069DC07 /* Secretive.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Secretive.xctestplan; sourceTree = "<group>"; }; 508A590F241EEF6D0069DC07 /* Secretive.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Secretive.xctestplan; sourceTree = "<group>"; };
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = InternetAccessPolicy.plist; path = SecretAgent/InternetAccessPolicy.plist; sourceTree = SOURCE_ROOT; };
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDirectoryController.swift; sourceTree = "<group>"; }; 5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDirectoryController.swift; sourceTree = "<group>"; };
5091D31F2519D56D0049FD9B /* BriefTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BriefTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5091D31F2519D56D0049FD9B /* BriefTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BriefTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5091D3212519D56D0049FD9B /* BriefTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BriefTests.swift; sourceTree = "<group>"; }; 5091D3212519D56D0049FD9B /* SemVerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemVerTests.swift; sourceTree = "<group>"; };
5091D3232519D56D0049FD9B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 5091D3232519D56D0049FD9B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = "<group>"; }; 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = "<group>"; };
5099A02623FE34FA0062B6F2 /* SmartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCard.swift; sourceTree = "<group>"; }; 5099A02623FE34FA0062B6F2 /* SmartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCard.swift; sourceTree = "<group>"; };
@ -290,6 +295,7 @@
5099A07B240242BA0062B6F2 /* AgentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentTests.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>"; };
509FA3B525B53C49005E2535 /* ReleaseParsingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseParsingTests.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; };
50A3B79024026B7600D209EA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 50A3B79024026B7600D209EA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
50A3B79324026B7600D209EA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; }; 50A3B79324026B7600D209EA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
@ -430,6 +436,7 @@
508A58B1241ED1EA0069DC07 /* Controllers */, 508A58B1241ED1EA0069DC07 /* Controllers */,
50617D8623FCE48E0099B055 /* Assets.xcassets */, 50617D8623FCE48E0099B055 /* Assets.xcassets */,
50617D8E23FCE48E0099B055 /* Info.plist */, 50617D8E23FCE48E0099B055 /* Info.plist */,
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
50617D8F23FCE48E0099B055 /* Secretive.entitlements */, 50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
506772C62424784600034DED /* Credits.rtf */, 506772C62424784600034DED /* Credits.rtf */,
50617D8823FCE48E0099B055 /* Preview Content */, 50617D8823FCE48E0099B055 /* Preview Content */,
@ -560,7 +567,8 @@
5091D3202519D56D0049FD9B /* BriefTests */ = { 5091D3202519D56D0049FD9B /* BriefTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5091D3212519D56D0049FD9B /* BriefTests.swift */, 5091D3212519D56D0049FD9B /* SemVerTests.swift */,
509FA3B525B53C49005E2535 /* ReleaseParsingTests.swift */,
5091D3232519D56D0049FD9B /* Info.plist */, 5091D3232519D56D0049FD9B /* Info.plist */,
); );
path = BriefTests; path = BriefTests;
@ -630,6 +638,7 @@
50A3B79024026B7600D209EA /* Assets.xcassets */, 50A3B79024026B7600D209EA /* Assets.xcassets */,
50A3B79524026B7600D209EA /* Main.storyboard */, 50A3B79524026B7600D209EA /* Main.storyboard */,
50A3B79824026B7600D209EA /* Info.plist */, 50A3B79824026B7600D209EA /* Info.plist */,
508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */,
50A3B79924026B7600D209EA /* SecretAgent.entitlements */, 50A3B79924026B7600D209EA /* SecretAgent.entitlements */,
50A3B79224026B7600D209EA /* Preview Content */, 50A3B79224026B7600D209EA /* Preview Content */,
); );
@ -922,6 +931,7 @@
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 */, 506772C72424784600034DED /* Credits.rtf in Resources */,
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -981,6 +991,7 @@
50A3B79724026B7600D209EA /* Main.storyboard in Resources */, 50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */, 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
50A3B79124026B7600D209EA /* Assets.xcassets in Resources */, 50A3B79124026B7600D209EA /* Assets.xcassets in Resources */,
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1065,7 +1076,8 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5091D3222519D56D0049FD9B /* BriefTests.swift in Sources */, 509FA3B625B53C49005E2535 /* ReleaseParsingTests.swift in Sources */,
5091D3222519D56D0049FD9B /* SemVerTests.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -7,7 +7,7 @@ extension ApplicationDirectoryController {
var isInApplicationsDirectory: Bool { var isInApplicationsDirectory: Bool {
let bundlePath = Bundle.main.bundlePath let bundlePath = Bundle.main.bundlePath
for directory in NSSearchPathForDirectoriesInDomains(.applicationDirectory, .allDomainsMask, true) { for directory in NSSearchPathForDirectoriesInDomains(.allApplicationsDirectory, .allDomainsMask, true) {
if bundlePath.hasPrefix(directory) { if bundlePath.hasPrefix(directory) {
return true return true
} }

View File

@ -0,0 +1,31 @@
<?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>ApplicationDescription</key>
<string>Secretive is an app for storing and managing SSH keys in the Secure Enclave</string>
<key>DeveloperName</key>
<string>Max Goedjen</string>
<key>Website</key>
<string>https://github.com/maxgoedjen/secretive</string>
<key>Connections</key>
<array>
<dict>
<key>IsIncoming</key>
<false/>
<key>Host</key>
<string>api.github.com</string>
<key>NetworkProtocol</key>
<string>TCP</string>
<key>Port</key>
<string>443</string>
<key>Purpose</key>
<string>Secretive checks GitHub for new versions and security updates.</string>
<key>DenyConsequences</key>
<string>If you deny these connections, you will not be notified about new versions and critical security updates.</string>
</dict>
</array>
<key>Services</key>
<array/>
</dict>
</plist>

View File

@ -53,14 +53,14 @@ struct EmptyStoreModifiableView: View {
CGPoint(x: g.size.width / 2, y: g.size.height * (1/2)), control2: CGPoint(x: g.size.width / 2, y: g.size.height * (1/2)), control2:
CGPoint(x: g.size.width * (3/4), y: g.size.height * (1/2))) CGPoint(x: g.size.width * (3/4), y: g.size.height * (1/2)))
path.addCurve(to: path.addCurve(to:
CGPoint(x: g.size.width, y: 0), control1: CGPoint(x: g.size.width - 13, y: 0), control1:
CGPoint(x: g.size.width, y: g.size.height * (1/2)), control2: CGPoint(x: g.size.width - 13 , y: g.size.height * (1/2)), control2:
CGPoint(x: g.size.width, y: 0)) CGPoint(x: g.size.width - 13, y: 0))
}.stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round)) }.stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round))
Path { path in Path { path in
path.move(to: CGPoint(x: g.size.width - 10, y: 0)) path.move(to: CGPoint(x: g.size.width - 23, y: 0))
path.addLine(to: CGPoint(x: g.size.width, y: -10)) path.addLine(to: CGPoint(x: g.size.width - 13, y: -10))
path.addLine(to: CGPoint(x: g.size.width + 10, y: 0)) path.addLine(to: CGPoint(x: g.size.width - 3, y: 0))
}.fill() }.fill()
}.frame(height: (windowGeometry.size.height/2) - 20).padding() }.frame(height: (windowGeometry.size.height/2) - 20).padding()
Text("No Secrets").bold() Text("No Secrets").bold()

View File

@ -10,19 +10,33 @@ struct SecretDetailView<SecretType: Secret>: View {
var body: some View { var body: some View {
Form { Form {
Section { Section {
CopyableView(title: "Fingerprint", image: Image(systemName: "touchid"), text: keyWriter.openSSHFingerprint(secret: secret)) CopyableView(title: "SHA256 Fingerprint", image: Image(systemName: "touchid"), text: keyWriter.openSSHSHA256Fingerprint(secret: secret))
Spacer() Spacer()
.frame(height: 20) .frame(height: 20)
CopyableView(title: "Public Key", image: Image(systemName: "key"), text: keyWriter.openSSHString(secret: secret)) CopyableView(title: "MD5 Fingerprint", image: Image(systemName: "touchid"), text: keyWriter.openSSHMD5Fingerprint(secret: secret))
Spacer()
.frame(height: 20)
CopyableView(title: "Public Key", image: Image(systemName: "key"), text: keyString)
Spacer() Spacer()
} }
} }
.padding() .padding()
.frame(minHeight: 200, maxHeight: .infinity) .frame(minHeight: 200, maxHeight: .infinity)
} }
var dashedKeyName: String {
secret.name.replacingOccurrences(of: " ", with: "-")
}
var dashedHostName: String {
["secretive", Host.current().localizedName, "local"]
.compactMap { $0 }
.joined(separator: ".")
.replacingOccurrences(of: " ", with: "-")
}
var keyString: String { var keyString: String {
keyWriter.openSSHString(secret: secret) keyWriter.openSSHString(secret: secret, comment: "\(dashedKeyName)@\(dashedHostName)")
} }
func copy() { func copy() {