use core::ops::Deref;
use bitflags::bitflags;
use encdec::{Encode, Decode, DecodeOwned};
use rand_core::{RngCore, CryptoRng};
use sha2::{Sha512, Digest};
use strum::{Display, EnumString, EnumVariantNames};
use crate::{
error::ManifestError,
types::{PublicKey, Checksum, Signature, PrivateKey, Stringish},
VerifyError,
};
pub const MANIFEST_VERSION: u16 = 0x0001;
pub const MANIFEST_LEN: usize = 2 + 2
+ 16 + 24
+ 4 + 32
+ 2 + 2 + 32
+ ed25519_dalek::PUBLIC_KEY_LENGTH
+ ed25519_dalek::SIGNATURE_LENGTH;
#[derive(Copy, Clone, Debug, PartialEq, Display, EnumString, EnumVariantNames)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[repr(u16)]
pub enum MetadataFormat {
Binary = 0x0000,
Json = 0x0001,
Cbor = 0x0002,
Other = 0xFFFF,
}
bitflags! {
pub struct Flags: u16 {
const TRANSIENT_KEY = 1 << 0;
}
}
#[derive(Clone, Debug, PartialEq, Encode, DecodeOwned)]
pub struct Manifest {
pub version: u16,
pub flags: u16,
pub app_name: Stringish<16>,
pub app_version: Stringish<24>,
pub app_len: u32,
pub app_csum: Checksum,
pub meta_kind: u16,
pub meta_len: u16,
pub meta_csum: Checksum,
pub key: PublicKey,
pub sig: Signature,
}
impl Manifest {
pub fn flags(&self) -> Flags {
Flags::from_bits_truncate(self.flags)
}
pub fn app_name(&self) -> &str {
self.app_name.as_ref()
}
pub fn app_version(&self) -> &str {
self.app_version.as_ref()
}
pub fn app_len(&self) -> usize {
self.app_len as usize
}
pub fn meta_len(&self) -> usize {
self.meta_len as usize
}
pub fn sign<RNG: RngCore + CryptoRng + Default>(&mut self, signing_key: PrivateKey) -> Result<(), ManifestError> {
let digest = self.digest();
let public_key = ed25519_dalek::PublicKey::from(signing_key.deref());
let keys = ed25519_dalek::Keypair{ public: public_key, secret: signing_key.inner() };
let sig = keys.sign_prehashed(digest, None)
.map_err(|_e| ManifestError::SigningFailed)?;
self.sig = Signature::from(sig);
Ok(())
}
pub fn verify(&self, allowed_keys: &[PublicKey]) -> Result<(), ManifestError> {
let signing_key = match allowed_keys.iter().find(|k| *k == &self.key) {
Some(k) => k,
None => return Err(ManifestError::NoMatchingKey),
};
let digest = self.digest();
let sig = ed25519_dalek::Signature::try_from(&self.sig)?;
signing_key.verify_prehashed(digest, None, &sig)
.map_err(|e| ManifestError::VerificationFailed)?;
Ok(())
}
pub fn check(&self, app: &[u8], meta: &[u8]) -> Result<(), VerifyError> {
self.check_sig()?;
let app_csum = Checksum::compute(app);
self.check_app(app.len(), &app_csum)?;
let meta_csum = Checksum::compute(meta);
self.check_meta(meta.len(), &meta_csum)?;
Ok(())
}
pub fn check_precomputed(&self, app_csum: &Checksum, app_len: usize, meta_csum: &Checksum, meta_len: usize) -> Result<(), VerifyError> {
self.check_sig()?;
self.check_app(app_len, app_csum)?;
self.check_meta(meta_len, meta_csum)?;
Ok(())
}
fn check_sig(&self) -> Result<(), VerifyError> {
let digest = self.digest();
let sig = ed25519_dalek::Signature::try_from(&self.sig)
.map_err(|_| VerifyError::InvalidSignature)?;
self.key.deref().verify_prehashed(digest, None, &sig)
.map_err(|e| VerifyError::InvalidSignature)?;
Ok(())
}
fn check_app(&self, app_len: usize, app_csum: &Checksum) -> Result<(), VerifyError> {
if app_len != self.app_len() {
return Err(VerifyError::AppLengthMismatch)
}
if app_csum != &self.app_csum {
return Err(VerifyError::AppChecksumMismatch)
}
Ok(())
}
fn check_meta(&self, meta_len: usize, meta_csum: &Checksum) -> Result<(), VerifyError> {
if meta_len != self.meta_len() {
return Err(VerifyError::MetaLengthMismatch)
}
if meta_csum != &self.meta_csum {
return Err(VerifyError::MetaChecksumMismatch)
}
Ok(())
}
fn digest(&self) -> Sha512 {
let mut h = Sha512::new();
h.update(&self.version.to_le_bytes());
h.update(&self.flags.to_le_bytes());
h.update(&self.app_name.deref());
h.update(&self.app_version.deref());
h.update(&self.app_len.to_le_bytes());
h.update(&self.app_csum.deref());
h.update(&self.meta_kind.to_le_bytes());
h.update(&self.meta_len.to_le_bytes());
h.update(&self.meta_csum.deref());
h.update(&self.key.deref());
h
}
}
#[cfg(test)]
mod tests {
use ed25519_dalek::SIGNATURE_LENGTH;
use rand::rngs::OsRng;
use super::*;
#[test]
fn sign_verify() {
let private_key = PrivateKey::generate(&mut OsRng{});
let public_key = PublicKey::from(&private_key);
let mut m = Manifest {
version: MANIFEST_VERSION,
flags: Flags::TRANSIENT_KEY.bits(),
app_name: "test_app".into(),
app_version: "1.2.7".into(),
app_len: 64 * 1024,
app_csum: Checksum::compute(&[0xab; 32]),
meta_len: 1024,
meta_csum: Checksum::compute(&[0xbc; 32]),
meta_kind: MetadataFormat::Binary as u16,
key: public_key.clone(),
sig: Signature::empty(),
};
m.sign::<OsRng>(private_key).expect("Signing failed");
assert_eq!(&m.key, &public_key);
assert_ne!(m.sig.deref(), &[0u8; 64]);
let mut b = [0u8; 256];
let n = m.encode(&mut b).unwrap();
assert_eq!(n, MANIFEST_LEN);
assert_eq!(m.encode_len().unwrap(), MANIFEST_LEN);
let (m1, n1) = Manifest::decode(&b[..n]).unwrap();
assert_eq!(n, n1);
m1.verify(&[public_key]).expect("Verification failed");
}
#[test]
fn digests_match() {
let private_key = PrivateKey::generate(&mut OsRng{});
let public_key = PublicKey::from(&private_key);
let m = Manifest {
version: MANIFEST_VERSION,
flags: Flags::TRANSIENT_KEY.bits(),
app_name: "test_app".into(),
app_version: "1.2.7".into(),
app_len: 64 * 1024,
app_csum: Checksum::compute(&[0xab; 32]),
meta_len: 1024,
meta_csum: Checksum::compute(&[0xbc; 32]),
meta_kind: MetadataFormat::Binary as u16,
key: public_key.clone(),
sig: Signature::empty(),
};
let d = m.digest().finalize();
let mut b = [0u8; 256];
let n = m.encode(&mut b).unwrap();
let mut h = Sha512::new();
h.update(&b[..MANIFEST_LEN - SIGNATURE_LENGTH]);
let d1 = h.finalize();
assert_eq!(d, d1);
}
}