use crate::CryptoError;
use crate::b64;
use crate::box_seal;
use crate::hybrid;
use crate::hybrid::SecurityLevel;
use crate::suite::Suite;
pub fn seal_for_user(
plaintext: &[u8],
public_key_b64: &str,
pq_public_key_b64: Option<&str>,
) -> Result<String, CryptoError> {
match pq_public_key_b64 {
Some(pq) if !pq.is_empty() => hybrid::hybrid_seal(plaintext, pq),
_ => box_seal::box_seal(plaintext, public_key_b64),
}
}
pub fn seal_for_user_with_level(
plaintext: &[u8],
public_key_b64: &str,
pq_public_key_b64: Option<&str>,
level: SecurityLevel,
) -> Result<String, CryptoError> {
match pq_public_key_b64 {
Some(pq) if !pq.is_empty() => hybrid::hybrid_seal_with_level(plaintext, pq, level),
_ => box_seal::box_seal(plaintext, public_key_b64),
}
}
pub fn seal_for_user_with_suite(
plaintext: &[u8],
public_key_b64: &str,
pq_public_key_b64: Option<&str>,
suite: Suite,
level: SecurityLevel,
) -> Result<String, CryptoError> {
match pq_public_key_b64 {
Some(pq) if !pq.is_empty() => hybrid::hybrid_seal_suite(plaintext, pq, suite, level),
_ => box_seal::box_seal(plaintext, public_key_b64),
}
}
pub fn unseal_from_user(
ciphertext_b64: &str,
public_key_b64: &str,
private_key_b64: &str,
pq_secret_key_b64: Option<&str>,
) -> Result<String, CryptoError> {
if let Some(pq_sk) = pq_secret_key_b64 {
if !pq_sk.is_empty() && hybrid::is_hybrid_ciphertext(ciphertext_b64) {
if let Ok(pt) = hybrid::hybrid_open(ciphertext_b64, pq_sk) {
return Ok(b64::encode(&pt));
}
}
}
box_seal::box_seal_open(ciphertext_b64, public_key_b64, private_key_b64)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hybrid::{
generate_hybrid_keypair, generate_hybrid_keypair_512, generate_hybrid_keypair_1024,
};
use crate::keys::generate_keypair;
#[test]
fn legacy_roundtrip() {
let kp = generate_keypair();
let pt = b"context key material";
let ct = seal_for_user(pt, &kp.public_key, None).unwrap();
assert!(!hybrid::is_hybrid_ciphertext(&ct));
let opened = unseal_from_user(&ct, &kp.public_key, &kp.private_key, None).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn hybrid_roundtrip() {
let kp = generate_keypair();
let hkp = generate_hybrid_keypair();
let pt = b"context key material";
let ct = seal_for_user(pt, &kp.public_key, Some(&hkp.public_key)).unwrap();
assert!(hybrid::is_hybrid_ciphertext(&ct));
let opened =
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp.secret_key)).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn empty_pq_key_falls_back_to_legacy() {
let kp = generate_keypair();
let pt = b"context key";
let ct = seal_for_user(pt, &kp.public_key, Some("")).unwrap();
assert!(!hybrid::is_hybrid_ciphertext(&ct));
let opened = unseal_from_user(&ct, &kp.public_key, &kp.private_key, None).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn legacy_ct_with_pq_key_available() {
let kp = generate_keypair();
let hkp = generate_hybrid_keypair();
let pt = b"old pre-migration key";
let ct = seal_for_user(pt, &kp.public_key, None).unwrap();
let opened =
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp.secret_key)).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn cat5_seal_with_level_roundtrip() {
let kp = generate_keypair();
let hkp = generate_hybrid_keypair_1024();
let pt = b"cat5 context key material";
let ct = seal_for_user_with_level(
pt,
&kp.public_key,
Some(&hkp.public_key),
SecurityLevel::Cat5,
)
.unwrap();
assert!(hybrid::is_hybrid_ciphertext(&ct));
let opened =
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp.secret_key)).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn cat3_seal_with_level_roundtrip() {
let kp = generate_keypair();
let hkp = generate_hybrid_keypair();
let pt = b"cat3 via with_level";
let ct = seal_for_user_with_level(
pt,
&kp.public_key,
Some(&hkp.public_key),
SecurityLevel::Cat3,
)
.unwrap();
assert!(hybrid::is_hybrid_ciphertext(&ct));
let opened =
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp.secret_key)).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn with_level_no_pq_key_falls_back_to_legacy() {
let kp = generate_keypair();
let pt = b"no pq key";
let ct = seal_for_user_with_level(pt, &kp.public_key, None, SecurityLevel::Cat5).unwrap();
assert!(!hybrid::is_hybrid_ciphertext(&ct));
let opened = unseal_from_user(&ct, &kp.public_key, &kp.private_key, None).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn legacy_colliding_with_hybrid_tag_and_length_falls_back() {
use crate::box_seal;
let kp = generate_keypair();
let hkp = generate_hybrid_keypair_512();
let pt = vec![7u8; 900];
let mut sealed = None;
for _ in 0..200_000 {
let ct = box_seal::box_seal(&pt, &kp.public_key).unwrap();
if b64::decode(&ct).unwrap().first() == Some(&0x01) {
sealed = Some(ct);
break;
}
}
let ct = sealed.expect("should find a 0x01-leading legacy ct");
assert!(hybrid::is_hybrid_ciphertext(&ct));
let opened =
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp.secret_key)).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn real_hybrid_wrong_key_still_errors_with_fallback() {
let kp = generate_keypair();
let hkp1 = generate_hybrid_keypair();
let hkp2 = generate_hybrid_keypair();
let pt = b"real hybrid ciphertext";
let ct = seal_for_user(pt, &kp.public_key, Some(&hkp1.public_key)).unwrap();
assert!(
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp2.secret_key)).is_err()
);
}
#[test]
fn seal_for_user_with_suite_pure_cnsa2_roundtrip() {
use crate::hybrid::generate_hybrid_keypair_suite;
let kp = generate_keypair();
let hkp = generate_hybrid_keypair_suite(Suite::PureCnsa2, SecurityLevel::Cat5).unwrap();
let pt = b"pure CNSA-2.0 context key";
let ct = seal_for_user_with_suite(
pt,
&kp.public_key,
Some(&hkp.public_key),
Suite::PureCnsa2,
SecurityLevel::Cat5,
)
.unwrap();
assert!(hybrid::is_hybrid_ciphertext(&ct));
let opened =
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp.secret_key)).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
#[test]
fn seal_for_user_with_suite_matched_cat5_roundtrip() {
use crate::hybrid::generate_hybrid_keypair_suite;
let kp = generate_keypair();
let hkp = generate_hybrid_keypair_suite(Suite::HybridMatched, SecurityLevel::Cat5).unwrap();
let pt = b"matched cat-5 context key";
let ct = seal_for_user_with_suite(
pt,
&kp.public_key,
Some(&hkp.public_key),
Suite::HybridMatched,
SecurityLevel::Cat5,
)
.unwrap();
let opened =
unseal_from_user(&ct, &kp.public_key, &kp.private_key, Some(&hkp.secret_key)).unwrap();
assert_eq!(b64::decode(&opened).unwrap(), pt);
}
}