use crate::core::error::{PaserkError, PaserkResult};
pub const K1_RSA_CIPHERTEXT_SIZE: usize = 512;
pub const K1_SEAL_CIPHERTEXT_SIZE: usize = 32;
pub const K1_SEAL_TAG_SIZE: usize = 48;
pub const K1_SEAL_DATA_SIZE: usize =
K1_SEAL_TAG_SIZE + K1_SEAL_CIPHERTEXT_SIZE + K1_RSA_CIPHERTEXT_SIZE;
pub type K1SealOutput = (
[u8; K1_SEAL_TAG_SIZE],
[u8; K1_SEAL_CIPHERTEXT_SIZE],
[u8; K1_RSA_CIPHERTEXT_SIZE],
);
const EK_DOMAIN_BYTE: u8 = 0x01;
const AK_DOMAIN_BYTE: u8 = 0x02;
#[cfg(feature = "k1-insecure")]
pub fn seal_k1(
plaintext_key: &[u8; 32],
recipient_pk: &rsa::RsaPublicKey,
header: &str,
) -> PaserkResult<K1SealOutput> {
use aes::cipher::{KeyIvInit, StreamCipher};
use ctr::Ctr64BE;
use hmac::{Hmac, Mac};
use rand_core::{OsRng, TryRngCore};
use rsa::traits::PublicKeyParts;
use rsa::BigUint;
use sha2::{Digest, Sha384};
type HmacSha384 = Hmac<Sha384>;
type Aes256Ctr = Ctr64BE<aes::Aes256>;
let n = recipient_pk.n();
if n.bits() != 4096 {
return Err(PaserkError::InvalidKey);
}
let mut r_bytes = [0u8; 512];
OsRng
.try_fill_bytes(&mut r_bytes)
.map_err(|_| PaserkError::CryptoError)?;
r_bytes[0] &= 0x7F; r_bytes[0] |= 0x40;
let r = BigUint::from_bytes_be(&r_bytes);
let e = recipient_pk.e();
let c_bigint = r.modpow(e, n);
let c_bytes_raw = c_bigint.to_bytes_be();
let mut c = [0u8; K1_RSA_CIPHERTEXT_SIZE];
let start = K1_RSA_CIPHERTEXT_SIZE.saturating_sub(c_bytes_raw.len());
c[start..].copy_from_slice(&c_bytes_raw);
let c_hash = Sha384::digest(c);
let mut ek_mac =
<HmacSha384 as Mac>::new_from_slice(&c_hash).map_err(|_| PaserkError::CryptoError)?;
ek_mac.update(&[EK_DOMAIN_BYTE]);
ek_mac.update(header.as_bytes());
ek_mac.update(&r_bytes);
let ek_result = ek_mac.finalize().into_bytes();
let mut encryption_key = [0u8; 32];
encryption_key.copy_from_slice(&ek_result[..32]);
let mut counter = [0u8; 16];
counter.copy_from_slice(&ek_result[32..48]);
let mut ak_mac =
<HmacSha384 as Mac>::new_from_slice(&c_hash).map_err(|_| PaserkError::CryptoError)?;
ak_mac.update(&[AK_DOMAIN_BYTE]);
ak_mac.update(header.as_bytes());
ak_mac.update(&r_bytes);
let ak_result = ak_mac.finalize().into_bytes();
let mut auth_key = [0u8; 48];
auth_key.copy_from_slice(&ak_result[..48]);
let mut edk = *plaintext_key;
let mut cipher = Aes256Ctr::new(&encryption_key.into(), &counter.into());
cipher.apply_keystream(&mut edk);
let mut tag_mac =
<HmacSha384 as Mac>::new_from_slice(&auth_key).map_err(|_| PaserkError::CryptoError)?;
tag_mac.update(header.as_bytes());
tag_mac.update(&c);
tag_mac.update(&edk);
let tag_result = tag_mac.finalize().into_bytes();
let mut tag = [0u8; K1_SEAL_TAG_SIZE];
tag.copy_from_slice(&tag_result[..K1_SEAL_TAG_SIZE]);
zeroize::Zeroize::zeroize(&mut r_bytes);
zeroize::Zeroize::zeroize(&mut encryption_key);
zeroize::Zeroize::zeroize(&mut auth_key);
Ok((tag, edk, c))
}
#[cfg(feature = "k1-insecure")]
pub fn unseal_k1(
tag: &[u8; K1_SEAL_TAG_SIZE],
edk: &[u8; K1_SEAL_CIPHERTEXT_SIZE],
c: &[u8; K1_RSA_CIPHERTEXT_SIZE],
recipient_sk: &rsa::RsaPrivateKey,
header: &str,
) -> PaserkResult<[u8; 32]> {
use aes::cipher::{KeyIvInit, StreamCipher};
use ctr::Ctr64BE;
use hmac::{Hmac, Mac};
use rsa::traits::{PrivateKeyParts, PublicKeyParts};
use rsa::BigUint;
use sha2::{Digest, Sha384};
use subtle::ConstantTimeEq;
type HmacSha384 = Hmac<Sha384>;
type Aes256Ctr = Ctr64BE<aes::Aes256>;
let n = recipient_sk.n();
if n.bits() != 4096 {
return Err(PaserkError::InvalidKey);
}
let c_bigint = BigUint::from_bytes_be(c);
let d = recipient_sk.d();
let r_bigint = c_bigint.modpow(d, n);
let r_bytes_raw = r_bigint.to_bytes_be();
let mut r_bytes = [0u8; 512];
let start = 512_usize.saturating_sub(r_bytes_raw.len());
r_bytes[start..].copy_from_slice(&r_bytes_raw);
let c_hash = Sha384::digest(c);
let mut ak_mac =
<HmacSha384 as Mac>::new_from_slice(&c_hash).map_err(|_| PaserkError::CryptoError)?;
ak_mac.update(&[AK_DOMAIN_BYTE]);
ak_mac.update(header.as_bytes());
ak_mac.update(&r_bytes);
let ak_result = ak_mac.finalize().into_bytes();
let mut auth_key = [0u8; 48];
auth_key.copy_from_slice(&ak_result[..48]);
let mut tag_mac =
<HmacSha384 as Mac>::new_from_slice(&auth_key).map_err(|_| PaserkError::CryptoError)?;
tag_mac.update(header.as_bytes());
tag_mac.update(c);
tag_mac.update(edk);
let computed_tag = tag_mac.finalize().into_bytes();
let tag_valid: bool = computed_tag[..K1_SEAL_TAG_SIZE].ct_eq(tag).into();
zeroize::Zeroize::zeroize(&mut auth_key);
if !tag_valid {
zeroize::Zeroize::zeroize(&mut r_bytes);
return Err(PaserkError::AuthenticationFailed);
}
let mut ek_mac =
<HmacSha384 as Mac>::new_from_slice(&c_hash).map_err(|_| PaserkError::CryptoError)?;
ek_mac.update(&[EK_DOMAIN_BYTE]);
ek_mac.update(header.as_bytes());
ek_mac.update(&r_bytes);
let ek_result = ek_mac.finalize().into_bytes();
let mut encryption_key = [0u8; 32];
encryption_key.copy_from_slice(&ek_result[..32]);
let mut counter = [0u8; 16];
counter.copy_from_slice(&ek_result[32..48]);
let mut plaintext = *edk;
let mut cipher = Aes256Ctr::new(&encryption_key.into(), &counter.into());
cipher.apply_keystream(&mut plaintext);
zeroize::Zeroize::zeroize(&mut r_bytes);
zeroize::Zeroize::zeroize(&mut encryption_key);
Ok(plaintext)
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
#[cfg(feature = "k1-insecure")]
fn generate_test_keypair() -> PaserkResult<(rsa::RsaPrivateKey, rsa::RsaPublicKey)> {
use rsa::RsaPrivateKey;
let mut rng = rand::thread_rng();
let private_key =
RsaPrivateKey::new(&mut rng, 4096).map_err(|_| PaserkError::CryptoError)?;
let public_key = private_key.to_public_key();
Ok((private_key, public_key))
}
#[test]
#[cfg(feature = "k1-insecure")]
fn test_seal_unseal_roundtrip() -> PaserkResult<()> {
let (private_key, public_key) = generate_test_keypair()?;
let plaintext_key = [0x42u8; 32];
let header = "k1.seal.";
let (tag, edk, c) = seal_k1(&plaintext_key, &public_key, header)?;
assert_ne!(edk, plaintext_key);
let unsealed = unseal_k1(&tag, &edk, &c, &private_key, header)?;
assert_eq!(unsealed, plaintext_key);
Ok(())
}
#[test]
#[cfg(feature = "k1-insecure")]
fn test_seal_produces_different_output() -> PaserkResult<()> {
let (_, public_key) = generate_test_keypair()?;
let plaintext_key = [0x42u8; 32];
let header = "k1.seal.";
let (tag1, edk1, c1) = seal_k1(&plaintext_key, &public_key, header)?;
let (tag2, edk2, c2) = seal_k1(&plaintext_key, &public_key, header)?;
assert_ne!(c1, c2);
assert_ne!(edk1, edk2);
assert_ne!(tag1, tag2);
Ok(())
}
#[test]
#[cfg(feature = "k1-insecure")]
fn test_unseal_wrong_key() -> PaserkResult<()> {
let (_, public_key1) = generate_test_keypair()?;
let (private_key2, _) = generate_test_keypair()?;
let plaintext_key = [0x42u8; 32];
let header = "k1.seal.";
let (tag, edk, c) = seal_k1(&plaintext_key, &public_key1, header)?;
let result = unseal_k1(&tag, &edk, &c, &private_key2, header);
assert!(matches!(result, Err(PaserkError::AuthenticationFailed)));
Ok(())
}
#[test]
#[cfg(feature = "k1-insecure")]
fn test_unseal_modified_tag() -> PaserkResult<()> {
let (private_key, public_key) = generate_test_keypair()?;
let plaintext_key = [0x42u8; 32];
let header = "k1.seal.";
let (mut tag, edk, c) = seal_k1(&plaintext_key, &public_key, header)?;
tag[0] ^= 0xff;
let result = unseal_k1(&tag, &edk, &c, &private_key, header);
assert!(matches!(result, Err(PaserkError::AuthenticationFailed)));
Ok(())
}
#[test]
#[cfg(feature = "k1-insecure")]
fn test_rsa_ciphertext_size() -> PaserkResult<()> {
let (_, public_key) = generate_test_keypair()?;
let plaintext_key = [0x42u8; 32];
let header = "k1.seal.";
let (_, _, c) = seal_k1(&plaintext_key, &public_key, header)?;
assert_eq!(c.len(), 512);
Ok(())
}
#[test]
#[cfg(feature = "k1-insecure")]
fn test_reject_wrong_key_size() -> PaserkResult<()> {
use rsa::RsaPrivateKey;
let mut rng = rand::thread_rng();
let private_key =
RsaPrivateKey::new(&mut rng, 2048).map_err(|_| PaserkError::CryptoError)?;
let public_key = private_key.to_public_key();
let plaintext_key = [0x42u8; 32];
let header = "k1.seal.";
let result = seal_k1(&plaintext_key, &public_key, header);
assert!(matches!(result, Err(PaserkError::InvalidKey)));
Ok(())
}
}