mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-04-10 11:17:24 +02:00
Compare commits
4 Commits
automatic_
...
newcreatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13f30f0343 | ||
|
|
964ef1e201 | ||
|
|
ee850e58d1 | ||
|
|
e94346583e |
19
.github/scripts/signing.sh
vendored
19
.github/scripts/signing.sh
vendored
@@ -1,5 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Import certificate and private key
|
||||||
|
echo $SIGNING_DATA | base64 -d -o Signing.p12
|
||||||
|
security create-keychain -p ci ci.keychain
|
||||||
|
security default-keychain -s ci.keychain
|
||||||
|
security list-keychains -s ci.keychain
|
||||||
|
security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain
|
||||||
|
|
||||||
|
# Import Profiles
|
||||||
|
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
|
echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
|
||||||
|
HOST_UUID=`grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
||||||
|
cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
|
||||||
|
echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
|
||||||
|
AGENT_UUID=`grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
||||||
|
cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
|
||||||
|
|
||||||
# Create directories for ASC key
|
# Create directories for ASC key
|
||||||
mkdir ~/.private_keys
|
mkdir ~/.private_keys
|
||||||
echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey.p8
|
echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8
|
||||||
|
|||||||
16
.github/workflows/add-to-project.yml
vendored
16
.github/workflows/add-to-project.yml
vendored
@@ -1,16 +0,0 @@
|
|||||||
name: Add bugs to bugs project
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
add-to-project:
|
|
||||||
name: Add issue to project
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/add-to-project@v0.0.3
|
|
||||||
with:
|
|
||||||
project-url: https://github.com/users/maxgoedjen/projects/1
|
|
||||||
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
|
||||||
8
.github/workflows/nightly.yml
vendored
8
.github/workflows/nightly.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
- cron: "0 8 * * *"
|
- cron: "0 8 * * *"
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macOS-latest
|
runs-on: macos-11.0
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
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_14.1.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_13.2.1.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
@@ -40,12 +40,8 @@ jobs:
|
|||||||
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
||||||
- name: Document SHAs
|
- name: Document SHAs
|
||||||
run: |
|
run: |
|
||||||
echo "sha-512:"
|
|
||||||
shasum -a 512 Secretive.zip
|
shasum -a 512 Secretive.zip
|
||||||
shasum -a 512 Archive.zip
|
shasum -a 512 Archive.zip
|
||||||
echo "sha-256:"
|
|
||||||
shasum -a 256 Secretive.zip
|
|
||||||
shasum -a 256 Archive.zip
|
|
||||||
- name: Upload App to Artifacts
|
- name: Upload App to Artifacts
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
69
.github/workflows/release.yml
vendored
69
.github/workflows/release.yml
vendored
@@ -5,33 +5,11 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
jobs:
|
jobs:
|
||||||
# test:
|
test:
|
||||||
# runs-on: macOS-latest
|
runs-on: macos-11.0
|
||||||
# timeout-minutes: 10
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/checkout@v3
|
|
||||||
# - name: Setup Signing
|
|
||||||
# env:
|
|
||||||
# SIGNING_DATA: ${{ secrets.SIGNING_DATA }}
|
|
||||||
# SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
|
||||||
# HOST_PROFILE_DATA: ${{ secrets.HOST_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
|
|
||||||
# - name: Set Environment
|
|
||||||
# run: sudo xcrun xcode-select -s /Applications/Xcode_14.1.app
|
|
||||||
# - name: Test
|
|
||||||
# run: |
|
|
||||||
# pushd Sources/Packages
|
|
||||||
# swift test
|
|
||||||
# popd
|
|
||||||
|
|
||||||
build:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v1
|
||||||
- name: Setup Signing
|
- name: Setup Signing
|
||||||
env:
|
env:
|
||||||
SIGNING_DATA: ${{ secrets.SIGNING_DATA }}
|
SIGNING_DATA: ${{ secrets.SIGNING_DATA }}
|
||||||
@@ -42,7 +20,28 @@ jobs:
|
|||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
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_14.1.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_13.2.1.app
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
pushd Sources/Packages
|
||||||
|
swift test
|
||||||
|
popd
|
||||||
|
build:
|
||||||
|
runs-on: macos-11.0
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup Signing
|
||||||
|
env:
|
||||||
|
SIGNING_DATA: ${{ secrets.SIGNING_DATA }}
|
||||||
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
|
HOST_PROFILE_DATA: ${{ secrets.HOST_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
|
||||||
|
- name: Set Environment
|
||||||
|
run: sudo xcrun xcode-select -s /Applications/Xcode_13.2.1.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
@@ -53,32 +52,20 @@ jobs:
|
|||||||
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf
|
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive -destination "generic/platform=macOS" archive
|
|
||||||
- name: Export Products
|
|
||||||
env:
|
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
||||||
run: xcrun xcodebuild -exportArchive -archivePath Archive.xcarchive -exportPath Export -exportOptionsPlist Sources/Config/ExportOptions.plist -allowProvisioningUpdates -authenticationKeyIssuerID $APPLE_API_ISSUER -authenticationKeyID $APPLE_API_KEY_ID -authenticationKeyPath ~/.private_keys/AuthKey.p8
|
|
||||||
- name: Create ZIPs
|
- name: Create ZIPs
|
||||||
run: |
|
run: |
|
||||||
ditto -c -k --sequesterRsrc --keepParent Export/Secretive.app ./Secretive.zip
|
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
|
||||||
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_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
run: xcrun notarytool submit --key $(pwd)/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
|
||||||
- name: Document SHAs
|
- name: Document SHAs
|
||||||
run: |
|
run: |
|
||||||
echo "sha-512:"
|
|
||||||
shasum -a 512 Secretive.zip
|
shasum -a 512 Secretive.zip
|
||||||
shasum -a 512 Archive.zip
|
shasum -a 512 Archive.zip
|
||||||
echo "sha-256:"
|
|
||||||
shasum -a 256 Secretive.zip
|
|
||||||
shasum -a 256 Archive.zip
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
|
|||||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -3,12 +3,12 @@ name: Test
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: macOS-latest
|
runs-on: macos-11.0
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set Environment
|
- name: Set Environment
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_14.1.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_13.2.1.app
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
pushd Sources/Packages
|
pushd Sources/Packages
|
||||||
|
|||||||
@@ -26,15 +26,6 @@ Host *
|
|||||||
IdentityAgent /Users/$YOUR_USERNAME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh
|
IdentityAgent /Users/$YOUR_USERNAME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh
|
||||||
```
|
```
|
||||||
|
|
||||||
## nushell
|
|
||||||
|
|
||||||
Add this to your `~/.ssh/config` (the path should match the socket path from the setup flow).
|
|
||||||
|
|
||||||
```
|
|
||||||
Host *
|
|
||||||
IdentityAgent /Users/$YOUR_USERNAME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cyberduck
|
## Cyberduck
|
||||||
|
|
||||||
Add this to `~/Library/LaunchAgents/com.maxgoedjen.Secretive.SecretAgent.plist`
|
Add this to `~/Library/LaunchAgents/com.maxgoedjen.Secretive.SecretAgent.plist`
|
||||||
@@ -60,31 +51,6 @@ Add this to `~/Library/LaunchAgents/com.maxgoedjen.Secretive.SecretAgent.plist`
|
|||||||
|
|
||||||
Log out and log in again before launching Cyberduck.
|
Log out and log in again before launching Cyberduck.
|
||||||
|
|
||||||
## Mountain Duck
|
|
||||||
|
|
||||||
Add this to `~/Library/LaunchAgents/com.maxgoedjen.Secretive.SecretAgent.plist`
|
|
||||||
|
|
||||||
```
|
|
||||||
<?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>Label</key>
|
|
||||||
<string>link-ssh-auth-sock</string>
|
|
||||||
<key>ProgramArguments</key>
|
|
||||||
<array>
|
|
||||||
<string>/bin/sh</string>
|
|
||||||
<string>-c</string>
|
|
||||||
<string>/bin/ln -sf $HOME/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh $SSH_AUTH_SOCK</string>
|
|
||||||
</array>
|
|
||||||
<key>RunAtLoad</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
```
|
|
||||||
|
|
||||||
Log out and log in again before launching Mountain Duck.
|
|
||||||
|
|
||||||
## GitKraken
|
## GitKraken
|
||||||
|
|
||||||
Add this to `~/Library/LaunchAgents/com.maxgoedjen.Secretive.SecretAgent.plist`
|
Add this to `~/Library/LaunchAgents/com.maxgoedjen.Secretive.SecretAgent.plist`
|
||||||
|
|||||||
4
FAQ.md
4
FAQ.md
@@ -12,10 +12,6 @@ Secretive relies on the `SSH_AUTH_SOCK` environment variable being respected. Th
|
|||||||
|
|
||||||
Please run `ssh -Tv git@github.com` in your terminal and paste the output in a [new GitHub issue](https://github.com/maxgoedjen/secretive/issues/new) with a description of your issue.
|
Please run `ssh -Tv git@github.com` in your terminal and paste the output in a [new GitHub issue](https://github.com/maxgoedjen/secretive/issues/new) with a description of your issue.
|
||||||
|
|
||||||
### Secretive was working for me, but now it has stopped
|
|
||||||
|
|
||||||
Try running the "Setup Secretive" process by clicking on "Help", then "Setup Secretive." If that doesn't work, follow the process above.
|
|
||||||
|
|
||||||
### Secretive prompts me to type my password instead of using my Apple Watch
|
### Secretive prompts me to type my password instead of using my Apple Watch
|
||||||
|
|
||||||
1) Make sure you have enabled "Use your Apple Watch to unlock apps and your Mac" in System Preferences --> Security & Privacy:
|
1) Make sure you have enabled "Use your Apple Watch to unlock apps and your Mac" in System Preferences --> Security & Privacy:
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<?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>method</key>
|
|
||||||
<string>developer-id</string>
|
|
||||||
<key>teamID</key>
|
|
||||||
<string>Z72PRUAWF6</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -4,25 +4,6 @@ import OSLog
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
enum OpenSSHCertificateError: Error {
|
|
||||||
case unsupportedType
|
|
||||||
case parsingFailed
|
|
||||||
case doesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
extension OpenSSHCertificateError: CustomStringConvertible {
|
|
||||||
public var description: String {
|
|
||||||
switch self {
|
|
||||||
case .unsupportedType:
|
|
||||||
return "The key type was unsupported"
|
|
||||||
case .parsingFailed:
|
|
||||||
return "Failed to properly parse the SSH certificate"
|
|
||||||
case .doesNotExist:
|
|
||||||
return "Certificate does not exist"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
|
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
|
||||||
public class Agent {
|
public class Agent {
|
||||||
|
|
||||||
@@ -30,7 +11,6 @@ public class Agent {
|
|||||||
private let witness: SigningWitness?
|
private let witness: SigningWitness?
|
||||||
private let writer = OpenSSHKeyWriter()
|
private let writer = OpenSSHKeyWriter()
|
||||||
private let requestTracer = SigningRequestTracer()
|
private let requestTracer = SigningRequestTracer()
|
||||||
private let certsPath = (NSHomeDirectory() as NSString).appendingPathComponent("PublicKeys") as String
|
|
||||||
|
|
||||||
/// Initializes an agent with a store list and a witness.
|
/// Initializes an agent with a store list and a witness.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@@ -103,22 +83,12 @@ extension Agent {
|
|||||||
var count = UInt32(secrets.count).bigEndian
|
var count = UInt32(secrets.count).bigEndian
|
||||||
let countData = Data(bytes: &count, count: UInt32.bitWidth/8)
|
let countData = Data(bytes: &count, count: UInt32.bitWidth/8)
|
||||||
var keyData = Data()
|
var keyData = Data()
|
||||||
|
let writer = OpenSSHKeyWriter()
|
||||||
for secret in secrets {
|
for secret in secrets {
|
||||||
let keyBlob: Data
|
let keyBlob = writer.data(secret: secret)
|
||||||
let curveData: Data
|
|
||||||
|
|
||||||
if let (certBlob, certName) = try? checkForCert(secret: secret) {
|
|
||||||
keyBlob = certBlob
|
|
||||||
curveData = certName
|
|
||||||
} else {
|
|
||||||
keyBlob = writer.data(secret: secret)
|
|
||||||
curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
|
|
||||||
}
|
|
||||||
|
|
||||||
keyData.append(writer.lengthAndData(of: keyBlob))
|
keyData.append(writer.lengthAndData(of: keyBlob))
|
||||||
|
let curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
|
||||||
keyData.append(writer.lengthAndData(of: curveData))
|
keyData.append(writer.lengthAndData(of: curveData))
|
||||||
|
|
||||||
}
|
}
|
||||||
Logger().debug("Agent enumerated \(secrets.count) identities")
|
Logger().debug("Agent enumerated \(secrets.count) identities")
|
||||||
return countData + keyData
|
return countData + keyData
|
||||||
@@ -131,13 +101,7 @@ extension Agent {
|
|||||||
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
|
/// - Returns: An OpenSSH formatted Data payload containing the signed data response.
|
||||||
func sign(data: Data, provenance: SigningRequestProvenance) throws -> Data {
|
func sign(data: Data, provenance: SigningRequestProvenance) throws -> Data {
|
||||||
let reader = OpenSSHReader(data: data)
|
let reader = OpenSSHReader(data: data)
|
||||||
var hash = reader.readNextChunk()
|
let hash = reader.readNextChunk()
|
||||||
|
|
||||||
// Check if hash is actually an openssh certificate and reconstruct the public key if it is
|
|
||||||
if let certPublicKey = try? getPublicKeyFromCert(certBlob: hash) {
|
|
||||||
hash = certPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let (store, secret) = secret(matching: hash) else {
|
guard let (store, secret) = secret(matching: hash) else {
|
||||||
Logger().debug("Agent did not have a key matching \(hash as NSData)")
|
Logger().debug("Agent did not have a key matching \(hash as NSData)")
|
||||||
throw AgentError.noMatchingKey
|
throw AgentError.noMatchingKey
|
||||||
@@ -198,74 +162,6 @@ extension Agent {
|
|||||||
return signedData
|
return signedData
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconstructs a public key from a ``Data`` object that contains an OpenSSH certificate. Currently only ecdsa certificates are supported
|
|
||||||
/// - Parameter certBlock: The openssh certificate to extract the public key from
|
|
||||||
/// - Returns: A ``Data`` object containing the public key in OpenSSH wire format
|
|
||||||
func getPublicKeyFromCert(certBlob: Data) throws -> Data {
|
|
||||||
let reader = OpenSSHReader(data: certBlob)
|
|
||||||
let certType = String(decoding: reader.readNextChunk(), as: UTF8.self)
|
|
||||||
|
|
||||||
switch certType {
|
|
||||||
case "ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
|
||||||
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
|
|
||||||
"ecdsa-sha2-nistp521-cert-v01@openssh.com":
|
|
||||||
|
|
||||||
_ = reader.readNextChunk() // nonce
|
|
||||||
let curveIdentifier = reader.readNextChunk()
|
|
||||||
let publicKey = reader.readNextChunk()
|
|
||||||
|
|
||||||
if let curveType = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "").data(using: .utf8) {
|
|
||||||
return writer.lengthAndData(of: curveType) +
|
|
||||||
writer.lengthAndData(of: curveIdentifier) +
|
|
||||||
writer.lengthAndData(of: publicKey)
|
|
||||||
} else {
|
|
||||||
throw OpenSSHCertificateError.parsingFailed
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw OpenSSHCertificateError.unsupportedType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
|
|
||||||
/// - Parameter secret: The secret to search for a certificate with
|
|
||||||
/// - Returns: Two ``Data`` objects containing the certificate and certificate name respectively
|
|
||||||
func checkForCert(secret: AnySecret) throws -> (Data, Data) {
|
|
||||||
let minimalHex = writer.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
|
|
||||||
let certificatePath = certsPath.appending("/").appending("\(minimalHex)-cert.pub")
|
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: certificatePath) {
|
|
||||||
Logger().debug("Found certificate for \(secret.name)")
|
|
||||||
do {
|
|
||||||
let certContent = try String(contentsOfFile:certificatePath, encoding: .utf8)
|
|
||||||
let certElements = certContent.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: " ")
|
|
||||||
|
|
||||||
if certElements.count >= 2 {
|
|
||||||
if let certDecoded = Data(base64Encoded: certElements[1] as String) {
|
|
||||||
if certElements.count >= 3 {
|
|
||||||
if let certName = certElements[2].data(using: .utf8) {
|
|
||||||
return (certDecoded, certName)
|
|
||||||
} else if let certName = secret.name.data(using: .utf8) {
|
|
||||||
Logger().info("Certificate for \(secret.name) does not have a name tag, using secret name instead")
|
|
||||||
return (certDecoded, certName)
|
|
||||||
} else {
|
|
||||||
throw OpenSSHCertificateError.parsingFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger().warning("Certificate found for \(secret.name) but failed to decode base64 key")
|
|
||||||
throw OpenSSHCertificateError.parsingFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
Logger().warning("Certificate found for \(secret.name) but failed to load")
|
|
||||||
throw OpenSSHCertificateError.parsingFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw OpenSSHCertificateError.doesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Agent {
|
extension Agent {
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ extension SigningRequestTracer {
|
|||||||
func process(from pid: Int32) -> SigningRequestProvenance.Process {
|
func process(from pid: Int32) -> SigningRequestProvenance.Process {
|
||||||
var pidAndNameInfo = self.pidAndNameInfo(from: pid)
|
var pidAndNameInfo = self.pidAndNameInfo(from: pid)
|
||||||
let ppid = pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
|
let ppid = pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
|
||||||
let procName = withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in
|
let procName = String(cString: &pidAndNameInfo.kp_proc.p_comm.0)
|
||||||
String(cString: pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
|
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
|
||||||
_ = proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
|
_ = proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
|
||||||
let path = String(cString: pathPointer)
|
let path = String(cString: pathPointer)
|
||||||
|
|||||||
@@ -15,21 +15,15 @@ public class PublicKeyFileStoreController {
|
|||||||
|
|
||||||
/// Writes out the keys specified to disk.
|
/// Writes out the keys specified to disk.
|
||||||
/// - Parameter secrets: The Secrets to generate keys for.
|
/// - Parameter secrets: The Secrets to generate keys for.
|
||||||
/// - Parameter clear: Whether or not any untracked files in the directory should be removed.
|
/// - Parameter clear: Whether or not the directory should be erased before writing keys.
|
||||||
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
|
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
|
||||||
logger.log("Writing public keys to disk")
|
logger.log("Writing public keys to disk")
|
||||||
if clear {
|
if clear {
|
||||||
let validPaths = Set(secrets.map { publicKeyPath(for: $0) }).union(Set(secrets.map { sshCertificatePath(for: $0) }))
|
try? FileManager.default.removeItem(at: URL(fileURLWithPath: directory))
|
||||||
let untracked = Set(try FileManager.default.contentsOfDirectory(atPath: directory)
|
|
||||||
.map { "\(directory)/\($0)" })
|
|
||||||
.subtracting(validPaths)
|
|
||||||
for path in untracked {
|
|
||||||
try? FileManager.default.removeItem(at: URL(fileURLWithPath: path))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
try? FileManager.default.createDirectory(at: URL(fileURLWithPath: directory), withIntermediateDirectories: false, attributes: nil)
|
try? FileManager.default.createDirectory(at: URL(fileURLWithPath: directory), withIntermediateDirectories: false, attributes: nil)
|
||||||
for secret in secrets {
|
for secret in secrets {
|
||||||
let path = publicKeyPath(for: secret)
|
let path = path(for: secret)
|
||||||
guard let data = keyWriter.openSSHString(secret: secret).data(using: .utf8) else { continue }
|
guard let data = keyWriter.openSSHString(secret: secret).data(using: .utf8) else { continue }
|
||||||
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
|
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
|
||||||
}
|
}
|
||||||
@@ -40,18 +34,9 @@ public class PublicKeyFileStoreController {
|
|||||||
/// - Parameter secret: The Secret to return the path for.
|
/// - Parameter secret: The Secret to return the path for.
|
||||||
/// - Returns: The path to the Secret's public key.
|
/// - Returns: The path to the Secret's public key.
|
||||||
/// - Warning: This method returning a path does not imply that a key has been written to disk already. This method only describes where it will be written to.
|
/// - Warning: This method returning a path does not imply that a key has been written to disk already. This method only describes where it will be written to.
|
||||||
public func publicKeyPath<SecretType: Secret>(for secret: SecretType) -> String {
|
public func path<SecretType: Secret>(for secret: SecretType) -> String {
|
||||||
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
|
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
|
||||||
return directory.appending("/").appending("\(minimalHex).pub")
|
return directory.appending("/").appending("\(minimalHex).pub")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The path for a Secret's SSH Certificate public key.
|
|
||||||
/// - Parameter secret: The Secret to return the path for.
|
|
||||||
/// - Returns: The path to the SSH Certificate public key.
|
|
||||||
/// - Warning: This method returning a path does not imply that a key has a SSH certificates. This method only describes where it will be.
|
|
||||||
public func sshCertificatePath<SecretType: Secret>(for secret: SecretType) -> String {
|
|
||||||
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
|
|
||||||
return directory.appending("/").appending("\(minimalHex)-cert.pub")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
case Notifier.Constants.persistAuthenticationCategoryIdentitifier:
|
case Notifier.Constants.persistAuthenticationCategoryIdentitifier:
|
||||||
handlePersistAuthenticationResponse(response: response)
|
handlePersistAuthenticationResponse(response: response)
|
||||||
default:
|
default:
|
||||||
break
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
|||||||
@@ -697,7 +697,7 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = Secretive/Secretive.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Secretive/Secretive.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "";
|
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -713,7 +713,7 @@
|
|||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -830,13 +830,10 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = Secretive/Secretive.entitlements;
|
|
||||||
CODE_SIGN_IDENTITY = "";
|
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
|
||||||
ENABLE_HARDENED_RUNTIME = NO;
|
ENABLE_HARDENED_RUNTIME = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = Secretive/Info.plist;
|
INFOPLIST_FILE = Secretive/Info.plist;
|
||||||
@@ -847,7 +844,6 @@
|
|||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@@ -878,12 +874,9 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
|
||||||
CODE_SIGN_IDENTITY = "";
|
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = SecretAgent/Info.plist;
|
INFOPLIST_FILE = SecretAgent/Info.plist;
|
||||||
@@ -894,8 +887,6 @@
|
|||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
|
||||||
SKIP_INSTALL = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@@ -905,7 +896,6 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||||
@@ -920,8 +910,6 @@
|
|||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
|
||||||
SKIP_INSTALL = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -931,7 +919,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "";
|
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||||
@@ -946,8 +934,7 @@
|
|||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
|
||||||
SKIP_INSTALL = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ extension ContentView {
|
|||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "plus")
|
Image(systemName: "plus")
|
||||||
})
|
})
|
||||||
.popover(isPresented: $showingCreation, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) {
|
.sheet(isPresented: $showingCreation) {
|
||||||
if let modifiable = storeList.modifiableStore {
|
if let modifiable = storeList.modifiableStore {
|
||||||
CreateSecretView(store: modifiable, showing: $showingCreation)
|
CreateSecretView(store: modifiable, showing: $showingCreation)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,32 +8,40 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
|
|
||||||
@State private var name = ""
|
@State private var name = ""
|
||||||
@State private var requiresAuthentication = true
|
@State private var requiresAuthentication = true
|
||||||
|
@State private var test: ThumbnailPickerView.Item = ThumbnailPickerView.Item(name: "Test", description: "Hello", thumbnail: Text("Hello"))
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
Image(nsImage: NSApplication.shared.applicationIconImage)
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 64, height: 64)
|
|
||||||
.padding()
|
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Create a New Secret").bold()
|
Text("Create a New Secret")
|
||||||
|
.font(.largeTitle)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Text("Name:")
|
Text("Name:")
|
||||||
TextField("Shhhhh", text: $name).focusable()
|
TextField("Shhhhh", text: $name)
|
||||||
|
.focusable()
|
||||||
}
|
}
|
||||||
HStack {
|
if #available(macOS 12.0, *) {
|
||||||
VStack(spacing: 20) {
|
ThumbnailPickerView(items: [
|
||||||
Picker("", selection: $requiresAuthentication) {
|
ThumbnailPickerView.Item(name: "Requires Authentication Before Use", description: "You will be required to authenticate using Touch ID, Apple Watch, or password before each use.", thumbnail: AuthenticationView()),
|
||||||
Text("Requires Authentication (Biometrics or Password) before each use").tag(true)
|
ThumbnailPickerView.Item(name: "Notify on Use",
|
||||||
Text("Authentication not required when Mac is unlocked").tag(false)
|
description: "No authentication is required while your Mac is unlocked.",
|
||||||
}
|
thumbnail: NotificationView())
|
||||||
.pickerStyle(RadioGroupPickerStyle())
|
], selection: $test)
|
||||||
}
|
} else {
|
||||||
Spacer()
|
// HStack {
|
||||||
|
// VStack(spacing: 20) {
|
||||||
|
// 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(SegmentedPickerStyle())
|
||||||
|
// }
|
||||||
|
// Spacer()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,3 +63,193 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
showing = false
|
showing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ThumbnailPickerView: View {
|
||||||
|
|
||||||
|
let items: [Item]
|
||||||
|
@Binding var selection: Item
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
ForEach(items) { item in
|
||||||
|
VStack {
|
||||||
|
item.thumbnail
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(lineWidth: item.id == selection.id ? 5 : 0))
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
Text(item.name)
|
||||||
|
.bold()
|
||||||
|
Text(item.description)
|
||||||
|
}.onTapGesture {
|
||||||
|
selection = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onAppear {
|
||||||
|
selection = items.first!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ThumbnailPickerView {
|
||||||
|
|
||||||
|
struct Item: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
let name: String
|
||||||
|
let description: String
|
||||||
|
let thumbnail: AnyView
|
||||||
|
|
||||||
|
init<ViewType: View>(name: String, description: String, thumbnail: ViewType) {
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.thumbnail = AnyView(thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 12.0, *)
|
||||||
|
struct SystemBackgroundView: View {
|
||||||
|
|
||||||
|
let anchor: UnitPoint
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let mainScreen = NSScreen.main, let imageURL = NSWorkspace.shared.desktopImageURL(for: mainScreen) {
|
||||||
|
AsyncImage(url: imageURL) { phase in
|
||||||
|
switch phase {
|
||||||
|
case .empty, .failure:
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color(.systemPurple))
|
||||||
|
case .success(let image):
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.scaleEffect(3, anchor: anchor)
|
||||||
|
.clipped()
|
||||||
|
@unknown default:
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color(.systemPurple))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color(.systemPurple))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 12.0, *)
|
||||||
|
struct AuthenticationView: View {
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
SystemBackgroundView(anchor: .center)
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "touchid")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 100)
|
||||||
|
.foregroundColor(Color(.systemRed))
|
||||||
|
Spacer()
|
||||||
|
Text("Touch ID Prompt")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Text("Touch ID Detail prompt.Detail two.")
|
||||||
|
.font(.title3)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Text("Touch ID Detail prompt.Detail two.")
|
||||||
|
.font(.title3)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
Spacer()
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.frame(width: 275, height: 40, alignment: .center)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.frame(width: 275, height: 40, alignment: .center)
|
||||||
|
.foregroundColor(Color(.unemphasizedSelectedContentBackgroundColor))
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 15)
|
||||||
|
.foregroundStyle(.ultraThickMaterial)
|
||||||
|
)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(macOS 12.0, *)
|
||||||
|
struct NotificationView: View {
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
SystemBackgroundView(anchor: .topTrailing)
|
||||||
|
VStack {
|
||||||
|
Rectangle()
|
||||||
|
.background(Color.clear)
|
||||||
|
.foregroundStyle(.thinMaterial)
|
||||||
|
.frame(height: 35)
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Image(nsImage: NSApplication.shared.applicationIconImage)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 64, height: 64)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.padding()
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Secretive")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Text("Secretive wants to sign some request")
|
||||||
|
.font(.title3)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Text("Secretive wants to sign some request")
|
||||||
|
.font(.title3)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 15)
|
||||||
|
.foregroundStyle(.ultraThickMaterial)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
struct CreateSecretView_Previews: PreviewProvider {
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
Group {
|
||||||
|
CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true))
|
||||||
|
if #available(macOS 12.0, *) {
|
||||||
|
AuthenticationView().environment(\.colorScheme, .dark)
|
||||||
|
AuthenticationView().environment(\.colorScheme, .light)
|
||||||
|
NotificationView().environment(\.colorScheme, .dark)
|
||||||
|
NotificationView().environment(\.colorScheme, .light)
|
||||||
|
} else {
|
||||||
|
// Fallback on earlier versions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
CopyableView(title: "Public Key", image: Image(systemName: "key"), text: keyString)
|
CopyableView(title: "Public Key", image: Image(systemName: "key"), text: keyString)
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
CopyableView(title: "Public Key Path", image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret))
|
CopyableView(title: "Public Key Path", image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.path(for: secret))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user