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 Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "libpep"
edition = "2021"
version = "0.10.1"
version = "0.11.0"
authors = ["Bernard van Gastel <bvgastel@bitpowder.com>", "Job Doesburg <job@jobdoesburg.nl>"]
homepage = "https://github.com/NOLAI/libpep"
repository = "https://github.com/NOLAI/libpep"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nolai/libpep-wasm",
"version": "0.10.1",
"version": "0.11.0",
"description": "Library for polymorphic encryption and pseudonymization (in WASM)",
"repository": {
"type": "git",
Expand Down
3 changes: 1 addition & 2 deletions src/lib/client/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ pub use super::{decrypt, encrypt, Client};
#[cfg(feature = "offline")]
pub use super::{encrypt_global, OfflineClient};
pub use crate::data::simple::{Attribute, EncryptedAttribute, EncryptedPseudonym, Pseudonym};
pub use crate::factors::contexts::EncryptionContext;
pub use crate::factors::EncryptionSecret;
pub use crate::factors::contexts::{EncryptionContext, PseudonymizationDomain};
pub use crate::keys::{GlobalPublicKeys, SessionKeys};
98 changes: 95 additions & 3 deletions src/lib/data/json/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,101 @@ impl PEPJSONBuilder {

/// Add a string field as a pseudonym.
pub fn pseudonym(mut self, key: &str, value: &str) -> Self {
let pseudo = LongPseudonym::from_string_padded(value);
self.fields
.insert(key.to_string(), PEPJSONValue::Pseudonym(pseudo));
use crate::data::padding::Padded;
use crate::data::simple::{ElGamalEncryptable, Pseudonym};

// Try to decode as a direct 32-byte pseudonym value (hex string)
if let Some(pseudo) = Pseudonym::from_hex(value) {
self.fields
.insert(key.to_string(), PEPJSONValue::Pseudonym(pseudo));
return self;
}

// Try to decode as multi-block pseudonym (hex string with multiple 64-char blocks)
// Each block is 32 bytes = 64 hex chars
if value.len() > 64 && value.len().is_multiple_of(64) {
let num_blocks = value.len() / 64;
let mut blocks = Vec::with_capacity(num_blocks);
let mut all_decoded = true;

for i in 0..num_blocks {
let start = i * 64;
let end = start + 64;
if let Some(block) = Pseudonym::from_hex(&value[start..end]) {
blocks.push(block);
} else {
all_decoded = false;
break;
}
}

if all_decoded {
self.fields.insert(
key.to_string(),
PEPJSONValue::LongPseudonym(LongPseudonym(blocks)),
);
return self;
}
}

// Try to decode as 32 raw bytes
if value.len() == 32 {
if let Some(pseudo) = Pseudonym::from_slice(value.as_bytes()) {
self.fields
.insert(key.to_string(), PEPJSONValue::Pseudonym(pseudo));
return self;
}
}

// Try to decode as multi-block pseudonym (raw bytes, multiple of 32)
let bytes = value.as_bytes();
if bytes.len() > 32 && bytes.len().is_multiple_of(32) {
let num_blocks = bytes.len() / 32;
let mut blocks = Vec::with_capacity(num_blocks);
let mut all_decoded = true;

for i in 0..num_blocks {
let start = i * 32;
let end = start + 32;
if let Some(block) = Pseudonym::from_slice(&bytes[start..end]) {
blocks.push(block);
} else {
all_decoded = false;
break;
}
}

if all_decoded {
self.fields.insert(
key.to_string(),
PEPJSONValue::LongPseudonym(LongPseudonym(blocks)),
);
return self;
}
}

// Check if it fits in a single block with PKCS#7 padding (≤15 bytes)
if bytes.len() <= 15 {
// Use PKCS#7 padding for short strings
match Pseudonym::from_string_padded(value) {
Ok(pseudo) => {
self.fields
.insert(key.to_string(), PEPJSONValue::Pseudonym(pseudo));
}
Err(_) => {
// Fallback to long pseudonym if padding fails
let pseudo = LongPseudonym::from_string_padded(value);
self.fields
.insert(key.to_string(), PEPJSONValue::LongPseudonym(pseudo));
}
}
} else {
// Use long pseudonym for strings > 15 bytes
let pseudo = LongPseudonym::from_string_padded(value);
self.fields
.insert(key.to_string(), PEPJSONValue::LongPseudonym(pseudo));
}

self
}

Expand Down
116 changes: 103 additions & 13 deletions src/lib/data/json/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::data::long::{
LongAttribute, LongEncryptedAttribute, LongEncryptedPseudonym, LongPseudonym,
};
use crate::data::padding::Padded;
use crate::data::simple::{Attribute, EncryptedAttribute};
use crate::data::simple::{Attribute, EncryptedAttribute, EncryptedPseudonym, Pseudonym};
use crate::data::traits::{Encryptable, Encrypted, Transcryptable};
use crate::factors::RerandomizeFactor;
use crate::factors::TranscryptionInfo;
Expand Down Expand Up @@ -60,8 +60,14 @@ pub enum PEPJSONValue {
Null,
Bool(Attribute),
Number(Attribute),
String(LongAttribute),
Pseudonym(LongPseudonym),
/// Short string that fits in a single block (≤15 bytes)
String(Attribute),
/// Long string that requires multiple blocks
LongString(LongAttribute),
/// Short pseudonym from 32-byte value
Pseudonym(Pseudonym),
/// Long pseudonym (multiple 32-byte values or lizard-encoded)
LongPseudonym(LongPseudonym),
Array(Vec<PEPJSONValue>),
Object(HashMap<String, PEPJSONValue>),
}
Expand All @@ -83,8 +89,14 @@ pub enum EncryptedPEPJSONValue {
Null,
Bool(EncryptedAttribute),
Number(EncryptedAttribute),
String(LongEncryptedAttribute),
Pseudonym(LongEncryptedPseudonym),
/// Short string that fits in a single block (≤15 bytes)
String(EncryptedAttribute),
/// Long string that requires multiple blocks
LongString(LongEncryptedAttribute),
/// Short pseudonym from 32-byte value
Pseudonym(EncryptedPseudonym),
/// Long pseudonym (multiple 32-byte values or lizard-encoded)
LongPseudonym(LongEncryptedPseudonym),
Array(Vec<EncryptedPEPJSONValue>),
Object(HashMap<String, EncryptedPEPJSONValue>),
}
Expand Down Expand Up @@ -126,12 +138,24 @@ impl PEPJSONValue {
.map_err(|e| JsonError::StringPadding(format!("{e:?}")))?;
Ok(Value::String(string_val))
}
Self::LongString(attr) => {
let string_val = attr
.to_string_padded()
.map_err(|e| JsonError::StringPadding(format!("{e:?}")))?;
Ok(Value::String(string_val))
}
Self::Pseudonym(pseudo) => {
let string_val = pseudo
.to_string_padded()
.unwrap_or_else(|_| pseudo.to_hex());
Ok(Value::String(string_val))
}
Self::LongPseudonym(pseudo) => {
let string_val = pseudo
.to_string_padded()
.unwrap_or_else(|_| pseudo.to_hex());
Ok(Value::String(string_val))
}
Self::Array(arr) => {
let json_arr = arr
.iter()
Expand Down Expand Up @@ -172,7 +196,19 @@ impl PEPJSONValue {
.expect("9 bytes always fits in 16-byte block");
Self::Number(attr)
}
Value::String(s) => Self::String(LongAttribute::from_string_padded(s)),
Value::String(s) => {
// Check if string fits in a single block (≤15 bytes with PKCS#7 padding)
if s.len() <= 15 {
// Try to create a short string
match Attribute::from_string_padded(s) {
Ok(attr) => Self::String(attr),
Err(_) => Self::LongString(LongAttribute::from_string_padded(s)),
}
} else {
// Use long string for strings > 15 bytes
Self::LongString(LongAttribute::from_string_padded(s))
}
}
Value::Array(arr) => {
let mut out = Vec::with_capacity(arr.len());
out.extend(arr.iter().map(Self::from_value));
Expand Down Expand Up @@ -207,12 +243,18 @@ impl Encryptable for PEPJSONValue {
PEPJSONValue::Number(attr) => {
EncryptedPEPJSONValue::Number(attr.encrypt(&keys.attribute.public, rng))
}
PEPJSONValue::String(long_attr) => {
EncryptedPEPJSONValue::String(long_attr.encrypt(&keys.attribute.public, rng))
PEPJSONValue::String(attr) => {
EncryptedPEPJSONValue::String(attr.encrypt(&keys.attribute.public, rng))
}
PEPJSONValue::Pseudonym(long_pseudo) => {
EncryptedPEPJSONValue::Pseudonym(long_pseudo.encrypt(&keys.pseudonym.public, rng))
PEPJSONValue::LongString(long_attr) => {
EncryptedPEPJSONValue::LongString(long_attr.encrypt(&keys.attribute.public, rng))
}
PEPJSONValue::Pseudonym(pseudo) => {
EncryptedPEPJSONValue::Pseudonym(pseudo.encrypt(&keys.pseudonym.public, rng))
}
PEPJSONValue::LongPseudonym(long_pseudo) => EncryptedPEPJSONValue::LongPseudonym(
long_pseudo.encrypt(&keys.pseudonym.public, rng),
),
PEPJSONValue::Array(arr) => EncryptedPEPJSONValue::Array(
arr.iter().map(|item| item.encrypt(keys, rng)).collect(),
),
Expand All @@ -237,10 +279,16 @@ impl Encryptable for PEPJSONValue {
PEPJSONValue::Number(attr) => {
EncryptedPEPJSONValue::Number(attr.encrypt_global(&public_key.attribute, rng))
}
PEPJSONValue::String(long_attr) => {
EncryptedPEPJSONValue::String(long_attr.encrypt_global(&public_key.attribute, rng))
PEPJSONValue::String(attr) => {
EncryptedPEPJSONValue::String(attr.encrypt_global(&public_key.attribute, rng))
}
PEPJSONValue::Pseudonym(long_pseudo) => EncryptedPEPJSONValue::Pseudonym(
PEPJSONValue::LongString(long_attr) => EncryptedPEPJSONValue::LongString(
long_attr.encrypt_global(&public_key.attribute, rng),
),
PEPJSONValue::Pseudonym(pseudo) => {
EncryptedPEPJSONValue::Pseudonym(pseudo.encrypt_global(&public_key.pseudonym, rng))
}
PEPJSONValue::LongPseudonym(long_pseudo) => EncryptedPEPJSONValue::LongPseudonym(
long_pseudo.encrypt_global(&public_key.pseudonym, rng),
),
PEPJSONValue::Array(arr) => EncryptedPEPJSONValue::Array(
Expand Down Expand Up @@ -277,9 +325,15 @@ impl Encrypted for EncryptedPEPJSONValue {
EncryptedPEPJSONValue::String(enc) => {
Some(PEPJSONValue::String(enc.decrypt(&keys.attribute.secret)?))
}
EncryptedPEPJSONValue::LongString(enc) => Some(PEPJSONValue::LongString(
enc.decrypt(&keys.attribute.secret)?,
)),
EncryptedPEPJSONValue::Pseudonym(enc) => Some(PEPJSONValue::Pseudonym(
enc.decrypt(&keys.pseudonym.secret)?,
)),
EncryptedPEPJSONValue::LongPseudonym(enc) => Some(PEPJSONValue::LongPseudonym(
enc.decrypt(&keys.pseudonym.secret)?,
)),
EncryptedPEPJSONValue::Array(arr) => {
let mut out = Vec::with_capacity(arr.len());
for item in arr {
Expand Down Expand Up @@ -309,9 +363,15 @@ impl Encrypted for EncryptedPEPJSONValue {
EncryptedPEPJSONValue::String(enc) => {
PEPJSONValue::String(enc.decrypt(&keys.attribute.secret))
}
EncryptedPEPJSONValue::LongString(enc) => {
PEPJSONValue::LongString(enc.decrypt(&keys.attribute.secret))
}
EncryptedPEPJSONValue::Pseudonym(enc) => {
PEPJSONValue::Pseudonym(enc.decrypt(&keys.pseudonym.secret))
}
EncryptedPEPJSONValue::LongPseudonym(enc) => {
PEPJSONValue::LongPseudonym(enc.decrypt(&keys.pseudonym.secret))
}
EncryptedPEPJSONValue::Array(arr) => {
PEPJSONValue::Array(arr.iter().map(|x| x.decrypt(keys)).collect())
}
Expand Down Expand Up @@ -340,9 +400,15 @@ impl Encrypted for EncryptedPEPJSONValue {
EncryptedPEPJSONValue::String(enc) => Some(PEPJSONValue::String(
enc.decrypt_global(&secret_key.attribute)?,
)),
EncryptedPEPJSONValue::LongString(enc) => Some(PEPJSONValue::LongString(
enc.decrypt_global(&secret_key.attribute)?,
)),
EncryptedPEPJSONValue::Pseudonym(enc) => Some(PEPJSONValue::Pseudonym(
enc.decrypt_global(&secret_key.pseudonym)?,
)),
EncryptedPEPJSONValue::LongPseudonym(enc) => Some(PEPJSONValue::LongPseudonym(
enc.decrypt_global(&secret_key.pseudonym)?,
)),
EncryptedPEPJSONValue::Array(arr) => {
let mut out = Vec::with_capacity(arr.len());
for item in arr {
Expand Down Expand Up @@ -374,9 +440,15 @@ impl Encrypted for EncryptedPEPJSONValue {
EncryptedPEPJSONValue::String(enc) => {
PEPJSONValue::String(enc.decrypt_global(&secret_key.attribute))
}
EncryptedPEPJSONValue::LongString(enc) => {
PEPJSONValue::LongString(enc.decrypt_global(&secret_key.attribute))
}
EncryptedPEPJSONValue::Pseudonym(enc) => {
PEPJSONValue::Pseudonym(enc.decrypt_global(&secret_key.pseudonym))
}
EncryptedPEPJSONValue::LongPseudonym(enc) => {
PEPJSONValue::LongPseudonym(enc.decrypt_global(&secret_key.pseudonym))
}
EncryptedPEPJSONValue::Array(arr) => {
PEPJSONValue::Array(arr.iter().map(|x| x.decrypt_global(secret_key)).collect())
}
Expand Down Expand Up @@ -423,9 +495,15 @@ impl Encrypted for EncryptedPEPJSONValue {
EncryptedPEPJSONValue::String(enc) => {
EncryptedPEPJSONValue::String(enc.rerandomize_known(factor))
}
EncryptedPEPJSONValue::LongString(enc) => {
EncryptedPEPJSONValue::LongString(enc.rerandomize_known(factor))
}
EncryptedPEPJSONValue::Pseudonym(enc) => {
EncryptedPEPJSONValue::Pseudonym(enc.rerandomize_known(factor))
}
EncryptedPEPJSONValue::LongPseudonym(enc) => {
EncryptedPEPJSONValue::LongPseudonym(enc.rerandomize_known(factor))
}
EncryptedPEPJSONValue::Array(arr) => EncryptedPEPJSONValue::Array(
arr.iter().map(|x| x.rerandomize_known(factor)).collect(),
),
Expand Down Expand Up @@ -454,9 +532,15 @@ impl Encrypted for EncryptedPEPJSONValue {
EncryptedPEPJSONValue::String(enc) => EncryptedPEPJSONValue::String(
enc.rerandomize_known(&public_key.attribute.public, factor),
),
EncryptedPEPJSONValue::LongString(enc) => EncryptedPEPJSONValue::LongString(
enc.rerandomize_known(&public_key.attribute.public, factor),
),
EncryptedPEPJSONValue::Pseudonym(enc) => EncryptedPEPJSONValue::Pseudonym(
enc.rerandomize_known(&public_key.pseudonym.public, factor),
),
EncryptedPEPJSONValue::LongPseudonym(enc) => EncryptedPEPJSONValue::LongPseudonym(
enc.rerandomize_known(&public_key.pseudonym.public, factor),
),
EncryptedPEPJSONValue::Array(arr) => EncryptedPEPJSONValue::Array(
arr.iter()
.map(|x| x.rerandomize_known(public_key, factor))
Expand Down Expand Up @@ -484,9 +568,15 @@ impl Transcryptable for EncryptedPEPJSONValue {
EncryptedPEPJSONValue::String(enc) => {
EncryptedPEPJSONValue::String(enc.transcrypt(info))
}
EncryptedPEPJSONValue::LongString(enc) => {
EncryptedPEPJSONValue::LongString(enc.transcrypt(info))
}
EncryptedPEPJSONValue::Pseudonym(enc) => {
EncryptedPEPJSONValue::Pseudonym(enc.transcrypt(info))
}
EncryptedPEPJSONValue::LongPseudonym(enc) => {
EncryptedPEPJSONValue::LongPseudonym(enc.transcrypt(info))
}
EncryptedPEPJSONValue::Array(arr) => {
EncryptedPEPJSONValue::Array(arr.iter().map(|x| x.transcrypt(info)).collect())
}
Expand Down
6 changes: 4 additions & 2 deletions src/lib/data/json/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ impl EncryptedPEPJSONValue {
EncryptedPEPJSONValue::Null => JSONStructure::Null,
EncryptedPEPJSONValue::Bool(_) => JSONStructure::Bool,
EncryptedPEPJSONValue::Number(_) => JSONStructure::Number,
EncryptedPEPJSONValue::String(enc) => JSONStructure::String(enc.len()),
EncryptedPEPJSONValue::Pseudonym(enc) => JSONStructure::Pseudonym(enc.len()),
EncryptedPEPJSONValue::String(_enc) => JSONStructure::String(1),
EncryptedPEPJSONValue::LongString(enc) => JSONStructure::String(enc.len()),
EncryptedPEPJSONValue::Pseudonym(_enc) => JSONStructure::Pseudonym(1),
EncryptedPEPJSONValue::LongPseudonym(enc) => JSONStructure::Pseudonym(enc.len()),
EncryptedPEPJSONValue::Array(arr) => {
JSONStructure::Array(arr.iter().map(|item| item.structure()).collect())
}
Expand Down
Loading
Loading