Skip to content

Commit

Permalink
Make ORE ciphertext handling safer
Browse files Browse the repository at this point in the history
Left ciphertexts, which are not IND-CPA secure, are now only generated if specifically requested.
This makes it harder to leak them accidentally.
  • Loading branch information
mpalmer committed Oct 3, 2022
1 parent 47a87a7 commit a4cbd1a
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 42 deletions.
4 changes: 2 additions & 2 deletions ruby/ext/enquo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions ruby/ext/enquo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
extern crate rutie;

use enquo_core::{Field, Root, I64};
use rutie::{Class, Integer, Module, Object, RString, VerifiedObject, VM};
use rutie::{Boolean, Class, Integer, Module, Object, RString, VerifiedObject, VM};

class!(EnquoRoot);
class!(EnquoRootKeyStatic);
Expand Down Expand Up @@ -81,12 +81,16 @@ impl VerifiedObject for EnquoRootKeyStatic {
methods!(
EnquoField,
rbself,
fn enquo_field_encrypt_i64(value: Integer, context: RString) -> RString {
fn enquo_field_encrypt_i64(value: Integer, context: RString, unsafe_parts: Boolean) -> RString {
let i = value.unwrap().to_i64();
let field = rbself.get_data(&*FIELD_WRAPPER);

let res = maybe_raise(
field.i64(i, &context.unwrap().to_vec_u8_unchecked()),
if unsafe_parts.unwrap().to_bool() {
I64::new_with_unsafe_parts(i, &context.unwrap().to_vec_u8_unchecked(), field)
} else {
I64::new(i, &context.unwrap().to_vec_u8_unchecked(), field)
},
"Failed to create encrypted i64",
);
RString::new_utf8(&serde_json::to_string(&res).unwrap())
Expand Down
4 changes: 2 additions & 2 deletions ruby/lib/enquo/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def self.new(*_)
raise RuntimeError, "Enquo::Field cannot be instantiated directly; use Enquo::Crypto#field instead"
end

def encrypt_i64(i, ctx)
def encrypt_i64(i, ctx, safety: nil)
unless i.is_a?(Integer)
raise ArgumentError, "Enquo::Field#encrypt_i64 can only encrypt integers"
end
Expand All @@ -17,7 +17,7 @@ def encrypt_i64(i, ctx)
raise ArgumentError, "Encryption context must be a string (got a #{ctx.class})"
end

_encrypt_i64(i, ctx)
_encrypt_i64(i, ctx, safety == :unsafe)
end

def decrypt_i64(data, ctx)
Expand Down
2 changes: 1 addition & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ edition = "2021"
aes-gcm-siv = "0.11"
ciborium = "0.2"
hmac = "0.12"
cretrit = "0.1"
cretrit = "0.2"
rand_chacha = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_bytes = "0.11"
Expand Down
48 changes: 31 additions & 17 deletions rust/src/crypto/ore64v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,45 @@ const ORE64v1_KEY_IDENTIFIER: &[u8] = b"ORE64v1.prf_key";

impl ORE64v1 {
pub fn new(plaintext: u64, _context: &[u8], field: &Field) -> Result<ORE64v1, Error> {
let mut key: [u8; 16] = Default::default();
let cipher = Self::cipher(field)?;
let ct = cipher.right_encrypt(plaintext.into()).map_err(|e| {
Error::EncryptionError(format!("Failed to encrypt ORE ciphertext: {:?}", e))
})?;

key.clone_from_slice(&field.subkey(ORE64v1_KEY_IDENTIFIER)?[0..16]);
Ok(ORE64v1 {
left: None,
right: ct.right.to_vec(),
})
}

let cipher = ore::Cipher::<8, 256>::new(key).map_err(|e| {
Error::EncryptionError(format!("Failed to initialize ORE cipher: {:?}", e))
})?;
let ct = cipher.encrypt(plaintext.into()).map_err(|e| {
pub fn new_with_left(plaintext: u64, _context: &[u8], field: &Field) -> Result<ORE64v1, Error> {
let cipher = Self::cipher(field)?;
let ct = cipher.full_encrypt(plaintext.into()).map_err(|e| {
Error::EncryptionError(format!("Failed to encrypt ORE ciphertext: {:?}", e))
})?;

Ok(ORE64v1 {
left: Some(
ct.left
.expect("CAN'T HAPPEN: cipher.encrypt returned ciphertext without left part!")
.expect(
"CAN'T HAPPEN: cipher.full_encrypt returned ciphertext without left part!",
)
.to_vec(),
),
right: ct.right.to_vec(),
})
}

fn cipher(field: &Field) -> Result<ore::Cipher<8, 256>, Error> {
let mut key: [u8; 16] = Default::default();

key.clone_from_slice(&field.subkey(ORE64v1_KEY_IDENTIFIER)?[0..16]);

ore::Cipher::<8, 256>::new(key).map_err(|e| {
Error::EncryptionError(format!("Failed to initialize ORE cipher: {:?}", e))
})
}

fn ciphertext(&self) -> Result<ore::CipherText<8, 256>, Error> {
Ok(ore::CipherText::<8, 256> {
left: match &self.left {
Expand Down Expand Up @@ -104,8 +122,8 @@ mod tests {

quickcheck! {
fn comparison(a: u64, b: u64) -> bool {
let ca = ORE64v1::new(a, b"test", &field()).unwrap();
let cb = ORE64v1::new(b, b"test", &field()).unwrap();
let ca = ORE64v1::new_with_left(a, b"test", &field()).unwrap();
let cb = ORE64v1::new_with_left(b, b"test", &field()).unwrap();

match ca.cmp(&cb) {
Ordering::Equal => a == b,
Expand All @@ -115,10 +133,8 @@ mod tests {
}

fn comparison_first_missing_left(a: u64, b: u64) -> bool {
let mut ca = ORE64v1::new(a, b"test", &field()).unwrap();
let cb = ORE64v1::new(b, b"test", &field()).unwrap();

ca.left = None;
let ca = ORE64v1::new(a, b"test", &field()).unwrap();
let cb = ORE64v1::new_with_left(b, b"test", &field()).unwrap();

match ca.cmp(&cb) {
Ordering::Equal => a == b,
Expand All @@ -128,10 +144,8 @@ mod tests {
}

fn comparison_second_missing_left(a: u64, b: u64) -> bool {
let ca = ORE64v1::new(a, b"test", &field()).unwrap();
let mut cb = ORE64v1::new(b, b"test", &field()).unwrap();

cb.left = None;
let ca = ORE64v1::new_with_left(a, b"test", &field()).unwrap();
let cb = ORE64v1::new(b, b"test", &field()).unwrap();

match ca.cmp(&cb) {
Ordering::Equal => a == b,
Expand Down
11 changes: 4 additions & 7 deletions rust/src/datatype/i64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,14 @@ impl I64 {
Ok(I64::v1(I64v1::new(i, context, field)?))
}

pub fn new_with_unsafe_parts(i: i64, context: &[u8], field: &Field) -> Result<I64, Error> {
Ok(I64::v1(I64v1::new_with_unsafe_parts(i, context, field)?))
}

pub fn decrypt(&self, context: &[u8], field: &Field) -> Result<i64, Error> {
match self {
I64::v1(i) => i.decrypt(context, field),
I64::Unknown => panic!("Can't decrypt Unknown version"),
}
}

pub fn clear_left_ciphertexts(&mut self) {
match self {
I64::v1(i) => i.clear_left_ciphertexts(),
I64::Unknown => panic!("Can't clear_left_ciphertexts from Unknown version"),
};
}
}
26 changes: 21 additions & 5 deletions rust/src/datatype/i64v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ const I64_OFFSET: i128 = 9_223_372_036_854_775_808;

impl I64v1 {
pub fn new(i: i64, context: &[u8], field: &Field) -> Result<I64v1, Error> {
Self::encrypt(i, context, field, false)
}

pub fn new_with_unsafe_parts(i: i64, context: &[u8], field: &Field) -> Result<I64v1, Error> {
Self::encrypt(i, context, field, true)
}

fn encrypt(i: i64, context: &[u8], field: &Field, include_left: bool) -> Result<I64v1, Error> {
let v = cbor!(i).map_err(|e| {
Error::EncodingError(format!("failed to convert i64 to ciborium value: {}", e))
})?;
Expand All @@ -35,7 +43,12 @@ impl I64v1 {
let u: u64 = ((i as i128) + I64_OFFSET)
.try_into()
.map_err(|_| Error::EncodingError(format!("failed to convert i64 {} to u64", i)))?;
let ore = ORE64v1::new(u, context, field)?;

let ore = if include_left {
ORE64v1::new_with_left(u, context, field)?
} else {
ORE64v1::new(u, context, field)?
};

Ok(I64v1 {
aes_ciphertext: aes,
Expand All @@ -61,10 +74,6 @@ impl I64v1 {
))),
}
}

pub fn clear_left_ciphertexts(&mut self) {
self.ore_ciphertext.left = None;
}
}

impl Ord for I64v1 {
Expand Down Expand Up @@ -122,4 +131,11 @@ mod tests {
dbg!(&s);
assert!(s.len() < 600, "s.len() == {}", s.len());
}

#[test]
fn default_encryption_is_safe() {
let value = I64v1::new(42, b"somecontext", &field()).unwrap();

assert!(matches!(value.ore_ciphertext.left, None));
}
}
6 changes: 1 addition & 5 deletions rust/src/field.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{datatype::I64, Error, KeyProvider, Root};
use crate::{Error, KeyProvider, Root};

pub struct Field {
field_key: Vec<u8>,
Expand Down Expand Up @@ -26,10 +26,6 @@ impl Field {
id.extend(name);
root.derive_key(&id)
}

pub fn i64(&self, i: i64, context: &[u8]) -> Result<I64, Error> {
I64::new(i, context, self)
}
}

#[cfg(test)]
Expand Down

0 comments on commit a4cbd1a

Please sign in to comment.