Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,4 @@ dist
.idea/

# OS specific
.DS_Store/
.DS_Store
110 changes: 110 additions & 0 deletions src/lib/high_level/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::low_level::elgamal::{ElGamal, ELGAMAL_LENGTH};
use derive_more::{Deref, From};
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::io::{Error, ErrorKind};

/// A pseudonym (in the background, this is a [`GroupElement`]) that can be used to identify a user
/// within a specific context, which can be encrypted, rekeyed and reshuffled.
Expand Down Expand Up @@ -196,6 +197,115 @@ pub trait Encryptable {
fn as_bytes(&self) -> Option<[u8; 16]> {
self.value().encode_lizard()
}

/// Encodes an arbitrary byte array into one or more encryptables
/// Uses PKCS#7 style padding where the padding byte value equals the number of padding bytes
fn from_bytes_padded(data: &[u8]) -> Vec<Self>
where
Self: Sized,
{
if data.is_empty() {
return vec![];
}

let mut result = Vec::new();

// Process all full blocks, that do not need padding
// Initialize the last block with the padding value
// Copy remaining data if there is any
for i in 0..(data.len() / 16) {
let start = i * 16;
// This is safe, as we know that the slice is 16 bytes long
result.push(Self::from_bytes(
&data[start..start + 16].try_into().unwrap(),
));
}

let remaining = data.len() % 16;
let padding_byte = (16 - remaining) as u8;

let mut last_block = [padding_byte; 16];

if remaining > 0 {
last_block[..remaining].copy_from_slice(&data[data.len() - remaining..]);
}

result.push(Self::from_bytes(&last_block));

result
}

/// Encodes an arbitrary string into one or more encryptables
/// Uses PKCS#7 style padding where the padding byte value equals the number of padding bytes
fn from_string_padded(text: &str) -> Vec<Self>
where
Self: Sized,
{
// Convert string to bytes and pass to the byte encoding function
Self::from_bytes_padded(text.as_bytes())
}

/// Decodes encryptables back to the original string
/// Returns an error if the decoded bytes are not valid UTF-8
fn to_string_padded(encryptables: &[Self]) -> Result<String, Error>
where
Self: Sized,
{
let bytes = Self::to_bytes_padded(encryptables)?;
String::from_utf8(bytes).map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))
}

/// Decodes encryptables back to the original byte array
fn to_bytes_padded(encryptables: &[Self]) -> Result<Vec<u8>, Error>
where
Self: Sized,
{
if encryptables.is_empty() {
return Err(Error::new(
ErrorKind::InvalidInput,
"No encryptables provided",
));
}

let mut result = Vec::with_capacity(encryptables.len() * 16);

// Copy over all blocks except the last one
// Validate padding and copy the data part of the last block
// Copy over all blocks except the last one
for data_point in &encryptables[..encryptables.len() - 1] {
let block = data_point.as_bytes().ok_or(Error::new(
ErrorKind::InvalidData,
"Encryptable conversion to bytes failed",
))?;
result.extend_from_slice(&block);
}

// This is safe, we know that there is at least one element in the slice
let last_block = encryptables.last().unwrap().as_bytes().ok_or(Error::new(
ErrorKind::InvalidData,
"Last encryptables conversion to bytes failed",
))?;

let padding_byte = last_block[15];

if padding_byte == 0 || padding_byte > 16 {
return Err(Error::new(ErrorKind::InvalidData, "Invalid padding"));
}

if last_block[16 - padding_byte as usize..]
.iter()
.any(|&b| b != padding_byte)
{
return Err(Error::new(ErrorKind::InvalidData, "Inconsistent padding"));
}

// Add the data part of the last block
let data_bytes = 16 - padding_byte as usize;
result.extend_from_slice(&last_block[..data_bytes]);

Ok(result)
}

/// Create multiple messages from a byte array.
/// TODO: remove this method, as it cannot handle data that is not a multiple of 16 bytes and padding should generally not belong in this library.
#[deprecated]
Expand Down
72 changes: 72 additions & 0 deletions src/lib/wasm/high_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,42 @@ impl WASMPseudonym {
pub fn as_bytes(&self) -> Option<Vec<u8>> {
self.0.as_bytes().map(|x| x.to_vec())
}

/// Create a collection of pseudonyms from an arbitrary-length string
/// Uses PKCS#7 style padding where the padding byte value equals the number of padding bytes
#[wasm_bindgen(js_name = fromStringPadded)]
pub fn from_string_padded(text: &str) -> Vec<WASMPseudonym> {
Pseudonym::from_string_padded(text)
.into_iter()
.map(WASMPseudonym::from)
.collect()
}

/// Create a collection of pseudonyms from an arbitrary-length byte array
/// Uses PKCS#7 style padding where the padding byte value equals the number of padding bytes
#[wasm_bindgen(js_name = fromBytesPadded)]
pub fn from_bytes_padded(data: Vec<u8>) -> Vec<WASMPseudonym> {
Pseudonym::from_bytes_padded(&data)
.into_iter()
.map(WASMPseudonym::from)
.collect()
}

/// Convert a collection of pseudonyms back to the original string
/// Returns null if the decoding fails (e.g., invalid padding or UTF-8)
#[wasm_bindgen(js_name = toStringPadded)]
pub fn to_string_padded(pseudonyms: Vec<WASMPseudonym>) -> Option<String> {
let rust_pseudonyms: Vec<Pseudonym> = pseudonyms.into_iter().map(|p| p.0).collect();
Pseudonym::to_string_padded(&rust_pseudonyms).ok()
}

/// Convert a collection of pseudonyms back to the original byte array
/// Returns null if the decoding fails (e.g., invalid padding)
#[wasm_bindgen(js_name = toBytesPadded)]
pub fn to_bytes_padded(pseudonyms: Vec<WASMPseudonym>) -> Option<Vec<u8>> {
let rust_pseudonyms: Vec<Pseudonym> = pseudonyms.into_iter().map(|p| p.0).collect();
Pseudonym::to_bytes_padded(&rust_pseudonyms).ok()
}
}

#[wasm_bindgen(js_class = "DataPoint")]
Expand Down Expand Up @@ -232,6 +268,42 @@ impl WASMDataPoint {
pub fn as_bytes(&self) -> Option<Vec<u8>> {
self.0.as_bytes().map(|x| x.to_vec())
}

/// Create a collection of data points from an arbitrary-length string
/// Uses PKCS#7 style padding where the padding byte value equals the number of padding bytes
#[wasm_bindgen(js_name = fromStringPadded)]
pub fn from_string_padded(text: &str) -> Vec<WASMDataPoint> {
DataPoint::from_string_padded(text)
.into_iter()
.map(WASMDataPoint::from)
.collect()
}

/// Create a collection of data points from an arbitrary-length byte array
/// Uses PKCS#7 style padding where the padding byte value equals the number of padding bytes
#[wasm_bindgen(js_name = fromBytesPadded)]
pub fn from_bytes_padded(data: Vec<u8>) -> Vec<WASMDataPoint> {
DataPoint::from_bytes_padded(&data)
.into_iter()
.map(WASMDataPoint::from)
.collect()
}

/// Convert a collection of data points back to the original string
/// Returns null if the decoding fails (e.g., invalid padding or UTF-8)
#[wasm_bindgen(js_name = toStringPadded)]
pub fn to_string_padded(data_points: Vec<WASMDataPoint>) -> Option<String> {
let rust_data_points: Vec<DataPoint> = data_points.into_iter().map(|p| p.0).collect();
DataPoint::to_string_padded(&rust_data_points).ok()
}

/// Convert a collection of data points back to the original byte array
/// Returns null if the decoding fails (e.g., invalid padding)
#[wasm_bindgen(js_name = toBytesPadded)]
pub fn to_bytes_padded(data_points: Vec<WASMDataPoint>) -> Option<Vec<u8>> {
let rust_data_points: Vec<DataPoint> = data_points.into_iter().map(|p| p.0).collect();
DataPoint::to_bytes_padded(&rust_data_points).ok()
}
}

#[wasm_bindgen(js_class = "EncryptedPseudonym")]
Expand Down
Loading
Loading