Skip to content
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

Factor out data transformations #35

Merged
merged 20 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
76148b9
Stubs for the beginning of a more modular specification
jschneider-bensch Sep 29, 2023
e160c22
Include the stub modules
jschneider-bensch Sep 29, 2023
80db350
Flesh out `blind_identifiable_datum`
jschneider-bensch Oct 2, 2023
b169186
Implement remaining data transformations
jschneider-bensch Oct 11, 2023
b372b96
Merge branch 'main' into jonas/refactor
jschneider-bensch Oct 11, 2023
c7cf9e5
Missing blinding function for pseudonym conversion
jschneider-bensch Oct 12, 2023
eda34e1
Give in strings using a function instead of hardcoding
jschneider-bensch Oct 12, 2023
ccb2a11
Take reference to evaluator context
jschneider-bensch Oct 12, 2023
1b417d7
Documentation
jschneider-bensch Oct 12, 2023
2cc5897
Remove useless constructor
jschneider-bensch Oct 12, 2023
1b969f7
Use new data transformations
jschneider-bensch Oct 12, 2023
de35b6c
`cargo fmt` workspace
jschneider-bensch Oct 12, 2023
35eb5c2
Documentation for `data_types.rs`
jschneider-bensch Oct 13, 2023
df4e62f
Renaming -`Datum` types to -`Data`
jschneider-bensch Oct 13, 2023
234d459
Replace qualified paths
jschneider-bensch Oct 13, 2023
710b0e9
Top level doc comment for `data_transformations` module
jschneider-bensch Oct 13, 2023
5a1c89e
Introduce `const`s for... constants
jschneider-bensch Oct 13, 2023
998b6d1
Factor out double encryption and decryption
jschneider-bensch Oct 13, 2023
1d2b4b5
Rename fields of blinded identifiable/pseudonymous data
jschneider-bensch Oct 13, 2023
1362073
Fix documentation
jschneider-bensch Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions hacspec-scrambledb/scrambledb/src/data_transformations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
//! This module defines ScrambleDB transformations at the level of individual
//! pieces of data as defined in [`data_types`](crate::data_types).
//!
//! These transformations are:
//! - blinding identifiable and pseudonymous data
//! - pseudonymizing blinded identifiable data
//! - converting blinded pseudonymous data
//! - finalizing blinded pseudonymous data

use hacspec_lib::Randomness;
jschneider-bensch marked this conversation as resolved.
Show resolved Hide resolved
use oprf::coprf::{
coprf_online::{blind, blind_convert, blind_evaluate, prepare_blind_convert},
coprf_setup::{derive_key, BlindingPublicKey, CoPRFEvaluatorContext},
};

use crate::{data_types::*, error::Error, setup::StoreContext};

use self::double_hpke::{hpke_open_level_2, hpke_seal_level_1, hpke_seal_level_2};

pub(crate) mod double_hpke;

/// CoPRF context string for domain separation of intial pseudonymization.
const PSEUDONYMIZATION_CONTEXT: &[u8] = b"CoPRF-Context-Pseudonymization";

/// Blind an identifiable datum as a first step in initial pseudonym
/// generation.
///
/// Inputs:
/// - `bpk`: Receiver's blinding public key
/// - `ek`: Receiver's public encryption key
/// - `datum`: Identifiable data
/// - `randomness`: Random bytes
///
/// Output:
/// [Blinded data](crate::data_types::BlindedIdentifiableData) such that the
/// datum's handle is blinded for CoPRF evaluation and the datum's value is
/// level-1 encrypted.
pub fn blind_identifiable_datum(
bpk: &BlindingPublicKey,
jschneider-bensch marked this conversation as resolved.
Show resolved Hide resolved
ek: &[u8],
datum: &IdentifiableData,
randomness: &mut Randomness,
) -> Result<BlindedIdentifiableData, Error> {
// Blind orthonym towards receiver.
let blinded_handle = BlindedIdentifiableHandle(blind(
*bpk,
datum.handle.as_bytes(),
PSEUDONYMIZATION_CONTEXT.to_vec(),
randomness,
)?);

// Level-1 encrypt data value towards receiver.
let encrypted_data_value = hpke_seal_level_1(&datum.data_value, ek, randomness)?;

Ok(BlindedIdentifiableData {
blinded_handle,
encrypted_data_value,
})
}

/// Blind a pseudonymous datum as a first step in pseudonym
/// conversion.
///
/// Inputs:
/// - `store_context`: The data store's long term private state including the pseudonym
/// hardening keys
/// - `bpk`: Receiver's blinding public key
/// - `ek`: Receiver's public encryption key
/// - `datum`: Pseudonymized data
/// - `randomness`: Random bytes
///
/// Output:
/// [Blinded pseudonymized data](BlindedPseudonymizedData) such that the
/// datum's handle is blinded for CoPRF conversion and the datum's value is
/// level-1 encrypted.
pub fn blind_pseudonymized_datum(
store_context: &StoreContext,
bpk: &BlindingPublicKey,
ek: &[u8],
datum: &PseudonymizedData,
randomness: &mut Randomness,
) -> Result<BlindedPseudonymizedData, Error> {
// Blind recovered raw pseudonym towards receiver.
let blinded_handle = BlindedPseudonymizedHandle(prepare_blind_convert(
*bpk,
store_context.recover_raw_pseudonym(datum.handle.0)?,
randomness,
)?);

// Level-1 encrypt data value towards receiver.
let encrypted_data_value = hpke_seal_level_1(&datum.data_value, ek, randomness)?;

Ok(BlindedPseudonymizedData {
blinded_handle,
encrypted_data_value,
})
}

/// Obliviously pseudonymmize a blinded identifiable datum.
///
/// Inputs:
/// - `coprf_context`: The converter's CoPRF evaluation context
/// - `bpk`: The receiver's blinding public key
/// - `ek`: The receiver's public encryption key
/// - `datum`: A blinded datum output by [`blind_identifiable_datum`]
/// - `randomness`: Random bytes
///
/// Output:
/// [Blinded pseudonymized data](BlindedPseudonymizedData) such that the
/// datum's blinded handle has been obliviously evaluated to a pseudonym and
/// the datum's value has been level-2 encrypted towards the receiver.
pub fn pseudonymize_blinded_datum(
coprf_context: &CoPRFEvaluatorContext,
bpk: &BlindingPublicKey,
ek: &[u8],
datum: &BlindedIdentifiableData,
randomness: &mut Randomness,
) -> Result<BlindedPseudonymizedData, Error> {
let key = derive_key(
&coprf_context,
datum.encrypted_data_value.attribute_name.as_bytes(),
)?;

// Obliviously generate raw pseudonym.
let blinded_handle = BlindedPseudonymizedHandle(blind_evaluate(
key,
*bpk,
datum.blinded_handle.0,
randomness,
)?);

// Level-2 encrypt data value towards receiver.
let encrypted_data_value = hpke_seal_level_2(&datum.encrypted_data_value, ek, randomness)?;

Ok(BlindedPseudonymizedData {
blinded_handle,
encrypted_data_value,
})
}

/// Obliviously convert a blinded pseudonymous datum to a given target pseudonym key.
///
/// Inputs:
/// - `coprf_context`: The Converters CoPRF evaluation context
/// - `bpk`: The receiver's blinding public key
/// - `ek`: The receiver's public encryption key
/// - `conversion_target`: Target pseudonym key identifier
/// - `randomness`: Random bytes
///
/// Output:
/// [Blinded pseudonymized data](BlindedPseudonymizedData)such that the
/// datum's pseudonymous handle is converted to the target pseudonym key and
/// the datum's value is level-2 encrypted towards the receiver.
pub fn convert_blinded_datum(
coprf_context: &CoPRFEvaluatorContext,
bpk: &BlindingPublicKey,
ek: &[u8],
conversion_target: &[u8],
datum: &BlindedPseudonymizedData,
randomness: &mut Randomness,
) -> Result<BlindedPseudonymizedData, Error> {
// Re-derive original pseudonymization key.
let key_from = derive_key(
&coprf_context,
datum.encrypted_data_value.attribute_name.as_bytes(),
)?;

// Derive target key.
let key_to = derive_key(&coprf_context, conversion_target)?;

// Obliviously convert pseudonym.
let blinded_handle = BlindedPseudonymizedHandle(blind_convert(
*bpk,
key_from,
key_to,
datum.blinded_handle.0,
randomness,
)?);

// Level-2 encrypt data value towards receiver.
let encrypted_data_value = hpke_seal_level_2(&datum.encrypted_data_value, ek, randomness)?;

Ok(BlindedPseudonymizedData {
blinded_handle,
encrypted_data_value,
})
}

/// Finalize a blinded pseudonymous datum for storage or analysis.
///
/// Inputs:
/// - `store_context`: The data store's long term private state including the
/// receiver's coPRF unblinding key, private decryption key, as well as
/// pseudonym hardening key
/// - `datum`: blinded pseudonymous datum output by [`convert_blinded_datum`] or
/// [`pseudonymize_blinded_datum`]
///
/// Output:
/// [Pseudonymized data](PseudonymizedData) such that the datum's pseudonymous
/// handle has been unblinded and hardened and the datum's value has been
/// decrypted.
pub fn finalize_blinded_datum(
store_context: &StoreContext,
datum: &BlindedPseudonymizedData,
) -> Result<PseudonymizedData, Error> {
// Finalize pseudonym for storage.
let handle = FinalizedPseudonym(store_context.finalize_pseudonym(datum.blinded_handle.0)?);

// Decrypt data value for storage.
let data_value = hpke_open_level_2(&datum.encrypted_data_value, &store_context.hpke_sk)?;

Ok(PseudonymizedData { handle, data_value })
}
166 changes: 166 additions & 0 deletions hacspec-scrambledb/scrambledb/src/data_transformations/double_hpke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//! This module defines HPKE-based double encryption and decryption for use in
//! individual data tranformations as defined in
//! [`data_transformations`](crate::data_transformations).
//!
//! A plain text data value can be encrypted once to obtain a level-1
//! encryption of the data value.
//!
//! A level-1 encrypted data value can be encrypted a second time to obtain a
//! level-2 encryption of the data value.
//!
//! Only level-2 encrypted data values can be decrypted, and only if both
//! encryptions were performed towards the same receiver.

use libcrux::hpke::kem::Nsk;

use libcrux::hpke::{HpkeOpen, HpkeSeal};

use crate::SerializedHPKE;

use crate::data_types::{DataValue, EncryptedDataValue};

use libcrux::hpke::HPKEConfig;

use crate::error::Error;

use hacspec_lib::Randomness;

/// HPKE double encryption level 1 `info` string.
const HPKE_LEVEL_1_INFO: &[u8] = b"Hpke-Level-1";

/// HPKE double encryption level 2 `info` string.
const HPKE_LEVEL_2_INFO: &[u8] = b"Hpke-Level-2";

/// Level-1 encrypt a plain text data value.
///
/// Inputs:
/// - `data_value`: A plain text data value
/// - `ek`: The receivers public encryption key
/// - `randomness`: Random bytes
///
/// Output:
/// A level-1 encrypted data value.
///
/// Raises:
/// - `CorruptedData`: If the internal encryption fails.
///
/// Panics:
/// - on insufficient randomness
pub(crate) fn hpke_seal_level_1(
data_value: &DataValue,
ek: &[u8],
randomness: &mut Randomness,
) -> Result<EncryptedDataValue, Error> {
let HPKEConfig(_, kem, _, _) = crate::HPKE_CONF;
let encrypted_data_value = EncryptedDataValue {
attribute_name: data_value.attribute_name.clone(),
value: SerializedHPKE::from_hpke_ct(&HpkeSeal(
crate::HPKE_CONF,
ek,
HPKE_LEVEL_1_INFO,
b"",
&data_value.value,
None,
None,
None,
randomness.bytes(Nsk(kem)).unwrap().to_vec(),
)?)
.to_bytes(),
encryption_level: 1u8,
};
Ok(encrypted_data_value)
}

/// Level-2 encrypt a level-1 encrypted data value.
///
/// Inputs:
/// - `data_value`: A level-1 encrypted data value
/// - `ek`: The receivers public encryption key
/// - `randomness`: Random bytes
///
/// Output:
/// A level-2 encrypted data value.
///
/// Raises:
/// - `InvalidInput`: If the input data value is not level-1 encrypted.
/// - `CorruptedData`: If the internal encryption fails.
///
/// Panics:
/// - on insufficient randomness
pub(crate) fn hpke_seal_level_2(
data_value: &EncryptedDataValue,
ek: &[u8],
randomness: &mut Randomness,
) -> Result<EncryptedDataValue, Error> {
if data_value.encryption_level != 1u8 {
return Err(Error::InvalidInput);
}

let HPKEConfig(_, kem, _, _) = crate::HPKE_CONF;
let data_value = EncryptedDataValue {
attribute_name: data_value.attribute_name.clone(),
value: SerializedHPKE::from_hpke_ct(&HpkeSeal(
crate::HPKE_CONF,
ek,
HPKE_LEVEL_2_INFO,
b"",
&data_value.value,
None,
None,
None,
randomness.bytes(Nsk(kem)).unwrap().to_vec(),
)?)
.to_bytes(),
encryption_level: 2u8,
};
Ok(data_value)
}

/// Decrypt a level-2 encrypted data value.
///
/// Inputs:
/// - `data_value`: A Level-2 encrypted data value
/// - `sk`: The receiver's decryption key
///
/// Outputs:
/// A plain text data value.
///
/// Raises:
/// - `InvalidInput`: If the input data value is not level-2 encrypted.
/// - `CorruptedData`: If the internal decryption fails, e.g. because of
/// inconsistent level-1 and level-2 receivers.
pub(crate) fn hpke_open_level_2(
data_value: &EncryptedDataValue,
sk: &[u8],
) -> Result<DataValue, Error> {
if data_value.encryption_level != 2u8 {
return Err(Error::InvalidInput);
}

let outer_encryption = SerializedHPKE::from_bytes(&data_value.value).to_hpke_ct();
let inner_encryption = SerializedHPKE::from_bytes(&HpkeOpen(
crate::HPKE_CONF,
&outer_encryption,
sk,
HPKE_LEVEL_2_INFO,
b"",
None,
None,
None,
)?)
.to_hpke_ct();
let data_value = DataValue {
attribute_name: data_value.attribute_name.clone(),
value: HpkeOpen(
crate::HPKE_CONF,
&inner_encryption,
sk,
HPKE_LEVEL_1_INFO,
b"",
None,
None,
None,
)?,
};
Ok(data_value)
}
Loading