Compare commits
4 Commits
auth_split
...
fixquote
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d16a6ffb1 | ||
|
|
95658072e7 | ||
|
|
7eeb09b1ec | ||
|
|
e6f21c13b0 |
BIN
.github/readme/app-dark.png
vendored
|
Before Width: | Height: | Size: 668 KiB After Width: | Height: | Size: 520 KiB |
BIN
.github/readme/app-light.png
vendored
|
Before Width: | Height: | Size: 618 KiB After Width: | Height: | Size: 519 KiB |
BIN
.github/readme/localize_add.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
.github/readme/localize_sidebar.png
vendored
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
.github/readme/localize_translate.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
.github/readme/notification.png
vendored
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 1.4 MiB |
BIN
.github/readme/touchid.png
vendored
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 259 KiB |
2
.github/workflows/codeql.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
- if: matrix.build-mode == 'manual'
|
- if: matrix.build-mode == 'manual'
|
||||||
name: "Select Xcode"
|
name: "Select Xcode"
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
- if: matrix.build-mode == 'manual'
|
- if: matrix.build-mode == 'manual'
|
||||||
name: "Build"
|
name: "Build"
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
||||||
|
|||||||
29
.github/workflows/nightly.yml
vendored
@@ -3,6 +3,7 @@ name: Nightly
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 8 * * *"
|
- cron: "0 8 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -11,7 +12,6 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
attestations: write
|
attestations: write
|
||||||
actions: read
|
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
@@ -25,7 +25,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_26.4.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
@@ -33,30 +33,23 @@ jobs:
|
|||||||
DATE=$(date "+%Y-%m-%d")
|
DATE=$(date "+%Y-%m-%d")
|
||||||
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/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_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/Config/Config.xcconfig
|
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
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
||||||
- name: Move to Artifact Folder
|
- name: Create ZIP
|
||||||
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
|
|
||||||
- name: Upload App to Artifacts
|
|
||||||
id: upload
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Secretive
|
|
||||||
path: Artifact
|
|
||||||
- name: Download Zipped Artifact
|
|
||||||
id: download
|
|
||||||
env:
|
|
||||||
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
|
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
|
||||||
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.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 ~/.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: Upload App to Artifacts
|
||||||
|
id: upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Secretive.zip
|
||||||
|
path: Secretive.zip
|
||||||
- name: Attest
|
- name: Attest
|
||||||
id: attest
|
id: attest
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
|
|||||||
64
.github/workflows/oneoff.yml
vendored
@@ -1,64 +0,0 @@
|
|||||||
name: One-Off Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: macos-26
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: write
|
|
||||||
attestations: write
|
|
||||||
actions: read
|
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- 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_26.4.app
|
|
||||||
- name: Update Build Number
|
|
||||||
env:
|
|
||||||
RUN_ID: ${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
DATE=$(date "+%Y-%m-%d")
|
|
||||||
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_oneoff-$DATE/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/Config/Config.xcconfig
|
|
||||||
- name: Build
|
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
|
||||||
- name: Move to Artifact Folder
|
|
||||||
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
|
|
||||||
- name: Upload App to Artifacts
|
|
||||||
id: upload
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Secretive
|
|
||||||
path: Artifact
|
|
||||||
- name: Download Zipped Artifact
|
|
||||||
id: download
|
|
||||||
env:
|
|
||||||
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
|
|
||||||
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
|
|
||||||
- name: Notarize
|
|
||||||
env:
|
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
||||||
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: Attest
|
|
||||||
id: attest
|
|
||||||
uses: actions/attest-build-provenance@v2
|
|
||||||
with:
|
|
||||||
subject-name: "Secretive.zip"
|
|
||||||
subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }}
|
|
||||||
43
.github/workflows/release.yml
vendored
@@ -22,7 +22,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_26.4.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
- name: Test
|
- name: Test
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
||||||
# SPM doesn't seem to pick up on the tests currently?
|
# SPM doesn't seem to pick up on the tests currently?
|
||||||
@@ -32,7 +32,6 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: write
|
||||||
attestations: write
|
attestations: write
|
||||||
actions: read
|
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
@@ -47,7 +46,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_26.4.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
@@ -56,43 +55,37 @@ jobs:
|
|||||||
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//')
|
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//')
|
||||||
sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Sources/Config/Config.xcconfig
|
sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/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_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||||
sed -i '' -e "s/GITHUB_BUILD_URL/github.com\/maxgoedjen\/secretive\/actions\/runs\/$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
|
||||||
- name: Build
|
- name: Build
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
||||||
- name: Move to Artifact Folder
|
- name: Create ZIP
|
||||||
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
|
|
||||||
- name: Upload App to Artifacts
|
|
||||||
id: upload
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Secretive.zip
|
|
||||||
path: Artifact
|
|
||||||
- name: Download Zipped Artifact
|
|
||||||
id: download
|
|
||||||
env:
|
|
||||||
ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
|
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
|
||||||
https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.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 ~/.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: Upload App to Artifacts
|
||||||
|
id: upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Secretive.zip
|
||||||
|
path: Secretive.zip
|
||||||
- name: Attest
|
- name: Attest
|
||||||
id: attest
|
id: attest
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-path: "Secretive.zip"
|
subject-name: "Secretive.zip"
|
||||||
|
subject-digest: ${{ steps.upload.outputs.artifact-digest }}
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
|
run: |
|
||||||
|
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
|
||||||
|
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
|
||||||
|
gh release create $TAG_NAME -d -F .github/templates/release.md
|
||||||
|
gh release upload Secretive.zip
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
ATTESTATION_ID: ${{ steps.attest.outputs.attestation-id }}
|
ATTESTATION_ID: ${{ steps.attest.outputs.attestation-id }}
|
||||||
run: |
|
|
||||||
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
|
|
||||||
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
|
|
||||||
gh release create $TAG_NAME -d -F .github/templates/release.md
|
|
||||||
gh release upload $TAG_NAME Secretive.zip
|
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- name: Set Environment
|
- name: Set Environment
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app
|
||||||
- name: Test Main Packages
|
- name: Test Main Packages
|
||||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test
|
||||||
# SPM doesn't seem to pick up on the tests currently?
|
# SPM doesn't seem to pick up on the tests currently?
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -93,7 +93,3 @@ iOSInjectionProject/
|
|||||||
Archive.xcarchive
|
Archive.xcarchive
|
||||||
.DS_Store
|
.DS_Store
|
||||||
contents.xcworkspacedata
|
contents.xcworkspacedata
|
||||||
|
|
||||||
# Per-User Configs
|
|
||||||
|
|
||||||
Sources/Config/OpenSource.xcconfig
|
|
||||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
@@ -22,9 +22,6 @@ let package = Package(
|
|||||||
.library(
|
.library(
|
||||||
name: "SmartCardSecretKit",
|
name: "SmartCardSecretKit",
|
||||||
targets: ["SmartCardSecretKit"]),
|
targets: ["SmartCardSecretKit"]),
|
||||||
.library(
|
|
||||||
name: "SSHProtocolKit",
|
|
||||||
targets: ["SSHProtocolKit"]),
|
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
],
|
],
|
||||||
@@ -56,19 +53,6 @@ let package = Package(
|
|||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings
|
swiftSettings: swiftSettings
|
||||||
),
|
),
|
||||||
.target(
|
|
||||||
name: "SSHProtocolKit",
|
|
||||||
dependencies: ["SecretKit"],
|
|
||||||
path: "Sources/Packages/Sources/SSHProtocolKit",
|
|
||||||
resources: [localization],
|
|
||||||
swiftSettings: swiftSettings,
|
|
||||||
),
|
|
||||||
.testTarget(
|
|
||||||
name: "SSHProtocolKitTests",
|
|
||||||
dependencies: ["SSHProtocolKit"],
|
|
||||||
path: "Sources/Packages/Tests/SSHProtocolKitTests",
|
|
||||||
swiftSettings: swiftSettings,
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
18
README.md
@@ -1,11 +1,11 @@
|
|||||||
# Secretive [](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) 
|
# Secretive [](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) 
|
||||||
|
|
||||||
|
|
||||||
Secretive is an app for protecting and managing SSH keys with the Secure Enclave.
|
Secretive is an app for storing and managing SSH keys in the Secure Enclave. It is inspired by the [sekey project](https://github.com/sekey/sekey), but rewritten in Swift with no external dependencies and with a handy native management app.
|
||||||
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="/.github/readme/app-dark.png">
|
<source media="(prefers-color-scheme: dark)" srcset="/.github/readme/app-dark.png">
|
||||||
<source media="(prefers-color-scheme: light)" srcset="/.github/readme/app-light.png">
|
<img src="/.github/readme/app-light.png" alt="Screenshot of Secretive" width="600">
|
||||||
<img src="/.github/readme/app-dark.png" alt="Screenshot of Secretive" width="600">
|
|
||||||
</picture>
|
</picture>
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ Secretive is an app for protecting and managing SSH keys with the Secure Enclave
|
|||||||
|
|
||||||
### Safer Storage
|
### Safer Storage
|
||||||
|
|
||||||
The most common setup for SSH keys is just keeping them on disk, guarded by proper permissions. This is fine in most cases, but it's not super hard for malicious users or malware to copy your private key. If you protect your keys with the Secure Enclave, it's impossible to export them, by design.
|
The most common setup for SSH keys is just keeping them on disk, guarded by proper permissions. This is fine in most cases, but it's not super hard for malicious users or malware to copy your private key. If you store your keys in the Secure Enclave, it's impossible to export them, by design.
|
||||||
|
|
||||||
### Access Control
|
### Access Control
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Builds are produced by GitHub Actions with an auditable build and release genera
|
|||||||
|
|
||||||
### A Note Around Code Signing and Keychains
|
### A Note Around Code Signing and Keychains
|
||||||
|
|
||||||
While Secretive uses the Secure Enclave to protect keys, it still relies on Keychain APIs to store and access them. Keychain restricts reads of keys to the app (and specifically, the bundle ID) that created them. If you build Secretive from source, make sure you are consistent in which bundle ID you use so that the Keychain is able to locate your keys.
|
While Secretive uses the Secure Enclave for key storage, it still relies on Keychain APIs to access them. Keychain restricts reads of keys to the app (and specifically, the bundle ID) that created them. If you build Secretive from source, make sure you are consistent in which bundle ID you use so that the Keychain is able to locate your keys.
|
||||||
|
|
||||||
### Backups and Transfers to New Machines
|
### Backups and Transfers to New Machines
|
||||||
|
|
||||||
@@ -62,11 +62,3 @@ Because secrets in the Secure Enclave are not exportable, they are not able to b
|
|||||||
## Security
|
## Security
|
||||||
|
|
||||||
Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)
|
Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)
|
||||||
|
|
||||||
## Acknowledgements
|
|
||||||
|
|
||||||
### sekey
|
|
||||||
Secretive was inspired by the [sekey project](https://github.com/sekey/sekey).
|
|
||||||
|
|
||||||
### Localization
|
|
||||||
Secretive is localized to many languages by a generous team of volunteers. To learn more, see [LOCALIZING.md](LOCALIZING.md). Secretive's localization workflow is generously provided by [Crowdin](https://crowdin.com).
|
|
||||||
|
|||||||
@@ -1,8 +1,2 @@
|
|||||||
CI_VERSION = GITHUB_CI_VERSION
|
CI_VERSION = GITHUB_CI_VERSION
|
||||||
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
|
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
|
||||||
CI_BUILD_LINK = GITHUB_BUILD_URL
|
|
||||||
|
|
||||||
#include? "OpenSource.xcconfig"
|
|
||||||
|
|
||||||
SECRETIVE_BASE_BUNDLE_ID = $(SECRETIVE_BASE_BUNDLE_ID_OSS:default=com.maxgoedjen.Secretive)
|
|
||||||
SECRETIVE_DEVELOPMENT_TEAM = $(SECRETIVE_DEVELOPMENT_TEAM_OSS:default=Z72PRUAWF6)
|
|
||||||
|
|||||||
@@ -21,19 +21,13 @@ let package = Package(
|
|||||||
targets: ["SmartCardSecretKit"]),
|
targets: ["SmartCardSecretKit"]),
|
||||||
.library(
|
.library(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
targets: ["SecretAgentKit"]),
|
targets: ["SecretAgentKit", "XPCWrappers"]),
|
||||||
.library(
|
|
||||||
name: "Common",
|
|
||||||
targets: ["Common"]),
|
|
||||||
.library(
|
.library(
|
||||||
name: "Brief",
|
name: "Brief",
|
||||||
targets: ["Brief"]),
|
targets: ["Brief"]),
|
||||||
.library(
|
.library(
|
||||||
name: "XPCWrappers",
|
name: "XPCWrappers",
|
||||||
targets: ["XPCWrappers"]),
|
targets: ["XPCWrappers"]),
|
||||||
.library(
|
|
||||||
name: "SSHProtocolKit",
|
|
||||||
targets: ["SSHProtocolKit"]),
|
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
],
|
],
|
||||||
@@ -46,7 +40,7 @@ let package = Package(
|
|||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "SecretKitTests",
|
name: "SecretKitTests",
|
||||||
dependencies: ["SecretKit", "SecretAgentKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
|
dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
@@ -63,7 +57,7 @@ let package = Package(
|
|||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
dependencies: ["SecretKit", "SSHProtocolKit", "Common"],
|
dependencies: ["SecretKit"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
@@ -71,26 +65,9 @@ let package = Package(
|
|||||||
name: "SecretAgentKitTests",
|
name: "SecretAgentKitTests",
|
||||||
dependencies: ["SecretAgentKit"],
|
dependencies: ["SecretAgentKit"],
|
||||||
),
|
),
|
||||||
.target(
|
|
||||||
name: "SSHProtocolKit",
|
|
||||||
dependencies: ["SecretKit"],
|
|
||||||
resources: [localization],
|
|
||||||
swiftSettings: swiftSettings,
|
|
||||||
),
|
|
||||||
.testTarget(
|
|
||||||
name: "SSHProtocolKitTests",
|
|
||||||
dependencies: ["SSHProtocolKit"],
|
|
||||||
swiftSettings: swiftSettings,
|
|
||||||
),
|
|
||||||
.target(
|
|
||||||
name: "Common",
|
|
||||||
dependencies: ["SSHProtocolKit", "SecretKit"],
|
|
||||||
resources: [localization],
|
|
||||||
swiftSettings: swiftSettings,
|
|
||||||
),
|
|
||||||
.target(
|
.target(
|
||||||
name: "Brief",
|
name: "Brief",
|
||||||
dependencies: ["XPCWrappers", "SSHProtocolKit"],
|
dependencies: ["XPCWrappers"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
/// A release is a representation of a downloadable update.
|
/// A release is a representation of a downloadable update.
|
||||||
public struct Release: Codable, Sendable, Hashable {
|
public struct Release: Codable, Sendable {
|
||||||
|
|
||||||
/// The user-facing name of the release. Typically "Secretive 1.2.3"
|
/// The user-facing name of the release. Typically "Secretive 1.2.3"
|
||||||
public let name: String
|
public let name: String
|
||||||
@@ -16,8 +15,6 @@ public struct Release: Codable, Sendable, Hashable {
|
|||||||
/// A user-facing description of the contents of the update.
|
/// A user-facing description of the contents of the update.
|
||||||
public let body: String
|
public let body: String
|
||||||
|
|
||||||
public let attributedBody: AttributedString
|
|
||||||
|
|
||||||
/// Initializes a Release.
|
/// Initializes a Release.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - name: The user-facing name of the release.
|
/// - name: The user-facing name of the release.
|
||||||
@@ -29,56 +26,6 @@ public struct Release: Codable, Sendable, Hashable {
|
|||||||
self.prerelease = prerelease
|
self.prerelease = prerelease
|
||||||
self.html_url = html_url
|
self.html_url = html_url
|
||||||
self.body = body
|
self.body = body
|
||||||
self.attributedBody = AttributedString(_markdown: body)
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(_ release: GitHubRelease) {
|
|
||||||
self.name = release.name
|
|
||||||
self.prerelease = release.prerelease
|
|
||||||
self.html_url = release.html_url
|
|
||||||
self.body = release.body
|
|
||||||
self.attributedBody = AttributedString(_markdown: release.body)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct GitHubRelease: Codable, Sendable {
|
|
||||||
let name: String
|
|
||||||
let prerelease: Bool
|
|
||||||
let html_url: URL
|
|
||||||
let body: String
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate extension AttributedString {
|
|
||||||
|
|
||||||
init(_markdown markdown: String) {
|
|
||||||
let split = markdown.split(whereSeparator: \.isNewline)
|
|
||||||
let lines = split
|
|
||||||
.compactMap {
|
|
||||||
try? AttributedString(markdown: String($0), options: .init(allowsExtendedAttributes: true, interpretedSyntax: .full))
|
|
||||||
}
|
|
||||||
.map { (string: AttributedString) in
|
|
||||||
guard case let .header(level) = string.runs.first?.presentationIntent?.components.first?.kind else { return string }
|
|
||||||
return AttributedString("\n") + string
|
|
||||||
.transformingAttributes(\.font) { font in
|
|
||||||
font.value = switch level {
|
|
||||||
case 2: .headline.bold()
|
|
||||||
case 3: .headline
|
|
||||||
default: .subheadline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.transformingAttributes(\.underlineStyle) { underline in
|
|
||||||
underline.value = switch level {
|
|
||||||
case 2: .single
|
|
||||||
default: .none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+ AttributedString("\n")
|
|
||||||
}
|
|
||||||
self = lines.reduce(into: AttributedString()) { partialResult, next in
|
|
||||||
partialResult.append(next)
|
|
||||||
partialResult.append(AttributedString("\n"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SSHProtocolKit
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
extension URL {
|
|
||||||
|
|
||||||
public static var agentHomeURL: URL {
|
|
||||||
URL(fileURLWithPath: URL.homeDirectory.path().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID))
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var socketPath: String {
|
|
||||||
#if DEBUG
|
|
||||||
URL.agentHomeURL.appendingPathComponent("socket-debug.ssh").path()
|
|
||||||
#else
|
|
||||||
URL.agentHomeURL.appendingPathComponent("socket.ssh").path()
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var publicKeyDirectory: URL {
|
|
||||||
agentHomeURL.appending(component: "PublicKeys")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The path for a Secret's public key.
|
|
||||||
/// - Parameter secret: The Secret to return the path for.
|
|
||||||
/// - 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.
|
|
||||||
public static func publicKeyPath<SecretType: Secret>(for secret: SecretType, in directory: URL) -> String {
|
|
||||||
let keyWriter = OpenSSHPublicKeyWriter()
|
|
||||||
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
|
|
||||||
return directory.appending(component: "\(minimalHex).pub").path()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
|
|
||||||
public var normalizedPathAndFolder: (String, String) {
|
|
||||||
// All foundation-based normalization methods replace this with the container directly.
|
|
||||||
let processedPath = replacingOccurrences(of: "~", with: "/Users/\(NSUserName())")
|
|
||||||
let url = URL(filePath: processedPath)
|
|
||||||
let folder = url.deletingLastPathComponent().path()
|
|
||||||
return (processedPath, folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
public struct HexDataStyle<SequenceType: Sequence>: Hashable, Codable {
|
|
||||||
|
|
||||||
let separator: String
|
|
||||||
|
|
||||||
public init(separator: String) {
|
|
||||||
self.separator = separator
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension HexDataStyle: FormatStyle where SequenceType.Element == UInt8 {
|
|
||||||
|
|
||||||
public func format(_ value: SequenceType) -> String {
|
|
||||||
value
|
|
||||||
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }
|
|
||||||
.joined(separator: separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FormatStyle where Self == HexDataStyle<Data> {
|
|
||||||
|
|
||||||
public static func hex(separator: String = "") -> HexDataStyle<Data> {
|
|
||||||
HexDataStyle(separator: separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
extension FormatStyle where Self == HexDataStyle<Insecure.MD5Digest> {
|
|
||||||
|
|
||||||
public static func hex(separator: String = ":") -> HexDataStyle<Insecure.MD5Digest> {
|
|
||||||
HexDataStyle(separator: separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,11 @@ import CryptoKit
|
|||||||
import OSLog
|
import OSLog
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import AppKit
|
import AppKit
|
||||||
import SSHProtocolKit
|
|
||||||
|
|
||||||
/// 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 final class Agent: Sendable {
|
public final class Agent: Sendable {
|
||||||
|
|
||||||
private let storeList: SecretStoreList
|
private let storeList: SecretStoreList
|
||||||
private let authenticationHandler: AuthenticationHandler
|
|
||||||
private let witness: SigningWitness?
|
private let witness: SigningWitness?
|
||||||
private let publicKeyWriter = OpenSSHPublicKeyWriter()
|
private let publicKeyWriter = OpenSSHPublicKeyWriter()
|
||||||
private let signatureWriter = OpenSSHSignatureWriter()
|
private let signatureWriter = OpenSSHSignatureWriter()
|
||||||
@@ -20,10 +18,9 @@ public final class Agent: Sendable {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - storeList: The `SecretStoreList` to make available.
|
/// - storeList: The `SecretStoreList` to make available.
|
||||||
/// - witness: A witness to notify of requests.
|
/// - witness: A witness to notify of requests.
|
||||||
public init(storeList: SecretStoreList, authenticationHandler: AuthenticationHandler, witness: SigningWitness? = nil) {
|
public init(storeList: SecretStoreList, witness: SigningWitness? = nil) {
|
||||||
logger.debug("Agent is running")
|
logger.debug("Agent is running")
|
||||||
self.storeList = storeList
|
self.storeList = storeList
|
||||||
self.authenticationHandler = authenticationHandler
|
|
||||||
self.witness = witness
|
self.witness = witness
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
await certificateHandler.reloadCertificates(for: storeList.allSecrets)
|
await certificateHandler.reloadCertificates(for: storeList.allSecrets)
|
||||||
@@ -50,7 +47,6 @@ extension Agent {
|
|||||||
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
||||||
case .unknown(let value):
|
case .unknown(let value):
|
||||||
logger.error("Agent received unknown request of type \(value).")
|
logger.error("Agent received unknown request of type \(value).")
|
||||||
throw UnhandledRequestError()
|
|
||||||
default:
|
default:
|
||||||
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
|
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
|
||||||
throw UnhandledRequestError()
|
throw UnhandledRequestError()
|
||||||
@@ -106,19 +102,10 @@ extension Agent {
|
|||||||
|
|
||||||
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance)
|
||||||
|
|
||||||
let context: any AuthenticationContextProtocol
|
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance)
|
||||||
let offerPersistence: Bool
|
|
||||||
if let existing = await authenticationHandler.existingAuthenticationContextProtocol(secret: secret), existing.valid {
|
|
||||||
context = existing
|
|
||||||
offerPersistence = false
|
|
||||||
} else {
|
|
||||||
context = authenticationHandler.createAuthenticationContext(secret: secret, provenance: provenance, preauthorize: false)
|
|
||||||
offerPersistence = secret.authenticationRequirement.required
|
|
||||||
}
|
|
||||||
let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance, context: context)
|
|
||||||
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
|
let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation)
|
||||||
|
|
||||||
try await witness?.witness(accessTo: secret, from: store, by: provenance, offerPersistence: offerPersistence)
|
try await witness?.witness(accessTo: secret, from: store, by: provenance)
|
||||||
|
|
||||||
logger.debug("Agent signed request")
|
logger.debug("Agent signed request")
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
@unsafe @preconcurrency import LocalAuthentication
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
/// A context describing a persisted authentication.
|
|
||||||
public final class AuthenticationContext: AuthenticationContextProtocol {
|
|
||||||
|
|
||||||
/// The Secret to persist authentication for.
|
|
||||||
public let secret: AnySecret
|
|
||||||
/// The LAContext used to authorize the persistent context.
|
|
||||||
public let laContext: LAContext
|
|
||||||
/// An expiration date for the context.
|
|
||||||
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
|
||||||
let monotonicExpiration: UInt64
|
|
||||||
|
|
||||||
/// Initializes a context.
|
|
||||||
/// - Parameters:
|
|
||||||
/// - secret: The Secret to persist authentication for.
|
|
||||||
/// - context: The LAContext used to authorize the persistent context.
|
|
||||||
/// - duration: The duration of the authorization context, in seconds.
|
|
||||||
init<SecretType: Secret>(secret: SecretType, context: LAContext, duration: TimeInterval) {
|
|
||||||
self.secret = AnySecret(secret)
|
|
||||||
self.laContext = context
|
|
||||||
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
|
||||||
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A boolean describing whether or not the context is still valid.
|
|
||||||
public var valid: Bool {
|
|
||||||
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
|
|
||||||
}
|
|
||||||
|
|
||||||
public var expiration: Date {
|
|
||||||
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
|
|
||||||
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
|
|
||||||
return Date(timeIntervalSinceNow: remainingInSeconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public actor AuthenticationHandler: Sendable {
|
|
||||||
|
|
||||||
private var persistedContexts: [AnySecret: AuthenticationContext] = [:]
|
|
||||||
|
|
||||||
public init() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public nonisolated func createAuthenticationContext<SecretType: Secret>(secret: SecretType, provenance: SigningRequestProvenance, preauthorize: Bool) -> AuthenticationContextProtocol {
|
|
||||||
let newContext = LAContext()
|
|
||||||
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
|
|
||||||
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
|
||||||
return AuthenticationContext(secret: secret, context: newContext, duration: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func existingAuthenticationContextProtocol<SecretType: Secret>(secret: SecretType) -> AuthenticationContextProtocol? {
|
|
||||||
guard let persisted = persistedContexts[AnySecret(secret)], persisted.valid else { return nil }
|
|
||||||
return persisted
|
|
||||||
}
|
|
||||||
|
|
||||||
public func persistAuthentication<SecretType: Secret>(secret: SecretType, forDuration duration: TimeInterval) async throws {
|
|
||||||
let newContext = LAContext()
|
|
||||||
newContext.touchIDAuthenticationAllowableReuseDuration = duration
|
|
||||||
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
|
||||||
|
|
||||||
let formatter = DateComponentsFormatter()
|
|
||||||
formatter.unitsStyle = .spellOut
|
|
||||||
formatter.allowedUnits = [.hour, .minute, .day]
|
|
||||||
|
|
||||||
|
|
||||||
let durationString = formatter.string(from: duration)!
|
|
||||||
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
|
||||||
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
|
||||||
guard success else { return }
|
|
||||||
let context = AuthenticationContext(secret: secret, context: newContext, duration: duration)
|
|
||||||
persistedContexts[AnySecret(secret)] = context
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import SSHProtocolKit
|
|
||||||
|
|
||||||
/// Manages storage and lookup for OpenSSH certificates.
|
/// Manages storage and lookup for OpenSSH certificates.
|
||||||
public actor OpenSSHCertificateHandler: Sendable {
|
public actor OpenSSHCertificateHandler: Sendable {
|
||||||
|
|
||||||
private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
|
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory)
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler")
|
||||||
private let writer = OpenSSHPublicKeyWriter()
|
private let writer = OpenSSHPublicKeyWriter()
|
||||||
private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:]
|
private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:]
|
||||||
|
|||||||
@@ -1,49 +1,42 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Reads OpenSSH protocol data.
|
/// Reads OpenSSH protocol data.
|
||||||
public final class OpenSSHReader {
|
final class OpenSSHReader {
|
||||||
|
|
||||||
var remaining: Data
|
var remaining: Data
|
||||||
var done = false
|
|
||||||
|
|
||||||
/// Initialize the reader with an OpenSSH data payload.
|
/// Initialize the reader with an OpenSSH data payload.
|
||||||
/// - Parameter data: The data to read.
|
/// - Parameter data: The data to read.
|
||||||
public init(data: Data) {
|
init(data: Data) {
|
||||||
remaining = Data(data)
|
remaining = Data(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the next chunk of data from the playload.
|
/// Reads the next chunk of data from the playload.
|
||||||
/// - Returns: The next chunk of data.
|
/// - Returns: The next chunk of data.
|
||||||
public func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data {
|
func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data {
|
||||||
let length = try readNextBytes(as: UInt32.self, convertEndianness: convertEndianness)
|
let littleEndianLength = try readNextBytes(as: UInt32.self)
|
||||||
|
let length = convertEndianness ? Int(littleEndianLength.bigEndian) : Int(littleEndianLength)
|
||||||
guard remaining.count >= length else { throw .beyondBounds }
|
guard remaining.count >= length else { throw .beyondBounds }
|
||||||
let dataRange = 0..<Int(length)
|
let dataRange = 0..<length
|
||||||
let ret = Data(remaining[dataRange])
|
let ret = Data(remaining[dataRange])
|
||||||
remaining.removeSubrange(dataRange)
|
remaining.removeSubrange(dataRange)
|
||||||
if remaining.isEmpty {
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
public func readNextBytes<T: FixedWidthInteger>(as: T.Type, convertEndianness: Bool = true) throws(OpenSSHReaderError) -> T {
|
func readNextBytes<T>(as: T.Type) throws(OpenSSHReaderError) -> T {
|
||||||
let size = MemoryLayout<T>.size
|
let size = MemoryLayout<T>.size
|
||||||
guard remaining.count >= size else { throw .beyondBounds }
|
guard remaining.count >= size else { throw .beyondBounds }
|
||||||
let lengthRange = 0..<size
|
let lengthRange = 0..<size
|
||||||
let lengthChunk = remaining[lengthRange]
|
let lengthChunk = remaining[lengthRange]
|
||||||
remaining.removeSubrange(lengthRange)
|
remaining.removeSubrange(lengthRange)
|
||||||
if remaining.isEmpty {
|
return unsafe lengthChunk.bytes.unsafeLoad(as: T.self)
|
||||||
done = true
|
|
||||||
}
|
|
||||||
let value = unsafe lengthChunk.bytes.unsafeLoad(as: T.self)
|
|
||||||
return convertEndianness ? T(value.bigEndian) : T(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
||||||
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader {
|
func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader {
|
||||||
OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness))
|
OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import SSHProtocolKit
|
|
||||||
|
|
||||||
public protocol SSHAgentInputParserProtocol {
|
public protocol SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
func parse(data: Data) async throws -> SSHAgent.Request
|
func parse(data: Data) async throws -> SSHAgent.Request
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
||||||
@@ -14,7 +13,7 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser")
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {
|
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ extension SigningRequestTracer {
|
|||||||
/// - Parameter pid: The process ID to look up.
|
/// - Parameter pid: The process ID to look up.
|
||||||
/// - Returns: A ``SecretKit.SigningRequestProvenance.Process`` describing the process.
|
/// - Returns: A ``SecretKit.SigningRequestProvenance.Process`` describing the process.
|
||||||
func process(from pid: Int32) -> SigningRequestProvenance.Process {
|
func process(from pid: Int32) -> SigningRequestProvenance.Process {
|
||||||
var pidAndNameInfo = unsafe self.pidAndNameInfo(from: pid)
|
var pidAndNameInfo = self.pidAndNameInfo(from: pid)
|
||||||
let ppid = unsafe pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
|
let ppid = unsafe pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
|
||||||
let procName = unsafe withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in
|
let procName = unsafe withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in
|
||||||
unsafe String(cString: pointer)
|
unsafe String(cString: pointer)
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ public protocol SigningWitness: Sendable {
|
|||||||
/// - secret: The `Secret` that will was used to sign the request.
|
/// - secret: The `Secret` that will was used to sign the request.
|
||||||
/// - store: The `Store` that signed the request..
|
/// - store: The `Store` that signed the request..
|
||||||
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
/// - provenance: A `SigningRequestProvenance` object describing the origin of the request.
|
||||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, offerPersistence: Bool) async throws
|
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,21 +36,16 @@ public struct SocketController {
|
|||||||
logger.debug("Socket controller path is clear")
|
logger.debug("Socket controller path is clear")
|
||||||
port = SocketPort(path: path)
|
port = SocketPort(path: path)
|
||||||
fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true)
|
fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true)
|
||||||
Task { @MainActor [fileHandle, sessionsContinuation, logger] in
|
Task { [fileHandle, sessionsContinuation, logger] in
|
||||||
// Create the sequence before triggering the notification to
|
for await notification in NotificationCenter.default.notifications(named: .NSFileHandleConnectionAccepted) {
|
||||||
// ensure it will not be missed.
|
|
||||||
let connectionAcceptedNotifications = NotificationCenter.default.notifications(named: .NSFileHandleConnectionAccepted)
|
|
||||||
|
|
||||||
fileHandle.acceptConnectionInBackgroundAndNotify()
|
|
||||||
|
|
||||||
for await notification in connectionAcceptedNotifications {
|
|
||||||
logger.debug("Socket controller accepted connection")
|
logger.debug("Socket controller accepted connection")
|
||||||
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { continue }
|
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { continue }
|
||||||
let session = Session(fileHandle: new)
|
let session = Session(fileHandle: new)
|
||||||
sessionsContinuation.yield(session)
|
sessionsContinuation.yield(session)
|
||||||
fileHandle.acceptConnectionInBackgroundAndNotify()
|
await fileHandle.acceptConnectionInBackgroundAndNotifyOnMainActor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fileHandle.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.Mode.common])
|
||||||
logger.debug("Socket listening at \(path)")
|
logger.debug("Socket listening at \(path)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +77,8 @@ extension SocketController {
|
|||||||
self.fileHandle = fileHandle
|
self.fileHandle = fileHandle
|
||||||
provenance = SigningRequestTracer().provenance(from: fileHandle)
|
provenance = SigningRequestTracer().provenance(from: fileHandle)
|
||||||
(messages, messagesContinuation) = AsyncStream.makeStream()
|
(messages, messagesContinuation) = AsyncStream.makeStream()
|
||||||
Task { @MainActor [messagesContinuation, logger] in
|
Task { [messagesContinuation, logger] in
|
||||||
// Create the sequence before triggering the notification to
|
for await _ in NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle) {
|
||||||
// ensure it will not be missed.
|
|
||||||
let dataAvailableNotifications = NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle)
|
|
||||||
|
|
||||||
fileHandle.waitForDataInBackgroundAndNotify()
|
|
||||||
|
|
||||||
for await _ in dataAvailableNotifications {
|
|
||||||
let data = fileHandle.availableData
|
let data = fileHandle.availableData
|
||||||
guard !data.isEmpty else {
|
guard !data.isEmpty else {
|
||||||
logger.debug("Socket controller received empty data, ending continuation.")
|
logger.debug("Socket controller received empty data, ending continuation.")
|
||||||
@@ -101,13 +90,16 @@ extension SocketController {
|
|||||||
logger.debug("Socket controller yielded data.")
|
logger.debug("Socket controller yielded data.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Task {
|
||||||
|
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes new data to the socket.
|
/// Writes new data to the socket.
|
||||||
/// - Parameter data: The data to write.
|
/// - Parameter data: The data to write.
|
||||||
@MainActor public func write(_ data: Data) throws {
|
public func write(_ data: Data) async throws {
|
||||||
try fileHandle.write(contentsOf: data)
|
try fileHandle.write(contentsOf: data)
|
||||||
fileHandle.waitForDataInBackgroundAndNotify()
|
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Closes the socket and cleans up resources.
|
/// Closes the socket and cleans up resources.
|
||||||
@@ -121,6 +113,22 @@ extension SocketController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension FileHandle {
|
||||||
|
|
||||||
|
/// Ensures waitForDataInBackgroundAndNotify will be called on the main actor.
|
||||||
|
@MainActor func waitForDataInBackgroundAndNotifyOnMainActor() {
|
||||||
|
waitForDataInBackgroundAndNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Ensures acceptConnectionInBackgroundAndNotify will be called on the main actor.
|
||||||
|
/// - Parameter modes: the runloop modes to use.
|
||||||
|
@MainActor func acceptConnectionInBackgroundAndNotifyOnMainActor(forModes modes: [RunLoop.Mode]? = [RunLoop.Mode.common]) {
|
||||||
|
acceptConnectionInBackgroundAndNotify(forModes: modes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private extension SocketPort {
|
private extension SocketPort {
|
||||||
|
|
||||||
convenience init(path: String) {
|
convenience init(path: String) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ SecretKit is a collection of protocols describing secrets and stores.
|
|||||||
|
|
||||||
### Authentication Persistence
|
### Authentication Persistence
|
||||||
|
|
||||||
- ``AuthenticationContextProtocol``
|
- ``PersistedAuthenticationContext``
|
||||||
|
|
||||||
### Errors
|
### Errors
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalAuthentication
|
|
||||||
|
|
||||||
/// Type eraser for SecretStore.
|
/// Type eraser for SecretStore.
|
||||||
open class AnySecretStore: SecretStore, @unchecked Sendable {
|
open class AnySecretStore: SecretStore, @unchecked Sendable {
|
||||||
@@ -9,7 +8,9 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
|||||||
private let _id: @Sendable () -> UUID
|
private let _id: @Sendable () -> UUID
|
||||||
private let _name: @MainActor @Sendable () -> String
|
private let _name: @MainActor @Sendable () -> String
|
||||||
private let _secrets: @MainActor @Sendable () -> [AnySecret]
|
private let _secrets: @MainActor @Sendable () -> [AnySecret]
|
||||||
private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance, AuthenticationContextProtocol) async throws -> Data
|
private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance) async throws -> Data
|
||||||
|
private let _existingPersistedAuthenticationContext: @Sendable (AnySecret) async -> PersistedAuthenticationContext?
|
||||||
|
private let _persistAuthentication: @Sendable (AnySecret, TimeInterval) async throws -> Void
|
||||||
private let _reloadSecrets: @Sendable () async -> Void
|
private let _reloadSecrets: @Sendable () async -> Void
|
||||||
|
|
||||||
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore {
|
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore {
|
||||||
@@ -18,7 +19,9 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
|||||||
_name = { secretStore.name }
|
_name = { secretStore.name }
|
||||||
_id = { secretStore.id }
|
_id = { secretStore.id }
|
||||||
_secrets = { secretStore.secrets.map { AnySecret($0) } }
|
_secrets = { secretStore.secrets.map { AnySecret($0) } }
|
||||||
_sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2, context: $3) }
|
_sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) }
|
||||||
|
_existingPersistedAuthenticationContext = { await secretStore.existingPersistedAuthenticationContext(secret: $0.base as! SecretStoreType.SecretType) }
|
||||||
|
_persistAuthentication = { try await secretStore.persistAuthentication(secret: $0.base as! SecretStoreType.SecretType, forDuration: $1) }
|
||||||
_reloadSecrets = { await secretStore.reloadSecrets() }
|
_reloadSecrets = { await secretStore.reloadSecrets() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +41,16 @@ open class AnySecretStore: SecretStore, @unchecked Sendable {
|
|||||||
return _secrets()
|
return _secrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol) async throws -> Data {
|
public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance) async throws -> Data {
|
||||||
try await _sign(data, secret, provenance, context)
|
try await _sign(data, secret, provenance)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func existingPersistedAuthenticationContext(secret: AnySecret) async -> PersistedAuthenticationContext? {
|
||||||
|
await _existingPersistedAuthenticationContext(secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func persistAuthentication(secret: AnySecret, forDuration duration: TimeInterval) async throws {
|
||||||
|
try await _persistAuthentication(secret, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reloadSecrets() async {
|
public func reloadSecrets() async {
|
||||||
@@ -53,7 +64,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
|||||||
private let _create: @Sendable (String, Attributes) async throws -> AnySecret
|
private let _create: @Sendable (String, Attributes) async throws -> AnySecret
|
||||||
private let _delete: @Sendable (AnySecret) async throws -> Void
|
private let _delete: @Sendable (AnySecret) async throws -> Void
|
||||||
private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void
|
private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void
|
||||||
private let _supportedKeyTypes: @Sendable () -> KeyAvailability
|
private let _supportedKeyTypes: @Sendable () -> [KeyType]
|
||||||
|
|
||||||
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
||||||
_create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) }
|
_create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) }
|
||||||
@@ -76,7 +87,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
|||||||
try await _update(secret, name, attributes)
|
try await _update(secret, name, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var supportedKeyTypes: KeyAvailability {
|
public var supportedKeyTypes: [KeyType] {
|
||||||
_supportedKeyTypes()
|
_supportedKeyTypes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
/// Generates OpenSSH representations of the public key sof secrets.
|
/// Generates OpenSSH representations of the public key sof secrets.
|
||||||
public struct OpenSSHPublicKeyWriter: Sendable {
|
public struct OpenSSHPublicKeyWriter: Sendable {
|
||||||
@@ -50,7 +49,9 @@ public struct OpenSSHPublicKeyWriter: Sendable {
|
|||||||
/// Generates an OpenSSH MD5 fingerprint string.
|
/// Generates an OpenSSH MD5 fingerprint string.
|
||||||
/// - Returns: OpenSSH MD5 fingerprint string.
|
/// - Returns: OpenSSH MD5 fingerprint string.
|
||||||
public func openSSHMD5Fingerprint<SecretType: Secret>(secret: SecretType) -> String {
|
public func openSSHMD5Fingerprint<SecretType: Secret>(secret: SecretType) -> String {
|
||||||
Insecure.MD5.hash(data: data(secret: secret)).formatted(.hex(separator: ":"))
|
Insecure.MD5.hash(data: data(secret: secret))
|
||||||
|
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }
|
||||||
|
.joined(separator: ":")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func comment<SecretType: Secret>(secret: SecretType) -> String {
|
public func comment<SecretType: Secret>(secret: SecretType) -> String {
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
/// Generates OpenSSH representations of Secrets.
|
/// Generates OpenSSH representations of Secrets.
|
||||||
public struct OpenSSHSignatureWriter: Sendable {
|
public struct OpenSSHSignatureWriter: Sendable {
|
||||||
@@ -30,28 +29,19 @@ public struct OpenSSHSignatureWriter: Sendable {
|
|||||||
|
|
||||||
extension OpenSSHSignatureWriter {
|
extension OpenSSHSignatureWriter {
|
||||||
|
|
||||||
/// Converts a fixed-width big-endian integer (e.g. r/s from CryptoKit rawRepresentation) into an SSH mpint.
|
|
||||||
/// Strips unnecessary leading zeros and prefixes `0x00` if needed to keep the value positive.
|
|
||||||
private func mpint(fromFixedWidthPositiveBytes bytes: Data) -> Data {
|
|
||||||
// mpint zero is encoded as a string with zero bytes of data.
|
|
||||||
guard let firstNonZeroIndex = bytes.firstIndex(where: { $0 != 0x00 }) else {
|
|
||||||
return Data()
|
|
||||||
}
|
|
||||||
|
|
||||||
let trimmed = Data(bytes[firstNonZeroIndex...])
|
|
||||||
|
|
||||||
if let first = trimmed.first, first >= 0x80 {
|
|
||||||
var prefixed = Data([0x00])
|
|
||||||
prefixed.append(trimmed)
|
|
||||||
return prefixed
|
|
||||||
}
|
|
||||||
return trimmed
|
|
||||||
}
|
|
||||||
|
|
||||||
func ecdsaSignature(_ rawRepresentation: Data, keyType: KeyType) -> Data {
|
func ecdsaSignature(_ rawRepresentation: Data, keyType: KeyType) -> Data {
|
||||||
let rawLength = rawRepresentation.count/2
|
let rawLength = rawRepresentation.count/2
|
||||||
let r = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[0..<rawLength]))
|
// Check if we need to pad with 0x00 to prevent certain
|
||||||
let s = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[rawLength...]))
|
// ssh servers from thinking r or s is negative
|
||||||
|
let paddingRange: ClosedRange<UInt8> = 0x80...0xFF
|
||||||
|
var r = Data(rawRepresentation[0..<rawLength])
|
||||||
|
if paddingRange ~= r.first! {
|
||||||
|
r.insert(0x00, at: 0)
|
||||||
|
}
|
||||||
|
var s = Data(rawRepresentation[rawLength...])
|
||||||
|
if paddingRange ~= s.first! {
|
||||||
|
s.insert(0x00, at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
var signatureChunk = Data()
|
var signatureChunk = Data()
|
||||||
signatureChunk.append(r.lengthAndData)
|
signatureChunk.append(r.lengthAndData)
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
import SecretKit
|
|
||||||
import SSHProtocolKit
|
|
||||||
import Common
|
|
||||||
|
|
||||||
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
|
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
|
||||||
public final class PublicKeyFileStoreController: Sendable {
|
public final class PublicKeyFileStoreController: Sendable {
|
||||||
@@ -12,8 +9,8 @@ public final class PublicKeyFileStoreController: Sendable {
|
|||||||
private let keyWriter = OpenSSHPublicKeyWriter()
|
private let keyWriter = OpenSSHPublicKeyWriter()
|
||||||
|
|
||||||
/// Initializes a PublicKeyFileStoreController.
|
/// Initializes a PublicKeyFileStoreController.
|
||||||
public init(directory: URL) {
|
public init(homeDirectory: URL) {
|
||||||
self.directory = directory
|
directory = homeDirectory.appending(component: "PublicKeys")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes out the keys specified to disk.
|
/// Writes out the keys specified to disk.
|
||||||
@@ -22,10 +19,9 @@ public final class PublicKeyFileStoreController: Sendable {
|
|||||||
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 { URL.publicKeyPath(for: $0, in: directory) })
|
let validPaths = Set(secrets.map { publicKeyPath(for: $0) }).union(Set(secrets.map { sshCertificatePath(for: $0) }))
|
||||||
.union(Set(secrets.map { sshCertificatePath(for: $0) }))
|
|
||||||
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
|
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
|
||||||
let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() }
|
let fullPathContents = contentsOfDirectory.map { "\(directory)/\($0)" }
|
||||||
|
|
||||||
let untracked = Set(fullPathContents)
|
let untracked = Set(fullPathContents)
|
||||||
.subtracting(validPaths)
|
.subtracting(validPaths)
|
||||||
@@ -36,13 +32,21 @@ public final class PublicKeyFileStoreController: Sendable {
|
|||||||
}
|
}
|
||||||
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil)
|
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil)
|
||||||
for secret in secrets {
|
for secret in secrets {
|
||||||
let path = URL.publicKeyPath(for: secret, in: directory)
|
let path = publicKeyPath(for: secret)
|
||||||
let data = Data(keyWriter.openSSHString(secret: secret).utf8)
|
let data = Data(keyWriter.openSSHString(secret: secret).utf8)
|
||||||
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
|
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
|
||||||
}
|
}
|
||||||
logger.log("Finished writing public keys")
|
logger.log("Finished writing public keys")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The path for a Secret's public key.
|
||||||
|
/// - Parameter secret: The Secret to return the path for.
|
||||||
|
/// - 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.
|
||||||
|
public func publicKeyPath<SecretType: Secret>(for secret: SecretType) -> String {
|
||||||
|
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
|
||||||
|
return directory.appending(component: "\(minimalHex).pub").path()
|
||||||
|
}
|
||||||
|
|
||||||
/// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory.
|
/// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory.
|
||||||
public var hasAnyCertificates: Bool {
|
public var hasAnyCertificates: Bool {
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import LocalAuthentication
|
|
||||||
|
|
||||||
/// Protocol describing an authentication context. This is an authorization that can be reused for multiple access to a secret that requires authentication for a specific period of time.
|
|
||||||
public protocol AuthenticationContextProtocol: Sendable {
|
|
||||||
/// Whether the context remains valid.
|
|
||||||
var valid: Bool { get }
|
|
||||||
/// The date at which the authorization expires and the context becomes invalid.
|
|
||||||
var expiration: Date { get }
|
|
||||||
|
|
||||||
var secret: AnySecret { get }
|
|
||||||
|
|
||||||
var laContext: LAContext { get }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Protocol describing a persisted authentication context. This is an authorization that can be reused for multiple access to a secret that requires authentication for a specific period of time.
|
||||||
|
public protocol PersistedAuthenticationContext: Sendable {
|
||||||
|
/// Whether the context remains valid.
|
||||||
|
var valid: Bool { get }
|
||||||
|
/// The date at which the authorization expires and the context becomes invalid.
|
||||||
|
var expiration: Date { get }
|
||||||
|
}
|
||||||
@@ -20,7 +20,20 @@ public protocol SecretStore<SecretType>: Identifiable, Sendable {
|
|||||||
/// - secret: The ``Secret`` to sign with.
|
/// - secret: The ``Secret`` to sign with.
|
||||||
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
|
/// - provenance: A ``SigningRequestProvenance`` describing where the request came from.
|
||||||
/// - Returns: The signed data.
|
/// - Returns: The signed data.
|
||||||
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol) async throws -> Data
|
func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) async throws -> Data
|
||||||
|
|
||||||
|
/// Checks to see if there is currently a valid persisted authentication for a given secret.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - secret: The ``Secret`` to check if there is a persisted authentication for.
|
||||||
|
/// - Returns: A persisted authentication context, if a valid one exists.
|
||||||
|
func existingPersistedAuthenticationContext(secret: SecretType) async -> PersistedAuthenticationContext?
|
||||||
|
|
||||||
|
/// Persists user authorization for access to a secret.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - secret: The ``Secret`` to persist the authorization for.
|
||||||
|
/// - duration: The duration that the authorization should persist for.
|
||||||
|
/// - Note: This is used for temporarily unlocking access to a secret which would otherwise require authentication every single use. This is useful for situations where the user anticipates several rapid accesses to a authorization-guarded secret.
|
||||||
|
func persistAuthentication(secret: SecretType, forDuration duration: TimeInterval) async throws
|
||||||
|
|
||||||
/// Requests that the store reload secrets from any backing store, if neccessary.
|
/// Requests that the store reload secrets from any backing store, if neccessary.
|
||||||
func reloadSecrets() async
|
func reloadSecrets() async
|
||||||
@@ -49,37 +62,10 @@ public protocol SecretStoreModifiable<SecretType>: SecretStore {
|
|||||||
/// - attributes: The new attributes for the secret.
|
/// - attributes: The new attributes for the secret.
|
||||||
func update(secret: SecretType, name: String, attributes: Attributes) async throws
|
func update(secret: SecretType, name: String, attributes: Attributes) async throws
|
||||||
|
|
||||||
var supportedKeyTypes: KeyAvailability { get }
|
var supportedKeyTypes: [KeyType] { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct KeyAvailability: Sendable {
|
|
||||||
|
|
||||||
public let available: [KeyType]
|
|
||||||
public let unavailable: [UnavailableKeyType]
|
|
||||||
|
|
||||||
public init(available: [KeyType], unavailable: [UnavailableKeyType]) {
|
|
||||||
self.available = available
|
|
||||||
self.unavailable = unavailable
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct UnavailableKeyType: Sendable {
|
|
||||||
public let keyType: KeyType
|
|
||||||
public let reason: Reason
|
|
||||||
|
|
||||||
public init(keyType: KeyType, reason: Reason) {
|
|
||||||
self.keyType = keyType
|
|
||||||
self.reason = reason
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Reason: Sendable {
|
|
||||||
case macOSUpdateRequired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension NSNotification.Name {
|
extension NSNotification.Name {
|
||||||
|
|
||||||
// Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets)
|
// Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets)
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ import Foundation
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
/// Describes the chain of applications that requested a signature operation.
|
/// Describes the chain of applications that requested a signature operation.
|
||||||
public struct SigningRequestProvenance: Hashable, Sendable {
|
public struct SigningRequestProvenance: Equatable, Sendable {
|
||||||
|
|
||||||
/// A list of processes involved in the request.
|
/// A list of processes involved in the request.
|
||||||
/// - Note: A chain will typically consist of many elements even for a simple request. For example, running `git fetch` in Terminal.app would generate a request chain of `ssh` -> `git` -> `zsh` -> `login` -> `Terminal.app`
|
/// - Note: A chain will typically consist of many elements even for a simple request. For example, running `git fetch` in Terminal.app would generate a request chain of `ssh` -> `git` -> `zsh` -> `login` -> `Terminal.app`
|
||||||
public var chain: [Process]
|
public var chain: [Process]
|
||||||
|
public init(root: Process) {
|
||||||
public var date: Date
|
|
||||||
|
|
||||||
public init(root: Process, date: Date = .now) {
|
|
||||||
self.chain = [root]
|
self.chain = [root]
|
||||||
self.date = date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -34,7 +30,7 @@ extension SigningRequestProvenance {
|
|||||||
extension SigningRequestProvenance {
|
extension SigningRequestProvenance {
|
||||||
|
|
||||||
/// Describes a process in a `SigningRequestProvenance` chain.
|
/// Describes a process in a `SigningRequestProvenance` chain.
|
||||||
public struct Process: Hashable, Sendable {
|
public struct Process: Equatable, Sendable {
|
||||||
|
|
||||||
/// The pid of the process.
|
/// The pid of the process.
|
||||||
public let pid: Int32
|
public let pid: Int32
|
||||||
|
|||||||
@@ -50,16 +50,16 @@ extension SecureEnclave {
|
|||||||
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
|
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
|
||||||
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
||||||
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
||||||
markMigrated(secret: secret, oldID: id)
|
try markMigrated(secret: secret, oldID: id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.log("Migrating \(name).")
|
logger.log("Migrating \(name).")
|
||||||
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
|
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
|
||||||
logger.log("Migrated \(name).")
|
logger.log("Migrated \(name).")
|
||||||
markMigrated(secret: secret, oldID: id)
|
try markMigrated(secret: secret, oldID: id)
|
||||||
migratedAny = true
|
migratedAny = true
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("Failed to migrate \(name): \(error.localizedDescription).")
|
logger.error("Failed to migrate \(name): \(error).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if migratedAny {
|
if migratedAny {
|
||||||
@@ -69,10 +69,10 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public func markMigrated(secret: Secret, oldID: Data) {
|
public func markMigrated(secret: Secret, oldID: Data) throws {
|
||||||
let updateQuery = KeychainDictionary([
|
let updateQuery = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: oldID
|
kSecAttrApplicationLabel: secret.id
|
||||||
])
|
])
|
||||||
|
|
||||||
let newID = oldID + Constants.migrationMagicNumber
|
let newID = oldID + Constants.migrationMagicNumber
|
||||||
@@ -82,7 +82,7 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
logger.warning("Failed to mark \(secret.name) as migrated: \(status).")
|
throw KeychainError(statusCode: status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import LocalAuthentication
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
extension SecureEnclave {
|
||||||
|
|
||||||
|
/// A context describing a persisted authentication.
|
||||||
|
final class PersistentAuthenticationContext: PersistedAuthenticationContext {
|
||||||
|
|
||||||
|
/// The Secret to persist authentication for.
|
||||||
|
let secret: Secret
|
||||||
|
/// The LAContext used to authorize the persistent context.
|
||||||
|
nonisolated(unsafe) let context: LAContext
|
||||||
|
/// An expiration date for the context.
|
||||||
|
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
||||||
|
let monotonicExpiration: UInt64
|
||||||
|
|
||||||
|
/// Initializes a context.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - secret: The Secret to persist authentication for.
|
||||||
|
/// - context: The LAContext used to authorize the persistent context.
|
||||||
|
/// - duration: The duration of the authorization context, in seconds.
|
||||||
|
init(secret: Secret, context: LAContext, duration: TimeInterval) {
|
||||||
|
self.secret = secret
|
||||||
|
unsafe self.context = context
|
||||||
|
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
||||||
|
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A boolean describing whether or not the context is still valid.
|
||||||
|
var valid: Bool {
|
||||||
|
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiration: Date {
|
||||||
|
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
|
||||||
|
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
|
||||||
|
return Date(timeIntervalSinceNow: remainingInSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actor PersistentAuthenticationHandler: Sendable {
|
||||||
|
|
||||||
|
private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:]
|
||||||
|
|
||||||
|
func existingPersistedAuthenticationContext(secret: Secret) -> PersistentAuthenticationContext? {
|
||||||
|
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
|
||||||
|
return persisted
|
||||||
|
}
|
||||||
|
|
||||||
|
func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
|
||||||
|
let newContext = LAContext()
|
||||||
|
newContext.touchIDAuthenticationAllowableReuseDuration = duration
|
||||||
|
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
||||||
|
|
||||||
|
let formatter = DateComponentsFormatter()
|
||||||
|
formatter.unitsStyle = .spellOut
|
||||||
|
formatter.allowedUnits = [.hour, .minute, .day]
|
||||||
|
|
||||||
|
|
||||||
|
let durationString = formatter.string(from: duration)!
|
||||||
|
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
||||||
|
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
||||||
|
guard success else { return }
|
||||||
|
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
||||||
|
persistedAuthenticationContexts[secret] = context
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ extension SecureEnclave {
|
|||||||
}
|
}
|
||||||
public let id = UUID()
|
public let id = UUID()
|
||||||
public let name = String(localized: .secureEnclave)
|
public let name = String(localized: .secureEnclave)
|
||||||
|
private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
|
||||||
|
|
||||||
/// Initializes a Store.
|
/// Initializes a Store.
|
||||||
@MainActor public init() {
|
@MainActor public init() {
|
||||||
@@ -36,7 +37,16 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
// MARK: SecretStore
|
// MARK: SecretStore
|
||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol) async throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
|
||||||
|
var context: LAContext
|
||||||
|
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
|
||||||
|
context = unsafe existing.context
|
||||||
|
} else {
|
||||||
|
let newContext = LAContext()
|
||||||
|
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
|
||||||
|
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
||||||
|
context = newContext
|
||||||
|
}
|
||||||
|
|
||||||
let queryAttributes = KeychainDictionary([
|
let queryAttributes = KeychainDictionary([
|
||||||
kSecClass: Constants.keyClass,
|
kSecClass: Constants.keyClass,
|
||||||
@@ -62,15 +72,15 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
switch attributes.keyType {
|
switch attributes.keyType {
|
||||||
case .ecdsa256:
|
case .ecdsa256:
|
||||||
let key = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: keyData, authenticationContext: context.laContext)
|
let key = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: keyData, authenticationContext: context)
|
||||||
return try key.signature(for: data).rawRepresentation
|
return try key.signature(for: data).rawRepresentation
|
||||||
case .mldsa65:
|
case .mldsa65:
|
||||||
guard #available(macOS 26.0, *) else { throw UnsupportedAlgorithmError() }
|
guard #available(macOS 26.0, *) else { throw UnsupportedAlgorithmError() }
|
||||||
let key = try CryptoKit.SecureEnclave.MLDSA65.PrivateKey(dataRepresentation: keyData, authenticationContext: context.laContext)
|
let key = try CryptoKit.SecureEnclave.MLDSA65.PrivateKey(dataRepresentation: keyData, authenticationContext: context)
|
||||||
return try key.signature(for: data)
|
return try key.signature(for: data)
|
||||||
case .mldsa87:
|
case .mldsa87:
|
||||||
guard #available(macOS 26.0, *) else { throw UnsupportedAlgorithmError() }
|
guard #available(macOS 26.0, *) else { throw UnsupportedAlgorithmError() }
|
||||||
let key = try CryptoKit.SecureEnclave.MLDSA87.PrivateKey(dataRepresentation: keyData, authenticationContext: context.laContext)
|
let key = try CryptoKit.SecureEnclave.MLDSA87.PrivateKey(dataRepresentation: keyData, authenticationContext: context)
|
||||||
return try key.signature(for: data)
|
return try key.signature(for: data)
|
||||||
default:
|
default:
|
||||||
throw UnsupportedAlgorithmError()
|
throw UnsupportedAlgorithmError()
|
||||||
@@ -78,6 +88,14 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
|
||||||
|
await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
|
||||||
|
try await persistentAuthenticationHandler.persistAuthentication(secret: secret, forDuration: duration)
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor public func reloadSecrets() {
|
@MainActor public func reloadSecrets() {
|
||||||
let before = secrets
|
let before = secrets
|
||||||
secrets.removeAll()
|
secrets.removeAll()
|
||||||
@@ -168,22 +186,17 @@ extension SecureEnclave {
|
|||||||
await reloadSecrets()
|
await reloadSecrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
public let supportedKeyTypes: KeyAvailability = {
|
public var supportedKeyTypes: [KeyType] {
|
||||||
let macOS26Keys: [KeyType] = [.mldsa65, .mldsa87]
|
if #available(macOS 26, *) {
|
||||||
let isAtLeastMacOS26 = if #available(macOS 26, *) {
|
[
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
return KeyAvailability(
|
|
||||||
available: [
|
|
||||||
.ecdsa256,
|
.ecdsa256,
|
||||||
] + (isAtLeastMacOS26 ? macOS26Keys : []),
|
.mldsa65,
|
||||||
unavailable: (isAtLeastMacOS26 ? [] : macOS26Keys).map {
|
.mldsa87,
|
||||||
KeyAvailability.UnavailableKeyType(keyType: $0, reason: .macOSUpdateRequired)
|
]
|
||||||
}
|
} else {
|
||||||
)
|
[.ecdsa256]
|
||||||
}()
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,11 @@ extension SmartCard {
|
|||||||
|
|
||||||
// MARK: Public API
|
// MARK: Public API
|
||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol) async throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
|
||||||
guard let tokenID = await state.tokenID else { fatalError() }
|
guard let tokenID = await state.tokenID else { fatalError() }
|
||||||
|
let context = LAContext()
|
||||||
|
context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
|
||||||
|
context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
||||||
let attributes = KeychainDictionary([
|
let attributes = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
||||||
@@ -83,6 +86,13 @@ extension SmartCard {
|
|||||||
return signature as Data
|
return signature as Data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws {
|
||||||
|
}
|
||||||
|
|
||||||
/// Reloads all secrets from the store.
|
/// Reloads all secrets from the store.
|
||||||
@MainActor public func reloadSecrets() {
|
@MainActor public func reloadSecrets() {
|
||||||
reloadSecretsInternal()
|
reloadSecretsInternal()
|
||||||
@@ -153,7 +163,7 @@ extension SmartCard.Store {
|
|||||||
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
|
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
|
||||||
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
|
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
|
||||||
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
let publicKey = publicKeyAttributes[kSecValueData] as! Data
|
||||||
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired)
|
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .unknown)
|
||||||
let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
|
let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
|
||||||
guard signatureAlgorithm(for: secret) != nil else { return nil }
|
guard signatureAlgorithm(for: secret) != nil else { return nil }
|
||||||
return secret
|
return secret
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension ProcessInfo {
|
|
||||||
private static let fallbackTeamID = "Z72PRUAWF6"
|
|
||||||
|
|
||||||
private static let teamID: String = {
|
|
||||||
#if DEBUG
|
|
||||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
|
||||||
assertionFailure("SecTaskCreateFromSelf failed")
|
|
||||||
return fallbackTeamID
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let value = SecTaskCopyValueForEntitlement(task, "com.apple.developer.team-identifier" as CFString, nil) as? String else {
|
|
||||||
assertionFailure("SecTaskCopyValueForEntitlement(com.apple.developer.team-identifier) failed")
|
|
||||||
return fallbackTeamID
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
#else
|
|
||||||
/// Always use hardcoded team ID for release builds, just in case.
|
|
||||||
return fallbackTeamID
|
|
||||||
#endif
|
|
||||||
}()
|
|
||||||
|
|
||||||
public var teamID: String { Self.teamID }
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
|
|||||||
newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||||
let exportedObject = exportedObject
|
let exportedObject = exportedObject
|
||||||
newConnection.exportedObject = exportedObject
|
newConnection.exportedObject = exportedObject
|
||||||
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
|
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
|
||||||
newConnection.resume()
|
newConnection.resume()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -34,9 +34,7 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
|
|||||||
if let error = error as? Codable & Error {
|
if let error = error as? Codable & Error {
|
||||||
reply(nil, NSError(error))
|
reply(nil, NSError(error))
|
||||||
} else {
|
} else {
|
||||||
// Sending cast directly tries to serialize it and crashes XPCEncoder.
|
reply(nil, error)
|
||||||
let cast = error as NSError
|
|
||||||
reply(nil, NSError(domain: cast.domain, code: cast.code, userInfo: [NSLocalizedDescriptionKey: error.localizedDescription]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error
|
|||||||
public init(serviceName: String, warmup: Bool = false) async throws {
|
public init(serviceName: String, warmup: Bool = false) async throws {
|
||||||
let connection = NSXPCConnection(serviceName: serviceName)
|
let connection = NSXPCConnection(serviceName: serviceName)
|
||||||
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||||
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
|
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
|
||||||
connection.resume()
|
connection.resume()
|
||||||
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
|
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Testing
|
|
||||||
import SSHProtocolKit
|
|
||||||
@testable import SecretKit
|
|
||||||
|
|
||||||
@Suite struct OpenSSHSignatureWriterTests {
|
|
||||||
|
|
||||||
private let writer = OpenSSHSignatureWriter()
|
|
||||||
|
|
||||||
@Test func ecdsaMpintStripsUnnecessaryLeadingZeros() throws {
|
|
||||||
let secret = Constants.ecdsa256Secret
|
|
||||||
|
|
||||||
// r has a leading 0x00 followed by 0x01 (< 0x80): the mpint must not keep the leading zero.
|
|
||||||
let rBytes: [UInt8] = [0x00] + (1...31).map { UInt8($0) }
|
|
||||||
let r = Data(rBytes)
|
|
||||||
// s has two leading 0x00 bytes followed by 0x7f (< 0x80): the mpint must not keep the leading zeros.
|
|
||||||
let sBytes: [UInt8] = [0x00, 0x00, 0x7f] + Array(repeating: UInt8(0x01), count: 29)
|
|
||||||
let s = Data(sBytes)
|
|
||||||
let rawRepresentation = r + s
|
|
||||||
|
|
||||||
let response = writer.data(secret: secret, signature: rawRepresentation)
|
|
||||||
let (parsedR, parsedS) = try parseEcdsaSignatureMpints(from: response)
|
|
||||||
|
|
||||||
#expect(parsedR == Data((1...31).map { UInt8($0) }))
|
|
||||||
#expect(parsedS == Data([0x7f] + Array(repeating: UInt8(0x01), count: 29)))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test func ecdsaMpintPrefixesZeroWhenHighBitSet() throws {
|
|
||||||
let secret = Constants.ecdsa256Secret
|
|
||||||
|
|
||||||
// r starts with 0x80 (high bit set): mpint must be prefixed with 0x00.
|
|
||||||
let r = Data([UInt8(0x80)] + Array(repeating: UInt8(0x01), count: 31))
|
|
||||||
let s = Data([UInt8(0x01)] + Array(repeating: UInt8(0x02), count: 31))
|
|
||||||
let rawRepresentation = r + s
|
|
||||||
|
|
||||||
let response = writer.data(secret: secret, signature: rawRepresentation)
|
|
||||||
let (parsedR, parsedS) = try parseEcdsaSignatureMpints(from: response)
|
|
||||||
|
|
||||||
#expect(parsedR == Data([0x00, 0x80] + Array(repeating: UInt8(0x01), count: 31)))
|
|
||||||
#expect(parsedS == Data([0x01] + Array(repeating: UInt8(0x02), count: 31)))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension OpenSSHSignatureWriterTests {
|
|
||||||
|
|
||||||
enum Constants {
|
|
||||||
static let ecdsa256Secret = TestSecret(
|
|
||||||
id: Data(),
|
|
||||||
name: "Test Key (ECDSA 256)",
|
|
||||||
publicKey: Data(repeating: 0x01, count: 65),
|
|
||||||
attributes: Attributes(
|
|
||||||
keyType: KeyType(algorithm: .ecdsa, size: 256),
|
|
||||||
authentication: .notRequired,
|
|
||||||
publicKeyAttribution: "test@example.com"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ParseError: Error {
|
|
||||||
case eof
|
|
||||||
case invalidAlgorithm
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEcdsaSignatureMpints(from openSSHSignedData: Data) throws -> (r: Data, s: Data) {
|
|
||||||
let reader = OpenSSHReader(data: openSSHSignedData)
|
|
||||||
|
|
||||||
// Prefix
|
|
||||||
_ = try reader.readNextBytes(as: UInt32.self)
|
|
||||||
|
|
||||||
let algorithm = try reader.readNextChunkAsString()
|
|
||||||
guard algorithm == "ecdsa-sha2-nistp256" else {
|
|
||||||
throw ParseError.invalidAlgorithm
|
|
||||||
}
|
|
||||||
|
|
||||||
let sigReader = try reader.readNextChunkAsSubReader()
|
|
||||||
let r = try sigReader.readNextChunk()
|
|
||||||
let s = try sigReader.readNextChunk()
|
|
||||||
return (r, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
public struct TestSecret: SecretKit.Secret {
|
|
||||||
|
|
||||||
public let id: Data
|
|
||||||
public let name: String
|
|
||||||
public let publicKey: Data
|
|
||||||
public var attributes: Attributes
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
@testable import SSHProtocolKit
|
|
||||||
@testable import SecretKit
|
@testable import SecretKit
|
||||||
@testable import SecretAgentKit
|
@testable import SecretAgentKit
|
||||||
|
|
||||||
@@ -45,8 +44,8 @@ import CryptoKit
|
|||||||
let agent = Agent(storeList: list)
|
let agent = Agent(storeList: list)
|
||||||
let response = await agent.handle(request: request, provenance: .test)
|
let response = await agent.handle(request: request, provenance: .test)
|
||||||
let responseReader = OpenSSHReader(data: response)
|
let responseReader = OpenSSHReader(data: response)
|
||||||
let length = try responseReader.readNextBytes(as: UInt32.self)
|
let length = try responseReader.readNextBytes(as: UInt32.self).bigEndian
|
||||||
let type = try responseReader.readNextBytes(as: UInt8.self)
|
let type = try responseReader.readNextBytes(as: UInt8.self).bigEndian
|
||||||
#expect(length == response.count - MemoryLayout<UInt32>.size)
|
#expect(length == response.count - MemoryLayout<UInt32>.size)
|
||||||
#expect(type == SSHAgent.Response.agentSignResponse.rawValue)
|
#expect(type == SSHAgent.Response.agentSignResponse.rawValue)
|
||||||
let outer = OpenSSHReader(data: responseReader.remaining)
|
let outer = OpenSSHReader(data: responseReader.remaining)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
import SSHProtocolKit
|
@testable import SecretAgentKit
|
||||||
|
@testable import SecureEnclaveSecretKit
|
||||||
|
@testable import SmartCardSecretKit
|
||||||
|
|
||||||
@Suite struct OpenSSHReaderTests {
|
@Suite struct OpenSSHReaderTests {
|
||||||
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import SSHProtocolKit
|
|
||||||
|
|
||||||
struct Stub {}
|
struct Stub {}
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ extension Stub {
|
|||||||
print("Public Key OpenSSH: \(OpenSSHPublicKeyWriter().openSSHString(secret: secret))")
|
print("Public Key OpenSSH: \(OpenSSHPublicKeyWriter().openSSHString(secret: secret))")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data {
|
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
guard !shouldThrow else {
|
guard !shouldThrow else {
|
||||||
throw NSError(domain: "test", code: 0, userInfo: nil)
|
throw NSError(domain: "test", code: 0, userInfo: nil)
|
||||||
}
|
}
|
||||||
@@ -57,7 +56,7 @@ extension Stub {
|
|||||||
return try privateKey.signature(for: data).rawRepresentation
|
return try privateKey.signature(for: data).rawRepresentation
|
||||||
}
|
}
|
||||||
|
|
||||||
public func existingAuthenticationContextProtocol(secret: Stub.Secret) -> AuthenticationContextProtocol? {
|
public func existingPersistedAuthenticationContext(secret: Stub.Secret) -> PersistedAuthenticationContext? {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
@testable import SecretKit
|
@testable import SecretKit
|
||||||
import SSHProtocolKit
|
@testable import SecureEnclaveSecretKit
|
||||||
|
@testable import SmartCardSecretKit
|
||||||
|
|
||||||
@Suite struct OpenSSHPublicKeyWriterTests {
|
@Suite struct OpenSSHPublicKeyWriterTests {
|
||||||
|
|
||||||
@@ -46,8 +47,8 @@ import SSHProtocolKit
|
|||||||
extension OpenSSHPublicKeyWriterTests {
|
extension OpenSSHPublicKeyWriterTests {
|
||||||
|
|
||||||
enum Constants {
|
enum Constants {
|
||||||
static let ecdsa256Secret = TestSecret(id: Data(), name: "Test Key (ECDSA 256)", publicKey: Data(base64Encoded: "BOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 256), authentication: .notRequired, publicKeyAttribution: "test@example.com"))
|
static let ecdsa256Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 256)", publicKey: Data(base64Encoded: "BOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 256), authentication: .notRequired, publicKeyAttribution: "test@example.com"))
|
||||||
static let ecdsa384Secret = TestSecret(id: Data(), name: "Test Key (ECDSA 384)", publicKey: Data(base64Encoded: "BG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 384), authentication: .notRequired, publicKeyAttribution: "test@example.com"))
|
static let ecdsa384Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 384)", publicKey: Data(base64Encoded: "BG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 384), authentication: .notRequired, publicKeyAttribution: "test@example.com"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,6 @@ import SmartCardSecretKit
|
|||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import Brief
|
import Brief
|
||||||
import Observation
|
import Observation
|
||||||
import Common
|
|
||||||
|
|
||||||
@main
|
@main
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
@@ -22,13 +21,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}()
|
}()
|
||||||
private let updater = Updater(checkOnLaunch: true)
|
private let updater = Updater(checkOnLaunch: true)
|
||||||
private let notifier = Notifier()
|
private let notifier = Notifier()
|
||||||
private let authenticationHandler = AuthenticationHandler()
|
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory)
|
||||||
private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
|
|
||||||
private lazy var agent: Agent = {
|
private lazy var agent: Agent = {
|
||||||
Agent(storeList: storeList, authenticationHandler: authenticationHandler, witness: notifier)
|
Agent(storeList: storeList, witness: notifier)
|
||||||
}()
|
}()
|
||||||
private lazy var socketController: SocketController = {
|
private lazy var socketController: SocketController = {
|
||||||
let path = URL.socketPath as String
|
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
|
||||||
return SocketController(path: path)
|
return SocketController(path: path)
|
||||||
}()
|
}()
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
|
||||||
@@ -36,14 +34,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
logger.debug("SecretAgent finished launching")
|
logger.debug("SecretAgent finished launching")
|
||||||
Task {
|
Task {
|
||||||
|
let inputParser = try await XPCAgentInputParser()
|
||||||
for await session in socketController.sessions {
|
for await session in socketController.sessions {
|
||||||
Task {
|
Task {
|
||||||
let inputParser = try await XPCAgentInputParser()
|
|
||||||
do {
|
do {
|
||||||
for await message in session.messages {
|
for await message in session.messages {
|
||||||
let request = try await inputParser.parse(data: message)
|
let request = try await inputParser.parse(data: message)
|
||||||
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
||||||
try session.write(agentResponse)
|
try await session.write(agentResponse)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
try session.close()
|
try session.close()
|
||||||
@@ -51,11 +49,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Task { [notifier, authenticationHandler] in
|
|
||||||
await notifier.registerPersistenceHandler {
|
|
||||||
try await authenticationHandler.persistAuthentication(secret: $0, forDuration: $1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Task {
|
Task {
|
||||||
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
||||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import SecretKit
|
|||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import Brief
|
import Brief
|
||||||
|
|
||||||
typealias PersistAction = (@Sendable (AnySecret, TimeInterval) async throws -> Void)
|
|
||||||
|
|
||||||
final class Notifier: Sendable {
|
final class Notifier: Sendable {
|
||||||
|
|
||||||
private let notificationDelegate = NotificationDelegate()
|
private let notificationDelegate = NotificationDelegate()
|
||||||
@@ -17,12 +15,6 @@ final class Notifier: Sendable {
|
|||||||
let updateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction, ignoreAction], intentIdentifiers: [], options: [])
|
let updateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction, ignoreAction], intentIdentifiers: [], options: [])
|
||||||
let criticalUpdateCategory = UNNotificationCategory(identifier: Constants.criticalUpdateCategoryIdentitifier, actions: [updateAction], intentIdentifiers: [], options: [])
|
let criticalUpdateCategory = UNNotificationCategory(identifier: Constants.criticalUpdateCategoryIdentitifier, actions: [updateAction], intentIdentifiers: [], options: [])
|
||||||
|
|
||||||
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory])
|
|
||||||
UNUserNotificationCenter.current().delegate = notificationDelegate
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerPersistenceHandler(action: @escaping PersistAction) async {
|
|
||||||
let rawDurations = [
|
let rawDurations = [
|
||||||
Measurement(value: 1, unit: UnitDuration.minutes),
|
Measurement(value: 1, unit: UnitDuration.minutes),
|
||||||
Measurement(value: 5, unit: UnitDuration.minutes),
|
Measurement(value: 5, unit: UnitDuration.minutes),
|
||||||
@@ -32,9 +24,11 @@ final class Notifier: Sendable {
|
|||||||
|
|
||||||
let doNotPersistAction = UNNotificationAction(identifier: Constants.doNotPersistActionIdentitifier, title: String(localized: .persistAuthenticationDeclineButton), options: [])
|
let doNotPersistAction = UNNotificationAction(identifier: Constants.doNotPersistActionIdentitifier, title: String(localized: .persistAuthenticationDeclineButton), options: [])
|
||||||
var allPersistenceActions = [doNotPersistAction]
|
var allPersistenceActions = [doNotPersistAction]
|
||||||
|
|
||||||
let formatter = DateComponentsFormatter()
|
let formatter = DateComponentsFormatter()
|
||||||
formatter.unitsStyle = .spellOut
|
formatter.unitsStyle = .spellOut
|
||||||
formatter.allowedUnits = [.hour, .minute, .day]
|
formatter.allowedUnits = [.hour, .minute, .day]
|
||||||
|
|
||||||
var identifiers: [String: TimeInterval] = [:]
|
var identifiers: [String: TimeInterval] = [:]
|
||||||
for duration in rawDurations {
|
for duration in rawDurations {
|
||||||
let seconds = duration.converted(to: .seconds).value
|
let seconds = duration.converted(to: .seconds).value
|
||||||
@@ -49,11 +43,16 @@ final class Notifier: Sendable {
|
|||||||
if persistAuthenticationCategory.responds(to: Selector(("actionsMenuTitle"))) {
|
if persistAuthenticationCategory.responds(to: Selector(("actionsMenuTitle"))) {
|
||||||
persistAuthenticationCategory.setValue(String(localized: .persistAuthenticationAcceptButton), forKey: "_actionsMenuTitle")
|
persistAuthenticationCategory.setValue(String(localized: .persistAuthenticationAcceptButton), forKey: "_actionsMenuTitle")
|
||||||
}
|
}
|
||||||
var categories = await UNUserNotificationCenter.current().notificationCategories()
|
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory, persistAuthenticationCategory])
|
||||||
categories.insert(persistAuthenticationCategory)
|
UNUserNotificationCenter.current().delegate = notificationDelegate
|
||||||
UNUserNotificationCenter.current().setNotificationCategories(categories)
|
|
||||||
|
Task {
|
||||||
|
await notificationDelegate.state.setPersistenceState(options: identifiers) { secret, store, duration in
|
||||||
|
guard let duration = duration else { return }
|
||||||
|
try? await store.persistAuthentication(secret: secret, forDuration: duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await notificationDelegate.state.setPersistenceState(options: identifiers, action: action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func prompt() {
|
func prompt() {
|
||||||
@@ -61,7 +60,7 @@ final class Notifier: Sendable {
|
|||||||
notificationCenter.requestAuthorization(options: .alert) { _, _ in }
|
notificationCenter.requestAuthorization(options: .alert) { _, _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, offerPersistence: Bool) async {
|
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async {
|
||||||
await notificationDelegate.state.setPending(secret: secret, store: store)
|
await notificationDelegate.state.setPending(secret: secret, store: store)
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
@@ -70,7 +69,7 @@ final class Notifier: Sendable {
|
|||||||
notificationContent.userInfo[Constants.persistSecretIDKey] = secret.id.description
|
notificationContent.userInfo[Constants.persistSecretIDKey] = secret.id.description
|
||||||
notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description
|
notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description
|
||||||
notificationContent.interruptionLevel = .timeSensitive
|
notificationContent.interruptionLevel = .timeSensitive
|
||||||
if offerPersistence {
|
if await store.existingPersistedAuthenticationContext(secret: secret) == nil && secret.authenticationRequirement.required {
|
||||||
notificationContent.categoryIdentifier = Constants.persistAuthenticationCategoryIdentitifier
|
notificationContent.categoryIdentifier = Constants.persistAuthenticationCategoryIdentitifier
|
||||||
}
|
}
|
||||||
if let iconURL = provenance.origin.iconURL, let attachment = try? UNNotificationAttachment(identifier: "icon", url: iconURL, options: nil) {
|
if let iconURL = provenance.origin.iconURL, let attachment = try? UNNotificationAttachment(identifier: "icon", url: iconURL, options: nil) {
|
||||||
@@ -104,8 +103,8 @@ extension Notifier: SigningWitness {
|
|||||||
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws {
|
func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws {
|
||||||
}
|
}
|
||||||
|
|
||||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, offerPersistence: Bool) async throws {
|
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws {
|
||||||
await notify(accessTo: secret, from: store, by: provenance, offerPersistence: offerPersistence)
|
await notify(accessTo: secret, from: store, by: provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -134,24 +133,28 @@ extension Notifier {
|
|||||||
final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, Sendable {
|
final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, Sendable {
|
||||||
|
|
||||||
fileprivate actor State {
|
fileprivate actor State {
|
||||||
|
typealias PersistAction = (@Sendable (AnySecret, AnySecretStore, TimeInterval?) async -> Void)
|
||||||
typealias IgnoreAction = (@Sendable (Release) async -> Void)
|
typealias IgnoreAction = (@Sendable (Release) async -> Void)
|
||||||
fileprivate var release: Release?
|
fileprivate var release: Release?
|
||||||
fileprivate var ignoreAction: IgnoreAction?
|
fileprivate var ignoreAction: IgnoreAction?
|
||||||
fileprivate var persistAction: PersistAction?
|
fileprivate var persistAction: PersistAction?
|
||||||
fileprivate var persistOptions: [String: TimeInterval] = [:]
|
fileprivate var persistOptions: [String: TimeInterval] = [:]
|
||||||
|
fileprivate var pendingPersistableStores: [String: AnySecretStore] = [:]
|
||||||
fileprivate var pendingPersistableSecrets: [String: AnySecret] = [:]
|
fileprivate var pendingPersistableSecrets: [String: AnySecret] = [:]
|
||||||
|
|
||||||
func setPending(secret: AnySecret, store: AnySecretStore) {
|
func setPending(secret: AnySecret, store: AnySecretStore) {
|
||||||
pendingPersistableSecrets[secret.id.description] = secret
|
pendingPersistableSecrets[secret.id.description] = secret
|
||||||
|
pendingPersistableStores[store.id.description] = store
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrievePending(secretID: String, optionID: String) -> (AnySecret, TimeInterval)? {
|
func retrievePending(secretID: String, storeID: String, optionID: String) -> (AnySecret, AnySecretStore, TimeInterval)? {
|
||||||
guard let secret = pendingPersistableSecrets[secretID],
|
guard let secret = pendingPersistableSecrets[secretID],
|
||||||
|
let store = pendingPersistableStores[storeID],
|
||||||
let options = persistOptions[optionID] else {
|
let options = persistOptions[optionID] else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
pendingPersistableSecrets.removeValue(forKey: secretID)
|
pendingPersistableSecrets.removeValue(forKey: secretID)
|
||||||
return (secret, options)
|
return (secret, store, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setPersistenceState(options: [String: TimeInterval], action: @escaping PersistAction) {
|
func setPersistenceState(options: [String: TimeInterval], action: @escaping PersistAction) {
|
||||||
@@ -199,12 +202,13 @@ final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handlePersistAuthenticationResponse(response: UNNotificationResponse) async {
|
func handlePersistAuthenticationResponse(response: UNNotificationResponse) async {
|
||||||
guard let secretID = response.notification.request.content.userInfo[Notifier.Constants.persistSecretIDKey] as? String else {
|
guard let secretID = response.notification.request.content.userInfo[Notifier.Constants.persistSecretIDKey] as? String,
|
||||||
|
let storeID = response.notification.request.content.userInfo[Notifier.Constants.persistStoreIDKey] as? String else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let optionID = response.actionIdentifier
|
let optionID = response.actionIdentifier
|
||||||
guard let (secret, persistOptions) = await state.retrievePending(secretID: secretID, optionID: optionID) else { return }
|
guard let (secret, store, persistOptions) = await state.retrievePending(secretID: secretID, storeID: storeID, optionID: optionID) else { return }
|
||||||
try? await state.persistAction?(secret, persistOptions)
|
await state.persistAction?(secret, store, persistOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,24 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.hardened-process</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations.enable-pure-data</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations.no-tagged-receive</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.dyld-ro</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>com.apple.security.hardened-process.hardened-heap</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.smartcard</key>
|
<key>com.apple.security.smartcard</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
|
||||||
<string>2</string>
|
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>
|
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>
|
||||||
|
|||||||
@@ -2,25 +2,18 @@ import Foundation
|
|||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import Brief
|
import Brief
|
||||||
import XPCWrappers
|
import XPCWrappers
|
||||||
import OSLog
|
|
||||||
import SSHProtocolKit
|
|
||||||
|
|
||||||
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
|
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
|
||||||
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "XPCAgentInputParser")
|
|
||||||
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
|
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
|
||||||
|
|
||||||
public init() async throws {
|
public init() async throws {
|
||||||
logger.debug("Creating XPCAgentInputParser")
|
|
||||||
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
|
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
|
||||||
logger.debug("XPCAgentInputParser is warmed up.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parse(data: Data) async throws -> SSHAgent.Request {
|
public func parse(data: Data) async throws -> SSHAgent.Request {
|
||||||
logger.debug("Parsing input")
|
try await session.send(data)
|
||||||
defer { logger.debug("Parsed input") }
|
|
||||||
return try await session.send(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|||||||
@@ -1,22 +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>com.apple.security.hardened-process</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations.enable-pure-data</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations.no-tagged-receive</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.dyld-ro</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.hardened-heap</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
|
||||||
<string>2</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -2,7 +2,6 @@ import Foundation
|
|||||||
import OSLog
|
import OSLog
|
||||||
import XPCWrappers
|
import XPCWrappers
|
||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import SSHProtocolKit
|
|
||||||
|
|
||||||
final class SecretAgentInputParser: NSObject, XPCProtocol {
|
final class SecretAgentInputParser: NSObject, XPCProtocol {
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; };
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */; };
|
||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
||||||
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 5002C3AA2EEF483300FFAD22 /* XPCWrappers */; };
|
50033AC327813F1700253856 /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50033AC227813F1700253856 /* BundleIDs.swift */; };
|
||||||
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
|
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3A278005E800DF2006 /* SecretKit */; };
|
||||||
5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; };
|
5003EF3D278005F300DF2006 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3C278005F300DF2006 /* Brief */; };
|
||||||
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; };
|
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF3E278005F300DF2006 /* SecretAgentKit */; };
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; };
|
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; };
|
||||||
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; };
|
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; };
|
||||||
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||||
|
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
||||||
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||||
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
|
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
|
||||||
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
@@ -26,18 +27,22 @@
|
|||||||
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
|
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
|
||||||
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; };
|
501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; };
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
||||||
|
504788EC2E680DC800B4556F /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788EB2E680DC400B4556F /* URLs.swift */; };
|
||||||
504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; };
|
504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; };
|
||||||
504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; };
|
504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; };
|
||||||
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
|
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; };
|
||||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
|
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; };
|
||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
||||||
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
||||||
|
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
||||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
|
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
|
||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
|
||||||
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
|
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
|
||||||
|
506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; };
|
||||||
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
||||||
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
50692D282E6FDB8D0043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D242E6FDB8D0043C7BB /* main.swift */; };
|
50692D282E6FDB8D0043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D242E6FDB8D0043C7BB /* main.swift */; };
|
||||||
@@ -61,7 +66,6 @@
|
|||||||
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 */; };
|
||||||
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */; };
|
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */; };
|
||||||
50B832C02F62202A00D2FCB8 /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = 50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */; };
|
|
||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
||||||
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; };
|
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */; };
|
||||||
@@ -69,12 +73,6 @@
|
|||||||
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
||||||
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145B2EDB9CDF00B121F1 /* Common */; };
|
|
||||||
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145D2EDB9CE400B121F1 /* Common */; };
|
|
||||||
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; };
|
|
||||||
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
|
|
||||||
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
|
||||||
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -182,23 +180,24 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSecretView.swift; sourceTree = "<group>"; };
|
2C4A9D2E2636FFD3008CC8E2 /* EditSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSecretView.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>"; };
|
||||||
|
50033AC227813F1700253856 /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = "<group>"; };
|
||||||
5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; };
|
5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = "<group>"; };
|
||||||
500666D02F04786900328939 /* SecretiveUpdater.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretiveUpdater.entitlements; sourceTree = "<group>"; };
|
|
||||||
500666D12F04787200328939 /* SecretAgentInputParser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgentInputParser.entitlements; sourceTree = "<group>"; };
|
|
||||||
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Resources/Localizable.xcstrings; sourceTree = SOURCE_ROOT; };
|
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Resources/Localizable.xcstrings; sourceTree = SOURCE_ROOT; };
|
||||||
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 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
|
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
|
||||||
501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = "<group>"; };
|
501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.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>"; };
|
||||||
|
504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = "<group>"; };
|
||||||
504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = "<group>"; };
|
504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = "<group>"; };
|
||||||
504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = "<group>"; };
|
504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = "<group>"; };
|
||||||
504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = "<group>"; };
|
504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = "<group>"; };
|
||||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = "<group>"; };
|
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.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>"; };
|
||||||
5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = "<group>"; };
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; };
|
||||||
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
50617D8223FCE48E0099B055 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
50617D8223FCE48E0099B055 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
|
50617D8623FCE48E0099B055 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
50617D8923FCE48E0099B055 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
50617D8923FCE48E0099B055 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
50617D8E23FCE48E0099B055 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
50617D8E23FCE48E0099B055 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Secretive.entitlements; sourceTree = "<group>"; };
|
50617D8F23FCE48E0099B055 /* Secretive.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Secretive.entitlements; sourceTree = "<group>"; };
|
||||||
@@ -206,6 +205,7 @@
|
|||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = "<group>"; };
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
|
5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
|
||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
||||||
|
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
||||||
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
||||||
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
||||||
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveUpdater.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveUpdater.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -228,6 +228,7 @@
|
|||||||
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>"; };
|
||||||
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; };
|
||||||
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>"; };
|
||||||
|
50A3B79624026B7600D209EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
50A3B79824026B7600D209EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
50A3B79824026B7600D209EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
50A3B79924026B7600D209EA /* SecretAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgent.entitlements; sourceTree = "<group>"; };
|
50A3B79924026B7600D209EA /* SecretAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgent.entitlements; sourceTree = "<group>"; };
|
||||||
50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsView.swift; sourceTree = "<group>"; };
|
50AE96FF2E5C1A420018C710 /* IntegrationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsView.swift; sourceTree = "<group>"; };
|
||||||
@@ -238,10 +239,6 @@
|
|||||||
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = "<group>"; };
|
|
||||||
50E4C4C22E7765DF00C73783 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
|
||||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
|
|
||||||
F418C9A82F0C57F000E9ADF8 /* OpenSource.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = OpenSource.xcconfig; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -249,7 +246,6 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */,
|
|
||||||
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
|
5003EF3B278005E800DF2006 /* SecretKit in Frameworks */,
|
||||||
501421622781262300BBAA70 /* Brief in Frameworks */,
|
501421622781262300BBAA70 /* Brief in Frameworks */,
|
||||||
5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
|
5003EF5F2780081600DF2006 /* SecureEnclaveSecretKit in Frameworks */,
|
||||||
@@ -270,7 +266,6 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */,
|
|
||||||
50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */,
|
50692E6C2E6FFA510043C7BB /* SecretAgentKit in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -284,23 +279,29 @@
|
|||||||
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */,
|
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */,
|
||||||
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */,
|
5003EF3F278005F300DF2006 /* SecretAgentKit in Frameworks */,
|
||||||
5003EF41278005FA00DF2006 /* SecretKit in Frameworks */,
|
5003EF41278005FA00DF2006 /* SecretKit in Frameworks */,
|
||||||
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
504788ED2E681EB200B4556F /* Modifiers */ = {
|
50033AC427813F1C00253856 /* Helpers */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
50033AC227813F1700253856 /* BundleIDs.swift */,
|
||||||
|
);
|
||||||
|
path = Helpers;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
504788ED2E681EB200B4556F /* Styles */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */,
|
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
||||||
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
||||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
||||||
);
|
);
|
||||||
path = Modifiers;
|
path = Styles;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
504788EE2E681EC300B4556F /* Secrets */ = {
|
504788EE2E681EC300B4556F /* Secrets */ = {
|
||||||
@@ -334,7 +335,6 @@
|
|||||||
504788F02E681F0100B4556F /* Views */ = {
|
504788F02E681F0100B4556F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50E4C4C22E7765DF00C73783 /* AboutView.swift */,
|
|
||||||
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
||||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
||||||
@@ -374,10 +374,12 @@
|
|||||||
50617D8223FCE48E0099B055 /* App.swift */,
|
50617D8223FCE48E0099B055 /* App.swift */,
|
||||||
508A58B0241ED1C40069DC07 /* Views */,
|
508A58B0241ED1C40069DC07 /* Views */,
|
||||||
508A58B1241ED1EA0069DC07 /* Controllers */,
|
508A58B1241ED1EA0069DC07 /* Controllers */,
|
||||||
|
50033AC427813F1C00253856 /* Helpers */,
|
||||||
|
50617D8623FCE48E0099B055 /* Assets.xcassets */,
|
||||||
50617D8E23FCE48E0099B055 /* Info.plist */,
|
50617D8E23FCE48E0099B055 /* Info.plist */,
|
||||||
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
||||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
|
|
||||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
||||||
|
506772C62424784600034DED /* Credits.rtf */,
|
||||||
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
|
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
|
||||||
50617D8823FCE48E0099B055 /* Preview Content */,
|
50617D8823FCE48E0099B055 /* Preview Content */,
|
||||||
);
|
);
|
||||||
@@ -398,7 +400,6 @@
|
|||||||
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */ = {
|
50692D272E6FDB8D0043C7BB /* SecretiveUpdater */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
500666D02F04786900328939 /* SecretiveUpdater.entitlements */,
|
|
||||||
50692D232E6FDB8D0043C7BB /* Info.plist */,
|
50692D232E6FDB8D0043C7BB /* Info.plist */,
|
||||||
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */,
|
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */,
|
||||||
50692D242E6FDB8D0043C7BB /* main.swift */,
|
50692D242E6FDB8D0043C7BB /* main.swift */,
|
||||||
@@ -410,7 +411,6 @@
|
|||||||
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */ = {
|
50692E662E6FF9E20043C7BB /* SecretAgentInputParser */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
500666D12F04787200328939 /* SecretAgentInputParser.entitlements */,
|
|
||||||
50692E622E6FF9E20043C7BB /* Info.plist */,
|
50692E622E6FF9E20043C7BB /* Info.plist */,
|
||||||
50692E632E6FF9E20043C7BB /* main.swift */,
|
50692E632E6FF9E20043C7BB /* main.swift */,
|
||||||
50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */,
|
50692E642E6FF9E20043C7BB /* SecretAgentInputParser.swift */,
|
||||||
@@ -423,7 +423,6 @@
|
|||||||
children = (
|
children = (
|
||||||
508A590F241EEF6D0069DC07 /* Secretive.xctestplan */,
|
508A590F241EEF6D0069DC07 /* Secretive.xctestplan */,
|
||||||
508A58AB241E121B0069DC07 /* Config.xcconfig */,
|
508A58AB241E121B0069DC07 /* Config.xcconfig */,
|
||||||
F418C9A82F0C57F000E9ADF8 /* OpenSource.xcconfig */,
|
|
||||||
);
|
);
|
||||||
path = Config;
|
path = Config;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -433,7 +432,7 @@
|
|||||||
children = (
|
children = (
|
||||||
504788EF2E681ED700B4556F /* Configuration */,
|
504788EF2E681ED700B4556F /* Configuration */,
|
||||||
504788EE2E681EC300B4556F /* Secrets */,
|
504788EE2E681EC300B4556F /* Secrets */,
|
||||||
504788ED2E681EB200B4556F /* Modifiers */,
|
504788ED2E681EB200B4556F /* Styles */,
|
||||||
504788F02E681F0100B4556F /* Views */,
|
504788F02E681F0100B4556F /* Views */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@@ -442,9 +441,11 @@
|
|||||||
508A58B1241ED1EA0069DC07 /* Controllers */ = {
|
508A58B1241ED1EA0069DC07 /* Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
504788EB2E680DC400B4556F /* URLs.swift */,
|
||||||
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
||||||
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
|
5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */,
|
||||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
||||||
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */,
|
||||||
);
|
);
|
||||||
path = Controllers;
|
path = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -506,7 +507,6 @@
|
|||||||
5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */,
|
5003EF5E2780081600DF2006 /* SecureEnclaveSecretKit */,
|
||||||
5003EF602780081600DF2006 /* SmartCardSecretKit */,
|
5003EF602780081600DF2006 /* SmartCardSecretKit */,
|
||||||
501421612781262300BBAA70 /* Brief */,
|
501421612781262300BBAA70 /* Brief */,
|
||||||
50E0145B2EDB9CDF00B121F1 /* Common */,
|
|
||||||
);
|
);
|
||||||
productName = Secretive;
|
productName = Secretive;
|
||||||
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
|
productReference = 50617D7F23FCE48E0099B055 /* Secretive.app */;
|
||||||
@@ -548,7 +548,6 @@
|
|||||||
name = SecretAgentInputParser;
|
name = SecretAgentInputParser;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
|
50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
|
||||||
5002C3AA2EEF483300FFAD22 /* XPCWrappers */,
|
|
||||||
);
|
);
|
||||||
productName = SecretAgentInputParser;
|
productName = SecretAgentInputParser;
|
||||||
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
|
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
|
||||||
@@ -578,7 +577,6 @@
|
|||||||
5003EF40278005FA00DF2006 /* SecretKit */,
|
5003EF40278005FA00DF2006 /* SecretKit */,
|
||||||
5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */,
|
5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */,
|
||||||
5003EF642780081B00DF2006 /* SmartCardSecretKit */,
|
5003EF642780081B00DF2006 /* SmartCardSecretKit */,
|
||||||
50E0145D2EDB9CE400B121F1 /* Common */,
|
|
||||||
);
|
);
|
||||||
productName = SecretAgent;
|
productName = SecretAgent;
|
||||||
productReference = 50A3B78A24026B7500D209EA /* SecretAgent.app */;
|
productReference = 50A3B78A24026B7500D209EA /* SecretAgent.app */;
|
||||||
@@ -592,7 +590,7 @@
|
|||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 2600;
|
LastSwiftUpdateCheck = 2600;
|
||||||
LastUpgradeCheck = 2640;
|
LastUpgradeCheck = 2600;
|
||||||
ORGANIZATIONNAME = "Max Goedjen";
|
ORGANIZATIONNAME = "Max Goedjen";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
50617D7E23FCE48D0099B055 = {
|
50617D7E23FCE48D0099B055 = {
|
||||||
@@ -615,6 +613,7 @@
|
|||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
en,
|
en,
|
||||||
|
Base,
|
||||||
it,
|
it,
|
||||||
fr,
|
fr,
|
||||||
de,
|
de,
|
||||||
@@ -644,8 +643,9 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
|
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
|
||||||
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */,
|
|
||||||
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */,
|
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */,
|
||||||
|
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */,
|
||||||
|
506772C72424784600034DED /* Credits.rtf in Resources */,
|
||||||
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -654,7 +654,6 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
50B832C02F62202A00D2FCB8 /* InternetAccessPolicy.plist in Resources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -672,8 +671,8 @@
|
|||||||
50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
|
50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
|
||||||
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */,
|
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */,
|
||||||
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
|
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
|
||||||
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */,
|
|
||||||
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
||||||
|
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -686,10 +685,9 @@
|
|||||||
files = (
|
files = (
|
||||||
504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
|
504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
|
||||||
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
|
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
|
||||||
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */,
|
|
||||||
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
||||||
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */,
|
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
|
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
||||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
||||||
@@ -699,12 +697,14 @@
|
|||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
||||||
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
||||||
|
50033AC327813F1700253856 /* BundleIDs.swift in Sources */,
|
||||||
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */,
|
50BDCB722E63BAF20072D2E7 /* AgentStatusView.swift in Sources */,
|
||||||
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
||||||
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
||||||
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */,
|
50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */,
|
||||||
50153E20250AFCB200525160 /* UpdateView.swift in Sources */,
|
50153E20250AFCB200525160 /* UpdateView.swift in Sources */,
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */,
|
||||||
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */,
|
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */,
|
||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
||||||
@@ -784,7 +784,7 @@
|
|||||||
50A3B79524026B7600D209EA /* Main.storyboard */ = {
|
50A3B79524026B7600D209EA /* Main.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
5059933F2E7A3B5B0092CFFA /* en */,
|
50A3B79624026B7600D209EA /* Base */,
|
||||||
);
|
);
|
||||||
name = Main.storyboard;
|
name = Main.storyboard;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -948,7 +948,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -971,7 +971,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
};
|
};
|
||||||
@@ -988,7 +988,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Secretive/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -1011,7 +1011,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
|
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
|
||||||
};
|
};
|
||||||
@@ -1021,19 +1021,16 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
|
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
@@ -1048,9 +1045,9 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1067,16 +1064,13 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
@@ -1091,9 +1085,9 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1109,19 +1103,16 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretiveUpdater/SecretiveUpdater.entitlements;
|
|
||||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
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;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
@@ -1136,9 +1127,9 @@
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -1155,25 +1146,22 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
|
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1190,23 +1178,20 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1222,26 +1207,23 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgentInputParser/SecretAgentInputParser.entitlements;
|
|
||||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
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;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
INFOPLIST_FILE = SecretAgentInputParser/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -1362,7 +1344,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@@ -1371,17 +1353,14 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements;
|
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -1398,7 +1377,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@@ -1412,13 +1391,11 @@
|
|||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -1435,7 +1412,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -1450,13 +1427,11 @@
|
|||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"SecretAgent/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = Z72PRUAWF6;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
|
||||||
ENABLE_POINTER_AUTHENTICATION = YES;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
||||||
@@ -1473,7 +1448,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
|
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
|
||||||
};
|
};
|
||||||
@@ -1535,10 +1510,6 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
5002C3AA2EEF483300FFAD22 /* XPCWrappers */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = XPCWrappers;
|
|
||||||
};
|
|
||||||
5003EF3A278005E800DF2006 /* SecretKit */ = {
|
5003EF3A278005E800DF2006 /* SecretKit */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = SecretKit;
|
productName = SecretKit;
|
||||||
@@ -1587,14 +1558,6 @@
|
|||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = SecretAgentKit;
|
productName = SecretAgentKit;
|
||||||
};
|
};
|
||||||
50E0145B2EDB9CDF00B121F1 /* Common */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = Common;
|
|
||||||
};
|
|
||||||
50E0145D2EDB9CE400B121F1 /* Common */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = Common;
|
|
||||||
};
|
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 50617D7723FCE48D0099B055 /* Project object */;
|
rootObject = 50617D7723FCE48D0099B055 /* Project object */;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2640"
|
LastUpgradeVersion = "2600"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -14,8 +14,7 @@
|
|||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<TestPlans>
|
<TestPlans>
|
||||||
<TestPlanReference
|
<TestPlanReference
|
||||||
reference = "container:Config/Secretive.xctestplan"
|
reference = "container:Config/Secretive.xctestplan">
|
||||||
default = "YES">
|
|
||||||
</TestPlanReference>
|
</TestPlanReference>
|
||||||
</TestPlans>
|
</TestPlans>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2640"
|
LastUpgradeVersion = "2600"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2640"
|
LastUpgradeVersion = "2600"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -4,88 +4,6 @@ import SecureEnclaveSecretKit
|
|||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
import Brief
|
import Brief
|
||||||
|
|
||||||
@main
|
|
||||||
struct Secretive: App {
|
|
||||||
|
|
||||||
@Environment(\.agentLaunchController) var agentLaunchController
|
|
||||||
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
|
||||||
|
|
||||||
@SceneBuilder var body: some Scene {
|
|
||||||
WindowGroup {
|
|
||||||
ContentView()
|
|
||||||
.environment(EnvironmentValues._secretStoreList)
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
|
||||||
Task {
|
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
|
||||||
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
|
||||||
guard hasRunSetup && !explicitlyDisabled else { return }
|
|
||||||
agentLaunchController.check()
|
|
||||||
guard !agentLaunchController.developmentBuild else { return }
|
|
||||||
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
|
|
||||||
// Relaunch the agent, since it'll be running from earlier update still
|
|
||||||
try await agentLaunchController.forceLaunch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.commands {
|
|
||||||
AppCommands()
|
|
||||||
}
|
|
||||||
WindowGroup(id: String(describing: IntegrationsView.self)) {
|
|
||||||
IntegrationsView()
|
|
||||||
}
|
|
||||||
.windowResizability(.contentMinSize)
|
|
||||||
WindowGroup(id: String(describing: AboutView.self)) {
|
|
||||||
AboutView()
|
|
||||||
}
|
|
||||||
.windowStyle(.hiddenTitleBar)
|
|
||||||
.windowResizability(.contentSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Secretive {
|
|
||||||
|
|
||||||
struct AppCommands: Commands {
|
|
||||||
|
|
||||||
@Environment(\.openWindow) var openWindow
|
|
||||||
@Environment(\.openURL) var openURL
|
|
||||||
@FocusedValue(\.showCreateSecret) var showCreateSecret
|
|
||||||
|
|
||||||
var body: some Commands {
|
|
||||||
CommandGroup(replacing: .appInfo) {
|
|
||||||
Button(.aboutMenuBarTitle, systemImage: "info.circle") {
|
|
||||||
openWindow(id: String(describing: AboutView.self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandGroup(before: CommandGroupPlacement.appSettings) {
|
|
||||||
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
|
|
||||||
openWindow(id: String(describing: IntegrationsView.self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandGroup(after: CommandGroupPlacement.newItem) {
|
|
||||||
Button(.appMenuNewSecretButton, systemImage: "plus") {
|
|
||||||
showCreateSecret?()
|
|
||||||
}
|
|
||||||
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
|
|
||||||
.disabled(showCreateSecret?.isEnabled == false)
|
|
||||||
}
|
|
||||||
CommandGroup(replacing: .help) {
|
|
||||||
Button(.appMenuHelpButton) {
|
|
||||||
openURL(Constants.helpURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SidebarCommands()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Constants {
|
|
||||||
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension EnvironmentValues {
|
extension EnvironmentValues {
|
||||||
|
|
||||||
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
||||||
@@ -99,8 +17,8 @@ extension EnvironmentValues {
|
|||||||
return list
|
return list
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private static let _agentLaunchController = AgentLaunchController()
|
private static let _agentStatusChecker = AgentStatusChecker()
|
||||||
@Entry var agentLaunchController: any AgentLaunchControllerProtocol = _agentLaunchController
|
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
||||||
private static let _updater: any UpdaterProtocol = {
|
private static let _updater: any UpdaterProtocol = {
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
return Updater(checkOnLaunch: hasRunSetup)
|
return Updater(checkOnLaunch: hasRunSetup)
|
||||||
@@ -115,22 +33,88 @@ extension EnvironmentValues {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FocusedValues {
|
@main
|
||||||
@Entry var showCreateSecret: OpenSheet?
|
struct Secretive: App {
|
||||||
}
|
|
||||||
|
@Environment(\.agentStatusChecker) var agentStatusChecker
|
||||||
|
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||||
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
|
@State private var showingSetup = false
|
||||||
|
@State private var showingIntegrations = false
|
||||||
|
@State private var showingCreation = false
|
||||||
|
|
||||||
final class OpenSheet {
|
@SceneBuilder var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
let closure: () -> Void
|
ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup)
|
||||||
let isEnabled: Bool
|
.environment(EnvironmentValues._secretStoreList)
|
||||||
|
.onAppear {
|
||||||
init(isEnabled: Bool = true, closure: @escaping () -> Void) {
|
if !hasRunSetup {
|
||||||
self.isEnabled = isEnabled
|
showingSetup = true
|
||||||
self.closure = closure
|
}
|
||||||
}
|
}
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||||
func callAsFunction() {
|
guard hasRunSetup else { return }
|
||||||
closure()
|
agentStatusChecker.check()
|
||||||
|
if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild {
|
||||||
|
// Relaunch the agent, since it'll be running from earlier update still
|
||||||
|
reinstallAgent()
|
||||||
|
} else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild {
|
||||||
|
forceLaunchAgent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showingIntegrations) {
|
||||||
|
IntegrationsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.commands {
|
||||||
|
CommandGroup(before: CommandGroupPlacement.appSettings) {
|
||||||
|
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
|
||||||
|
showingIntegrations = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandGroup(after: CommandGroupPlacement.newItem) {
|
||||||
|
Button(.appMenuNewSecretButton) {
|
||||||
|
showingCreation = true
|
||||||
|
}
|
||||||
|
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
|
||||||
|
}
|
||||||
|
CommandGroup(replacing: .help) {
|
||||||
|
Button(.appMenuHelpButton) {
|
||||||
|
NSWorkspace.shared.open(Constants.helpURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SidebarCommands()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Secretive {
|
||||||
|
|
||||||
|
private func reinstallAgent() {
|
||||||
|
Task {
|
||||||
|
_ = await LaunchAgentController().install()
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
agentStatusChecker.check()
|
||||||
|
if !agentStatusChecker.running {
|
||||||
|
forceLaunchAgent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func forceLaunchAgent() {
|
||||||
|
// We've run setup, we didn't just update, launchd is just not doing it's thing.
|
||||||
|
// Force a launch directly.
|
||||||
|
Task {
|
||||||
|
_ = await LaunchAgentController().forceLaunch()
|
||||||
|
agentStatusChecker.check()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-16x16@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-16x16@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-32x32@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-32x32@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-128x128@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-128x128@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-256x256@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-256x256@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-512x512@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Icon-macOS-ClearDark-1024x1024@1x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 856 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 356 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 356 KiB |
6
Sources/Secretive/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,26 +2,18 @@ import Foundation
|
|||||||
import AppKit
|
import AppKit
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
import ServiceManagement
|
|
||||||
import Common
|
|
||||||
|
|
||||||
@MainActor protocol AgentLaunchControllerProtocol: Observable, Sendable {
|
@MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable {
|
||||||
var running: Bool { get }
|
var running: Bool { get }
|
||||||
var developmentBuild: Bool { get }
|
var developmentBuild: Bool { get }
|
||||||
var process: NSRunningApplication? { get }
|
var process: NSRunningApplication? { get }
|
||||||
func check()
|
func check()
|
||||||
func install() async throws
|
|
||||||
func uninstall() async throws
|
|
||||||
func forceLaunch() async throws
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Observable @MainActor final class AgentLaunchController: AgentLaunchControllerProtocol {
|
@Observable @MainActor final class AgentStatusChecker: AgentStatusCheckerProtocol {
|
||||||
|
|
||||||
var running: Bool = false
|
var running: Bool = false
|
||||||
var process: NSRunningApplication? = nil
|
var process: NSRunningApplication? = nil
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
|
|
||||||
private let service = SMAppService.loginItem(identifier: Bundle.agentBundleID)
|
|
||||||
|
|
||||||
nonisolated init() {
|
nonisolated init() {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
@@ -41,7 +33,7 @@ import Common
|
|||||||
|
|
||||||
// The process corresponding to this instance of Secretive
|
// The process corresponding to this instance of Secretive
|
||||||
var instanceSecretAgentProcess: NSRunningApplication? {
|
var instanceSecretAgentProcess: NSRunningApplication? {
|
||||||
// TODO: CHECK VERSION
|
// FIXME: CHECK VERSION
|
||||||
let agents = allSecretAgentProcesses
|
let agents = allSecretAgentProcesses
|
||||||
for agent in agents {
|
for agent in agents {
|
||||||
guard let url = agent.bundleURL else { continue }
|
guard let url = agent.bundleURL else { continue }
|
||||||
@@ -57,47 +49,6 @@ import Common
|
|||||||
Bundle.main.bundleURL.isXcodeURL
|
Bundle.main.bundleURL.isXcodeURL
|
||||||
}
|
}
|
||||||
|
|
||||||
func install() async throws {
|
|
||||||
logger.debug("Installing agent")
|
|
||||||
try? await service.unregister()
|
|
||||||
// This is definitely a bit of a "seems to work better" thing but:
|
|
||||||
// Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old
|
|
||||||
// and start new?
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
try service.register()
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func uninstall() async throws {
|
|
||||||
logger.debug("Uninstalling agent")
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
try await service.unregister()
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func forceLaunch() async throws {
|
|
||||||
logger.debug("Agent is not running, attempting to force launch by reinstalling")
|
|
||||||
try await install()
|
|
||||||
if running {
|
|
||||||
logger.debug("Agent successfully force launched by reinstalling")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("Agent is not running, attempting to force launch by launching directly")
|
|
||||||
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
|
||||||
let config = NSWorkspace.OpenConfiguration()
|
|
||||||
config.activates = false
|
|
||||||
do {
|
|
||||||
try await NSWorkspace.shared.openApplication(at: url, configuration: config)
|
|
||||||
logger.debug("Agent force launched")
|
|
||||||
try await Task.sleep(for: .seconds(1))
|
|
||||||
} catch {
|
|
||||||
logger.error("Error force launching \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension URL {
|
extension URL {
|
||||||
|
|||||||
65
Sources/Secretive/Controllers/LaunchAgentController.swift
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import Foundation
|
||||||
|
import ServiceManagement
|
||||||
|
import AppKit
|
||||||
|
import OSLog
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
struct LaunchAgentController {
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive", category: "LaunchAgentController")
|
||||||
|
|
||||||
|
func install() async -> Bool {
|
||||||
|
logger.debug("Installing agent")
|
||||||
|
_ = setEnabled(false)
|
||||||
|
// This is definitely a bit of a "seems to work better" thing but:
|
||||||
|
// Seems to more reliably hit if these are on separate runloops, otherwise it seems like it sometimes doesn't kill old
|
||||||
|
// and start new?
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
let result = await MainActor.run {
|
||||||
|
setEnabled(true)
|
||||||
|
}
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstall() async -> Bool {
|
||||||
|
logger.debug("Uninstalling agent")
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
let result = await MainActor.run {
|
||||||
|
setEnabled(false)
|
||||||
|
}
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func forceLaunch() async -> Bool {
|
||||||
|
logger.debug("Agent is not running, attempting to force launch")
|
||||||
|
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
||||||
|
let config = NSWorkspace.OpenConfiguration()
|
||||||
|
config.activates = false
|
||||||
|
do {
|
||||||
|
try await NSWorkspace.shared.openApplication(at: url, configuration: config)
|
||||||
|
logger.debug("Agent force launched")
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
logger.error("Error force launching \(error.localizedDescription)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setEnabled(_ enabled: Bool) -> Bool {
|
||||||
|
let service = SMAppService.loginItem(identifier: Bundle.agentBundleID)
|
||||||
|
do {
|
||||||
|
if enabled {
|
||||||
|
try service.register()
|
||||||
|
} else {
|
||||||
|
try service.unregister()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
Sources/Secretive/Controllers/URLs.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
|
||||||
|
static var agentHomeURL: URL {
|
||||||
|
URL(fileURLWithPath: URL.homeDirectory.path().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID))
|
||||||
|
}
|
||||||
|
|
||||||
|
static var socketPath: String {
|
||||||
|
URL.agentHomeURL.appendingPathComponent("socket.ssh").path()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
|
||||||
|
var normalizedPathAndFolder: (String, String) {
|
||||||
|
// All foundation-based normalization methods replace this with the container directly.
|
||||||
|
let processedPath = replacingOccurrences(of: "~", with: "/Users/\(NSUserName())")
|
||||||
|
let url = URL(filePath: processedPath)
|
||||||
|
let folder = url.deletingLastPathComponent().path()
|
||||||
|
return (processedPath, folder)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
Sources/Secretive/Credits.rtf
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{\rtf1\ansi\ansicpg1252\cocoartf2580
|
||||||
|
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
|
{\colortbl;\red255\green255\blue255;}
|
||||||
|
{\*\expandedcolortbl;;}
|
||||||
|
\margl1440\margr1440\vieww9000\viewh8400\viewkind0
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6119\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive"}}{\fldrslt
|
||||||
|
\f0\fs24 \cf0 GitHub Repository}}
|
||||||
|
\f0\fs24 \
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
\cf0 \
|
||||||
|
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
||||||
|
\
|
||||||
|
Special Thanks To:\
|
||||||
|
\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/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/danielctull"}}{\fldrslt Daniel Tull}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/esttorhe"}}{\fldrslt Esteban Torres}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/joeblau"}}{\fldrslt Joe Blau}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/mergesort"}}{\fldrslt Joe Fabisevich}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
||||||
@@ -20,8 +20,6 @@
|
|||||||
<string>$(CI_VERSION)</string>
|
<string>$(CI_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CI_BUILD_NUMBER)</string>
|
<string>$(CI_BUILD_NUMBER)</string>
|
||||||
<key>GitHubBuildLog</key>
|
|
||||||
<string>https://$(CI_BUILD_LINK)</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
class PreviewAgentLaunchController: AgentLaunchControllerProtocol {
|
class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
|
||||||
|
|
||||||
let running: Bool
|
let running: Bool
|
||||||
let process: NSRunningApplication?
|
let process: NSRunningApplication?
|
||||||
@@ -15,13 +15,4 @@ class PreviewAgentLaunchController: AgentLaunchControllerProtocol {
|
|||||||
func check() {
|
func check() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func install() async throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
func uninstall() async throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
func forceLaunch() async throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ extension Preview {
|
|||||||
self.init(secrets: new)
|
self.init(secrets: new)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data {
|
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? {
|
func existingPersistedAuthenticationContext(secret: Preview.Secret) -> PersistedAuthenticationContext? {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,17 +60,16 @@ extension Preview {
|
|||||||
let id = UUID()
|
let id = UUID()
|
||||||
var name: String { "Modifiable Preview Store" }
|
var name: String { "Modifiable Preview Store" }
|
||||||
let secrets: [Secret]
|
let secrets: [Secret]
|
||||||
var supportedKeyTypes: KeyAvailability {
|
var supportedKeyTypes: [KeyType] {
|
||||||
return KeyAvailability(
|
if #available(macOS 26, *) {
|
||||||
available: [
|
[
|
||||||
.ecdsa256,
|
.ecdsa256,
|
||||||
.mldsa65,
|
.mldsa65,
|
||||||
.mldsa87
|
.mldsa87,
|
||||||
],
|
|
||||||
unavailable: [
|
|
||||||
.init(keyType: .ecdsa384, reason: .macOSUpdateRequired)
|
|
||||||
]
|
]
|
||||||
)
|
} else {
|
||||||
|
[.ecdsa256]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(secrets: [Secret]) {
|
init(secrets: [Secret]) {
|
||||||
@@ -82,11 +81,11 @@ extension Preview {
|
|||||||
self.init(secrets: new)
|
self.init(secrets: new)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data {
|
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? {
|
func existingPersistedAuthenticationContext(secret: Preview.Secret) -> PersistedAuthenticationContext? {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,16 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.hardened-process</key>
|
<key>com.apple.security.hardened-process</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.hardened-process.checked-allocations</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations.enable-pure-data</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.checked-allocations.no-tagged-receive</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.hardened-process.dyld-ro</key>
|
<key>com.apple.security.hardened-process.dyld-ro</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.hardened-process.enhanced-security-version</key>
|
||||||
|
<integer>1</integer>
|
||||||
<key>com.apple.security.hardened-process.hardened-heap</key>
|
<key>com.apple.security.hardened-process.hardened-heap</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
|
<key>com.apple.security.hardened-process.platform-restrictions</key>
|
||||||
<string>1</string>
|
<integer>2</integer>
|
||||||
<key>com.apple.security.smartcard</key>
|
<key>com.apple.security.smartcard</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
|
||||||
<string>2</string>
|
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>
|
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ struct ConfigurationItemView<Content: View>: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
switch action {
|
switch action {
|
||||||
case .copy(let string):
|
case .copy(let string):
|
||||||
Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
|
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
||||||
NSPasteboard.general.declareTypes([.string], owner: nil)
|
NSPasteboard.general.declareTypes([.string], owner: nil)
|
||||||
NSPasteboard.general.setString(string, forType: .string)
|
NSPasteboard.general.setString(string, forType: .string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,47 @@ struct IntegrationsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} detail: {
|
} detail: {
|
||||||
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
||||||
|
.fauxToolbar {
|
||||||
|
Button(.setupDoneButton) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
.normalButton()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.toolbar {
|
|
||||||
Button(.setupDoneButton) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.hiddenToolbar()
|
|
||||||
.windowBackgroundStyle(.thinMaterial)
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
selectedInstruction = instructions.gettingStarted
|
selectedInstruction = instructions.gettingStarted
|
||||||
}
|
}
|
||||||
.frame(minWidth: 400, minHeight: 400)
|
.frame(minHeight: 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
func fauxToolbar<Content: View>(content: () -> Content) -> some View {
|
||||||
|
modifier(FauxToolbarModifier(toolbarContent: content()))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FauxToolbarModifier<ToolbarContent: View>: ViewModifier {
|
||||||
|
|
||||||
|
var toolbarContent: ToolbarContent
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
content
|
||||||
|
Divider()
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
toolbarContent
|
||||||
|
.padding(.top, 8)
|
||||||
|
.padding(.trailing, 16)
|
||||||
|
.padding(.bottom, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import SwiftUI
|
|||||||
struct SetupView: View {
|
struct SetupView: View {
|
||||||
|
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@Environment(\.agentLaunchController) private var agentLaunchController
|
|
||||||
@Binding var setupComplete: Bool
|
@Binding var setupComplete: Bool
|
||||||
|
|
||||||
@State var showingIntegrations = false
|
@State var showingIntegrations = false
|
||||||
@@ -32,7 +31,7 @@ struct SetupView: View {
|
|||||||
) {
|
) {
|
||||||
installed = true
|
installed = true
|
||||||
Task {
|
Task {
|
||||||
try? await agentLaunchController.install()
|
await LaunchAgentController().install()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,10 +85,7 @@ struct SetupView: View {
|
|||||||
integrations = true
|
integrations = true
|
||||||
}, content: {
|
}, content: {
|
||||||
IntegrationsView()
|
IntegrationsView()
|
||||||
.frame(minWidth: 500, minHeight: 400)
|
|
||||||
})
|
})
|
||||||
.frame(idealWidth: 600)
|
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +172,10 @@ struct StepView<Content: View>: View {
|
|||||||
.frame(width: 20)
|
.frame(width: 20)
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(title)
|
Text(title)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
.bold()
|
.bold()
|
||||||
Text(description)
|
Text(description)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
if let detail {
|
if let detail {
|
||||||
Text(detail)
|
Text(detail)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.italic()
|
.italic()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import SSHProtocolKit
|
|
||||||
import Common
|
|
||||||
|
|
||||||
struct ToolConfigurationView: View {
|
struct ToolConfigurationView: View {
|
||||||
|
|
||||||
@@ -12,7 +10,6 @@ struct ToolConfigurationView: View {
|
|||||||
|
|
||||||
@State var creating = false
|
@State var creating = false
|
||||||
@State var selectedSecret: AnySecret?
|
@State var selectedSecret: AnySecret?
|
||||||
@State var email = ""
|
|
||||||
|
|
||||||
init(selectedInstruction: ConfigurationFileInstructions) {
|
init(selectedInstruction: ConfigurationFileInstructions) {
|
||||||
self.selectedInstruction = selectedInstruction
|
self.selectedInstruction = selectedInstruction
|
||||||
@@ -35,7 +32,6 @@ struct ToolConfigurationView: View {
|
|||||||
selectedSecret = created
|
selectedSecret = created
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fixedSize()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,12 +47,6 @@ struct ToolConfigurationView: View {
|
|||||||
.tag(secret)
|
.tag(secret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextField(text: $email, prompt: Text(.integrationsConfigureUsingEmailPlaceholder)) {
|
|
||||||
Text(.integrationsConfigureUsingEmailTitle)
|
|
||||||
Text(.integrationsConfigureUsingEmailSubtitle)
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
} header: {
|
} header: {
|
||||||
Text(.integrationsConfigureUsingSecretHeader)
|
Text(.integrationsConfigureUsingSecretHeader)
|
||||||
}
|
}
|
||||||
@@ -69,7 +59,7 @@ struct ToolConfigurationView: View {
|
|||||||
Section {
|
Section {
|
||||||
ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path))
|
ConfigurationItemView(title: .integrationsPathTitle, value: stepGroup.path, action: .revealInFinder(stepGroup.path))
|
||||||
ForEach(stepGroup.steps, id: \.self.key) { step in
|
ForEach(stepGroup.steps, id: \.self.key) { step in
|
||||||
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(placeholdersReplaced(text: String(localized: step)))) {
|
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(String(localized: step))) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(placeholdersReplaced(text: String(localized: step)))
|
Text(placeholdersReplaced(text: String(localized: step)))
|
||||||
.padding(8)
|
.padding(8)
|
||||||
@@ -111,11 +101,10 @@ struct ToolConfigurationView: View {
|
|||||||
func placeholdersReplaced(text: String) -> String {
|
func placeholdersReplaced(text: String) -> String {
|
||||||
guard let selectedSecret else { return text }
|
guard let selectedSecret else { return text }
|
||||||
let writer = OpenSSHPublicKeyWriter()
|
let writer = OpenSSHPublicKeyWriter()
|
||||||
let gitAllowedSignersString = [email.isEmpty ? String(localized: .integrationsConfigureUsingEmailPlaceholder) : email, writer.openSSHString(secret: selectedSecret)]
|
let fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
|
||||||
.joined(separator: " ")
|
|
||||||
return text
|
return text
|
||||||
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: gitAllowedSignersString)
|
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: writer.openSSHString(secret: selectedSecret))
|
||||||
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: URL.publicKeyPath(for: selectedSecret, in: URL.publicKeyDirectory))
|
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct WindowBackgroundStyleModifier: ViewModifier {
|
|
||||||
|
|
||||||
let shapeStyle: any ShapeStyle
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if #available(macOS 15.0, *) {
|
|
||||||
content
|
|
||||||
.containerBackground(
|
|
||||||
shapeStyle, for: .window
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func windowBackgroundStyle(_ style: some ShapeStyle) -> some View {
|
|
||||||
modifier(WindowBackgroundStyleModifier(shapeStyle: style))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HiddenToolbarModifier: ViewModifier {
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if #available(macOS 15.0, *) {
|
|
||||||
content
|
|
||||||
.toolbarBackgroundVisibility(.hidden, for: .automatic)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func hiddenToolbar() -> some View {
|
|
||||||
modifier(HiddenToolbarModifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -75,23 +75,10 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
Section {
|
Section {
|
||||||
VStack {
|
VStack {
|
||||||
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
||||||
ForEach(store.supportedKeyTypes.available, id: \.self) { option in
|
ForEach(store.supportedKeyTypes, id: \.self) { option in
|
||||||
Text(String(describing: option))
|
Text(String(describing: option))
|
||||||
.tag(option)
|
.tag(option)
|
||||||
}
|
.font(.caption)
|
||||||
Divider()
|
|
||||||
ForEach(store.supportedKeyTypes.unavailable, id: \.keyType) { option in
|
|
||||||
VStack {
|
|
||||||
Button {
|
|
||||||
} label: {
|
|
||||||
Text(String(describing: option.keyType))
|
|
||||||
switch option.reason {
|
|
||||||
case .macOSUpdateRequired:
|
|
||||||
Text(.createSecretKeyTypeMacOSUpdateRequiredLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.selectionDisabled()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if keyType?.algorithm == .mldsa {
|
if keyType?.algorithm == .mldsa {
|
||||||
@@ -132,7 +119,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
keyType = store.supportedKeyTypes.available.first
|
keyType = store.supportedKeyTypes.first
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import Common
|
|
||||||
import SSHProtocolKit
|
|
||||||
|
|
||||||
struct SecretDetailView<SecretType: Secret>: View {
|
struct SecretDetailView<SecretType: Secret>: View {
|
||||||
|
|
||||||
let secret: SecretType
|
let secret: SecretType
|
||||||
|
|
||||||
private let keyWriter = OpenSSHPublicKeyWriter()
|
private let keyWriter = OpenSSHPublicKeyWriter()
|
||||||
|
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -22,7 +21,7 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString)
|
CopyableView(title: .secretDetailPublicKeyLabel, image: Image(systemName: "key"), text: keyString)
|
||||||
Spacer()
|
Spacer()
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory), showRevealInFinder: true)
|
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.publicKeyPath(for: secret), showRevealInFinder: true)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,18 +24,6 @@ struct SecretListItemView: View {
|
|||||||
Text(secret.name)
|
Text(secret.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $isRenaming, onDismiss: {
|
|
||||||
renamedSecret(secret)
|
|
||||||
}, content: {
|
|
||||||
if let modifiable = store as? AnySecretStoreModifiable {
|
|
||||||
EditSecretView(store: modifiable, secret: secret)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.showingDeleteConfirmation(isPresented: $isDeleting, secret, store as? AnySecretStoreModifiable) { deleted in
|
|
||||||
if deleted {
|
|
||||||
deletedSecret(secret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
if store is AnySecretStoreModifiable {
|
if store is AnySecretStoreModifiable {
|
||||||
Button(action: { isRenaming = true }) {
|
Button(action: { isRenaming = true }) {
|
||||||
@@ -48,5 +36,17 @@ struct SecretListItemView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.showingDeleteConfirmation(isPresented: $isDeleting, secret, store as? AnySecretStoreModifiable) { deleted in
|
||||||
|
if deleted {
|
||||||
|
deletedSecret(secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $isRenaming, onDismiss: {
|
||||||
|
renamedSecret(secret)
|
||||||
|
}, content: {
|
||||||
|
if let modifiable = store as? AnySecretStoreModifiable {
|
||||||
|
EditSecretView(store: modifiable, secret: secret)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ extension View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ToolbarCircleButtonModifier: ViewModifier {
|
struct MenuButtonModifier: ViewModifier {
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
if #available(macOS 26.0, *) {
|
if #available(macOS 26.0, *) {
|
||||||
@@ -40,8 +40,8 @@ struct ToolbarCircleButtonModifier: ViewModifier {
|
|||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
|
|
||||||
func toolbarCircleButton() -> some View {
|
func menuButton() -> some View {
|
||||||
modifier(ToolbarCircleButtonModifier())
|
modifier(MenuButtonModifier())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||