mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-07-02 02:03:36 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
9d6bbd2a04 | |||
35aea0729d | |||
c5bd4c2189 | |||
8114acf50a | |||
cd965b9ec6 | |||
f30d1f802f | |||
af479408a4 | |||
42b034270a | |||
dca340ad40 | |||
8dfabd35aa | |||
9c28efab5c | |||
698a69a034 | |||
4105c1d6f6 | |||
4de805dd37 | |||
0544287141 | |||
8ec19d1fb7 | |||
6c3748b6bf | |||
dc91f61dba | |||
702388fdf8 |
BIN
.github/readme/app.png
vendored
BIN
.github/readme/app.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 456 KiB |
7
.github/scripts/signing.sh
vendored
7
.github/scripts/signing.sh
vendored
@ -10,10 +10,13 @@ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keyc
|
|||||||
|
|
||||||
# Import Profiles
|
# Import Profiles
|
||||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
|
|
||||||
echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
|
echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
|
||||||
HOST_UUID=`grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
HOST_UUID=`grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
||||||
cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
|
cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
|
||||||
echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
|
echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
|
||||||
AGENT_UUID=`grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
AGENT_UUID=`grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
||||||
cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
|
cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
|
||||||
|
|
||||||
|
# Create directories for ASC key
|
||||||
|
mkdir ~/.private_keys
|
||||||
|
echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8
|
||||||
|
49
.github/workflows/release.yml
vendored
49
.github/workflows/release.yml
vendored
@ -16,9 +16,11 @@ jobs:
|
|||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
||||||
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
||||||
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
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.5.1.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,26 +28,17 @@ 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 }}
|
||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
||||||
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
||||||
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
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.5.1.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
@ -63,13 +56,37 @@ jobs:
|
|||||||
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive ./Archive.zip
|
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive ./Archive.zip
|
||||||
- name: Notarize
|
- name: Notarize
|
||||||
env:
|
env:
|
||||||
APPLE_USERNAME: ${{ secrets.APPLE_USERNAME }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
run: xcrun altool --notarize-app --primary-bundle-id "com.maxgoedjen.secretive.host" --username $APPLE_USERNAME --password $APPLE_PASSWORD --file Secretive.zip
|
run: xcrun altool --notarize-app --primary-bundle-id "com.maxgoedjen.secretive.host" --apiKey $APPLE_API_KEY_ID --apiIssuer $APPLE_API_ISSUER --file Secretive.zip
|
||||||
- name: Document SHAs
|
- name: Document SHAs
|
||||||
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
|
||||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -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.5.1.app
|
||||||
- name: Test
|
- name: Test
|
||||||
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive
|
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive
|
||||||
|
@ -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 {
|
||||||
|
104
BriefTests/ReleaseParsingTests.swift
Normal file
104
BriefTests/ReleaseParsingTests.swift
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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")
|
@ -14,6 +14,14 @@ Secretive is designed to be easily auditable by people who are considering using
|
|||||||
|
|
||||||
All contributors must abide by the [Code of Conduct](CODE_OF_CONDUCT.md)
|
All contributors must abide by the [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
If you make a material contribution to the app, please add yourself to the end of the [credits](https://github.com/maxgoedjen/secretive/blob/main/Secretive/Credits.rtf).
|
||||||
|
|
||||||
|
## Collaborator Status
|
||||||
|
|
||||||
|
I will not grant collaborator access to any contributors for this repository. This is basically just because collaborators [can accesss the secrets Secretive uses for the signing credentials stored in the repository](https://docs.github.com/en/actions/reference/encrypted-secrets#accessing-your-secrets).
|
||||||
|
|
||||||
## Secretive is Opinionated
|
## Secretive is Opinionated
|
||||||
|
|
||||||
I'm releasing Secretive as open source so that other people can use it and audit it, feeling comfortable in knowing that the source is available so they can see what it's doing. I have a pretty strong idea of what I'd like this project to look like, and I may respectfully decline contributions that don't line up with that vision. If you'd like to propose a change before implementing, please feel free to [Open an Issue with the proposed tag](https://github.com/maxgoedjen/secretive/issues/new?labels=proposed).
|
I'm releasing Secretive as open source so that other people can use it and audit it, feeling comfortable in knowing that the source is available so they can see what it's doing. I have a pretty strong idea of what I'd like this project to look like, and I may respectfully decline contributions that don't line up with that vision. If you'd like to propose a change before implementing, please feel free to [Open an Issue with the proposed tag](https://github.com/maxgoedjen/secretive/issues/new?labels=proposed).
|
||||||
|
4
FAQ.md
4
FAQ.md
@ -24,6 +24,10 @@ Please run `ssh -Tv git@github.com` in your terminal and paste the output in a [
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
### 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).
|
||||||
|
BIN
Icon.sketch
BIN
Icon.sketch
Binary file not shown.
@ -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
|
||||||
|
|
||||||
|
31
SecretAgent/InternetAccessPolicy.plist
Normal file
31
SecretAgent/InternetAccessPolicy.plist
Normal 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'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>
|
7
SecretKit/Common/BundleIDs.swift
Normal file
7
SecretKit/Common/BundleIDs.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
extension Bundle {
|
||||||
|
public var agentBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "Host", with: "SecretAgent"))!}
|
||||||
|
public var hostBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "SecretAgent", with: "Host"))!}
|
||||||
|
}
|
@ -49,10 +49,12 @@ public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
|
|||||||
|
|
||||||
private let _create: (String, Bool) throws -> Void
|
private let _create: (String, Bool) throws -> Void
|
||||||
private let _delete: (AnySecret) throws -> Void
|
private let _delete: (AnySecret) throws -> Void
|
||||||
|
private let _update: (AnySecret, String) throws -> Void
|
||||||
|
|
||||||
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
||||||
_create = { try secretStore.create(name: $0, requiresAuthentication: $1) }
|
_create = { try secretStore.create(name: $0, requiresAuthentication: $1) }
|
||||||
_delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) }
|
_delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) }
|
||||||
|
_update = { try secretStore.update(secret: $0.base as! SecretStoreType.SecretType, name: $1) }
|
||||||
super.init(secretStore)
|
super.init(secretStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,4 +66,7 @@ public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
|
|||||||
try _delete(secret)
|
try _delete(secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func update(secret: AnySecret, name: String) throws {
|
||||||
|
try _update(secret, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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: ":")
|
||||||
|
@ -17,6 +17,7 @@ public protocol SecretStoreModifiable: SecretStore {
|
|||||||
|
|
||||||
func create(name: String, requiresAuthentication: Bool) throws
|
func create(name: String, requiresAuthentication: Bool) throws
|
||||||
func delete(secret: SecretType) throws
|
func delete(secret: SecretType) throws
|
||||||
|
func update(secret: SecretType, name: String) throws
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ extension SecureEnclave {
|
|||||||
let deleteAttributes = [
|
let deleteAttributes = [
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: secret.id as CFData
|
kSecAttrApplicationLabel: secret.id as CFData
|
||||||
] as CFDictionary
|
] as CFDictionary
|
||||||
let status = SecItemDelete(deleteAttributes)
|
let status = SecItemDelete(deleteAttributes)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
throw KeychainError(statusCode: status)
|
throw KeychainError(statusCode: status)
|
||||||
@ -76,6 +76,23 @@ extension SecureEnclave {
|
|||||||
reloadSecrets()
|
reloadSecrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func update(secret: Secret, name: String) throws {
|
||||||
|
let updateQuery = [
|
||||||
|
kSecClass: kSecClassKey,
|
||||||
|
kSecAttrApplicationLabel: secret.id as CFData
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
let updatedAttributes = [
|
||||||
|
kSecAttrLabel: name,
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
||||||
|
if status != errSecSuccess {
|
||||||
|
throw KeychainError(statusCode: status)
|
||||||
|
}
|
||||||
|
reloadSecrets()
|
||||||
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data {
|
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
let context = LAContext()
|
let context = LAContext()
|
||||||
context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""
|
context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""
|
||||||
|
@ -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() {
|
||||||
|
@ -7,9 +7,10 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */; };
|
||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
||||||
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
|
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
|
||||||
50153E22250DECA300525160 /* SecretListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListView.swift */; };
|
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
||||||
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; };
|
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; };
|
||||||
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
||||||
@ -61,8 +62,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 +76,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 */; };
|
||||||
@ -84,6 +88,7 @@
|
|||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
||||||
50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A2240789E600AF2719 /* OpenSSHReader.swift */; };
|
50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A2240789E600AF2719 /* OpenSSHReader.swift */; };
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
|
FA0B34672599619E0013AB3A /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0B34662599619E0013AB3A /* BundleIDs.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -218,9 +223,10 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameSecretView.swift; sourceTree = "<group>"; };
|
||||||
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
|
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
|
||||||
50153E21250DECA300525160 /* SecretListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListView.swift; sourceTree = "<group>"; };
|
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
|
||||||
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
||||||
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
||||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
||||||
@ -274,9 +280,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 +298,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>"; };
|
||||||
@ -302,6 +311,7 @@
|
|||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
||||||
50C385A2240789E600AF2719 /* OpenSSHReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenSSHReader.swift; path = SecretKit/Common/OpenSSH/OpenSSHReader.swift; sourceTree = SOURCE_ROOT; };
|
50C385A2240789E600AF2719 /* OpenSSHReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenSSHReader.swift; path = SecretKit/Common/OpenSSH/OpenSSHReader.swift; sourceTree = SOURCE_ROOT; };
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
||||||
|
FA0B34662599619E0013AB3A /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -430,6 +440,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 */,
|
||||||
@ -532,10 +543,11 @@
|
|||||||
children = (
|
children = (
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
||||||
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */,
|
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */,
|
||||||
50153E21250DECA300525160 /* SecretListView.swift */,
|
50153E21250DECA300525160 /* SecretListItemView.swift */,
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
||||||
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
||||||
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
||||||
|
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */,
|
||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
||||||
506772C82425BB8500034DED /* NoStoresView.swift */,
|
506772C82425BB8500034DED /* NoStoresView.swift */,
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
||||||
@ -560,7 +572,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;
|
||||||
@ -583,6 +596,7 @@
|
|||||||
5068389F2415EA4F00F55094 /* Erasers */,
|
5068389F2415EA4F00F55094 /* Erasers */,
|
||||||
506838A42415EA6800F55094 /* OpenSSH */,
|
506838A42415EA6800F55094 /* OpenSSH */,
|
||||||
5068389D241471CD00F55094 /* SecretStoreList.swift */,
|
5068389D241471CD00F55094 /* SecretStoreList.swift */,
|
||||||
|
FA0B34662599619E0013AB3A /* BundleIDs.swift */,
|
||||||
);
|
);
|
||||||
path = Common;
|
path = Common;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -630,6 +644,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 +937,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 +997,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;
|
||||||
};
|
};
|
||||||
@ -991,6 +1008,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */,
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
@ -1008,7 +1026,7 @@
|
|||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
||||||
50617D8323FCE48E0099B055 /* App.swift in Sources */,
|
50617D8323FCE48E0099B055 /* App.swift in Sources */,
|
||||||
506772C92425BB8500034DED /* NoStoresView.swift in Sources */,
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */,
|
||||||
50153E22250DECA300525160 /* SecretListView.swift in Sources */,
|
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */,
|
||||||
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
|
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
|
||||||
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
|
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
|
||||||
);
|
);
|
||||||
@ -1026,6 +1044,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
FA0B34672599619E0013AB3A /* BundleIDs.swift in Sources */,
|
||||||
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */,
|
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */,
|
||||||
50617DC723FCE4EA0099B055 /* SecretStore.swift in Sources */,
|
50617DC723FCE4EA0099B055 /* SecretStore.swift in Sources */,
|
||||||
5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */,
|
5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */,
|
||||||
@ -1065,7 +1084,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;
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
protocol AgentStatusCheckerProtocol: ObservableObject {
|
protocol AgentStatusCheckerProtocol: ObservableObject {
|
||||||
var running: Bool { get }
|
var running: Bool { get }
|
||||||
@ -20,7 +21,7 @@ class AgentStatusChecker: ObservableObject, AgentStatusCheckerProtocol {
|
|||||||
|
|
||||||
// All processes, including ones from older versions, etc
|
// All processes, including ones from older versions, etc
|
||||||
var secretAgentProcesses: [NSRunningApplication] {
|
var secretAgentProcesses: [NSRunningApplication] {
|
||||||
NSRunningApplication.runningApplications(withBundleIdentifier: Constants.secretAgentAppID)
|
NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.agentBundleID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The process corresponding to this instance of Secretive
|
// The process corresponding to this instance of Secretive
|
||||||
@ -37,10 +38,4 @@ class AgentStatusChecker: ObservableObject, AgentStatusCheckerProtocol {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AgentStatusChecker {
|
|
||||||
|
|
||||||
enum Constants {
|
|
||||||
static let secretAgentAppID = "com.maxgoedjen.Secretive.SecretAgent"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@ import Foundation
|
|||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
import AppKit
|
import AppKit
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
struct LaunchAgentController {
|
struct LaunchAgentController {
|
||||||
|
|
||||||
func install(completion: (() -> Void)? = nil) {
|
func install(completion: (() -> Void)? = nil) {
|
||||||
Logger().debug("Installing agent")
|
Logger().debug("Installing agent")
|
||||||
_ = setEnabled(false)
|
_ = setEnabled(false)
|
||||||
@ -21,8 +22,12 @@ struct LaunchAgentController {
|
|||||||
func forceLaunch(completion: ((Bool) -> Void)?) {
|
func forceLaunch(completion: ((Bool) -> Void)?) {
|
||||||
Logger().debug("Agent is not running, attempting to force launch")
|
Logger().debug("Agent is not running, attempting to force launch")
|
||||||
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
||||||
NSWorkspace.shared.openApplication(at: url, configuration: NSWorkspace.OpenConfiguration()) { app, error in
|
let config = NSWorkspace.OpenConfiguration()
|
||||||
completion?(error == nil)
|
config.activates = false
|
||||||
|
NSWorkspace.shared.openApplication(at: url, configuration: config) { app, error in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion?(error == nil)
|
||||||
|
}
|
||||||
if let error = error {
|
if let error = error {
|
||||||
Logger().error("Error force launching \(error.localizedDescription)")
|
Logger().error("Error force launching \(error.localizedDescription)")
|
||||||
} else {
|
} else {
|
||||||
@ -32,7 +37,7 @@ struct LaunchAgentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setEnabled(_ enabled: Bool) -> Bool {
|
private func setEnabled(_ enabled: Bool) -> Bool {
|
||||||
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, enabled)
|
SMLoginItemSetEnabled(Bundle.main.agentBundleID as CFString, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
struct ShellConfigurationController {
|
struct ShellConfigurationController {
|
||||||
|
|
||||||
let socketPath = (NSHomeDirectory().replacingOccurrences(of: "com.maxgoedjen.Secretive.Host", with: "com.maxgoedjen.Secretive.SecretAgent") as NSString).appendingPathComponent("socket.ssh") as String
|
let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.main.hostBundleID, with: Bundle.main.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String
|
||||||
|
|
||||||
var shellInstructions: [ShellConfigInstruction] {
|
var shellInstructions: [ShellConfigInstruction] {
|
||||||
[
|
[
|
||||||
|
ShellConfigInstruction(shell: "global",
|
||||||
|
shellConfigDirectory: "~/.ssh/",
|
||||||
|
shellConfigFilename: "config",
|
||||||
|
text: "Host *\n\tIdentityAgent \(socketPath)"),
|
||||||
ShellConfigInstruction(shell: "zsh",
|
ShellConfigInstruction(shell: "zsh",
|
||||||
shellConfigDirectory: "~/",
|
shellConfigDirectory: "~/",
|
||||||
shellConfigFilename: ".zshrc",
|
shellConfigFilename: ".zshrc",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{\rtf1\ansi\ansicpg1252\cocoartf2511
|
{\rtf1\ansi\ansicpg1252\cocoartf2580
|
||||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
{\colortbl;\red255\green255\blue255;}
|
{\colortbl;\red255\green255\blue255;}
|
||||||
{\*\expandedcolortbl;;}
|
{\*\expandedcolortbl;;}
|
||||||
@ -12,6 +12,19 @@
|
|||||||
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
||||||
\
|
\
|
||||||
Special Thanks To:\
|
Special Thanks To:\
|
||||||
|
\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive/graphs/contributors"}}{\fldrslt Contributors}}:\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/0xflotus"}}{\fldrslt 0xflotus}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/aaron-trout"}}{\fldrslt Aaron Trout}}\
|
||||||
|
\pard\pardeftab720\partightenfactor0
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/EppO"}}{\fldrslt \cf0 Florent Monbillard}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/vladimyr"}}{\fldrslt Dario Vladovi\uc0\u263 }}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/lavalleeale"}}{\fldrslt Alex Lavallee}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/joshheyse"}}{\fldrslt Josh}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/diesal11"}}{\fldrslt Dylan Lundy}}\
|
||||||
|
\
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
\cf0 Testers:\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/bdash"}}{\fldrslt Mark Rowe}}\
|
{\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/danielctull"}}{\fldrslt Daniel Tull}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
||||||
@ -20,5 +33,4 @@ Special Thanks To:\
|
|||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\
|
{\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/mergesort"}}{\fldrslt Joe Fabisevich}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/zacwest"}}{\fldrslt Zac West}}}
|
|
31
Secretive/InternetAccessPolicy.plist
Normal file
31
Secretive/InternetAccessPolicy.plist
Normal 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>
|
@ -42,7 +42,6 @@ extension Preview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StoreModifiable: Store, SecretStoreModifiable {
|
class StoreModifiable: Store, SecretStoreModifiable {
|
||||||
|
|
||||||
override var name: String { "Modifiable Preview Store" }
|
override var name: String { "Modifiable Preview Store" }
|
||||||
|
|
||||||
func create(name: String, requiresAuthentication: Bool) throws {
|
func create(name: String, requiresAuthentication: Bool) throws {
|
||||||
@ -50,8 +49,10 @@ extension Preview {
|
|||||||
|
|
||||||
func delete(secret: Preview.Secret) throws {
|
func delete(secret: Preview.Secret) throws {
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
func update(secret: Preview.Secret, name: String) throws {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Preview {
|
extension Preview {
|
||||||
|
@ -2,10 +2,10 @@ import SwiftUI
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
|
|
||||||
struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||||
|
|
||||||
@ObservedObject var store: StoreType
|
@ObservedObject var store: StoreType
|
||||||
@Binding var showing: Bool
|
@Binding var showing: Bool
|
||||||
|
|
||||||
@State private var name = ""
|
@State private var name = ""
|
||||||
@State private var requiresAuthentication = true
|
@State private var requiresAuthentication = true
|
||||||
|
|
||||||
@ -26,8 +26,12 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
TextField("Shhhhh", text: $name).focusable()
|
TextField("Shhhhh", text: $name).focusable()
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Toggle(isOn: $requiresAuthentication) {
|
VStack(spacing: 20) {
|
||||||
Text("Requires Authentication (Biometrics or Password)")
|
Picker("", selection: $requiresAuthentication) {
|
||||||
|
Text("Requires Authentication (Biometrics or Password) before each use").tag(true)
|
||||||
|
Text("Authentication not required when Mac is unlocked").tag(false)
|
||||||
|
}
|
||||||
|
.pickerStyle(RadioGroupPickerStyle())
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@ -45,7 +49,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
}.padding()
|
}.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
func save() {
|
func save() {
|
||||||
try! store.create(name: name, requiresAuthentication: requiresAuthentication)
|
try! store.create(name: name, requiresAuthentication: requiresAuthentication)
|
||||||
showing = false
|
showing = false
|
||||||
|
@ -30,9 +30,6 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
TextField(secret.name, text: $confirm)
|
TextField(secret.name, text: $confirm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onExitCommand {
|
|
||||||
dismissalBlock(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -47,6 +44,9 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.frame(minWidth: 400)
|
.frame(minWidth: 400)
|
||||||
|
.onExitCommand {
|
||||||
|
dismissalBlock(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete() {
|
func delete() {
|
||||||
|
@ -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()
|
||||||
|
50
Secretive/Views/RenameSecretView.swift
Normal file
50
Secretive/Views/RenameSecretView.swift
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
struct RenameSecretView<StoreType: SecretStoreModifiable>: View {
|
||||||
|
|
||||||
|
@ObservedObject var store: StoreType
|
||||||
|
let secret: StoreType.SecretType
|
||||||
|
var dismissalBlock: (_ renamed: Bool) -> ()
|
||||||
|
|
||||||
|
@State private var newName = ""
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Image(nsImage: NSApp.applicationIconImage)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 64, height: 64)
|
||||||
|
.padding()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Text("Type your new name for \"\(secret.name)\" below.")
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
TextField(secret.name, text: $newName).focusable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Button("Rename", action: rename)
|
||||||
|
.disabled(newName.count == 0)
|
||||||
|
.keyboardShortcut(.return)
|
||||||
|
Button("Cancel") {
|
||||||
|
dismissalBlock(false)
|
||||||
|
}.keyboardShortcut(.cancelAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(minWidth: 400)
|
||||||
|
.onExitCommand {
|
||||||
|
dismissalBlock(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rename() {
|
||||||
|
try? store.update(secret: secret, name: newName)
|
||||||
|
dismissalBlock(true)
|
||||||
|
}
|
||||||
|
}
|
@ -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() {
|
||||||
|
54
Secretive/Views/SecretListItemView.swift
Normal file
54
Secretive/Views/SecretListItemView.swift
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
struct SecretListItemView: View {
|
||||||
|
|
||||||
|
@ObservedObject var store: AnySecretStore
|
||||||
|
var secret: AnySecret
|
||||||
|
@Binding var activeSecret: AnySecret.ID?
|
||||||
|
|
||||||
|
@State var isDeleting: Bool = false
|
||||||
|
@State var isRenaming: Bool = false
|
||||||
|
|
||||||
|
var deletedSecret: (AnySecret) -> Void
|
||||||
|
var renamedSecret: (AnySecret) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let showingPopupWrapped = Binding(
|
||||||
|
get: { isDeleting || isRenaming },
|
||||||
|
set: { if $0 == false { isDeleting = false; isRenaming = false } }
|
||||||
|
)
|
||||||
|
|
||||||
|
return NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: $activeSecret) {
|
||||||
|
Text(secret.name)
|
||||||
|
}.contextMenu {
|
||||||
|
if store is AnySecretStoreModifiable {
|
||||||
|
Button(action: { isRenaming = true }) {
|
||||||
|
Text("Rename")
|
||||||
|
}
|
||||||
|
Button(action: { isDeleting = true }) {
|
||||||
|
Text("Delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.popover(isPresented: showingPopupWrapped) {
|
||||||
|
if let modifiable = store as? AnySecretStoreModifiable {
|
||||||
|
if isDeleting {
|
||||||
|
DeleteSecretView(store: modifiable, secret: secret) { deleted in
|
||||||
|
isDeleting = false
|
||||||
|
if deleted {
|
||||||
|
deletedSecret(secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if isRenaming {
|
||||||
|
RenameSecretView(store: modifiable, secret: secret) { renamed in
|
||||||
|
isRenaming = false
|
||||||
|
if renamed {
|
||||||
|
renamedSecret(secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
struct SecretListView: View {
|
|
||||||
|
|
||||||
@ObservedObject var store: AnySecretStore
|
|
||||||
@Binding var activeSecret: AnySecret.ID?
|
|
||||||
@Binding var deletingSecret: AnySecret?
|
|
||||||
|
|
||||||
var deletedSecret: (AnySecret) -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ForEach(store.secrets) { secret in
|
|
||||||
NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: $activeSecret) {
|
|
||||||
Text(secret.name)
|
|
||||||
}.contextMenu {
|
|
||||||
if store is AnySecretStoreModifiable {
|
|
||||||
Button(action: { delete(secret: secret) }) {
|
|
||||||
Text("Delete")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.popover(isPresented: .constant(deletingSecret == secret)) {
|
|
||||||
if let modifiable = store as? AnySecretStoreModifiable {
|
|
||||||
DeleteSecretView(store: modifiable, secret: secret) { deleted in
|
|
||||||
deletingSecret = nil
|
|
||||||
if deleted {
|
|
||||||
deletedSecret(AnySecret(secret))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func delete<SecretType: Secret>(secret: SecretType) {
|
|
||||||
deletingSecret = AnySecret(secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,10 +6,17 @@ struct StoreListView: View {
|
|||||||
@Binding var showingCreation: Bool
|
@Binding var showingCreation: Bool
|
||||||
|
|
||||||
@State private var activeSecret: AnySecret.ID?
|
@State private var activeSecret: AnySecret.ID?
|
||||||
@State private var deletingSecret: AnySecret?
|
|
||||||
|
|
||||||
@EnvironmentObject private var storeList: SecretStoreList
|
@EnvironmentObject private var storeList: SecretStoreList
|
||||||
|
|
||||||
|
private func secretDeleted(secret: AnySecret) {
|
||||||
|
activeSecret = nextDefaultSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
private func secretRenamed(secret: AnySecret) {
|
||||||
|
activeSecret = nextDefaultSecret
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(selection: $activeSecret) {
|
List(selection: $activeSecret) {
|
||||||
@ -19,9 +26,15 @@ struct StoreListView: View {
|
|||||||
if store.secrets.isEmpty {
|
if store.secrets.isEmpty {
|
||||||
EmptyStoreView(store: store, activeSecret: $activeSecret)
|
EmptyStoreView(store: store, activeSecret: $activeSecret)
|
||||||
} else {
|
} else {
|
||||||
SecretListView(store: store, activeSecret: $activeSecret, deletingSecret: $deletingSecret, deletedSecret: { _ in
|
ForEach(store.secrets) { secret in
|
||||||
activeSecret = nextDefaultSecret
|
SecretListItemView(
|
||||||
})
|
store: store,
|
||||||
|
secret: secret,
|
||||||
|
activeSecret: $activeSecret,
|
||||||
|
deletedSecret: self.secretDeleted,
|
||||||
|
renamedSecret: self.secretRenamed
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,9 +46,7 @@ struct StoreListView: View {
|
|||||||
}
|
}
|
||||||
.frame(minWidth: 100, idealWidth: 240)
|
.frame(minWidth: 100, idealWidth: 240)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StoreListView {
|
extension StoreListView {
|
||||||
|
Reference in New Issue
Block a user