From d22712aca31bb7eaa17c70a12758cf0073a4d22d Mon Sep 17 00:00:00 2001 From: Bradley Mackey Date: Tue, 15 Sep 2026 10:40:43 +0400 Subject: [PATCH] Pin the KDF parameter chains for every production deriver Backup.Secure.v1 had no drift protection because a pinned key vector costs minutes of KDF. uniqueAlgorithmIdentifier already encodes the whole chain, so pin the exact identifier strings, the persisted signature raw values, and the signature lookup table instead. Co-Authored-By: Claude Fable 5 --- .../VaultKeyDeriverParameterPinTests.swift | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Vault/Tests/VaultKeygenTests/VaultKeyDeriverParameterPinTests.swift diff --git a/Vault/Tests/VaultKeygenTests/VaultKeyDeriverParameterPinTests.swift b/Vault/Tests/VaultKeygenTests/VaultKeyDeriverParameterPinTests.swift new file mode 100644 index 000000000..6ed7eed76 --- /dev/null +++ b/Vault/Tests/VaultKeygenTests/VaultKeyDeriverParameterPinTests.swift @@ -0,0 +1,65 @@ +import Foundation +import Testing +import VaultKeygen + +/// Pins the exact KDF chain — algorithm order, key length, iteration +/// counts, variants, and cost factors — for every production deriver. +/// +/// `uniqueAlgorithmIdentifier` encodes all parameters (including nesting +/// via `COMBINATION<...|...>`), so any drift in the chains fails here on +/// every run. This exists because a full pinned-vector test of +/// `Backup.Secure.v1` costs minutes of KDF per run and would rot skipped; +/// the fast pinned vectors in `VaultKeyDeriverTests` prove the shared +/// composition machinery, and this test pins the secure parameters. +struct VaultKeyDeriverParameterPinTests { + @Test + func backupSecureV1_pinsExactKDFChain() { + // The deriver that protects stolen backups. Changing any of these + // parameters is a new keygen VERSION (a new signature), never an + // edit to v1 — existing backups derive with these exact values. + #expect(VaultKeyDeriver.Backup.Secure.v1 + .uniqueAlgorithmIdentifier == + "COMBINATION|HKDF|SCRYPT>") + } + + @Test + func backupFastV1_pinsExactKDFChain() { + #expect(VaultKeyDeriver.Backup.Fast.v1 + .uniqueAlgorithmIdentifier == + "COMBINATION|HKDF|SCRYPT>") + } + + @Test + func itemSecureV1_pinsExactKDFChain() { + #expect(VaultKeyDeriver.Item.Secure.v1 + .uniqueAlgorithmIdentifier == + "COMBINATION|PBKDF2>") + } + + @Test + func itemFastV1_pinsExactKDFChain() { + #expect(VaultKeyDeriver.Item.Fast.v1 + .uniqueAlgorithmIdentifier == + "COMBINATION|PBKDF2>") + } + + @Test + func signatureIDs_areStable() { + // These raw values are persisted in backups and the keychain to + // look up the deriver at decrypt time — they can never change. + #expect(VaultKeyDeriver.Signature.backupSecureV1.rawValue == "vault.keygen.backup.secure.v1") + #expect(VaultKeyDeriver.Signature.backupFastV1.rawValue == "vault.keygen.backup.fast.v1") + #expect(VaultKeyDeriver.Signature.itemSecureV1.rawValue == "vault.keygen.item.secure.v1") + #expect(VaultKeyDeriver.Signature.itemFastV1.rawValue == "vault.keygen.item.fast.v1") + #expect(VaultKeyDeriver.Signature.testing.rawValue == "vault.keygen.testing") + #expect(VaultKeyDeriver.Signature.failing.rawValue == "vault.keygen.failing") + } + + @Test + func lookup_returnsDeriverMatchingEverySignature() { + for signature in VaultKeyDeriver.Signature.allCases { + let deriver = VaultKeyDeriver.lookup(signature: signature) + #expect(deriver.signature == signature) + } + } +}