Skip to content

Commit a3a2941

Browse files
authored
Merge pull request #231 from cipherstash/check-column-configuration-on-decrypt
feat: check column configuration against encrypted column before decrypt
2 parents adb080f + 81cff57 commit a3a2941

4 files changed

Lines changed: 71 additions & 3 deletions

File tree

docs/errors.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- [Unknown column](#encrypt-unknown-column)
2222
- [Unknown table](#encrypt-unknown-table)
2323
- [Unknown index term](#encrypt-unknown-index-term)
24+
- [Column configuration mismatch](#encrypt-column-config-mismatch)
2425

2526
- Decrypt errors:
2627
- [Column could not be deserialised](#encrypt-column-could-not-be-deserialised)
@@ -392,6 +393,34 @@ Unknown Index Term for column '{column_name}' in table '{table_name}'.
392393

393394

394395
<!-- ---------------------------------------------------------------------------------------------------- -->
396+
<!-- ---------------------------------------------------------------------------------------------------- -->
397+
398+
399+
## Column configuration mismatch <a id='encrypt-column-config-mismatch'></a>
400+
401+
A returned encrypted column does not match the column configuration.
402+
403+
### Error message
404+
405+
```
406+
Column configuration for column '{column_name}' in table '{table_name}' does not match the encrypted column.
407+
```
408+
409+
### Notes
410+
411+
CipherStash Proxy validates that encrypted columns match the configuration before decrypting any data.
412+
If the table and column are not the same, this error is returned.
413+
The check is there to help prevent "confused deputy" issues and the error should *never* appear during normal operation.
414+
415+
If the error persists, please contact CipherStash [support](https://cipherstash.com/support).
416+
417+
418+
### Further reading
419+
420+
[AWS: The confused deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html)
421+
[Wikipedia: Confused deputy problem](https://en.wikipedia.org/wiki/Confused_deputy_problem)
422+
423+
<!-- ---------------------------------------------------------------------------------------------------- -->
395424

396425

397426

docs/how-to.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ This will output the version of EQL installed.
163163
In your existing PostgreSQL database, you store your data in tables and columns.
164164
Those columns have types like `integer`, `text`, `timestamp`, and `boolean`.
165165
When storing encrypted data in PostgreSQL with Proxy, you use a special column type called `eql_v1_encrypted`, which is [provided by EQL](#setting-up-the-database-schema).
166-
`eql_v1_encrypted` is a container column type that can be used for any type of encrypted data you want to store or search, whether they are numbers (`int`, `small_int`, `big_int`), text (`text`), dates and times (`date`), or booleans (`boolean`).
166+
`eql_v1_encrypted` is a container column type that can be used for any type of encrypted data you want to store or search, whether they are numbers (`int`, `small_int`, `big_int`), text (`text`), dates and times (`date`. `timestamp`), or booleans (`boolean`).
167167

168168
Create a table with an encrypted column for `email`:
169169

packages/cipherstash-proxy/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ pub enum EncryptError {
214214
#[error("Column '{column}' in table '{table}' could not be encrypted. For help visit {}#encrypt-column-could-not-be-encrypted", ERROR_DOC_BASE_URL)]
215215
ColumnCouldNotBeEncrypted { table: String, column: String },
216216

217+
#[error("Column configuration for column '{column}' in table '{table}' does not match the encrypted column. For help visit {}#encrypt-column-config-mismatch", ERROR_DOC_BASE_URL)]
218+
ColumnConfigurationMismatch { table: String, column: String },
219+
217220
/// This should in practice be unreachable
218221
#[error("Missing encrypt configuration for column type `{plaintext_type}`. For help visit {}#encrypt-missing-encrypt-configuration", ERROR_DOC_BASE_URL)]
219222
MissingEncryptConfiguration { plaintext_type: String },

packages/cipherstash-proxy/src/postgresql/backend.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use super::message_buffer::MessageBuffer;
44
use super::messages::error_response::ErrorResponse;
55
use super::messages::row_description::RowDescription;
66
use super::messages::BackendCode;
7+
use super::Column;
78
use crate::connect::Sender;
89
use crate::encrypt::Encrypt;
910
use crate::eql::EqlEncrypted;
10-
use crate::error::Error;
11-
use crate::log::{DEVELOPMENT, MAPPER, PROTOCOL};
11+
use crate::error::{EncryptError, Error};
12+
use crate::log::{DECRYPT, DEVELOPMENT, MAPPER, PROTOCOL};
1213
use crate::postgresql::context::Portal;
1314
use crate::postgresql::messages::data_row::DataRow;
1415
use crate::postgresql::messages::param_description::ParamDescription;
@@ -270,6 +271,8 @@ where
270271

271272
let start = Instant::now();
272273

274+
self.check_column_config(projection_columns, &ciphertexts)?;
275+
273276
// Decrypt CipherText -> Plaintext
274277
let plaintexts = self.encrypt.decrypt(ciphertexts).await.inspect_err(|_| {
275278
counter!(DECRYPTION_ERROR_TOTAL).increment(1);
@@ -313,6 +316,39 @@ where
313316
Ok(())
314317
}
315318

319+
fn check_column_config(
320+
&mut self,
321+
projection_columns: &[Option<Column>],
322+
ciphertexts: &[Option<EqlEncrypted>],
323+
) -> Result<(), Error> {
324+
for (col, ct) in projection_columns.iter().zip(ciphertexts) {
325+
match (col, ct) {
326+
(Some(col), Some(ct)) => {
327+
if col.identifier != ct.identifier {
328+
return Err(EncryptError::ColumnConfigurationMismatch {
329+
table: col.identifier.table.to_owned(),
330+
column: col.identifier.column.to_owned(),
331+
}
332+
.into());
333+
}
334+
}
335+
// configured column with NULL ciphertext
336+
(Some(_), None) => {}
337+
// unconfigured column *should* have no ciphertext,
338+
(None, None) => {}
339+
// ciphertext with no column configuration is bad
340+
(None, Some(ct)) => {
341+
return Err(EncryptError::ColumnConfigurationMismatch {
342+
table: ct.identifier.table.to_owned(),
343+
column: ct.identifier.column.to_owned(),
344+
}
345+
.into());
346+
}
347+
}
348+
}
349+
Ok(())
350+
}
351+
316352
async fn parameter_description_handler(
317353
&self,
318354
bytes: &BytesMut,

0 commit comments

Comments
 (0)