Skip to content

ORE Operators #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 24, 2025
Merged

ORE Operators #83

merged 4 commits into from
Jan 24, 2025

Conversation

tobyhede
Copy link
Contributor

@tobyhede tobyhede commented Jan 22, 2025

Add ORE operators for the cs_encrypted_v1 type
Also generalises equality operators to support both unique and ore indexes.

Addresses:
https://linear.app/cipherstash/issue/CIP-1148/implement-ore-operators-for-cs-encrypted-v1
https://linear.app/cipherstash/issue/CIP-1149/equality-operator-needs-to-handle-both-unique-and-ore-index-types

@tobyhede tobyhede changed the title I don't want to talk about it ORE Operators Jan 23, 2025
@tobyhede tobyhede force-pushed the CIP-1148/i-dont-want-to-talk-about-it branch 2 times, most recently from da620ec to bfa139f Compare January 23, 2025 05:59
@tobyhede tobyhede force-pushed the CIP-1148/i-dont-want-to-talk-about-it branch from bfa139f to 7442ae8 Compare January 23, 2025 06:04
o boolean;
BEGIN
BEGIN
u := (SELECT cs_unique_v1(a) = cs_unique_v1(b));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% what the threat model might be but its worth noting that this comparison won't run in constant time. It might leave the possibility for an attacker to learn the value of the plaintext in certain circumstances.

There's a constant time comparison function in the EQL repo somewhere I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this:

CREATE FUNCTION __bytea_ct_eq(a bytea, b bytea) RETURNS boolean AS $$

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cs_unique_v1 is the existing function, and simply extracts the index value from the eql::ciphertext

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unique indexes are cast as text, so this ends up using the default text eq operators.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's what I figured. The standard equality operator will bail as soon as a character differs which can lead to timing side-channel attacks. A constant time comparison should always take the same amount of time regardless of which character index differs first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right. We should fix that for unique in general then

END;

BEGIN
o := (SELECT cs_ore_64_8_v1(a) = cs_ore_64_8_v1(b));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cs_ore_64_8_v1 is same as unique, pulls the field from the json
The returned data is cast as ore_64_8_v1 and then the underlying ore operators are used for the comparison

Comment on lines +1 to +57
DROP OPERATOR IF EXISTS @> (cs_encrypted_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS @> (cs_encrypted_v1, cs_match_index_v1);
DROP OPERATOR IF EXISTS @> (cs_match_index_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS <@ (cs_encrypted_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS <@ (cs_encrypted_v1, cs_match_index_v1);
DROP OPERATOR IF EXISTS <@ (cs_match_index_v1, cs_encrypted_v1);


DROP OPERATOR IF EXISTS <= (ore_64_8_v1, cs_encrypted_v1);
DROP OPERATOR IF EXISTS <= (cs_encrypted_v1, ore_64_8_v1);
DROP OPERATOR IF EXISTS <= (jsonb, cs_encrypted_v1);
DROP OPERATOR IF EXISTS <= (cs_encrypted_v1, jsonb);
DROP OPERATOR IF EXISTS <= (cs_encrypted_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS >=(ore_64_8_v1, cs_encrypted_v1);
DROP OPERATOR IF EXISTS >= (jsonb, cs_encrypted_v1);
DROP OPERATOR IF EXISTS >= (cs_encrypted_v1, ore_64_8_v1);
DROP OPERATOR IF EXISTS >= (cs_encrypted_v1, jsonb);
DROP OPERATOR IF EXISTS >=(cs_encrypted_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS <(ore_64_8_v1, cs_encrypted_v1);
DROP OPERATOR IF EXISTS <(cs_encrypted_v1, ore_64_8_v1);
DROP OPERATOR IF EXISTS < (jsonb, cs_encrypted_v1);
DROP OPERATOR IF EXISTS < (cs_encrypted_v1, jsonb);
DROP OPERATOR IF EXISTS < (cs_encrypted_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS > (ore_64_8_v1, cs_encrypted_v1);
DROP OPERATOR IF EXISTS > (jsonb, cs_encrypted_v1);
DROP OPERATOR IF EXISTS > (cs_encrypted_v1, ore_64_8_v1);
DROP OPERATOR IF EXISTS > (cs_encrypted_v1, jsonb);
DROP OPERATOR IF EXISTS > (cs_encrypted_v1, cs_encrypted_v1);


DROP OPERATOR IF EXISTS = (cs_encrypted_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS = (cs_encrypted_v1, jsonb);
DROP OPERATOR IF EXISTS = (jsonb, cs_encrypted_v1);

DROP OPERATOR IF EXISTS = (cs_encrypted_v1, cs_unique_index_v1);
DROP OPERATOR IF EXISTS = (cs_unique_index_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS = (cs_encrypted_v1, ore_64_8_v1);
DROP OPERATOR IF EXISTS = (ore_64_8_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS <> (cs_encrypted_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS <> (cs_encrypted_v1, jsonb);
DROP OPERATOR IF EXISTS <> (jsonb, cs_encrypted_v1);

DROP OPERATOR IF EXISTS <> (cs_encrypted_v1, cs_unique_index_v1);
DROP OPERATOR IF EXISTS <> (cs_unique_index_v1, cs_encrypted_v1);

DROP OPERATOR IF EXISTS <> (ore_64_8_v1, cs_encrypted_v1);
DROP OPERATOR IF EXISTS <> (cs_encrypted_v1, ore_64_8_v1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I haven't checked that you've captured all the operators here. I'm assuming running the script will show if that's not the case!

@@ -0,0 +1,90 @@
\set ON_ERROR_STOP on
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL.

-- MATCH <@ OPERATORS
DO $$
BEGIN
-- SANITY CHECK FOR UNIQUE payloads
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the comment. Is that a copy-paste error or am I missing something about what this test does?

Comment on lines +60 to +62
ASSERT (SELECT EXISTS (
SELECT id FROM users WHERE name_encrypted < ore_cs_encrypted::jsonb
));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are fine but in my experience ORE tests with hard-coded values can give false confidence. Because an incorrect implementation will still "work" ~50% of the time. Not for this PR but we may want to think about how to generate random ORE encrypted values for every test (like a property test) to validate correctness more robustly.

Copy link
Contributor

@coderdan coderdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EPIC PR. Nice work.

A couple of suggestions for future improvements.

@tobyhede tobyhede merged commit f919fb3 into main Jan 24, 2025
4 checks passed
@tobyhede tobyhede deleted the CIP-1148/i-dont-want-to-talk-about-it branch January 24, 2025 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants