diff --git a/passKit/Models/PasswordStore.swift b/passKit/Models/PasswordStore.swift index 10e5220..65bf31c 100644 --- a/passKit/Models/PasswordStore.swift +++ b/passKit/Models/PasswordStore.swift @@ -445,7 +445,7 @@ public class PasswordStore { // Walk up the directory hierarchy, but never escape the store root let storeRoot = storeURL.absoluteURL.resolvingSymlinksInPath() var current = storeRoot.appendingPathComponent(relativePath).resolvingSymlinksInPath() - + while current.path.hasPrefix(storeRoot.path) { let candidate = current.appendingPathComponent(".gpg-id") var isDir: ObjCBool = false diff --git a/plans/02-multi-recipient-encryption-plan.md b/plans/02-multi-recipient-encryption-plan.md index 28defd4..132cfce 100644 --- a/plans/02-multi-recipient-encryption-plan.md +++ b/plans/02-multi-recipient-encryption-plan.md @@ -30,7 +30,7 @@ The codebase does **not** support encrypting to multiple public keys. Every laye ### 1. `findGPGID(from:) -> [String]` -Split file contents by newline, trim each line, filter empty lines. Return array of key IDs. Callers that only need a single key (e.g. for decryption routing) can use `.first`. +Split file contents by newline, trim each line, filter empty lines. Return array of key IDs. ### 2. `PGPInterface` protocol @@ -60,10 +60,10 @@ When a store lists multiple key IDs in `.gpg-id`, the user needs the public keys ### Current state -- The keychain holds exactly **one** `pgpPublicKey` blob and **one** `pgpPrivateKey` blob. -- The import UI (armor paste, URL, file picker) has one public key field + one private key field. Importing **replaces** the previous key pair entirely. -- Both `GopenPGPInterface` and `ObjectivePGPInterface` *can* parse multiple keys from a single armored blob (e.g. concatenated armor blocks). So if the user pastes multiple public keys into the single field, they would be parsed — but the encrypt path only uses one key, and the UI doesn't communicate this. -- There is no UI for viewing which key IDs are loaded. +- The keychain holds **one** `pgpPublicKey` blob and **one** `pgpPrivateKey` blob, but each can contain **multiple concatenated armored key blocks**. Both interface implementations parse all keys from these blobs. +- The import UI (armor paste, URL, file picker) has one public key field + one private key field. Importing **replaces** the set of keys entirely, there is no append mode for adding additional keys or managing existing keys. +- There is no UI for viewing which key IDs are loaded or for importing additional recipient-only public keys, nor for viewing the key metadata. +- There is no UI for viewing or editing `.gpg-id` files, which are the source of truth for which keys are used for encryption. ### Key storage approach @@ -142,20 +142,19 @@ This can be expensive for large directories. Show progress and allow cancellatio ## Implementation Order -| Step | Description | Depends On | -|------|-------------|------------| -| 1 | `findGPGID` returns `[String]` + update callers | — | -| 2 | `PGPInterface` protocol change (`keyIDs: [String]?`) | — | -| 3 | `GopenPGPInterface` multi-key encryption | Step 2 | -| 4 | `ObjectivePGPInterface` multi-key encryption | Step 2 | -| 5 | `PGPAgent` updated overloads | Steps 2-4 | -| 6 | `PasswordStore.encrypt()` uses `[String]` from `findGPGID` | Steps 1+5 | -| 7 | UI: import additional recipient public keys | Step 5 | -| 8 | UI: view loaded key IDs | Step 5 | -| 9a | UI: view `.gpg-id` in password detail / folder view | Step 1 | -| 9b | UI: edit `.gpg-id` | Step 9a | -| 10 | Re-encryption when `.gpg-id` changes | Steps 6+9b | -| T | Tests (see testing section) | Steps 1-10 | +| Step | Description | Status | Depends On | +|------|-------------|--------|------------| +| 1 | `findGPGIDs` returns `[String]` + update callers | ✅ Done | — | +| 2 | `PGPInterface` protocol change (`keyIDs: [String]`) | ✅ Done | — | +| 3 | `GopenPGPInterface` multi-key encryption | ✅ Done | Step 2 | +| 4 | `ObjectivePGPInterface` multi-key encryption | ✅ Done | Step 2 | +| 5 | `PGPAgent` updated overloads | ✅ Done | Steps 2-4 | +| 6 | `PasswordStore.encrypt()` uses `[String]` from `findGPGIDs` | ✅ Done | Steps 1+5 | +| 7 | UI: import additional recipient public keys | Not started | Step 5 | +| 8 | UI: view loaded key IDs and metadata | Not started | Step 5 | +| 9a | UI: view `.gpg-id` in password detail / folder view | Not started | Step 1 | +| 9b | UI: edit `.gpg-id` | Not started | Step 9a | +| 10 | Re-encryption when `.gpg-id` changes | Not started | Steps 6+9b | ---