Repository navigation
Description
Background
Stack Encrypt (packages/stack-encrypt) is the Rust engine that every CipherStash language SDK runs on. EQL (Encrypt Query Language) is the SQL library we install into a customer's PostgreSQL database. It stores each encrypted value as a JSON payload in a typed column, such as public.eql_v4_text_eq. The eql-bindings crate (packages/eql/crates/eql-bindings) has one Rust type for each EQL v4 column type, such as TextEq or IntegerOrdOre, and one for each query operand, such as TextEqQuery. The eql-codegen crate generates these types from the EQL catalog.
Stack Encrypt has two sets of encryption traits. The old pair is EncryptFrom and DecryptInto, which encrypt from a borrowed plaintext. The new pair is EncryptTarget and DecryptSource, which take the plaintext by value and wipe it (packages/stack-encrypt/src/target/conversion.rs:18). The Stack Encrypt API plan removes the old pair in #1488.
Problem
Every EQL v4 type implements only the old pair. The generated types derive EncryptFrom and DecryptInto (eql-codegen/src/bindings.rs). The hand-written term types in eql-bindings/src/encryption.rs implement only the old traits too: Ciphertext (lines 211 and 232), Hmac256 (line 275), BloomFilter (line 395) and SearchBloomFilter (line 447). The order terms are in the same position.
So a Rust program cannot encrypt or decrypt an EQL value through the new API. It cannot call value.encrypt_into(&keyset) for a TextEq, or String::decrypt_from(stored, &keyset). Three things wait on this:
- stack-encrypt refactor: remove transitional traits and redundant execution APIs #1488 cannot remove the old traits while EQL depends on them.
- The plan's "EQL types as fields" item, a Rust record with a
TextEqfield, needs the EQL types to implement the new traits first. - The EQL v3 to v4 migration codec in test(protect-ffi): migrate live EQL v3 data to v4 end to end #1496 reaches the EQL types through the by-name dispatch for language bindings, because the typed path it should use is the one being removed.
Pull request #1504 adds the EncryptTarget and DecryptSource derives, and leaves the EQL implementations for later. No issue tracks them.
Proposal
- Have
eql-codegenemit#[derive(EncryptTarget, DecryptSource)]next to the existing derives on every scalar EQL v4 type and every query type. Keep the old derives until stack-encrypt refactor: remove transitional traits and redundant execution APIs #1488: the Go guest and the dynamic targets still use them. - Implement
EncryptTargetandDecryptSourceon the hand-written term types ineql-bindings/src/encryption.rs. - Encrypt takes the column as a required context:
RequiredContext<NonEmpty<Identifier>>, built withIdentifier::for_column(table, column)and stored ini. Decrypt takes the stored column, and checks it against a column the caller passes, if any (ExpectedContext<Identifier>). A value from another column is refused before any key is requested. - Keep both null forms. A field typed
Option<TextEq>mapsNoneto SQL NULL.TextEqoverOption<String>seals a null, as Gov0.1.0-rc.3does. - Run
mise run types:generateinpackages/eql, and commit the result.
Out of scope: the JSON tree types (JsonTreeDocument and the types that open it), and EQL types as fields of a derived record.
Acceptance
- Every scalar EQL v4 type and query type implements
EncryptTarget, and every stored type implementsDecryptSource. - For every type, a test shows that the new path writes the same bytes as the old path, for the same plaintext, column and keyset.
- Both null forms round-trip through the new path, with the same bytes as the old one.
- Decrypting with a different column is refused before any key is requested.
-
types:checkandcargo test -p eql-codegenpass.
Relationship to other work
- Blocked by feat(stack-encrypt)!: derive owned record declarations with EncryptTarget and DecryptSource #1504, which adds the derives (stack-encrypt refactor: migrate record derives and plans to owned declarations #1483).
- Blocks stack-encrypt: a target cannot take its context from the plaintext it encrypts — add
Encryption::context_from, and test a v3-to-v4 migration shim #1586 (Encryption::context_fromand the migration shim test), and the plan's "EQL types as fields" item. - stack-encrypt refactor: remove transitional traits and redundant execution APIs #1488 removes the old derives from these types afterwards.
Activity
ci: keep Biome off the pinned v4 terms fixture
docs(eql-bindings,stack-encrypt): rewrite the prose of the owned EQL …
refactor(eql-bindings): use MatchTerms, not its deprecated alias Matc…