wechat-protocol 1.5.1

Native WeChat transport, authentication and protocol codecs
Documentation
use crate::MAX_MESSAGE_BYTES;
use anyhow::{ensure, Result};
use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression};
use openssl::{
    bn::{BigNum, BigNumContext},
    derive::Deriver,
    ec::{EcGroup, EcKey, EcPoint, PointConversionForm},
    hash::{hash, MessageDigest},
    nid::Nid,
    pkey::{PKey, Private},
    rand::rand_bytes,
    sign::{Signer, Verifier},
    symm::{Cipher, Crypter, Mode},
};
use std::io::{Read, Write};
use zeroize::Zeroize;

pub fn random<const N: usize>() -> Result<[u8; N]> {
    let mut b = [0; N];
    rand_bytes(&mut b)?;
    Ok(b)
}
pub fn sha256(b: &[u8]) -> Vec<u8> {
    hash(MessageDigest::sha256(), b)
        .expect("OpenSSL SHA256")
        .to_vec()
}
pub fn md5(b: &[u8]) -> Vec<u8> {
    hash(MessageDigest::md5(), b).expect("OpenSSL MD5").to_vec()
}
pub fn hmac(key: &[u8], input: &[u8]) -> Result<Vec<u8>> {
    let key = PKey::hmac(key)?;
    let mut s = Signer::new(MessageDigest::sha256(), &key)?;
    s.update(input)?;
    Ok(s.sign_to_vec()?)
}
pub fn expand(secret: &[u8], info: &[u8], size: usize) -> Result<Vec<u8>> {
    ensure!(size > 0 && size <= 255 * 32, "Invalid HKDF size");
    let mut last = Vec::new();
    let mut out = Vec::new();
    for i in 1..=255u8 {
        last = hmac(secret, &[last.as_slice(), info, &[i]].concat())?;
        out.extend_from_slice(&last);
        if out.len() >= size {
            break;
        }
    }
    out.truncate(size);
    last.zeroize();
    Ok(out)
}
pub fn deflate(plain: &[u8]) -> Result<Vec<u8>> {
    ensure!(plain.len() <= MAX_MESSAGE_BYTES, "Plaintext too large");
    let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
    e.write_all(plain)?;
    Ok(e.finish()?)
}
pub fn inflate(zipped: &[u8]) -> Result<Vec<u8>> {
    let mut decoder = ZlibDecoder::new(zipped);
    let mut out = Vec::new();
    decoder
        .by_ref()
        .take((MAX_MESSAGE_BYTES + 1) as u64)
        .read_to_end(&mut out)?;
    ensure!(
        out.len() <= MAX_MESSAGE_BYTES,
        "Decompression limit exceeded"
    );
    ensure!(
        decoder.total_in() as usize == zipped.len(),
        "Trailing compressed bytes"
    );
    Ok(out)
}
pub fn gcm_seal(key: &[u8], iv: &[u8], aad: &[u8], plain: &[u8]) -> Result<(Vec<u8>, [u8; 16])> {
    let cipher = match key.len() {
        16 => Cipher::aes_128_gcm(),
        24 => Cipher::aes_192_gcm(),
        _ => anyhow::bail!("Invalid GCM key"),
    };
    ensure!(iv.len() == 12, "Invalid GCM nonce");
    let mut c = Crypter::new(cipher, Mode::Encrypt, key, Some(iv))?;
    c.aad_update(aad)?;
    let mut out = vec![0; plain.len() + cipher.block_size()];
    let n = c.update(plain, &mut out)?;
    let m = c.finalize(&mut out[n..])?;
    out.truncate(n + m);
    let mut tag = [0; 16];
    c.get_tag(&mut tag)?;
    Ok((out, tag))
}
pub fn gcm_open(
    key: &[u8],
    iv: &[u8],
    aad: &[u8],
    encrypted: &[u8],
    tag: &[u8],
) -> Result<Vec<u8>> {
    let cipher = match key.len() {
        16 => Cipher::aes_128_gcm(),
        24 => Cipher::aes_192_gcm(),
        _ => anyhow::bail!("Invalid GCM key"),
    };
    ensure!(iv.len() == 12 && tag.len() == 16, "Invalid GCM envelope");
    let mut c = Crypter::new(cipher, Mode::Decrypt, key, Some(iv))?;
    c.aad_update(aad)?;
    c.set_tag(tag)?;
    let mut out = vec![0; encrypted.len() + cipher.block_size()];
    let n = c.update(encrypted, &mut out)?;
    let m = match c.finalize(&mut out[n..]) {
        Ok(n) => n,
        Err(e) => {
            out.zeroize();
            return Err(e.into());
        }
    };
    out.truncate(n + m);
    Ok(out)
}
pub fn envelope_seal(plain: &[u8], key: &[u8], aad: &[u8], iv: [u8; 12]) -> Result<Vec<u8>> {
    ensure!(key.len() == 24, "Expected AES192 key");
    let (body, tag) = gcm_seal(key, &iv, aad, &deflate(plain)?)?;
    Ok([body, iv.to_vec(), tag.to_vec()].concat())
}
pub fn envelope_open(input: &[u8], key: &[u8], aad: &[u8]) -> Result<Vec<u8>> {
    ensure!(
        input.len() >= 29 && input.len() <= MAX_MESSAGE_BYTES + 65536,
        "Invalid encrypted envelope"
    );
    let n = input.len();
    let zipped = gcm_open(
        key,
        &input[n - 28..n - 16],
        aad,
        &input[..n - 28],
        &input[n - 16..],
    )?;
    inflate(&zipped)
}
pub fn cbc(plain: &[u8], key: &[u8], mode: Mode) -> Result<Vec<u8>> {
    ensure!(!key.is_empty() && key.len() <= 65536, "Invalid origin key");
    ensure!(
        plain.len() <= MAX_MESSAGE_BYTES + 65536,
        "CBC body too large"
    );
    if matches!(mode, Mode::Decrypt) {
        ensure!(
            !plain.is_empty() && plain.len().is_multiple_of(16),
            "Invalid CBC ciphertext"
        );
    }
    let mut normalized = [0; 16];
    let n = key.len().min(16);
    normalized[..n].copy_from_slice(&key[..n]);
    let result = if matches!(mode, Mode::Decrypt) {
        openssl::symm::decrypt(Cipher::aes_128_cbc(), &normalized, Some(&normalized), plain)
    } else {
        openssl::symm::encrypt(Cipher::aes_128_cbc(), &normalized, Some(&normalized), plain)
    };
    normalized.zeroize();
    Ok(result?)
}

pub struct Ecdh {
    key: PKey<Private>,
    nid: Nid,
}
impl Ecdh {
    pub fn generate(nid: Nid) -> Result<Self> {
        let group = EcGroup::from_curve_name(nid)?;
        Ok(Self {
            key: PKey::from_ec_key(EcKey::generate(&group)?)?,
            nid,
        })
    }
    pub fn from_private(nid: Nid, private: &[u8]) -> Result<Self> {
        let group = EcGroup::from_curve_name(nid)?;
        let scalar = BigNum::from_slice(private)?;
        let mut ctx = BigNumContext::new()?;
        let mut public = EcPoint::new(&group)?;
        public.mul_generator2(&group, &scalar, &mut ctx)?;
        let key = EcKey::from_private_components(&group, &scalar, &public)?;
        key.check_key()?;
        Ok(Self {
            key: PKey::from_ec_key(key)?,
            nid,
        })
    }
    pub fn public(&self) -> Result<Vec<u8>> {
        let key = self.key.ec_key()?;
        let mut ctx = BigNumContext::new()?;
        Ok(key
            .public_key()
            .to_bytes(key.group(), PointConversionForm::UNCOMPRESSED, &mut ctx)?)
    }
    pub fn derive(&self, point: &[u8]) -> Result<Vec<u8>> {
        let group = EcGroup::from_curve_name(self.nid)?;
        let mut ctx = BigNumContext::new()?;
        let p = EcPoint::from_bytes(&group, point, &mut ctx)?;
        let peer = EcKey::from_public_key(&group, &p)?;
        peer.check_key()?;
        let peer = PKey::from_ec_key(peer)?;
        let mut d = Deriver::new(&self.key)?;
        d.set_peer(&peer)?;
        Ok(d.derive_to_vec()?)
    }
}
pub fn verify_signature(pem: &str, data: &[u8], signature: &[u8]) -> Result<()> {
    let key = PKey::public_key_from_pem(pem.as_bytes())?;
    let ec = key.ec_key()?;
    ensure!(
        ec.group().curve_name() == Some(Nid::X9_62_PRIME256V1),
        "Unsupported signature pin curve"
    );
    let mut v = Verifier::new(MessageDigest::sha256(), &key)?;
    v.update(data)?;
    ensure!(
        v.verify(signature)?,
        "Pinned server signature verification failed"
    );
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn rfc5869() {
        let prk = hex::decode("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5")
            .unwrap();
        assert_eq!(
            hex::encode(expand(&prk, &hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(), 42).unwrap()),
            "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
        );
    }
    #[test]
    fn p224_p256_shared_secrets() {
        for nid in [Nid::SECP224R1, Nid::X9_62_PRIME256V1] {
            let a = Ecdh::from_private(nid, &[1]).unwrap();
            let b = Ecdh::from_private(nid, &[2]).unwrap();
            assert_eq!(
                a.derive(&b.public().unwrap()).unwrap(),
                b.derive(&a.public().unwrap()).unwrap()
            );
        }
    }
    #[test]
    fn session_known_answer() {
        let key: Vec<u8> = (0..24).collect();
        let nonce = std::array::from_fn(|i| i as u8);
        let fixture=hex::decode("9e65093555920046fdfa90d437e2f0b922d46623768c7870b390381087bb0d11fa5af671c9b301db000102030405060708090a0b0d4ecbc764b639df9549f03800165ea6").unwrap();
        assert_eq!(
            envelope_seal(b"synthetic native session request", &key, b"", nonce).unwrap(),
            fixture
        );
        assert_eq!(
            envelope_open(&fixture, &key, b"").unwrap(),
            b"synthetic native session request"
        );
        for i in [0, fixture.len() - 28, fixture.len() - 1] {
            let mut bad = fixture.clone();
            bad[i] ^= 1;
            assert!(envelope_open(&bad, &key, b"").is_err());
        }
    }
}