Review tenant encryption-key rotation

src/key-rotation-types.tsTypeScript
@@ -0,0 +1,96 @@
1+export interface EncryptedDocument {
2+ id: string;
3+ tenantId: string;
4+ revision: number;
5+ keyId: string;
6+ ciphertext: Uint8Array;
7+ quarantined: boolean;
8+}
9+
10+export type KeyRotationPhase =
11+ | "created"
12+ | "target-active"
13+ | "scanning"
14+ | "verifying"
15+ | "complete";
16+
17+export interface KeyRotationRecord {
18+ tenantId: string;
19+ sourceKeyId: string;
20+ targetKeyId: string;
21+ phase: KeyRotationPhase;
22+ checkpoint?: string;
23+ writerEpoch?: number;
24+}
25+
26+export interface TenantRotationLease {
27+ readonly fencingToken: number;
28+ release(): Promise<void>;
29+}
30+
31+export interface KeyRotationDependencies {
32+ keys: {
33+ active(tenantId: string): Promise<string>;
34+ create(tenantId: string): Promise<string>;
35+ activate(tenantId: string, keyId: string): Promise<void>;
36+ retire(tenantId: string, keyId: string): Promise<void>;
37+ };
38+ cryptography: {
39+ decrypt(keyId: string, ciphertext: Uint8Array): Promise<Uint8Array>;
40+ encrypt(keyId: string, plaintext: Uint8Array): Promise<Uint8Array>;
41+ };
42+ documents: {
43+ scan(tenantId: string, afterId: string | undefined, limit: number): Promise<EncryptedDocument[]>;
44+ put(document: EncryptedDocument): Promise<void>;
45+ countUsingKey(tenantId: string, keyId: string): Promise<number>;
46+ get(
47+ tenantId: string,
48+ documentId: string,
49+ ): Promise<EncryptedDocument | undefined>;
50+ compareAndSwapReencrypted(
51+ tenantId: string,
52+ documentId: string,
53+ expectedRevision: number,
54+ replacement: { keyId: string; ciphertext: Uint8Array },
55+ ): Promise<"updated" | "revision-conflict">;
56+ countNonQuarantinedUsingKey(
57+ tenantId: string,
58+ keyId: string,
59+ ): Promise<number>;
60+ };
61+ checkpoints: {
62+ load(tenantId: string): Promise<string | undefined>;
63+ save(tenantId: string, documentId: string): Promise<void>;
64+ };
65+ leases: {
66+ acquire(tenantId: string): Promise<TenantRotationLease>;
67+ };
68+ rotations: {
69+ startOrResume(
70+ tenantId: string,
71+ lease: TenantRotationLease,
72+ ): Promise<KeyRotationRecord>;
73+ save(
74+ record: KeyRotationRecord,
75+ lease: TenantRotationLease,
76+ ): Promise<void>;
77+ };
78+ quarantine: {
79+ record(input: {
80+ tenantId: string;
81+ documentId: string;
82+ revision: number;
83+ keyId: string;
84+ reason: "decrypt-failed";
85+ }): Promise<void>;
86+ countReferencingKey(tenantId: string, keyId: string): Promise<number>;
87+ };
88+ writers: {
89+ activateTarget(
90+ tenantId: string,
91+ targetKeyId: string,
92+ lease: TenantRotationLease,
93+ ): Promise<number>;
94+ waitUntilAcknowledged(tenantId: string, writerEpoch: number): Promise<void>;
95+ };
96+}