use crate::vec_truncate;
use crate::Error;
use crate::packet::{
Key,
key,
};
use crate::Result;
use crate::types::{
Curve,
HashAlgorithm,
SymmetricAlgorithm,
PublicKeyAlgorithm,
};
use crate::utils::{
write_be_u64,
read_be_u64,
};
use crate::crypto::SessionKey;
use crate::crypto::mem::Protected;
use crate::crypto::mpis::{MPI, PublicKey, SecretKeyMaterial, Ciphertext};
use nettle::{cipher, curve25519, mode, mode::Mode, ecc, ecdh, random::Yarrow};
#[allow(non_snake_case)]
pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>,
session_key: &SessionKey)
-> Result<Ciphertext>
where R: key::KeyRole
{
let mut rng = Yarrow::default();
if let &PublicKey::ECDH {
ref curve, ref q,..
} = recipient.mpis() {
match curve {
Curve::Cv25519 => {
let R = q.decode_point(curve)?.0;
let v: Protected =
curve25519::private_key(&mut rng).into();
let mut VB = [0x40; 1 + curve25519::CURVE25519_SIZE];
curve25519::mul_g(&mut VB[1..], &v)
.expect("buffers are of the wrong size");
let VB = MPI::new(&VB);
let mut S: Protected =
vec![0; curve25519::CURVE25519_SIZE].into();
curve25519::mul(&mut S, &v, R)
.expect("buffers are of the wrong size");
encrypt_shared(recipient, session_key, VB, &S)
}
Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => {
let (Rx, Ry) = q.decode_point(curve)?;
let (R, v, field_sz) = match curve {
Curve::NistP256 => {
let R = ecc::Point::new::<ecc::Secp256r1>(Rx, Ry)?;
let v =
ecc::Scalar::new_random::<ecc::Secp256r1, _>(&mut rng);
let field_sz = 256;
(R, v, field_sz)
}
Curve::NistP384 => {
let R = ecc::Point::new::<ecc::Secp384r1>(Rx, Ry)?;
let v =
ecc::Scalar::new_random::<ecc::Secp384r1, _>(&mut rng);
let field_sz = 384;
(R, v, field_sz)
}
Curve::NistP521 => {
let R = ecc::Point::new::<ecc::Secp521r1>(Rx, Ry)?;
let v =
ecc::Scalar::new_random::<ecc::Secp521r1, _>(&mut rng);
let field_sz = 521;
(R, v, field_sz)
}
_ => unreachable!(),
};
let VB = ecdh::point_mul_g(&v);
let (VBx, VBy) = VB.as_bytes();
let VB = MPI::new_weierstrass(&VBx, &VBy, field_sz);
let S = ecdh::point_mul(&v, &R)?;
let (Sx, Sy) = S.as_bytes();
Protected::from(Sy);
let mut Sx = Vec::from(Sx);
while Sx.len() < (field_sz + 7) / 8 {
Sx.insert(0, 0);
}
encrypt_shared(recipient, session_key, VB, &Sx.into())
}
Curve::BrainpoolP256 | Curve::BrainpoolP512 =>
Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),
Curve::Unknown(_) | Curve::Ed25519 =>
Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),
Curve::__Nonexhaustive => unreachable!(),
}
} else {
Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into())
}
}
#[allow(non_snake_case)]
pub fn encrypt_shared<R>(recipient: &Key<key::PublicParts, R>,
session_key: &SessionKey, VB: MPI,
S: &Protected)
-> Result<Ciphertext>
where R: key::KeyRole
{
match recipient.mpis() {
&PublicKey::ECDH{ ref curve, ref hash, ref sym,.. } => {
let mut m = Vec::with_capacity(40);
m.extend_from_slice(session_key);
let m = pkcs5_pad(m.into(), 40);
let param = make_param(recipient, curve, hash, sym);
#[allow(non_snake_case)]
let Z = kdf(S, sym.key_size()?, *hash, ¶m)?;
#[allow(non_snake_case)]
let C = aes_key_wrap(*sym, &Z, &m)?;
Ok(Ciphertext::ECDH {
e: VB,
key: C.into_boxed_slice(),
})
}
_ =>
Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()),
}
}
#[allow(non_snake_case)]
pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>,
recipient_sec: &SecretKeyMaterial,
ciphertext: &Ciphertext)
-> Result<SessionKey>
where R: key::KeyRole
{
match (recipient.mpis(), recipient_sec, ciphertext) {
(PublicKey::ECDH { ref curve, ..},
SecretKeyMaterial::ECDH { ref scalar, },
Ciphertext::ECDH { ref e, .. }) =>
{
let S: Protected = match curve {
Curve::Cv25519 => {
let V = e.decode_point(curve)?.0;
let missing = curve25519::CURVE25519_SIZE
.saturating_sub(scalar.value().len());
let mut r = [0u8; curve25519::CURVE25519_SIZE];
r[missing..].copy_from_slice(scalar.value());
r.reverse();
let mut S: Protected =
vec![0; curve25519::CURVE25519_SIZE].into();
let res = curve25519::mul(&mut S, &r[..], V);
unsafe {
memsec::memzero(r.as_mut_ptr(),
curve25519::CURVE25519_SIZE);
}
res.expect("buffers are of the wrong size");
S
}
Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => {
let (Vx, Vy) = e.decode_point(curve)?;
let (V, r, field_sz) = match curve {
Curve::NistP256 => {
let V =
ecc::Point::new::<ecc::Secp256r1>(&Vx, &Vy)?;
let r =
ecc::Scalar::new::<ecc::Secp256r1>(scalar.value())?;
(V, r, 256)
}
Curve::NistP384 => {
let V =
ecc::Point::new::<ecc::Secp384r1>(&Vx, &Vy)?;
let r =
ecc::Scalar::new::<ecc::Secp384r1>(scalar.value())?;
(V, r, 384)
}
Curve::NistP521 => {
let V =
ecc::Point::new::<ecc::Secp521r1>(&Vx, &Vy)?;
let r =
ecc::Scalar::new::<ecc::Secp521r1>(scalar.value())?;
(V, r, 521)
}
_ => unreachable!(),
};
let S = ecdh::point_mul(&r, &V)?;
let (Sx, Sy) = S.as_bytes();
Protected::from(Sy);
let mut Sx = Vec::from(Sx);
while Sx.len() < (field_sz + 7) / 8 {
Sx.insert(0, 0);
}
Sx.into()
}
_ => {
return Err(Error::UnsupportedEllipticCurve(curve.clone()).into());
}
};
decrypt_shared(recipient, &S, ciphertext)
}
_ =>
Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()),
}
}
#[allow(non_snake_case)]
pub fn decrypt_shared<R>(recipient: &Key<key::PublicParts, R>,
S: &Protected,
ciphertext: &Ciphertext)
-> Result<SessionKey>
where R: key::KeyRole
{
match (recipient.mpis(), ciphertext) {
(PublicKey::ECDH { ref curve, ref hash, ref sym, ..},
Ciphertext::ECDH { ref key, .. }) => {
let param = make_param(recipient, curve, hash, sym);
#[allow(non_snake_case)]
let Z = kdf(&S, sym.key_size()?, *hash, ¶m)?;
let m = aes_key_unwrap(*sym, &Z, key)?;
let cipher = SymmetricAlgorithm::from(m[0]);
let m = pkcs5_unpad(m, 1 + cipher.key_size()? + 2)?;
Ok(m.into())
},
_ =>
Err(Error::InvalidArgument(
"Expected an ECDH key and ciphertext".into()).into()),
}
}
fn make_param<P, R>(recipient: &Key<P, R>,
curve: &Curve, hash: &HashAlgorithm,
sym: &SymmetricAlgorithm)
-> Vec<u8>
where P: key::KeyParts,
R: key::KeyRole
{
let fp = recipient.fingerprint();
let mut param = Vec::with_capacity(
1 + curve.oid().len() + 1 + 4 + 20 + fp.as_slice().len());
param.push(curve.oid().len() as u8);
param.extend_from_slice(curve.oid());
param.push(PublicKeyAlgorithm::ECDH.into());
param.push(3);
param.push(1);
param.push((*hash).into());
param.push((*sym).into());
param.extend_from_slice(b"Anonymous Sender ");
param.extend_from_slice(fp.as_slice());
assert_eq!(param.len(),
1 + curve.oid().len() + 1 + 4 + 20 + fp.as_slice().len());
param
}
pub fn kdf(x: &Protected, obits: usize, hash: HashAlgorithm, param: &[u8])
-> Result<Protected> {
let mut hash = hash.context()?;
if obits > hash.digest_size() {
return Err(
Error::InvalidArgument("Hash digest too short".into()).into());
}
hash.update(&[0, 0, 0, 1]);
hash.update(x);
hash.update(param);
let mut key: Protected = vec![0; obits].into();
hash.digest(&mut key);
Ok(key)
}
pub fn pkcs5_pad(sk: Protected, target_len: usize) -> Protected {
let mut buf: Vec<u8> = unsafe {
sk.into_vec()
};
let missing = target_len - buf.len();
assert!(missing <= 0xff);
for _ in 0..missing {
buf.push(missing as u8);
}
assert_eq!(buf.len(), target_len);
buf.into()
}
pub fn pkcs5_unpad(sk: Protected, target_len: usize) -> Result<Protected> {
if sk.len() > 0xff {
return Err(Error::InvalidArgument("message too large".into()).into());
}
if sk.len() < target_len {
return Err(Error::InvalidArgument("message too small".into()).into());
}
let mut buf: Vec<u8> = unsafe {
sk.into_vec()
};
let mut good = true;
let missing = (buf.len() - target_len) as u8;
for &b in &buf[target_len..] {
good = b == missing && good;
}
if good {
vec_truncate(&mut buf, target_len);
Ok(buf.into())
} else {
let sk: Protected = buf.into();
drop(sk);
Err(Error::InvalidArgument("bad padding".into()).into())
}
}
pub fn aes_key_wrap(algo: SymmetricAlgorithm, key: &Protected,
plaintext: &Protected)
-> Result<Vec<u8>> {
use crate::SymmetricAlgorithm::*;
if plaintext.len() % 8 != 0 {
return Err(Error::InvalidArgument(
"Plaintext must be a multiple of 8".into()).into());
}
if key.len() != algo.key_size()? {
return Err(Error::InvalidArgument("Bad key size".into()).into());
}
let mut cipher: Box<dyn Mode> = match algo {
AES128 => Box::new(
mode::Cbc::<cipher::Aes128>::with_encrypt_key(key)?),
AES192 => Box::new(
mode::Cbc::<cipher::Aes192>::with_encrypt_key(key)?),
AES256 => Box::new(
mode::Cbc::<cipher::Aes256>::with_encrypt_key(key)?),
_ => return Err(Error::UnsupportedSymmetricAlgorithm(algo).into()),
};
let n = plaintext.len() / 8;
let mut ciphertext = vec![0; 8 + plaintext.len()];
let mut a = AES_KEY_WRAP_IV;
{
let r = &mut ciphertext[8..];
r.copy_from_slice(plaintext);
let mut b = [0; 16];
let mut tmp = [0; 16];
let mut iv: Protected = vec![0; cipher.block_size()].into();
for j in 0..6 {
for i in 0..n {
write_be_u64(&mut tmp[..8], a);
&mut tmp[8..].copy_from_slice(&r[8 * i..8 * (i + 1)]);
iv.iter_mut().for_each(|p| *p = 0); cipher.encrypt(&mut iv, &mut b, &tmp)?;
a = read_be_u64(&b[..8]) ^ ((n * j) + i + 1) as u64;
&mut r[8 * i..8 * (i + 1)].copy_from_slice(&b[8..]);
}
}
}
write_be_u64(&mut ciphertext[..8], a);
Ok(ciphertext)
}
pub fn aes_key_unwrap(algo: SymmetricAlgorithm, key: &Protected,
ciphertext: &[u8])
-> Result<Protected> {
use crate::SymmetricAlgorithm::*;
if ciphertext.len() % 8 != 0 {
return Err(Error::InvalidArgument(
"Ciphertext must be a multiple of 8".into()).into());
}
if key.len() != algo.key_size()? {
return Err(Error::InvalidArgument("Bad key size".into()).into());
}
let mut cipher: Box<dyn Mode> = match algo {
AES128 => Box::new(
mode::Cbc::<cipher::Aes128>::with_decrypt_key(key)?),
AES192 => Box::new(
mode::Cbc::<cipher::Aes192>::with_decrypt_key(key)?),
AES256 => Box::new(
mode::Cbc::<cipher::Aes256>::with_decrypt_key(key)?),
_ => return Err(Error::UnsupportedSymmetricAlgorithm(algo).into()),
};
let n = ciphertext.len() / 8 - 1;
let mut plaintext = Vec::with_capacity(ciphertext.len() - 8);
let mut a = read_be_u64(&ciphertext[..8]);
plaintext.extend_from_slice(&ciphertext[8..]);
let mut plaintext: Protected = plaintext.into();
{
let r = &mut plaintext;
let mut b = [0; 16];
let mut tmp = [0; 16];
let mut iv: Protected = vec![0; cipher.block_size()].into();
for j in (0..6_usize).into_iter().map(|x| 5 - x) {
for i in (0..n).into_iter().map(|x| n - 1 - x) {
write_be_u64(&mut tmp[..8], a ^ ((n * j) + i + 1) as u64);
&mut tmp[8..].copy_from_slice(&r[8 * i..8 * (i + 1)]);
iv.iter_mut().for_each(|p| *p = 0); cipher.decrypt(&mut iv, &mut b, &tmp)?;
a = read_be_u64(&b[..8]);
&mut r[8 * i..8 * (i + 1)].copy_from_slice(&b[8..]);
}
}
}
if a == AES_KEY_WRAP_IV {
Ok(plaintext)
} else {
Err(Error::InvalidArgument("Bad key".into()).into())
}
}
const AES_KEY_WRAP_IV: u64 = 0xa6a6a6a6a6a6a6a6;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pkcs5_padding() {
let v = pkcs5_pad(vec![0, 0, 0].into(), 8);
assert_eq!(&v, &Protected::from(&[0, 0, 0, 5, 5, 5, 5, 5][..]));
let v = pkcs5_unpad(v, 3).unwrap();
assert_eq!(&v, &Protected::from(&[0, 0, 0][..]));
let v = pkcs5_pad(vec![].into(), 8);
assert_eq!(&v, &Protected::from(&[8, 8, 8, 8, 8, 8, 8, 8][..]));
let v = pkcs5_unpad(v, 0).unwrap();
assert_eq!(&v, &Protected::from(&[][..]));
}
#[test]
fn aes_wrapping() {
struct Test {
algo: SymmetricAlgorithm,
kek: &'static [u8],
key_data: &'static [u8],
ciphertext: &'static [u8],
}
const TESTS: &[Test] = &[
Test {
algo: SymmetricAlgorithm::AES128,
kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F],
key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
ciphertext: &[0x1F, 0xA6, 0x8B, 0x0A, 0x81, 0x12, 0xB4, 0x47,
0xAE, 0xF3, 0x4B, 0xD8, 0xFB, 0x5A, 0x7B, 0x82,
0x9D, 0x3E, 0x86, 0x23, 0x71, 0xD2, 0xCF, 0xE5],
},
Test {
algo: SymmetricAlgorithm::AES192,
kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17],
key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
ciphertext: &[0x96, 0x77, 0x8B, 0x25, 0xAE, 0x6C, 0xA4, 0x35,
0xF9, 0x2B, 0x5B, 0x97, 0xC0, 0x50, 0xAE, 0xD2,
0x46, 0x8A, 0xB8, 0xA1, 0x7A, 0xD8, 0x4E, 0x5D],
},
Test {
algo: SymmetricAlgorithm::AES256,
kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F],
key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
ciphertext: &[0x64, 0xE8, 0xC3, 0xF9, 0xCE, 0x0F, 0x5B, 0xA2,
0x63, 0xE9, 0x77, 0x79, 0x05, 0x81, 0x8A, 0x2A,
0x93, 0xC8, 0x19, 0x1E, 0x7D, 0x6E, 0x8A, 0xE7],
},
Test {
algo: SymmetricAlgorithm::AES192,
kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17],
key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07],
ciphertext: &[0x03, 0x1D, 0x33, 0x26, 0x4E, 0x15, 0xD3, 0x32,
0x68, 0xF2, 0x4E, 0xC2, 0x60, 0x74, 0x3E, 0xDC,
0xE1, 0xC6, 0xC7, 0xDD, 0xEE, 0x72, 0x5A, 0x93,
0x6B, 0xA8, 0x14, 0x91, 0x5C, 0x67, 0x62, 0xD2],
},
Test {
algo: SymmetricAlgorithm::AES256,
kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F],
key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07],
ciphertext: &[0xA8, 0xF9, 0xBC, 0x16, 0x12, 0xC6, 0x8B, 0x3F,
0xF6, 0xE6, 0xF4, 0xFB, 0xE3, 0x0E, 0x71, 0xE4,
0x76, 0x9C, 0x8B, 0x80, 0xA3, 0x2C, 0xB8, 0x95,
0x8C, 0xD5, 0xD1, 0x7D, 0x6B, 0x25, 0x4D, 0xA1],
},
Test {
algo: SymmetricAlgorithm::AES256,
kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F],
key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F],
ciphertext: &[0x28, 0xC9, 0xF4, 0x04, 0xC4, 0xB8, 0x10, 0xF4,
0xCB, 0xCC, 0xB3, 0x5C, 0xFB, 0x87, 0xF8, 0x26,
0x3F, 0x57, 0x86, 0xE2, 0xD8, 0x0E, 0xD3, 0x26,
0xCB, 0xC7, 0xF0, 0xE7, 0x1A, 0x99, 0xF4, 0x3B,
0xFB, 0x98, 0x8B, 0x9B, 0x7A, 0x02, 0xDD, 0x21],
},
];
for test in TESTS {
let ciphertext = aes_key_wrap(test.algo,
&test.kek.into(),
&test.key_data.into())
.unwrap();
assert_eq!(test.ciphertext, &ciphertext[..]);
let key_data = aes_key_unwrap(test.algo,
&test.kek.into(),
&ciphertext[..])
.unwrap();
assert_eq!(&Protected::from(test.key_data), &key_data);
}
}
}