mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-04-10 03:07:22 +02:00
Compare commits
26 Commits
v3.0.2
...
sshextensi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ece3865d9a | ||
|
|
4033a5b947 | ||
|
|
6b1f5bbb7c | ||
|
|
2cc0157290 | ||
|
|
f848eb659e | ||
|
|
faa622e379 | ||
|
|
9f2c6d9e84 | ||
|
|
afb48529c7 | ||
|
|
6c56039ece | ||
|
|
2b712864d6 | ||
|
|
845b1ec313 | ||
|
|
595de41f03 | ||
|
|
d82f404166 | ||
|
|
a3bfcb316c | ||
|
|
bba4fb9e7c | ||
|
|
32a1a0bca9 | ||
|
|
bb0b6d8dc3 | ||
|
|
c63d87cbec | ||
|
|
65bc6c1a69 | ||
|
|
275b6ef9bb | ||
|
|
f13bc23991 | ||
|
|
3a67d59519 | ||
|
|
d9a3f0c813 | ||
|
|
84d5a56fb0 | ||
|
|
3bb0cc4a0e | ||
|
|
516e37fdde |
2
.github/workflows/codeql.yml
vendored
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.0.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.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
|
||||||
|
|||||||
27
.github/workflows/nightly.yml
vendored
27
.github/workflows/nightly.yml
vendored
@@ -3,7 +3,6 @@ name: Nightly
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 8 * * *"
|
- cron: "0 8 * * *"
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -12,6 +11,7 @@ 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.0.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
RUN_ID: ${{ github.run_id }}
|
RUN_ID: ${{ github.run_id }}
|
||||||
@@ -36,20 +36,27 @@ jobs:
|
|||||||
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/Config/Config.xcconfig
|
||||||
- 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: Create ZIP
|
- 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: |
|
run: |
|
||||||
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./Secretive.zip
|
curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
|
||||||
|
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
Normal file
64
.github/workflows/oneoff.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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
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.0.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.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,6 +32,7 @@ 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:
|
||||||
@@ -46,7 +47,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.0.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
@@ -58,34 +59,40 @@ jobs:
|
|||||||
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/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||||
- 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: Create ZIP
|
- name: Move to Artifact Folder
|
||||||
run: |
|
run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
|
||||||
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive/Products/Applications/Secretive.app ./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: Upload App to Artifacts
|
- name: Upload App to Artifacts
|
||||||
id: upload
|
id: upload
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Secretive.zip
|
name: Secretive.zip
|
||||||
path: Secretive.zip
|
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
|
- name: Attest
|
||||||
id: attest
|
id: attest
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-name: "Secretive.zip"
|
subject-path: "Secretive.zip"
|
||||||
subject-digest: sha256:${{ 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 $TAG_NAME 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
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.0.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.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
4
.gitignore
vendored
@@ -93,3 +93,7 @@ iOSInjectionProject/
|
|||||||
Archive.xcarchive
|
Archive.xcarchive
|
||||||
.DS_Store
|
.DS_Store
|
||||||
contents.xcworkspacedata
|
contents.xcworkspacedata
|
||||||
|
|
||||||
|
# Per-User Configs
|
||||||
|
|
||||||
|
Sources/Config/OpenSource.xcconfig
|
||||||
@@ -22,6 +22,9 @@ let package = Package(
|
|||||||
.library(
|
.library(
|
||||||
name: "SmartCardSecretKit",
|
name: "SmartCardSecretKit",
|
||||||
targets: ["SmartCardSecretKit"]),
|
targets: ["SmartCardSecretKit"]),
|
||||||
|
.library(
|
||||||
|
name: "SSHProtocolKit",
|
||||||
|
targets: ["SSHProtocolKit"]),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
],
|
],
|
||||||
@@ -53,6 +56,19 @@ 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,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
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
|
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,13 +21,19 @@ let package = Package(
|
|||||||
targets: ["SmartCardSecretKit"]),
|
targets: ["SmartCardSecretKit"]),
|
||||||
.library(
|
.library(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
targets: ["SecretAgentKit", "XPCWrappers"]),
|
targets: ["SecretAgentKit"]),
|
||||||
|
.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: [
|
||||||
],
|
],
|
||||||
@@ -40,7 +46,7 @@ let package = Package(
|
|||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "SecretKitTests",
|
name: "SecretKitTests",
|
||||||
dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
|
dependencies: ["SecretKit", "SecretAgentKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
@@ -57,7 +63,7 @@ let package = Package(
|
|||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "SecretAgentKit",
|
name: "SecretAgentKit",
|
||||||
dependencies: ["SecretKit"],
|
dependencies: ["SecretKit", "SSHProtocolKit", "Common"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
@@ -65,9 +71,26 @@ 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"],
|
dependencies: ["XPCWrappers", "SSHProtocolKit"],
|
||||||
resources: [localization],
|
resources: [localization],
|
||||||
swiftSettings: swiftSettings,
|
swiftSettings: swiftSettings,
|
||||||
),
|
),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
46
Sources/Packages/Sources/Common/URLs.swift
Normal file
46
Sources/Packages/Sources/Common/URLs.swift
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
37
Sources/Packages/Sources/SSHProtocolKit/Data+Hex.swift
Normal file
37
Sources/Packages/Sources/SSHProtocolKit/Data+Hex.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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 {
|
||||||
@@ -18,7 +19,7 @@ public struct OpenSSHPublicKeyWriter: Sendable {
|
|||||||
("nistp" + String(describing: secret.keyType.size)).lengthAndData +
|
("nistp" + String(describing: secret.keyType.size)).lengthAndData +
|
||||||
secret.publicKey.lengthAndData
|
secret.publicKey.lengthAndData
|
||||||
case .mldsa:
|
case .mldsa:
|
||||||
// https://www.ietf.org/archive/id/draft-sfluhrer-ssh-mldsa-04.txt
|
// https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05
|
||||||
openSSHIdentifier(for: secret.keyType).lengthAndData +
|
openSSHIdentifier(for: secret.keyType).lengthAndData +
|
||||||
secret.publicKey.lengthAndData
|
secret.publicKey.lengthAndData
|
||||||
case .rsa:
|
case .rsa:
|
||||||
@@ -49,9 +50,7 @@ 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))
|
Insecure.MD5.hash(data: data(secret: secret)).formatted(.hex(separator: ":"))
|
||||||
.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 {
|
||||||
67
Sources/Packages/Sources/SSHProtocolKit/OpenSSHReader.swift
Normal file
67
Sources/Packages/Sources/SSHProtocolKit/OpenSSHReader.swift
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Reads OpenSSH protocol data.
|
||||||
|
public final class OpenSSHReader {
|
||||||
|
|
||||||
|
var remaining: Data
|
||||||
|
var done = false
|
||||||
|
|
||||||
|
/// Initialize the reader with an OpenSSH data payload.
|
||||||
|
/// - Parameter data: The data to read.
|
||||||
|
public init(data: Data) {
|
||||||
|
remaining = Data(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the next chunk of data from the playload.
|
||||||
|
/// - Returns: The next chunk of data.
|
||||||
|
public func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data {
|
||||||
|
let length = try readNextBytes(as: UInt32.self, convertEndianness: convertEndianness)
|
||||||
|
guard remaining.count >= length else { throw .beyondBounds }
|
||||||
|
let dataRange = 0..<Int(length)
|
||||||
|
let ret = Data(remaining[dataRange])
|
||||||
|
remaining.removeSubrange(dataRange)
|
||||||
|
if remaining.isEmpty {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
public func readNextBytes<T: FixedWidthInteger>(as: T.Type, convertEndianness: Bool = true) throws(OpenSSHReaderError) -> T {
|
||||||
|
let size = MemoryLayout<T>.size
|
||||||
|
guard remaining.count >= size else { throw .beyondBounds }
|
||||||
|
let lengthRange = 0..<size
|
||||||
|
let lengthChunk = remaining[lengthRange]
|
||||||
|
remaining.removeSubrange(lengthRange)
|
||||||
|
if remaining.isEmpty {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
let value = unsafe lengthChunk.bytes.unsafeLoad(as: T.self)
|
||||||
|
return convertEndianness ? T(value.bigEndian) : T(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func readNextByteAsBool() throws(OpenSSHReaderError) -> Bool {
|
||||||
|
let size = MemoryLayout<Bool>.size
|
||||||
|
guard remaining.count >= size else { throw .beyondBounds }
|
||||||
|
let lengthRange = 0..<size
|
||||||
|
let lengthChunk = remaining[lengthRange]
|
||||||
|
remaining.removeSubrange(lengthRange)
|
||||||
|
if remaining.isEmpty {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
return unsafe lengthChunk.bytes.unsafeLoad(as: Bool.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
||||||
|
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader {
|
||||||
|
OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OpenSSHReaderError: Error, Codable {
|
||||||
|
case incorrectFormat
|
||||||
|
case beyondBounds
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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 {
|
||||||
@@ -16,7 +17,7 @@ public struct OpenSSHSignatureWriter: Sendable {
|
|||||||
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
|
// https://datatracker.ietf.org/doc/html/rfc5656#section-3.1
|
||||||
ecdsaSignature(signature, keyType: secret.keyType)
|
ecdsaSignature(signature, keyType: secret.keyType)
|
||||||
case .mldsa:
|
case .mldsa:
|
||||||
// https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-00#name-public-key-algorithms
|
// https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05
|
||||||
mldsaSignature(signature, keyType: secret.keyType)
|
mldsaSignature(signature, keyType: secret.keyType)
|
||||||
case .rsa:
|
case .rsa:
|
||||||
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
|
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
|
||||||
@@ -29,19 +30,28 @@ 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
|
||||||
// Check if we need to pad with 0x00 to prevent certain
|
let r = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[0..<rawLength]))
|
||||||
// ssh servers from thinking r or s is negative
|
let s = mpint(fromFixedWidthPositiveBytes: Data(rawRepresentation[rawLength...]))
|
||||||
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)
|
||||||
@@ -19,7 +19,7 @@ extension SSHAgent {
|
|||||||
case lock
|
case lock
|
||||||
case unlock
|
case unlock
|
||||||
case addSmartcardKeyConstrained
|
case addSmartcardKeyConstrained
|
||||||
case protocolExtension
|
case protocolExtension(ProtocolExtension)
|
||||||
case unknown(UInt8)
|
case unknown(UInt8)
|
||||||
|
|
||||||
public var protocolID: UInt8 {
|
public var protocolID: UInt8 {
|
||||||
@@ -60,18 +60,82 @@ extension SSHAgent {
|
|||||||
|
|
||||||
public struct SignatureRequestContext: Sendable, Codable {
|
public struct SignatureRequestContext: Sendable, Codable {
|
||||||
public let keyBlob: Data
|
public let keyBlob: Data
|
||||||
public let dataToSign: Data
|
public let dataToSign: SignaturePayload
|
||||||
|
|
||||||
public init(keyBlob: Data, dataToSign: Data) {
|
public init(keyBlob: Data, dataToSign: SignaturePayload) {
|
||||||
self.keyBlob = keyBlob
|
self.keyBlob = keyBlob
|
||||||
self.dataToSign = dataToSign
|
self.dataToSign = dataToSign
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var empty: SignatureRequestContext {
|
public static var empty: SignatureRequestContext {
|
||||||
SignatureRequestContext(keyBlob: Data(), dataToSign: Data())
|
SignatureRequestContext(keyBlob: Data(), dataToSign: SignaturePayload(raw: Data(), decoded: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SignaturePayload: Sendable, Codable {
|
||||||
|
|
||||||
|
public let raw: Data
|
||||||
|
public let decoded: DecodedPayload?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
raw: Data,
|
||||||
|
decoded: DecodedPayload?
|
||||||
|
) {
|
||||||
|
self.raw = raw
|
||||||
|
self.decoded = decoded
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DecodedPayload: Sendable, Codable {
|
||||||
|
case sshConnection(SSHConnectionPayload)
|
||||||
|
case sshSig(SSHSigPayload)
|
||||||
|
|
||||||
|
public struct SSHConnectionPayload: Sendable, Codable {
|
||||||
|
|
||||||
|
public let username: String
|
||||||
|
public let hasSignature: Bool
|
||||||
|
public let publicKeyAlgorithm: String
|
||||||
|
public let publicKey: Data
|
||||||
|
public let hostKey: Data
|
||||||
|
|
||||||
|
public init(
|
||||||
|
username: String,
|
||||||
|
hasSignature: Bool,
|
||||||
|
publicKeyAlgorithm: String,
|
||||||
|
publicKey: Data,
|
||||||
|
hostKey: Data
|
||||||
|
) {
|
||||||
|
self.username = username
|
||||||
|
self.hasSignature = hasSignature
|
||||||
|
self.publicKeyAlgorithm = publicKeyAlgorithm
|
||||||
|
self.publicKey = publicKey
|
||||||
|
self.hostKey = hostKey
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SSHSigPayload: Sendable, Codable {
|
||||||
|
|
||||||
|
public let namespace: String
|
||||||
|
public let hashAlgorithm: String
|
||||||
|
public let hash: Data
|
||||||
|
|
||||||
|
public init(
|
||||||
|
namespace: String,
|
||||||
|
hashAlgorithm: String,
|
||||||
|
hash: Data,
|
||||||
|
) {
|
||||||
|
self.namespace = namespace
|
||||||
|
self.hashAlgorithm = hashAlgorithm
|
||||||
|
self.hash = hash
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
|
/// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1
|
||||||
@@ -88,8 +152,8 @@ extension SSHAgent {
|
|||||||
switch self {
|
switch self {
|
||||||
case .agentFailure: "SSH_AGENT_FAILURE"
|
case .agentFailure: "SSH_AGENT_FAILURE"
|
||||||
case .agentSuccess: "SSH_AGENT_SUCCESS"
|
case .agentSuccess: "SSH_AGENT_SUCCESS"
|
||||||
case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER"
|
case .agentIdentitiesAnswer: "SSH2_AGENT_IDENTITIES_ANSWER"
|
||||||
case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE"
|
case .agentSignResponse: "SSH2_AGENT_SIGN_RESPONSE"
|
||||||
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
|
case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE"
|
||||||
case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE"
|
case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
// Extensions, as defined in https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.agent
|
||||||
|
|
||||||
|
extension SSHAgent {
|
||||||
|
|
||||||
|
public enum ProtocolExtension: CustomDebugStringConvertible, Codable, Sendable {
|
||||||
|
case openSSH(OpenSSHExtension)
|
||||||
|
case unknown(String)
|
||||||
|
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch self {
|
||||||
|
case let .openSSH(protocolExtension):
|
||||||
|
protocolExtension.debugDescription
|
||||||
|
case .unknown(let string):
|
||||||
|
"Unknown (\(string))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var empty: ProtocolExtension {
|
||||||
|
.unknown("empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ProtocolExtensionParsingError: Error {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SSHAgent.ProtocolExtension {
|
||||||
|
|
||||||
|
public enum OpenSSHExtension: CustomDebugStringConvertible, Codable, Sendable {
|
||||||
|
case sessionBind(SessionBindContext)
|
||||||
|
case unknown(String)
|
||||||
|
|
||||||
|
public static var domain: String {
|
||||||
|
"openssh.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
public var name: String {
|
||||||
|
switch self {
|
||||||
|
case .sessionBind:
|
||||||
|
"session-bind"
|
||||||
|
case .unknown(let name):
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var debugDescription: String {
|
||||||
|
"\(name)@\(OpenSSHExtension.domain)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SSHAgent.ProtocolExtension.OpenSSHExtension {
|
||||||
|
|
||||||
|
public struct SessionBindContext: Codable, Sendable {
|
||||||
|
|
||||||
|
public let hostKey: Data
|
||||||
|
public let sessionID: Data
|
||||||
|
public let signature: Data
|
||||||
|
public let forwarding: Bool
|
||||||
|
|
||||||
|
public init(hostKey: Data, sessionID: Data, signature: Data, forwarding: Bool) {
|
||||||
|
self.hostKey = hostKey
|
||||||
|
self.sessionID = sessionID
|
||||||
|
self.signature = signature
|
||||||
|
self.forwarding = forwarding
|
||||||
|
}
|
||||||
|
|
||||||
|
public static let empty = SessionBindContext(hostKey: Data(), sessionID: Data(), signature: Data(), forwarding: false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ 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 {
|
||||||
@@ -14,6 +15,8 @@ public final class Agent: Sendable {
|
|||||||
private let certificateHandler = OpenSSHCertificateHandler()
|
private let certificateHandler = OpenSSHCertificateHandler()
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent")
|
||||||
|
|
||||||
|
@MainActor private var sessionID: SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext?
|
||||||
|
|
||||||
/// Initializes an agent with a store list and a witness.
|
/// Initializes an agent with a store list and a witness.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - storeList: The `SecretStoreList` to make available.
|
/// - storeList: The `SecretStoreList` to make available.
|
||||||
@@ -32,6 +35,7 @@ public final class Agent: Sendable {
|
|||||||
extension Agent {
|
extension Agent {
|
||||||
|
|
||||||
public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data {
|
public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data {
|
||||||
|
logger.debug("Agent received request of type \(request.debugDescription)")
|
||||||
// Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting
|
// Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting
|
||||||
await reloadSecretsIfNeccessary()
|
await reloadSecretsIfNeccessary()
|
||||||
var response = Data()
|
var response = Data()
|
||||||
@@ -42,11 +46,37 @@ extension Agent {
|
|||||||
response.append(await identities())
|
response.append(await identities())
|
||||||
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)")
|
||||||
case .signRequest(let context):
|
case .signRequest(let context):
|
||||||
|
if let boundSession = await sessionID {
|
||||||
|
switch context.dataToSign.decoded {
|
||||||
|
case .sshConnection(let payload):
|
||||||
|
guard payload.hostKey == boundSession.hostKey else {
|
||||||
|
logger.error("Agent received bind request, but host key does not match signature reqeust host key.")
|
||||||
|
throw BindingFailure()
|
||||||
|
}
|
||||||
|
case .sshSig:
|
||||||
|
// SSHSIG does not have a host binding payload.
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
response.append(SSHAgent.Response.agentSignResponse.data)
|
response.append(SSHAgent.Response.agentSignResponse.data)
|
||||||
response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance))
|
response.append(try await sign(data: context.dataToSign.raw, keyBlob: context.keyBlob, provenance: provenance))
|
||||||
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
||||||
|
case .protocolExtension(.openSSH(.sessionBind(let bind))):
|
||||||
|
response = try await MainActor.run {
|
||||||
|
guard sessionID == nil else {
|
||||||
|
logger.error("Agent received bind request, but already bound.")
|
||||||
|
throw BindingFailure()
|
||||||
|
}
|
||||||
|
logger.debug("Agent bound")
|
||||||
|
sessionID = bind
|
||||||
|
return SSHAgent.Response.agentSuccess.data
|
||||||
|
}
|
||||||
|
logger.debug("Agent returned \(SSHAgent.Response.agentSuccess.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()
|
||||||
@@ -143,6 +173,7 @@ extension Agent {
|
|||||||
|
|
||||||
struct NoMatchingKeyError: Error {}
|
struct NoMatchingKeyError: Error {}
|
||||||
struct UnhandledRequestError: Error {}
|
struct UnhandledRequestError: Error {}
|
||||||
|
struct BindingFailure: Error {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
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(homeDirectory: URL.homeDirectory)
|
private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
|
||||||
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,47 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Reads OpenSSH protocol data.
|
|
||||||
final class OpenSSHReader {
|
|
||||||
|
|
||||||
var remaining: Data
|
|
||||||
|
|
||||||
/// Initialize the reader with an OpenSSH data payload.
|
|
||||||
/// - Parameter data: The data to read.
|
|
||||||
init(data: Data) {
|
|
||||||
remaining = Data(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads the next chunk of data from the playload.
|
|
||||||
/// - Returns: The next chunk of data.
|
|
||||||
func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data {
|
|
||||||
let littleEndianLength = try readNextBytes(as: UInt32.self)
|
|
||||||
let length = convertEndianness ? Int(littleEndianLength.bigEndian) : Int(littleEndianLength)
|
|
||||||
guard remaining.count >= length else { throw .beyondBounds }
|
|
||||||
let dataRange = 0..<length
|
|
||||||
let ret = Data(remaining[dataRange])
|
|
||||||
remaining.removeSubrange(dataRange)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func readNextBytes<T>(as: T.Type) throws(OpenSSHReaderError) -> T {
|
|
||||||
let size = MemoryLayout<T>.size
|
|
||||||
guard remaining.count >= size else { throw .beyondBounds }
|
|
||||||
let lengthRange = 0..<size
|
|
||||||
let lengthChunk = remaining[lengthRange]
|
|
||||||
remaining.removeSubrange(lengthRange)
|
|
||||||
return unsafe lengthChunk.bytes.unsafeLoad(as: T.self)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {
|
|
||||||
try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader {
|
|
||||||
OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum OpenSSHReaderError: Error, Codable {
|
|
||||||
case beyondBounds
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
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 {
|
||||||
@@ -9,8 +12,8 @@ public final class PublicKeyFileStoreController: Sendable {
|
|||||||
private let keyWriter = OpenSSHPublicKeyWriter()
|
private let keyWriter = OpenSSHPublicKeyWriter()
|
||||||
|
|
||||||
/// Initializes a PublicKeyFileStoreController.
|
/// Initializes a PublicKeyFileStoreController.
|
||||||
public init(homeDirectory: URL) {
|
public init(directory: URL) {
|
||||||
directory = homeDirectory.appending(component: "PublicKeys")
|
self.directory = directory
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes out the keys specified to disk.
|
/// Writes out the keys specified to disk.
|
||||||
@@ -19,7 +22,7 @@ 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 { publicKeyPath(for: $0) })
|
let validPaths = Set(secrets.map { URL.publicKeyPath(for: $0, in: directory) })
|
||||||
.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.appending(path: $0).path() }
|
||||||
@@ -33,21 +36,13 @@ 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 = publicKeyPath(for: secret)
|
let path = URL.publicKeyPath(for: secret, in: directory)
|
||||||
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,11 +1,14 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import SSHProtocolKit
|
||||||
|
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
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 {
|
||||||
@@ -13,7 +16,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 {
|
||||||
@@ -52,8 +55,8 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
return .unlock
|
return .unlock
|
||||||
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
|
case SSHAgent.Request.addSmartcardKeyConstrained.protocolID:
|
||||||
return .addSmartcardKeyConstrained
|
return .addSmartcardKeyConstrained
|
||||||
case SSHAgent.Request.protocolExtension.protocolID:
|
case SSHAgent.Request.protocolExtension(.empty).protocolID:
|
||||||
return .protocolExtension
|
return .protocolExtension(try protocolExtension(from: body))
|
||||||
default:
|
default:
|
||||||
return .unknown(rawRequestInt)
|
return .unknown(rawRequestInt)
|
||||||
}
|
}
|
||||||
@@ -63,12 +66,152 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
|
|
||||||
extension SSHAgentInputParser {
|
extension SSHAgentInputParser {
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let userAuthMagic: UInt8 = 50 // SSH2_MSG_USERAUTH_REQUEST
|
||||||
|
static let sshSigMagic = Data("SSHSIG".utf8)
|
||||||
|
}
|
||||||
|
|
||||||
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
|
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
|
||||||
let reader = OpenSSHReader(data: data)
|
let reader = OpenSSHReader(data: data)
|
||||||
let rawKeyBlob = try reader.readNextChunk()
|
let rawKeyBlob = try reader.readNextChunk()
|
||||||
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
||||||
let dataToSign = try reader.readNextChunk()
|
let rawPayload = try reader.readNextChunk()
|
||||||
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign)
|
let payload: SSHAgent.Request.SignatureRequestContext.SignaturePayload
|
||||||
|
do {
|
||||||
|
if rawPayload.count > 6 && rawPayload[0..<6] == Constants.sshSigMagic {
|
||||||
|
payload = .init(raw: rawPayload, decoded: .sshSig(try sshSigPayload(from: rawPayload[6...])))
|
||||||
|
} else {
|
||||||
|
payload = .init(raw: rawPayload, decoded: .sshConnection(try sshConnectionPayload(from: rawPayload)))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
payload = .init(raw: rawPayload, decoded: nil)
|
||||||
|
}
|
||||||
|
return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshSigPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHSigPayload {
|
||||||
|
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L79
|
||||||
|
let payloadReader = OpenSSHReader(data: data)
|
||||||
|
let namespace = try payloadReader.readNextChunkAsString()
|
||||||
|
_ = try payloadReader.readNextChunk() // reserved
|
||||||
|
let hashAlgorithm = try payloadReader.readNextChunkAsString()
|
||||||
|
let hash = try payloadReader.readNextChunk()
|
||||||
|
return .init(
|
||||||
|
namespace: namespace,
|
||||||
|
hashAlgorithm: hashAlgorithm,
|
||||||
|
hash: hash
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshConnectionPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHConnectionPayload {
|
||||||
|
let payloadReader = OpenSSHReader(data: data)
|
||||||
|
_ = try payloadReader.readNextChunk()
|
||||||
|
let magic = try payloadReader.readNextBytes(as: UInt8.self, convertEndianness: false)
|
||||||
|
guard magic == Constants.userAuthMagic else { throw .incorrectFormat }
|
||||||
|
let username = try payloadReader.readNextChunkAsString()
|
||||||
|
_ = try payloadReader.readNextChunkAsString() // "ssh-connection"
|
||||||
|
_ = try payloadReader.readNextChunkAsString() // "publickey-hostbound-v00@openssh.com"
|
||||||
|
let hasSignature = try payloadReader.readNextByteAsBool()
|
||||||
|
let algorithm = try payloadReader.readNextChunkAsString()
|
||||||
|
let publicKeyReader = try payloadReader.readNextChunkAsSubReader()
|
||||||
|
_ = try publicKeyReader.readNextChunk()
|
||||||
|
_ = try publicKeyReader.readNextChunk()
|
||||||
|
let publicKey = try publicKeyReader.readNextChunk()
|
||||||
|
let hostKeyReader = try payloadReader.readNextChunkAsSubReader()
|
||||||
|
_ = try hostKeyReader.readNextChunk()
|
||||||
|
let hostKey = try hostKeyReader.readNextChunk()
|
||||||
|
return .init(
|
||||||
|
username: username,
|
||||||
|
hasSignature: hasSignature,
|
||||||
|
publicKeyAlgorithm: algorithm,
|
||||||
|
publicKey: publicKey,
|
||||||
|
hostKey: hostKey,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func protocolExtension(from data: Data) throws(AgentParsingError) -> SSHAgent.ProtocolExtension {
|
||||||
|
do {
|
||||||
|
let reader = OpenSSHReader(data: data)
|
||||||
|
let nameRaw = try reader.readNextChunkAsString()
|
||||||
|
let nameSplit = nameRaw.split(separator: "@")
|
||||||
|
guard nameSplit.count == 2 else {
|
||||||
|
throw AgentParsingError.invalidData
|
||||||
|
}
|
||||||
|
let (name, domain) = (nameSplit[0], nameSplit[1])
|
||||||
|
switch domain {
|
||||||
|
case SSHAgent.ProtocolExtension.OpenSSHExtension.domain:
|
||||||
|
switch name {
|
||||||
|
case SSHAgent.ProtocolExtension.OpenSSHExtension.sessionBind(.empty).name:
|
||||||
|
let hostkeyBlob = try reader.readNextChunkAsSubReader()
|
||||||
|
let hostKeyType = try hostkeyBlob.readNextChunkAsString()
|
||||||
|
let hostKeyData = try hostkeyBlob.readNextChunk()
|
||||||
|
let sessionID = try reader.readNextChunk()
|
||||||
|
let signatureBlob = try reader.readNextChunkAsSubReader()
|
||||||
|
_ = try signatureBlob.readNextChunk() // key type again
|
||||||
|
let signature = try signatureBlob.readNextChunk()
|
||||||
|
let forwarding = try reader.readNextByteAsBool()
|
||||||
|
switch hostKeyType {
|
||||||
|
// FIXME: FACTOR OUT?
|
||||||
|
case "ssh-ed25519":
|
||||||
|
let hostKey = try CryptoKit.Curve25519.Signing.PublicKey(rawRepresentation: hostKeyData)
|
||||||
|
guard hostKey.isValidSignature(signature, for: sessionID) else {
|
||||||
|
throw AgentParsingError.incorrectSignature
|
||||||
|
}
|
||||||
|
case "ecdsa-sha2-nistp256":
|
||||||
|
let hostKey = try CryptoKit.P256.Signing.PublicKey(rawRepresentation: hostKeyData)
|
||||||
|
guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else {
|
||||||
|
throw AgentParsingError.incorrectSignature
|
||||||
|
}
|
||||||
|
case "ecdsa-sha2-nistp384":
|
||||||
|
let hostKey = try CryptoKit.P384.Signing.PublicKey(rawRepresentation: hostKeyData)
|
||||||
|
guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else {
|
||||||
|
throw AgentParsingError.incorrectSignature
|
||||||
|
}
|
||||||
|
case "ssh-mldsa-65":
|
||||||
|
if #available(macOS 26.0, *) {
|
||||||
|
let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData)
|
||||||
|
guard hostKey.isValidSignature(signature, for: sessionID) else {
|
||||||
|
throw AgentParsingError.incorrectSignature
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw AgentParsingError.unhandledRequest
|
||||||
|
}
|
||||||
|
case "ssh-mldsa-87":
|
||||||
|
if #available(macOS 26.0, *) {
|
||||||
|
let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData)
|
||||||
|
guard hostKey.isValidSignature(signature, for: sessionID) else {
|
||||||
|
throw AgentParsingError.incorrectSignature
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw AgentParsingError.unhandledRequest
|
||||||
|
}
|
||||||
|
case "ssh-rsa":
|
||||||
|
// FIXME: HANDLE
|
||||||
|
throw AgentParsingError.unhandledRequest
|
||||||
|
default:
|
||||||
|
throw AgentParsingError.unhandledRequest
|
||||||
|
}
|
||||||
|
let context = SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext(
|
||||||
|
hostKey: hostKeyData,
|
||||||
|
sessionID: sessionID,
|
||||||
|
signature: signature,
|
||||||
|
forwarding: forwarding
|
||||||
|
)
|
||||||
|
return .openSSH(.sessionBind(context))
|
||||||
|
default:
|
||||||
|
return .openSSH(.unknown(String(name)))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return .unknown(nameRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch let error as OpenSSHReaderError {
|
||||||
|
throw .openSSHReader(error)
|
||||||
|
} catch let error as AgentParsingError {
|
||||||
|
throw error
|
||||||
|
} catch {
|
||||||
|
throw .unknownRequest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func certificatePublicKeyBlob(from hash: Data) -> Data? {
|
func certificatePublicKeyBlob(from hash: Data) -> Data? {
|
||||||
@@ -103,6 +246,7 @@ extension SSHAgentInputParser {
|
|||||||
case unknownRequest
|
case unknownRequest
|
||||||
case unhandledRequest
|
case unhandledRequest
|
||||||
case invalidData
|
case invalidData
|
||||||
|
case incorrectSignature
|
||||||
case openSSHReader(OpenSSHReaderError)
|
case openSSHReader(OpenSSHReaderError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 = self.pidAndNameInfo(from: pid)
|
var pidAndNameInfo = unsafe 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)
|
||||||
|
|||||||
@@ -36,16 +36,21 @@ 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 { [fileHandle, sessionsContinuation, logger] in
|
Task { @MainActor [fileHandle, sessionsContinuation, logger] in
|
||||||
for await notification in NotificationCenter.default.notifications(named: .NSFileHandleConnectionAccepted) {
|
// Create the sequence before triggering the notification to
|
||||||
|
// 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)
|
||||||
await fileHandle.acceptConnectionInBackgroundAndNotifyOnMainActor()
|
fileHandle.acceptConnectionInBackgroundAndNotify()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileHandle.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.Mode.common])
|
|
||||||
logger.debug("Socket listening at \(path)")
|
logger.debug("Socket listening at \(path)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +82,14 @@ 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 { [messagesContinuation, logger] in
|
Task { @MainActor [messagesContinuation, logger] in
|
||||||
for await _ in NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable, object: fileHandle) {
|
// Create the sequence before triggering the notification to
|
||||||
|
// 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.")
|
||||||
@@ -90,16 +101,13 @@ 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.
|
||||||
public func write(_ data: Data) async throws {
|
@MainActor public func write(_ data: Data) throws {
|
||||||
try fileHandle.write(contentsOf: data)
|
try fileHandle.write(contentsOf: data)
|
||||||
await fileHandle.waitForDataInBackgroundAndNotifyOnMainActor()
|
fileHandle.waitForDataInBackgroundAndNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Closes the socket and cleans up resources.
|
/// Closes the socket and cleans up resources.
|
||||||
@@ -113,22 +121,6 @@ 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) {
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
|
/// A context describing a persisted authentication.
|
||||||
|
package final class PersistentAuthenticationContext<SecretType: Secret>: PersistedAuthenticationContext {
|
||||||
|
|
||||||
|
/// The Secret to persist authentication for.
|
||||||
|
let secret: SecretType
|
||||||
|
/// The LAContext used to authorize the persistent context.
|
||||||
|
package 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: SecretType, 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.
|
||||||
|
package var valid: Bool {
|
||||||
|
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
|
||||||
|
}
|
||||||
|
|
||||||
|
package 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package actor PersistentAuthenticationHandler<SecretType: Secret>: Sendable {
|
||||||
|
|
||||||
|
private var persistedAuthenticationContexts: [SecretType: PersistentAuthenticationContext<SecretType>] = [:]
|
||||||
|
|
||||||
|
package init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
package func existingPersistedAuthenticationContext(secret: SecretType) -> PersistentAuthenticationContext<SecretType>? {
|
||||||
|
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
|
||||||
|
return persisted
|
||||||
|
}
|
||||||
|
|
||||||
|
package func persistAuthentication(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 = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
||||||
|
persistedAuthenticationContexts[secret] = context
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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,7 +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()
|
private let persistentAuthenticationHandler = PersistentAuthenticationHandler<Secret>()
|
||||||
|
|
||||||
/// Initializes a Store.
|
/// Initializes a Store.
|
||||||
@MainActor public init() {
|
@MainActor public init() {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ extension SmartCard {
|
|||||||
public var secrets: [Secret] {
|
public var secrets: [Secret] {
|
||||||
state.secrets
|
state.secrets
|
||||||
}
|
}
|
||||||
|
private let persistentAuthenticationHandler = PersistentAuthenticationHandler<Secret>()
|
||||||
|
|
||||||
/// Initializes a Store.
|
/// Initializes a Store.
|
||||||
public init() {
|
public init() {
|
||||||
@@ -58,9 +59,15 @@ extension SmartCard {
|
|||||||
|
|
||||||
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) 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()
|
var context: LAContext
|
||||||
context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
|
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
|
||||||
context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
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 attributes = KeychainDictionary([
|
let attributes = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
||||||
@@ -86,11 +93,12 @@ extension SmartCard {
|
|||||||
return signature as Data
|
return signature as Data
|
||||||
}
|
}
|
||||||
|
|
||||||
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
|
public func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
|
||||||
nil
|
await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws {
|
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
|
||||||
|
try await persistentAuthenticationHandler.persistAuthentication(secret: secret, forDuration: duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reloads all secrets from the store.
|
/// Reloads all secrets from the store.
|
||||||
@@ -163,7 +171,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: .unknown)
|
let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired)
|
||||||
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
|
||||||
|
|||||||
26
Sources/Packages/Sources/XPCWrappers/TeamID.swift
Normal file
26
Sources/Packages/Sources/XPCWrappers/TeamID.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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] = Z72PRUAWF6")
|
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
|
||||||
newConnection.resume()
|
newConnection.resume()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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] = Z72PRUAWF6")
|
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"")
|
||||||
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,8 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
@testable import SecretKit
|
@testable import SecretKit
|
||||||
@testable import SecureEnclaveSecretKit
|
import SSHProtocolKit
|
||||||
@testable import SmartCardSecretKit
|
|
||||||
|
|
||||||
@Suite struct OpenSSHPublicKeyWriterTests {
|
@Suite struct OpenSSHPublicKeyWriterTests {
|
||||||
|
|
||||||
@@ -47,8 +46,8 @@ import Testing
|
|||||||
extension OpenSSHPublicKeyWriterTests {
|
extension OpenSSHPublicKeyWriterTests {
|
||||||
|
|
||||||
enum Constants {
|
enum Constants {
|
||||||
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 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 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"))
|
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"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
@testable import SecretAgentKit
|
import SSHProtocolKit
|
||||||
@testable import SecureEnclaveSecretKit
|
|
||||||
@testable import SmartCardSecretKit
|
|
||||||
|
|
||||||
@Suite struct OpenSSHReaderTests {
|
@Suite struct OpenSSHReaderTests {
|
||||||
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
11
Sources/Packages/Tests/SSHProtocolKitTests/TestSecret.swift
Normal file
11
Sources/Packages/Tests/SSHProtocolKitTests/TestSecret.swift
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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,6 +1,7 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -44,8 +45,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).bigEndian
|
let length = try responseReader.readNextBytes(as: UInt32.self)
|
||||||
let type = try responseReader.readNextBytes(as: UInt8.self).bigEndian
|
let type = try responseReader.readNextBytes(as: UInt8.self)
|
||||||
#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,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
import SSHProtocolKit
|
||||||
|
|
||||||
struct Stub {}
|
struct Stub {}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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 {
|
||||||
@@ -21,12 +22,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 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, witness: notifier)
|
Agent(storeList: storeList, witness: notifier)
|
||||||
}()
|
}()
|
||||||
private lazy var socketController: SocketController = {
|
private lazy var socketController: SocketController = {
|
||||||
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
|
let path = URL.socketPath 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")
|
||||||
@@ -34,14 +35,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 await session.write(agentResponse)
|
try session.write(agentResponse)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
try session.close()
|
try session.close()
|
||||||
|
|||||||
@@ -2,8 +2,24 @@
|
|||||||
<!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>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import SecretAgentKit
|
|||||||
import Brief
|
import Brief
|
||||||
import XPCWrappers
|
import XPCWrappers
|
||||||
import OSLog
|
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 {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?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,6 +2,7 @@ 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 */; };
|
||||||
50033AC327813F1700253856 /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50033AC227813F1700253856 /* BundleIDs.swift */; };
|
5002C3AB2EEF483300FFAD22 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 5002C3AA2EEF483300FFAD22 /* XPCWrappers */; };
|
||||||
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 */; };
|
||||||
@@ -26,13 +26,11 @@
|
|||||||
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 */; };
|
||||||
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 */; };
|
||||||
@@ -63,6 +61,7 @@
|
|||||||
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 */; };
|
||||||
@@ -70,6 +69,8 @@
|
|||||||
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 */; };
|
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; };
|
||||||
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
|
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
|
||||||
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
||||||
@@ -181,20 +182,19 @@
|
|||||||
/* 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>"; };
|
||||||
50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; };
|
|
||||||
5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = "<group>"; };
|
5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; 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>"; };
|
||||||
@@ -241,6 +241,7 @@
|
|||||||
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.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>"; };
|
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>"; };
|
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 */
|
||||||
@@ -248,6 +249,7 @@
|
|||||||
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 */,
|
||||||
@@ -268,6 +270,7 @@
|
|||||||
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;
|
||||||
@@ -281,20 +284,13 @@
|
|||||||
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 */
|
||||||
50033AC427813F1C00253856 /* Helpers */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
50033AC227813F1700253856 /* BundleIDs.swift */,
|
|
||||||
);
|
|
||||||
path = Helpers;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
504788ED2E681EB200B4556F /* Modifiers */ = {
|
504788ED2E681EB200B4556F /* Modifiers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -378,7 +374,6 @@
|
|||||||
50617D8223FCE48E0099B055 /* App.swift */,
|
50617D8223FCE48E0099B055 /* App.swift */,
|
||||||
508A58B0241ED1C40069DC07 /* Views */,
|
508A58B0241ED1C40069DC07 /* Views */,
|
||||||
508A58B1241ED1EA0069DC07 /* Controllers */,
|
508A58B1241ED1EA0069DC07 /* Controllers */,
|
||||||
50033AC427813F1C00253856 /* Helpers */,
|
|
||||||
50617D8E23FCE48E0099B055 /* Info.plist */,
|
50617D8E23FCE48E0099B055 /* Info.plist */,
|
||||||
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
||||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
|
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
|
||||||
@@ -403,6 +398,7 @@
|
|||||||
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 */,
|
||||||
@@ -414,6 +410,7 @@
|
|||||||
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 */,
|
||||||
@@ -426,6 +423,7 @@
|
|||||||
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>";
|
||||||
@@ -444,11 +442,9 @@
|
|||||||
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>";
|
||||||
@@ -510,6 +506,7 @@
|
|||||||
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 */;
|
||||||
@@ -551,6 +548,7 @@
|
|||||||
name = SecretAgentInputParser;
|
name = SecretAgentInputParser;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
|
50692E6B2E6FFA510043C7BB /* SecretAgentKit */,
|
||||||
|
5002C3AA2EEF483300FFAD22 /* XPCWrappers */,
|
||||||
);
|
);
|
||||||
productName = SecretAgentInputParser;
|
productName = SecretAgentInputParser;
|
||||||
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
|
productReference = 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */;
|
||||||
@@ -580,6 +578,7 @@
|
|||||||
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 */;
|
||||||
@@ -593,7 +592,7 @@
|
|||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 2600;
|
LastSwiftUpdateCheck = 2600;
|
||||||
LastUpgradeCheck = 2600;
|
LastUpgradeCheck = 2640;
|
||||||
ORGANIZATIONNAME = "Max Goedjen";
|
ORGANIZATIONNAME = "Max Goedjen";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
50617D7E23FCE48D0099B055 = {
|
50617D7E23FCE48D0099B055 = {
|
||||||
@@ -655,6 +654,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
50B832C02F62202A00D2FCB8 /* InternetAccessPolicy.plist in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -690,7 +690,6 @@
|
|||||||
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
||||||
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.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 */,
|
||||||
@@ -700,14 +699,12 @@
|
|||||||
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 */,
|
||||||
@@ -951,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 = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -974,7 +971,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
};
|
};
|
||||||
@@ -991,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 = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_ENHANCED_SECURITY = YES;
|
ENABLE_ENHANCED_SECURITY = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -1014,7 +1011,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
|
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
|
||||||
};
|
};
|
||||||
@@ -1024,16 +1021,19 @@
|
|||||||
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 = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
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;
|
||||||
@@ -1050,7 +1050,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1067,13 +1067,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_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;
|
||||||
@@ -1090,7 +1093,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1106,16 +1109,19 @@
|
|||||||
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*]" = Z72PRUAWF6;
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
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;
|
||||||
@@ -1132,7 +1138,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretiveUpdater";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -1149,13 +1155,16 @@
|
|||||||
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 = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
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;
|
||||||
@@ -1164,7 +1173,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1181,11 +1190,14 @@
|
|||||||
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;
|
||||||
@@ -1194,7 +1206,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1210,14 +1222,17 @@
|
|||||||
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*]" = Z72PRUAWF6;
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
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;
|
||||||
@@ -1226,7 +1241,7 @@
|
|||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentInputParser";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -1347,7 +1362,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).Host";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@@ -1356,14 +1371,17 @@
|
|||||||
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;
|
||||||
@@ -1380,7 +1398,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Test;
|
name = Test;
|
||||||
@@ -1394,11 +1412,13 @@
|
|||||||
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 = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
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;
|
||||||
@@ -1415,7 +1435,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -1430,11 +1450,13 @@
|
|||||||
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 = Z72PRUAWF6;
|
DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)";
|
||||||
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;
|
||||||
@@ -1451,7 +1473,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1;
|
MARKETING_VERSION = 1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
|
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
|
||||||
};
|
};
|
||||||
@@ -1513,6 +1535,10 @@
|
|||||||
/* 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;
|
||||||
@@ -1561,6 +1587,14 @@
|
|||||||
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 = "2600"
|
LastUpgradeVersion = "2640"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
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 = "2600"
|
LastUpgradeVersion = "2640"
|
||||||
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 = "2600"
|
LastUpgradeVersion = "2640"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Brief
|
|||||||
@main
|
@main
|
||||||
struct Secretive: App {
|
struct Secretive: App {
|
||||||
|
|
||||||
@Environment(\.agentStatusChecker) var agentStatusChecker
|
@Environment(\.agentLaunchController) var agentLaunchController
|
||||||
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||||
|
|
||||||
@SceneBuilder var body: some Scene {
|
@SceneBuilder var body: some Scene {
|
||||||
@@ -15,14 +15,16 @@ struct Secretive: App {
|
|||||||
ContentView()
|
ContentView()
|
||||||
.environment(EnvironmentValues._secretStoreList)
|
.environment(EnvironmentValues._secretStoreList)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
Task {
|
||||||
guard hasRunSetup else { return }
|
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||||
agentStatusChecker.check()
|
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
||||||
if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild {
|
guard hasRunSetup && !explicitlyDisabled else { return }
|
||||||
// Relaunch the agent, since it'll be running from earlier update still
|
agentLaunchController.check()
|
||||||
reinstallAgent()
|
guard !agentLaunchController.developmentBuild else { return }
|
||||||
} else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild {
|
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
|
||||||
forceLaunchAgent()
|
// Relaunch the agent, since it'll be running from earlier update still
|
||||||
|
try await agentLaunchController.forceLaunch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,30 +81,6 @@ extension Secretive {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
private enum Constants {
|
||||||
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
||||||
}
|
}
|
||||||
@@ -121,8 +99,8 @@ extension EnvironmentValues {
|
|||||||
return list
|
return list
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private static let _agentStatusChecker = AgentStatusChecker()
|
private static let _agentLaunchController = AgentLaunchController()
|
||||||
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
@Entry var agentLaunchController: any AgentLaunchControllerProtocol = _agentLaunchController
|
||||||
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)
|
||||||
|
|||||||
@@ -2,18 +2,26 @@ import Foundation
|
|||||||
import AppKit
|
import AppKit
|
||||||
import SecretKit
|
import SecretKit
|
||||||
import Observation
|
import Observation
|
||||||
|
import OSLog
|
||||||
|
import ServiceManagement
|
||||||
|
import Common
|
||||||
|
|
||||||
@MainActor protocol AgentStatusCheckerProtocol: Observable, Sendable {
|
@MainActor protocol AgentLaunchControllerProtocol: 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 AgentStatusChecker: AgentStatusCheckerProtocol {
|
@Observable @MainActor final class AgentLaunchController: AgentLaunchControllerProtocol {
|
||||||
|
|
||||||
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
|
||||||
@@ -33,7 +41,7 @@ import Observation
|
|||||||
|
|
||||||
// The process corresponding to this instance of Secretive
|
// The process corresponding to this instance of Secretive
|
||||||
var instanceSecretAgentProcess: NSRunningApplication? {
|
var instanceSecretAgentProcess: NSRunningApplication? {
|
||||||
// FIXME: CHECK VERSION
|
// TODO: 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 }
|
||||||
@@ -49,6 +57,47 @@ import Observation
|
|||||||
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 {
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
|
class PreviewAgentLaunchController: AgentLaunchControllerProtocol {
|
||||||
|
|
||||||
let running: Bool
|
let running: Bool
|
||||||
let process: NSRunningApplication?
|
let process: NSRunningApplication?
|
||||||
@@ -15,4 +15,13 @@ class PreviewAgentStatusChecker: AgentStatusCheckerProtocol {
|
|||||||
func check() {
|
func check() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func install() async throws {
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstall() async throws {
|
||||||
|
}
|
||||||
|
|
||||||
|
func forceLaunch() async throws {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,22 @@
|
|||||||
<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.platform-restrictions</key>
|
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
|
||||||
<integer>2</integer>
|
<string>1</string>
|
||||||
<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>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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
|
||||||
@@ -31,7 +32,7 @@ struct SetupView: View {
|
|||||||
) {
|
) {
|
||||||
installed = true
|
installed = true
|
||||||
Task {
|
Task {
|
||||||
await LaunchAgentController().install()
|
try? await agentLaunchController.install()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import SSHProtocolKit
|
||||||
|
import Common
|
||||||
|
|
||||||
struct ToolConfigurationView: View {
|
struct ToolConfigurationView: View {
|
||||||
|
|
||||||
@@ -10,6 +12,7 @@ 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
|
||||||
@@ -48,6 +51,12 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -60,7 +69,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(String(localized: step))) {
|
ConfigurationItemView(title: .integrationsAddThisTitle, action: .copy(placeholdersReplaced(text: String(localized: step)))) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(placeholdersReplaced(text: String(localized: step)))
|
Text(placeholdersReplaced(text: String(localized: step)))
|
||||||
.padding(8)
|
.padding(8)
|
||||||
@@ -102,10 +111,11 @@ 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 fileController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
|
let gitAllowedSignersString = [email.isEmpty ? String(localized: .integrationsConfigureUsingEmailPlaceholder) : email, writer.openSSHString(secret: selectedSecret)]
|
||||||
|
.joined(separator: " ")
|
||||||
return text
|
return text
|
||||||
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: writer.openSSHString(secret: selectedSecret))
|
.replacingOccurrences(of: Instructions.Constants.publicKeyPlaceholder, with: gitAllowedSignersString)
|
||||||
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: fileController.publicKeyPath(for: selectedSecret))
|
.replacingOccurrences(of: Instructions.Constants.publicKeyPathPlaceholder, with: URL.publicKeyPath(for: selectedSecret, in: URL.publicKeyDirectory))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ struct ToolbarButtonStyle: PrimitiveButtonStyle {
|
|||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
.padding(.horizontal, 12)
|
.padding(.horizontal, 12)
|
||||||
.glassEffect(.regular.interactive().tint(tint))
|
.glassEffect(.regular.interactive().tint(tint))
|
||||||
|
.onTapGesture {
|
||||||
|
configuration.trigger()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
BorderedButtonStyle().makeBody(configuration: configuration)
|
BorderedButtonStyle().makeBody(configuration: configuration)
|
||||||
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
|
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
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 {
|
||||||
@@ -21,7 +22,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: publicKeyFileStoreController.publicKeyPath(for: secret), showRevealInFinder: true)
|
CopyableView(title: .secretDetailPublicKeyPathLabel, image: Image(systemName: "lock.doc"), text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory), showRevealInFinder: true)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ 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 }) {
|
||||||
@@ -36,17 +48,5 @@ 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import SwiftUI
|
|||||||
|
|
||||||
struct AgentStatusView: View {
|
struct AgentStatusView: View {
|
||||||
|
|
||||||
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if agentStatusChecker.running {
|
if agentLaunchController.running {
|
||||||
AgentRunningView()
|
AgentRunningView()
|
||||||
} else {
|
} else {
|
||||||
AgentNotRunningView()
|
AgentNotRunningView()
|
||||||
@@ -14,12 +14,13 @@ struct AgentStatusView: View {
|
|||||||
}
|
}
|
||||||
struct AgentRunningView: View {
|
struct AgentRunningView: View {
|
||||||
|
|
||||||
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
@Environment(\.agentLaunchController) private var agentLaunchController: any AgentLaunchControllerProtocol
|
||||||
|
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
if let process = agentStatusChecker.process {
|
if let process = agentLaunchController.process {
|
||||||
ConfigurationItemView(
|
ConfigurationItemView(
|
||||||
title: .agentDetailsLocationTitle,
|
title: .agentDetailsLocationTitle,
|
||||||
value: process.bundleURL!.path(),
|
value: process.bundleURL!.path(),
|
||||||
@@ -53,19 +54,14 @@ struct AgentRunningView: View {
|
|||||||
Menu(.agentDetailsRestartAgentButton) {
|
Menu(.agentDetailsRestartAgentButton) {
|
||||||
Button(.agentDetailsDisableAgentButton) {
|
Button(.agentDetailsDisableAgentButton) {
|
||||||
Task {
|
Task {
|
||||||
_ = await LaunchAgentController()
|
explicitlyDisabled = true
|
||||||
|
try? await agentLaunchController
|
||||||
.uninstall()
|
.uninstall()
|
||||||
agentStatusChecker.check()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} primaryAction: {
|
} primaryAction: {
|
||||||
Task {
|
Task {
|
||||||
let controller = LaunchAgentController()
|
try? await agentLaunchController.forceLaunch()
|
||||||
let installed = await controller.install()
|
|
||||||
if !installed {
|
|
||||||
_ = await controller.forceLaunch()
|
|
||||||
}
|
|
||||||
agentStatusChecker.check()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,9 +78,10 @@ struct AgentRunningView: View {
|
|||||||
|
|
||||||
struct AgentNotRunningView: View {
|
struct AgentNotRunningView: View {
|
||||||
|
|
||||||
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
@Environment(\.agentLaunchController) private var agentLaunchController
|
||||||
@State var triedRestart = false
|
@State var triedRestart = false
|
||||||
@State var loading = false
|
@State var loading = false
|
||||||
|
@AppStorage("explicitlyDisabled") var explicitlyDisabled = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
@@ -100,18 +97,14 @@ struct AgentNotRunningView: View {
|
|||||||
if !triedRestart {
|
if !triedRestart {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button {
|
Button {
|
||||||
|
explicitlyDisabled = false
|
||||||
guard !loading else { return }
|
guard !loading else { return }
|
||||||
loading = true
|
loading = true
|
||||||
Task {
|
Task {
|
||||||
let controller = LaunchAgentController()
|
try await agentLaunchController.forceLaunch()
|
||||||
let installed = await controller.install()
|
|
||||||
if !installed {
|
|
||||||
_ = await controller.forceLaunch()
|
|
||||||
}
|
|
||||||
agentStatusChecker.check()
|
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
if !agentStatusChecker.running {
|
if !agentLaunchController.running {
|
||||||
triedRestart = true
|
triedRestart = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,9 +138,9 @@ struct AgentNotRunningView: View {
|
|||||||
|
|
||||||
//#Preview {
|
//#Preview {
|
||||||
// AgentStatusView()
|
// AgentStatusView()
|
||||||
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: false))
|
// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: false))
|
||||||
//}
|
//}
|
||||||
//#Preview {
|
//#Preview {
|
||||||
// AgentStatusView()
|
// AgentStatusView()
|
||||||
// .environment(\.agentStatusChecker, PreviewAgentStatusChecker(running: true, process: .current))
|
// .environment(\.agentLaunchController, PreviewAgentLaunchController(running: true, process: .current))
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct ContentView: View {
|
|||||||
@Environment(\.openWindow) private var openWindow
|
@Environment(\.openWindow) private var openWindow
|
||||||
@Environment(\.secretStoreList) private var storeList
|
@Environment(\.secretStoreList) private var storeList
|
||||||
@Environment(\.updater) private var updater
|
@Environment(\.updater) private var updater
|
||||||
@Environment(\.agentStatusChecker) private var agentStatusChecker
|
@Environment(\.agentLaunchController) private var agentLaunchController
|
||||||
|
|
||||||
@AppStorage("defaultsHasRunSetup") private var hasRunSetup = false
|
@AppStorage("defaultsHasRunSetup") private var hasRunSetup = false
|
||||||
@State private var showingCreation = false
|
@State private var showingCreation = false
|
||||||
@@ -127,7 +127,7 @@ extension ContentView {
|
|||||||
showingAgentInfo = true
|
showingAgentInfo = true
|
||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
if agentStatusChecker.running {
|
if agentLaunchController.running {
|
||||||
Text(.agentRunningNoticeTitle)
|
Text(.agentRunningNoticeTitle)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
|
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
|
||||||
@@ -145,8 +145,8 @@ extension ContentView {
|
|||||||
})
|
})
|
||||||
.buttonStyle(
|
.buttonStyle(
|
||||||
ToolbarStatusButtonStyle(
|
ToolbarStatusButtonStyle(
|
||||||
lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75),
|
lightColor: agentLaunchController.running ? .black.opacity(0.05) : .red.opacity(0.75),
|
||||||
darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5),
|
darkColor: agentLaunchController.running ? .white.opacity(0.05) : .red.opacity(0.5),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
.popover(isPresented: $showingAgentInfo, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
||||||
|
|||||||
22
Sources/SecretiveUpdater/SecretiveUpdater.entitlements
Normal file
22
Sources/SecretiveUpdater/SecretiveUpdater.entitlements
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?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.enhanced-security-version-string</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>com.apple.security.hardened-process.hardened-heap</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
|
||||||
|
<string>2</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
56
configure_team_id.sh
Executable file
56
configure_team_id.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TEAM_ID_FILE=Sources/Config/OpenSource.xcconfig
|
||||||
|
|
||||||
|
function print_team_ids() {
|
||||||
|
echo ""
|
||||||
|
echo "FYI, here are the team IDs found in your Xcode preferences:"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
XCODEPREFS="$HOME/Library/Preferences/com.apple.dt.Xcode.plist"
|
||||||
|
TEAM_KEYS=(`/usr/libexec/PlistBuddy -c "Print :IDEProvisioningTeams" "$XCODEPREFS" | perl -lne 'print $1 if /^ (\S*) =/'`)
|
||||||
|
|
||||||
|
for KEY in $TEAM_KEYS
|
||||||
|
do
|
||||||
|
i=0
|
||||||
|
while true ; do
|
||||||
|
NAME=$(/usr/libexec/PlistBuddy -c "Print :IDEProvisioningTeams:$KEY:$i:teamName" "$XCODEPREFS" 2>/dev/null)
|
||||||
|
TEAMID=$(/usr/libexec/PlistBuddy -c "Print :IDEProvisioningTeams:$KEY:$i:teamID" "$XCODEPREFS" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$TEAMID - $NAME"
|
||||||
|
|
||||||
|
i=$(($i + 1))
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
print_team_ids
|
||||||
|
echo ""
|
||||||
|
echo "> What is your Apple Developer Team ID? (looks like 1A23BDCD)"
|
||||||
|
read TEAM_ID
|
||||||
|
else
|
||||||
|
TEAM_ID=$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$TEAM_ID" ]; then
|
||||||
|
echo "You must enter a team id"
|
||||||
|
print_team_ids
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Setting team ID to $TEAM_ID"
|
||||||
|
|
||||||
|
echo "// This file was automatically generated, do not edit directly." > $TEAM_ID_FILE
|
||||||
|
echo "" >> $TEAM_ID_FILE
|
||||||
|
echo "SECRETIVE_BASE_BUNDLE_ID_OSS=${TEAM_ID}.com.example.Secretive" >> $TEAM_ID_FILE
|
||||||
|
echo "SECRETIVE_DEVELOPMENT_TEAM_OSS=${TEAM_ID}" >> $TEAM_ID_FILE
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Successfully generated configuration at $TEAM_ID_FILE, you may now build the app using the \"Secretive\" target"
|
||||||
|
echo "You may need to close and re-open the project in Xcode if it's already open"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user