use crate::{
error::{AttributesError, CipherError, KdfError},
AttrId, AttrView, CipherAttrView, CipherView, DataView, Error, FingerprintView, KdfAttrView,
Multikey, Views,
};
#[cfg(feature = "legacy_chacha20_fallback")]
use chacha20::cipher::{KeyIvInit, StreamCipher};
#[cfg(feature = "legacy_chacha20_fallback")]
use chacha20::ChaCha20;
use chacha20::Nonce;
use multi_codec::Codec;
use multi_hash::{mh, Multihash};
use multi_trait::TryDecodeFrom;
use multi_util::Varuint;
use zeroize::Zeroizing;
use super::bcrypt::SALT_LENGTH;
pub const KEY_SIZE: usize = poly1305::KEY_SIZE;
#[cfg(test)]
pub(crate) fn nonce_length() -> usize {
Nonce::default().len()
}
pub(crate) struct View<'a> {
mk: &'a Multikey,
cipher: Option<&'a Multikey>,
}
impl<'a> View<'a> {
pub fn new(mk: &'a Multikey, cipher: &'a Multikey) -> Self {
Self {
mk,
cipher: Some(cipher),
}
}
}
impl<'a> TryFrom<&'a Multikey> for View<'a> {
type Error = Error;
fn try_from(mk: &'a Multikey) -> Result<Self, Self::Error> {
Ok(Self { mk, cipher: None })
}
}
impl<'a> AttrView for View<'a> {
fn is_encrypted(&self) -> bool {
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) {
if let Ok((b, _)) = Varuint::<bool>::try_decode_from(v.as_slice()) {
return b.to_inner();
}
}
false
}
fn is_secret_key(&self) -> bool {
true
}
fn is_public_key(&self) -> bool {
false
}
fn is_secret_key_share(&self) -> bool {
false
}
}
impl<'a> DataView for View<'a> {
fn key_bytes(&self) -> Result<Zeroizing<Vec<u8>>, Error> {
let key = self
.mk
.attributes
.get(&AttrId::KeyData)
.ok_or(AttributesError::MissingKey)?;
Ok(key.clone())
}
fn secret_bytes(&self) -> Result<Zeroizing<Vec<u8>>, Error> {
if self.is_encrypted() {
return Err(AttributesError::EncryptedKey.into());
}
self.key_bytes()
}
}
impl<'a> CipherAttrView for View<'a> {
fn cipher_codec(&self) -> Result<Codec, Error> {
Ok(Codec::Chacha20Poly1305)
}
#[allow(deprecated)]
fn nonce_bytes(&self) -> Result<Zeroizing<Vec<u8>>, Error> {
let nonce = self
.mk
.attributes
.get(&AttrId::CipherNonce)
.ok_or(CipherError::MissingNonce)?;
let nonce = Nonce::clone_from_slice(nonce);
Ok(nonce.to_vec().into())
}
fn key_length(&self) -> Result<usize, Error> {
Ok(KEY_SIZE)
}
}
impl<'a> KdfAttrView for View<'a> {
fn kdf_codec(&self) -> Result<Codec, Error> {
let codec = self
.mk
.attributes
.get(&AttrId::KdfCodec)
.ok_or(KdfError::MissingCodec)?;
Ok(Codec::try_from(codec.as_slice())?)
}
fn salt_bytes(&self) -> Result<Zeroizing<Vec<u8>>, Error> {
let salt = self
.mk
.attributes
.get(&AttrId::KdfSalt)
.ok_or(KdfError::MissingSalt)?;
if salt.len() != SALT_LENGTH {
Err(KdfError::InvalidSaltLen.into())
} else {
Ok(salt.clone())
}
}
fn rounds(&self) -> Result<usize, Error> {
let rounds = self
.mk
.attributes
.get(&AttrId::KdfRounds)
.ok_or(KdfError::MissingRounds)?;
Ok(Varuint::<usize>::try_from(rounds.as_slice())?.to_inner())
}
}
impl<'a> CipherView for View<'a> {
#[allow(deprecated)]
fn decrypt(&self) -> Result<Multikey, Error> {
let cipher = self.cipher.ok_or(CipherError::MissingCodec)?;
let attr = self.mk.attr_view()?;
if !attr.is_encrypted() || !attr.is_secret_key() {
return Err(CipherError::DecryptionFailed.into());
}
let nonce = {
let cattr = cipher.cipher_attr_view()?;
cattr.nonce_bytes()?
};
#[cfg(feature = "legacy_chacha20_fallback")]
let n = Nonce::clone_from_slice(&nonce);
let key = {
let kd = cipher.data_view()?;
let key = kd.secret_bytes()?;
if key.len() != self.key_length()? {
return Err(CipherError::InvalidKey.into());
}
key
};
#[cfg(feature = "legacy_chacha20_fallback")]
let k = chacha20::Key::clone_from_slice(&key);
let msg = {
let attr = self.mk.data_view()?;
attr.key_bytes()?
};
let dec = {
use chacha20poly1305::aead::{Aead, KeyInit};
use chacha20poly1305::{ChaCha20Poly1305, Nonce as AeadNonce};
match ChaCha20Poly1305::new_from_slice(&key) {
Ok(aead) => match aead.decrypt(AeadNonce::from_slice(&nonce), msg.as_slice()) {
Ok(pt) => Zeroizing::new(pt),
Err(_) => {
#[cfg(feature = "legacy_chacha20_fallback")]
{
eprintln!(
"multi-key: AEAD decryption failed, falling back to \
legacy unauthenticated ChaCha20 — re-encrypt this \
key to upgrade to authenticated ChaCha20Poly1305"
);
let mut chacha = ChaCha20::new(&k, &n);
let mut legacy = msg.clone();
chacha.apply_keystream(&mut legacy);
legacy
}
#[cfg(not(feature = "legacy_chacha20_fallback"))]
{
return Err(CipherError::DecryptionFailed.into());
}
}
},
Err(_) => return Err(CipherError::InvalidKey.into()),
}
};
let mut res = self.mk.clone();
let _ = res.attributes.remove(&AttrId::KeyIsEncrypted);
res.attributes.insert(AttrId::KeyData, dec);
let _ = res.attributes.remove(&AttrId::CipherCodec);
let _ = res.attributes.remove(&AttrId::CipherKeyLen);
let _ = res.attributes.remove(&AttrId::CipherNonce);
let _ = res.attributes.remove(&AttrId::KdfCodec);
let _ = res.attributes.remove(&AttrId::KdfSalt);
let _ = res.attributes.remove(&AttrId::KdfRounds);
Ok(res)
}
#[allow(deprecated)]
fn encrypt(&self) -> Result<Multikey, Error> {
let cipher = self.cipher.ok_or(CipherError::MissingCodec)?;
let attr = self.mk.attr_view()?;
if attr.is_encrypted() {
return Err(
CipherError::EncryptionFailed("key is encrypted already".to_string()).into(),
);
}
let nonce = {
let cattr = cipher.cipher_attr_view()?;
cattr.nonce_bytes()?
};
let key = {
let kd = cipher.data_view()?;
let key = kd.secret_bytes()?;
if key.len() != self.key_length()? {
return Err(CipherError::InvalidKey.into());
}
key
};
let msg = {
let kd = self.mk.data_view()?;
kd.secret_bytes()?
};
let enc = {
use chacha20poly1305::aead::{Aead, KeyInit};
use chacha20poly1305::{ChaCha20Poly1305, Nonce as AeadNonce};
let aead = ChaCha20Poly1305::new_from_slice(&key).map_err(|_| {
CipherError::EncryptionFailed("invalid cipher key length".to_string())
})?;
let ct = aead
.encrypt(AeadNonce::from_slice(&nonce), msg.as_slice())
.map_err(|_| CipherError::EncryptionFailed("AEAD seal failed".to_string()))?;
Zeroizing::new(ct)
};
let cattr = cipher.cipher_attr_view()?;
let cipher_codec: Vec<u8> = cipher.codec.into();
let key_length: Vec<u8> = Varuint(cattr.key_length()?).into();
let is_encrypted: Vec<u8> = Varuint(true).into();
let kattr = cipher.kdf_attr_view()?;
let kdf_codec: Vec<u8> = kattr.kdf_codec()?.into();
let salt = kattr.salt_bytes()?;
let rounds: Vec<u8> = Varuint(kattr.rounds()?).into();
let mut res = self.mk.clone();
res.attributes
.insert(AttrId::KeyIsEncrypted, is_encrypted.into());
res.attributes.insert(AttrId::KeyData, enc);
res.attributes
.insert(AttrId::CipherCodec, cipher_codec.into());
res.attributes
.insert(AttrId::CipherKeyLen, key_length.into());
res.attributes.insert(AttrId::CipherNonce, nonce.clone());
res.attributes.insert(AttrId::KdfCodec, kdf_codec.into());
res.attributes.insert(AttrId::KdfSalt, salt.clone());
res.attributes.insert(AttrId::KdfRounds, rounds.into());
Ok(res)
}
}
impl<'a> FingerprintView for View<'a> {
fn fingerprint(&self, codec: Codec) -> Result<Multihash, Error> {
let bytes = {
let kd = self.mk.data_view()?;
kd.key_bytes()?
};
Ok(mh::Builder::new_from_bytes(codec, bytes)?.try_build()?)
}
}