diff --git a/.cursor/commands/deslop.md b/.cursor/commands/deslop.md new file mode 100644 index 0000000..b2bf165 --- /dev/null +++ b/.cursor/commands/deslop.md @@ -0,0 +1,12 @@ +# Remove AI code slop + +Check the diff against main, and remove all AI generated slop introduced in this branch. + +This includes: +- Extra comments that a human wouldn't add or is inconsistent with the rest of the file +- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths) +- Casts to any to get around type issues +- Deeply nested code that should be refactored using early return guards to improve readability +- Any other style that is inconsistent with the file + +Report at the end with only a 1-3 sentence summary of what you changed. diff --git a/.github/scripts/signing.sh b/.github/scripts/signing.sh deleted file mode 100755 index b4799e5..0000000 --- a/.github/scripts/signing.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Import certificate and private key -echo $SIGNING_DATA | base64 -d -o Signing.p12 -security create-keychain -p ci ci.keychain -security default-keychain -s ci.keychain -security list-keychains -s ci.keychain -security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A -security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain - -# Import Profiles -mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles -echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile -HOST_UUID=`grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}"` -cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile -echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile -AGENT_UUID=`grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}"` -cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile - -# Create directories for ASC key -mkdir ~/.private_keys -echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f90208a..7fe7576 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -23,7 +23,22 @@ jobs: AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }} APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - run: ./.github/scripts/signing.sh + run: | + echo $SIGNING_DATA | base64 -d -o Signing.p12 + security create-keychain -p ci ci.keychain + security default-keychain -s ci.keychain + security list-keychains -s ci.keychain + security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile + HOST_UUID=$(grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}") + cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile + echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile + AGENT_UUID=$(grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}") + cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile + mkdir ~/.private_keys + echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 - name: Set Environment run: sudo xcrun xcode-select -s /Applications/Xcode_26.1.app - name: Update Build Number @@ -34,32 +49,70 @@ jobs: sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/g" Sources/Config/Config.xcconfig sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig - - name: Build + - name: Build App 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 }} + - name: Build CLI + run: swift build -c release --product SecretiveCLI --package-path Sources/Packages + - name: Codesign CLI run: | - curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \ - https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip + CLI_BINARY="Sources/Packages/.build/release/SecretiveCLI" + ENTITLEMENTS="Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements" + IDENTITY=$(security find-identity -p codesigning -v 2>/dev/null | grep "Developer ID Application" | head -n1 | awk -F'"' '{print $2}') + codesign --force --options runtime --sign "$IDENTITY" --identifier "com.maxgoedjen.Secretive.Host" --entitlements "$ENTITLEMENTS" "$CLI_BINARY" + - name: Prepare Artifact Folder + run: | + mkdir -p Artifact/App + mkdir -p Artifact/CLI + cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact/App/ + cp Sources/Packages/.build/release/SecretiveCLI Artifact/CLI/secretive + - name: Build Installer Package + run: | + pkgbuild --root Artifact/App --install-location /Applications --identifier com.maxgoedjen.Secretive.app --version 1.0 App.pkg + pkgbuild --root Artifact/CLI --install-location /usr/local/bin --identifier com.maxgoedjen.Secretive.cli --version 1.0 CLI.pkg + cat > distribution.xml << 'EOF' + + + Secretive + com.maxgoedjen + + + + + + + + + + + + + + + + + + App.pkg + CLI.pkg + + EOF + productbuild --distribution distribution.xml --package-path . Secretive-unsigned.pkg + INSTALLER_IDENTITY=$(security find-identity -p basic -v 2>/dev/null | grep "Developer ID Installer" | head -n1 | awk -F'"' '{print $2}') + productsign --sign "$INSTALLER_IDENTITY" Secretive-unsigned.pkg Secretive.pkg - 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 + run: | + xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER --wait Secretive.pkg + xcrun stapler staple Secretive.pkg + - name: Upload Installer to Artifacts + id: upload + uses: actions/upload-artifact@v4 + with: + name: Secretive.pkg + path: Secretive.pkg - name: Attest id: attest uses: actions/attest-build-provenance@v2 with: - subject-name: "Secretive.zip" - subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }} + subject-path: "Secretive.pkg" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77aebf4..dca11bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,22 @@ jobs: AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }} APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - run: ./.github/scripts/signing.sh + run: | + echo $SIGNING_DATA | base64 -d -o Signing.p12 + security create-keychain -p ci ci.keychain + security default-keychain -s ci.keychain + security list-keychains -s ci.keychain + security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile + HOST_UUID=$(grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}") + cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile + echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile + AGENT_UUID=$(grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}") + cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile + mkdir ~/.private_keys + echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 - name: Set Environment run: sudo xcrun xcode-select -s /Applications/Xcode_26.1.app - name: Test @@ -45,7 +60,22 @@ jobs: AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }} APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - run: ./.github/scripts/signing.sh + run: | + echo $SIGNING_DATA | base64 -d -o Signing.p12 + security create-keychain -p ci ci.keychain + security default-keychain -s ci.keychain + security list-keychains -s ci.keychain + security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile + HOST_UUID=$(grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}") + cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile + echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile + AGENT_UUID=$(grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}") + cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile + mkdir ~/.private_keys + echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 - name: Set Environment run: sudo xcrun xcode-select -s /Applications/Xcode_26.1.app - name: Update Build Number @@ -57,34 +87,73 @@ jobs: sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Sources/Config/Config.xcconfig sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig sed -i '' -e "s/GITHUB_BUILD_URL/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig - - name: Build + - name: Build App 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.zip - path: Artifact - - name: Download Zipped Artifact - id: download - env: - ZIP_ID: ${{ steps.upload.outputs.artifact-id }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build CLI + run: swift build -c release --product SecretiveCLI --package-path Sources/Packages + - name: Codesign CLI run: | - curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \ - https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip + CLI_BINARY="Sources/Packages/.build/release/SecretiveCLI" + ENTITLEMENTS="Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements" + IDENTITY=$(security find-identity -p codesigning -v 2>/dev/null | grep "Developer ID Application" | head -n1 | awk -F'"' '{print $2}') + codesign --force --options runtime --sign "$IDENTITY" --identifier "com.maxgoedjen.Secretive.Host" --entitlements "$ENTITLEMENTS" "$CLI_BINARY" + - name: Prepare Artifact Folder + run: | + mkdir -p Artifact/App + mkdir -p Artifact/CLI + cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact/App/ + cp Sources/Packages/.build/release/SecretiveCLI Artifact/CLI/secretive + - name: Build Installer Package + run: | + pkgbuild --root Artifact/App --install-location /Applications --identifier com.maxgoedjen.Secretive.app --version 1.0 App.pkg + pkgbuild --root Artifact/CLI --install-location /usr/local/bin --identifier com.maxgoedjen.Secretive.cli --version 1.0 CLI.pkg + cat > distribution.xml << 'EOF' + + + Secretive + com.maxgoedjen + + + + + + + + + + + + + + + + + + App.pkg + CLI.pkg + + EOF + productbuild --distribution distribution.xml --package-path . Secretive-unsigned.pkg + INSTALLER_IDENTITY=$(security find-identity -p basic -v 2>/dev/null | grep "Developer ID Installer" | head -n1 | awk -F'"' '{print $2}') + productsign --sign "$INSTALLER_IDENTITY" Secretive-unsigned.pkg Secretive.pkg - 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 + run: | + xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER --wait Secretive.pkg + xcrun stapler staple Secretive.pkg + - name: Upload Installer to Artifacts + id: upload + uses: actions/upload-artifact@v4 + with: + name: Secretive.pkg + path: Secretive.pkg - name: Attest id: attest uses: actions/attest-build-provenance@v2 with: - subject-path: "Secretive.zip" + subject-path: "Secretive.pkg" - name: Create Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -95,4 +164,4 @@ jobs: 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 + gh release upload $TAG_NAME Secretive.pkg diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..104a6b4 --- /dev/null +++ b/Makefile @@ -0,0 +1,89 @@ +# Creates a dev package containing the Secretive app and CLI +# Usage: make + +PROJECT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +BUILD_DIR := $(PROJECT_DIR)/build + +ARCHIVE := $(BUILD_DIR)/Archive.xcarchive +APP_BUNDLE := $(BUILD_DIR)/Secretive.app +APP_ROOT := $(BUILD_DIR)/AppPayload +APP_PKG := $(BUILD_DIR)/App.pkg +CLI_BIN := $(BUILD_DIR)/SecretiveCLI +CLI_ROOT := $(BUILD_DIR)/CLIPayload +CLI_PKG := $(BUILD_DIR)/CLI.pkg +DIST := $(BUILD_DIR)/distribution.xml +FINAL_PKG := $(BUILD_DIR)/Secretive-dev-unsigned.pkg + +XCODEBUILD := xcodebuild -project $(PROJECT_DIR)/Sources/Secretive.xcodeproj + +.PHONY: all clean + +all: $(FINAL_PKG) + @echo "Built: $(FINAL_PKG)" + +$(ARCHIVE): + @mkdir -p $(BUILD_DIR) + $(XCODEBUILD) -scheme Secretive -configuration Release CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" -archivePath $(ARCHIVE) archive + +$(APP_BUNDLE): $(ARCHIVE) + @rm -rf $(APP_BUNDLE) + cp -R $(ARCHIVE)/Products/Applications/Secretive.app $(APP_BUNDLE) + +$(CLI_BIN): + @mkdir -p $(BUILD_DIR) + cd $(PROJECT_DIR)/Sources/Packages && xcodebuild -scheme SecretiveCLI -configuration Release \ + -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" \ + SYMROOT=$(BUILD_DIR)/xcode-cli build + cp $(BUILD_DIR)/xcode-cli/Release/SecretiveCLI $(CLI_BIN) + cp -R $(BUILD_DIR)/xcode-cli/Release/*.bundle $(BUILD_DIR)/ 2>/dev/null || true + +$(APP_ROOT): $(APP_BUNDLE) + @rm -rf $(APP_ROOT) + @mkdir -p $(APP_ROOT) + cp -R $(APP_BUNDLE) $(APP_ROOT)/ + +$(CLI_ROOT): $(CLI_BIN) + @rm -rf $(CLI_ROOT) + @mkdir -p $(CLI_ROOT) + cp $(CLI_BIN) $(CLI_ROOT)/secretive + +$(APP_PKG): $(APP_ROOT) + pkgbuild --root $(APP_ROOT) --install-location /Applications --identifier com.maxgoedjen.Secretive.app --version 0.0.0-dev $(APP_PKG) + +$(CLI_PKG): $(CLI_ROOT) + pkgbuild --root $(CLI_ROOT) --install-location /usr/local/bin --identifier com.maxgoedjen.Secretive.cli --version 0.0.0-dev $(CLI_PKG) + +$(DIST): + @mkdir -p $(BUILD_DIR) + @printf '%s\n' \ + '' \ + '' \ + ' Secretive (Dev)' \ + ' com.maxgoedjen' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' ' \ + ' App.pkg' \ + ' CLI.pkg' \ + '' \ + > $(DIST) + +$(FINAL_PKG): $(APP_PKG) $(CLI_PKG) $(DIST) + productbuild --distribution $(DIST) --package-path $(BUILD_DIR) $(FINAL_PKG) + +clean: + rm -rf $(BUILD_DIR) diff --git a/Sources/Packages/Package.swift b/Sources/Packages/Package.swift index 44c0323..248b963 100644 --- a/Sources/Packages/Package.swift +++ b/Sources/Packages/Package.swift @@ -46,15 +46,21 @@ let package = Package( dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"], swiftSettings: swiftSettings, ), + .target( + name: "Localizations", + dependencies: [], + path: "Sources/SecretiveCLI/Generated", + swiftSettings: swiftSettings, + ), .target( name: "SecureEnclaveSecretKit", - dependencies: ["SecretKit"], + dependencies: ["SecretKit", "Localizations"], resources: [localization], swiftSettings: swiftSettings, ), .target( name: "SmartCardSecretKit", - dependencies: ["SecretKit"], + dependencies: ["SecretKit", "Localizations"], resources: [localization], swiftSettings: swiftSettings, ), @@ -93,9 +99,12 @@ let package = Package( dependencies: [ "SecretAgentKit", "SecureEnclaveSecretKit", + "SmartCardSecretKit", "SecretKit", "Common", + "Localizations", ], + exclude: ["Generated"], swiftSettings: swiftSettings, ), ] diff --git a/Sources/Packages/Sources/SecretiveCLI/Generated/Localizations.swift b/Sources/Packages/Sources/SecretiveCLI/Generated/Localizations.swift new file mode 100644 index 0000000..137b4ae --- /dev/null +++ b/Sources/Packages/Sources/SecretiveCLI/Generated/Localizations.swift @@ -0,0 +1,38 @@ +// Copyright Anysphere Inc. +// Localization extensions for String.LocalizationValue +// These are normally generated by Xcode from String Catalogs, but SPM doesn't do this automatically. + +import Foundation + +public extension String.LocalizationValue { + + // MARK: - Store Names + + static var secureEnclave: String.LocalizationValue { + "secure_enclave" + } + + static var smartCard: String.LocalizationValue { + "smart_card" + } + + // MARK: - Secret Names + + static var unnamedSecret: String.LocalizationValue { + "unnamed_secret" + } + + // MARK: - Authentication Context + + static var authContextRequestDenyButton: String.LocalizationValue { + "auth_context_request_deny_button" + } + + static func authContextRequestSignatureDescription(appName: String, secretName: String) -> String.LocalizationValue { + "auth_context_request_signature_description \(appName) \(secretName)" + } + + static func authContextPersistForDuration(secretName: String, duration: String) -> String.LocalizationValue { + "auth_context_persist_for_duration \(secretName) \(duration)" + } +} diff --git a/Sources/Packages/Sources/SecretiveCLI/README.md b/Sources/Packages/Sources/SecretiveCLI/README.md index 3967684..aa80aa6 100644 --- a/Sources/Packages/Sources/SecretiveCLI/README.md +++ b/Sources/Packages/Sources/SecretiveCLI/README.md @@ -2,23 +2,31 @@ A command-line interface for Secretive that provides full key management and SSH agent functionality, sharing the same keychain and socket path as the GUI application. -## Building +## Installation -Build the CLI using Swift Package Manager: +The CLI is distributed as part of the Secretive installer package (`.pkg`). When you install Secretive via the pkg installer, the CLI binary is automatically installed to `/usr/local/bin/secretive`. + +Download the latest release from [GitHub Releases](https://github.com/maxgoedjen/secretive/releases). + +## Building (Development) + +For local development, build the CLI using Swift Package Manager: ```bash -swift build -c release --product SecretiveCLI +swift build -c release --product SecretiveCLI --package-path Sources/Packages ``` -The binary will be located at `.build/release/SecretiveCLI`. +The binary will be located at `Sources/Packages/.build/release/SecretiveCLI`. -## Code Signing +**Note:** Production builds and code signing are handled automatically by GitHub Actions. See `.github/workflows/release.yml` and `.github/workflows/nightly.yml` for details. -To use the CLI with the same keychain access as the GUI app, you must sign it with the same bundle identifier and entitlements. +## Code Signing (Development) + +For local development with keychain access, you must sign the CLI with the same bundle identifier and entitlements as the GUI app. ### Required Entitlements -The CLI must be signed with entitlements that include the same keychain access group as the GUI app. Create an entitlements file (e.g., `SecretiveCLI.entitlements`) with: +The CLI uses the entitlements file at `SecretiveCLI.entitlements`: ```xml @@ -44,8 +52,8 @@ codesign --force \ --sign "Developer ID Application: YOUR_TEAM_NAME" \ --options runtime \ --identifier com.maxgoedjen.Secretive.Host \ - --entitlements SecretiveCLI.entitlements \ - .build/release/SecretiveCLI + --entitlements Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements \ + Sources/Packages/.build/release/SecretiveCLI ``` Replace `YOUR_TEAM_NAME` with your actual Developer ID or use your team's signing identity. @@ -60,22 +68,22 @@ Install and manage the SSH agent as a launchd service: ```bash # Install the agent as a launchd service -secretive-cli agent install +secretive agent install # Start the agent -secretive-cli agent start +secretive agent start # Check agent status -secretive-cli agent status +secretive agent status # Stop the agent -secretive-cli agent stop +secretive agent stop # Uninstall the agent -secretive-cli agent uninstall +secretive agent uninstall # Run agent in foreground (for testing) -secretive-cli agent run +secretive agent run ``` ### Key Management @@ -84,19 +92,19 @@ Manage SSH keys stored in the Secure Enclave: ```bash # Generate a new key -secretive-cli key generate "My Key Name" +secretive key generate "My Key Name" # List all keys -secretive-cli key list +secretive key list # Show public key for a specific key -secretive-cli key show "My Key Name" +secretive key show "My Key Name" # Delete a key -secretive-cli key delete "My Key Name" +secretive key delete "My Key Name" # Update key (note: attributes cannot be changed after creation) -secretive-cli key update "My Key Name" +secretive key update "My Key Name" ``` ## Socket Path @@ -126,4 +134,3 @@ This is achieved by signing the CLI with the same bundle identifier (`com.maxgoe - Keys created via CLI will appear in the GUI app and vice versa - The agent can be run either via launchd (recommended) or in foreground mode for testing - All key operations require appropriate authentication (Touch ID, Apple Watch, or password) as configured per key - diff --git a/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements new file mode 100644 index 0000000..246a51d --- /dev/null +++ b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements @@ -0,0 +1,13 @@ + + + + + com.apple.security.smartcard + + keychain-access-groups + + $(AppIdentifierPrefix)com.maxgoedjen.Secretive + + + + diff --git a/Sources/Packages/Sources/SecretiveCLI/main.swift b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.swift similarity index 95% rename from Sources/Packages/Sources/SecretiveCLI/main.swift rename to Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.swift index c70c334..69f47d0 100644 --- a/Sources/Packages/Sources/SecretiveCLI/main.swift +++ b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.swift @@ -231,23 +231,25 @@ extension SecretiveCLI { logger.info("SSH agent listening on \(socketPath)") print("SSH agent running on \(socketPath)") print("Set SSH_AUTH_SOCK=\(socketPath) to use this agent") + + func handleSession(_ session: SocketController.Session) async { + do { + for await message in session.messages { + let request = try parser.parse(data: message) + let response = await agent.handle(request: request, provenance: session.provenance) + try await MainActor.run { + try session.write(response) + } + } + } catch { + logger.error("Session error: \(error.localizedDescription)") + try? session.close() + } + } // Handle sessions for await session in socket.sessions { - Task { - do { - for await message in session.messages { - let request = try parser.parse(data: message) - let response = await agent.handle(request: request, provenance: session.provenance) - try await MainActor.run { - try session.write(response) - } - } - } catch { - logger.error("Session error: \(error.localizedDescription)") - try? session.close() - } - } + await handleSession(session) } }