wecanencrypt 0.1.0

Simple Rust OpenPGP library for encryption, signing, and key management (includes rpgp).
Documentation
use std::io::{self, BufRead};

use byteorder::{BigEndian, WriteBytesExt};
use bytes::{Buf, Bytes};
use num_bigint::BigUint;

use crate::pgp::{
    errors::{InvalidInputSnafu, Result},
    parsing_reader::BufReadParsing,
    ser::Serialize,
};

/// Number of bits we accept when reading or writing MPIs.
/// The value is the same as gnupgs.
const MAX_EXTERN_MPI_BITS: u16 = 16384;

/// Represents an owned MPI value.
/// The inner value is ready to be serialized, without the need to strip leading zeros.
///
/// Ref: <https://www.rfc-editor.org/rfc/rfc9580.html#name-multiprecision-integers>
#[derive(Default, Clone, PartialEq, Eq, derive_more::Debug)]
pub struct Mpi(#[debug("{}", hex::encode(_0))] Bytes);

impl Mpi {
    /// Wraps the given bytes as an MPI, must be normalized before
    /// Avoid if possible.
    ///
    /// Only used internally to handle reversed Curve 25519 "Mpis".
    pub(crate) fn from_raw(bytes: Bytes) -> Self {
        Mpi(bytes)
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Parses the given reader as an MPI.
    ///
    /// The buffer is expected to be length-prefixed.
    pub fn try_from_reader<B: BufRead>(mut i: B) -> Result<Self> {
        let len_bits = i.read_be_u16()?;

        if len_bits > MAX_EXTERN_MPI_BITS {
            return Err(InvalidInputSnafu.build());
        }

        let len_bytes = (len_bits + 7) >> 3;

        let mut n = i.take_bytes(usize::from(len_bytes))?.freeze();
        let n_stripped = match leading_zeros_offset(&n) {
            Some(offset) => {
                n.advance(offset);
                n
            }
            None => Bytes::new(),
        };

        Ok(Mpi(n_stripped))
    }

    /// Represent the data in `raw` as an Mpi.
    /// Note that `raw` is not expected to be length-prefixed!
    ///
    /// Strips leading zeros.
    pub fn from_slice(raw: &[u8]) -> Self {
        Self(strip_leading_zeros(raw).to_vec().into())
    }
}

/// Returns the bit length of a given slice.
#[inline]
fn bit_size(val: &[u8]) -> usize {
    if val.is_empty() {
        0
    } else {
        (val.len() * 8) - val[0].leading_zeros() as usize
    }
}

#[inline]
fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
    match leading_zeros_offset(bytes) {
        Some(offset) => &bytes[offset..],
        None => &[],
    }
}

#[inline]
fn leading_zeros_offset(bytes: &[u8]) -> Option<usize> {
    bytes.iter().position(|b| b != &0)
}

impl AsRef<[u8]> for Mpi {
    fn as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }
}

impl Serialize for Mpi {
    fn to_writer<W: io::Write>(&self, w: &mut W) -> Result<()> {
        let bytes = &self.0;
        let size = bit_size(bytes);
        w.write_u16::<BigEndian>(size as u16)?;
        w.write_all(bytes)?;

        Ok(())
    }

    fn write_len(&self) -> usize {
        2 + self.0.len()
    }
}

impl From<BigUint> for Mpi {
    fn from(other: BigUint) -> Self {
        Mpi(other.to_bytes_be().into())
    }
}

impl From<&BigUint> for Mpi {
    fn from(other: &BigUint) -> Self {
        Mpi(other.to_bytes_be().into())
    }
}

impl From<Mpi> for BigUint {
    fn from(other: Mpi) -> Self {
        BigUint::from_bytes_be(other.as_ref())
    }
}

#[cfg(test)]
mod tests {
    use proptest::prelude::*;

    use super::*;

    impl Arbitrary for Mpi {
        type Parameters = ();
        type Strategy = BoxedStrategy<Self>;

        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
            proptest::collection::vec(0u8..255, 1..500)
                .prop_map(|v| Mpi::from_slice(&v))
                .boxed()
        }
    }

    #[test]
    fn test_mpi() {
        // Decode the number `511` (`0x1FF` in hex).
        assert_eq!(
            Mpi::try_from_reader(&mut &[0x00, 0x09, 0x01, 0xFF][..]).unwrap(),
            Mpi::from_slice(&[0x01, 0xFF][..])
        );

        // Decode the number `2^255 + 7`.
        assert_eq!(
            Mpi::try_from_reader(
                &mut &[
                    0x01, 0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0x07
                ][..]
            )
            .unwrap(),
            Mpi::from_slice(
                &[
                    0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0x07
                ][..]
            )
        );
    }

    #[test]
    fn test_bignum_mpi() {
        let fixtures = [
            ("b4a71b058ac8aa1ddc453ab2663331c38f7645542815ac189a9af56d0e07a615469d3e08849650e03026d49259423cf00d089931cd700fd3a6e940bf83c81406e142a4b0a86f00738c7e1a9ff1b709f6bccc6cf900d0113a8e62e53d63be0a05105755b9efc6a4098c362c73fb422d40187d8e2382e88624d72caffceb13cec8fa0079c7d17883a46a1336471ab5be8cbb555c5d330d7fadb43318fa73b584edac312fa3302886bb5d04a05da3be2676c1fb94b3cf5c19d598659c3a7728ebab95f71721b662ac46aa9910726fe576d438f789c5ce2448f54546f254da814bcae1c35ee44b171e870ffa6403167a10e68573bdf155549274b431ff8e2418b627", "0800b4a71b058ac8aa1ddc453ab2663331c38f7645542815ac189a9af56d0e07a615469d3e08849650e03026d49259423cf00d089931cd700fd3a6e940bf83c81406e142a4b0a86f00738c7e1a9ff1b709f6bccc6cf900d0113a8e62e53d63be0a05105755b9efc6a4098c362c73fb422d40187d8e2382e88624d72caffceb13cec8fa0079c7d17883a46a1336471ab5be8cbb555c5d330d7fadb43318fa73b584edac312fa3302886bb5d04a05da3be2676c1fb94b3cf5c19d598659c3a7728ebab95f71721b662ac46aa9910726fe576d438f789c5ce2448f54546f254da814bcae1c35ee44b171e870ffa6403167a10e68573bdf155549274b431ff8e2418b627"),
            ("00e57192fa7bd6abd7d01331f0411eebff4651290af1329369cc3bb3b8ccbd7ba6e352400c3f64f637967e24524921ee04f1e0a79168781f0bec9029e34c8a1fb1c328a4b8d74c31429616a6ff4707bb56b71ab66643243087c8ff0d0c4883b3473c56deece9a83dbd06eef09fac3558003ae45f8898b8a9490aa79672eebdd7d985d051d62698f2da7eee33ba740e30fc5a93c3f16ca1490dfd62b84ba016c9da7c087a28a4e97d8af79c6b638bc22f20a8b5953bb83caa3dddaaf1d0dc15a3f7ed47870174af74e5308b856138771a10019fe4374389eb89d2280776e33fa2dd3526cec35cd86a9cf6c94253fe00c4b8a87a36451745116456833bb1a237", "07f0e57192fa7bd6abd7d01331f0411eebff4651290af1329369cc3bb3b8ccbd7ba6e352400c3f64f637967e24524921ee04f1e0a79168781f0bec9029e34c8a1fb1c328a4b8d74c31429616a6ff4707bb56b71ab66643243087c8ff0d0c4883b3473c56deece9a83dbd06eef09fac3558003ae45f8898b8a9490aa79672eebdd7d985d051d62698f2da7eee33ba740e30fc5a93c3f16ca1490dfd62b84ba016c9da7c087a28a4e97d8af79c6b638bc22f20a8b5953bb83caa3dddaaf1d0dc15a3f7ed47870174af74e5308b856138771a10019fe4374389eb89d2280776e33fa2dd3526cec35cd86a9cf6c94253fe00c4b8a87a36451745116456833bb1a237"),
        ];

        for (i, (raw, encoded)) in fixtures.iter().enumerate() {
            println!("fixture {i}");
            let n = hex::decode(raw).unwrap();

            let n_big = BigUint::from_bytes_be(&n);
            let n_mpi: Mpi = n_big.clone().into();
            let mut n_encoded = Vec::new();
            n_mpi.to_writer(&mut n_encoded).unwrap();

            assert_eq!(&n_encoded, &hex::decode(encoded).unwrap());

            let n_big2 = Mpi::try_from_reader(&mut &n_encoded[..]).unwrap();
            assert_eq!(n_big, n_big2.into());
        }
    }

    #[test]
    fn test_strip_leading_zeros_with_all_zeros() {
        let buf = [0u8, 0u8, 0u8];
        let stripped: &[u8] = strip_leading_zeros(&buf[..]);
        assert!(stripped.is_empty());
    }

    proptest! {
        #[test]
        fn mpi_bytes_wite_le(m: Mpi) {
            let mut buf = Vec::new();
            m.to_writer(&mut buf)?;

            prop_assert_eq!(m.write_len(), buf.len());
        }

        #[test]
        fn mpi_bytes_roundtrip(m: Mpi) {
            let mut buf = Vec::new();
            m.to_writer(&mut buf)?;

            let m_back = Mpi::try_from_reader(&mut &buf[..])?;
            prop_assert_eq!(m, m_back);
        }
    }
}