msix 0.4.0

Library for creating and signing msix packages.
Documentation
use super::pkcs7::{build_pkcs7, SPC_INDIRECT_DATA_OBJID, SPC_SIPINFO_OBJID};
use crate::Signer;
use anyhow::Result;
use byteorder::{BigEndian, ReadBytesExt};
use rasn::prelude::*;
use rasn_cms::pkcs7_compat::{EncapsulatedContentInfo, SignedData};
use rasn_cms::{ContentInfo, CONTENT_SIGNED_DATA};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use zip::ZipArchive;

const P7X_MAGIC: u32 = 0x504b4358;

pub fn read_p7x(path: &Path) -> Result<SignedData> {
    let f = File::open(path)?;
    let mut zip = ZipArchive::new(f)?;
    let mut r = zip.by_name("AppxSignature.p7x")?;
    let magic = r.read_u32::<BigEndian>()?;
    anyhow::ensure!(magic == P7X_MAGIC, "not a valid p7x file");
    let mut der = vec![];
    r.read_to_end(&mut der)?;
    let info = rasn::der::decode::<ContentInfo>(&der).map_err(|err| anyhow::anyhow!("{}", err))?;
    anyhow::ensure!(CONTENT_SIGNED_DATA == info.content_type);
    let data = rasn::der::decode::<SignedData>(info.content.as_bytes())
        .map_err(|err| anyhow::anyhow!("{}", err))?;
    Ok(data)
}

pub fn p7x(signer: &Signer, digests: &Digests) -> Vec<u8> {
    let payload = Payload::encode(digests);
    let encap_content_info = EncapsulatedContentInfo {
        content_type: SPC_INDIRECT_DATA_OBJID.into(),
        content: Any::new(payload),
    };
    let signed_data = build_pkcs7(signer, encap_content_info);
    let content_info = ContentInfo {
        content_type: CONTENT_SIGNED_DATA.into(),
        content: Any::new(rasn::der::encode(&signed_data).unwrap()),
    };
    let mut p7x = vec![];
    p7x.extend_from_slice(&P7X_MAGIC.to_be_bytes());
    p7x.extend(rasn::der::encode(&content_info).unwrap());
    p7x
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Digests {
    /// Zip contents hash.
    pub axpc: [u8; 32],
    /// Zip central directory hash.
    pub axcd: [u8; 32],
    /// Content types hash (uncompressed).
    pub axct: [u8; 32],
    /// Block map hash (uncompressed).
    pub axbm: [u8; 32],
    /// Content integrity hash (uncompressed, optional).
    pub axci: [u8; 32],
}

#[derive(AsnType, Clone, Debug, Eq, Encode, PartialEq)]
#[rasn(tag(context, 0))]
struct Payload {
    indirect_data: SpcIndirectData,
}

impl Payload {
    pub fn encode(digests: &Digests) -> Vec<u8> {
        let indirect_data = SpcIndirectData::new(digests);
        rasn::der::encode(&Self { indirect_data }).unwrap()
    }
}

#[derive(AsnType, Clone, Debug, Eq, Encode, PartialEq)]
struct SpcIndirectData {
    sip_info: SpcSipInfo,
    content: SpcIndirectDataContent,
}

impl SpcIndirectData {
    pub fn new(digests: &Digests) -> Self {
        let mut payload = Vec::with_capacity(184);
        payload.extend_from_slice(b"APPX");
        payload.extend_from_slice(b"AXPC");
        payload.extend_from_slice(&digests.axpc);
        payload.extend_from_slice(b"AXCD");
        payload.extend_from_slice(&digests.axcd);
        payload.extend_from_slice(b"AXCT");
        payload.extend_from_slice(&digests.axct);
        payload.extend_from_slice(b"AXBM");
        payload.extend_from_slice(&digests.axbm);
        payload.extend_from_slice(b"AXCI");
        payload.extend_from_slice(&digests.axci);
        Self {
            sip_info: Default::default(),
            content: SpcIndirectDataContent::new(payload),
        }
    }
}

#[derive(AsnType, Clone, Debug, Eq, Encode, PartialEq)]
struct SpcIndirectDataContent {
    oid: [Open; 2],
    payload: OctetString,
}

impl SpcIndirectDataContent {
    pub fn new(payload: Vec<u8>) -> Self {
        Self {
            oid: [
                Open::ObjectIdentifier(Oid::JOINT_ISO_ITU_T_COUNTRY_US_ORGANIZATION_GOV_CSOR_NIST_ALGORITHMS_HASH_SHA256.into()),
                Open::Null,
            ],
            payload: OctetString::from(payload),
        }
    }
}

#[derive(AsnType, Clone, Debug, Eq, Encode, PartialEq)]
struct SpcSipInfo {
    oid: ObjectIdentifier,
    data: SpcSipInfoContent,
}

impl Default for SpcSipInfo {
    fn default() -> Self {
        Self {
            oid: SPC_SIPINFO_OBJID.into(),
            data: Default::default(),
        }
    }
}

#[derive(AsnType, Clone, Debug, Eq, Encode, PartialEq)]
struct SpcSipInfoContent {
    i1: u32,
    s1: OctetString,
    i2: u32,
    i3: u32,
    i4: u32,
    i5: u32,
    i6: u32,
}

impl Default for SpcSipInfoContent {
    fn default() -> Self {
        const SPC_SIPINFO_MAGIC_INT: u32 = 0x0101_0000;
        const SPC_SIPINFO_MAGIC: [u8; 16] = [
            0x4b, 0xdf, 0xc5, 0x0a, 0x07, 0xce, 0xe2, 0x4d, 0xb7, 0x6e, 0x23, 0xc8, 0x39, 0xa0,
            0x9f, 0xd1,
        ];
        Self {
            i1: SPC_SIPINFO_MAGIC_INT,
            s1: OctetString::from(SPC_SIPINFO_MAGIC.to_vec()),
            i2: 0,
            i3: 0,
            i4: 0,
            i5: 0,
            i6: 0,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rsa::pkcs8::DecodePrivateKey;
    use rsa::{PaddingScheme, RsaPrivateKey};
    use sha2::{Digest, Sha256};

    const HASHES: Digests = Digests {
        axpc: [
            37, 112, 168, 185, 134, 72, 152, 136, 166, 55, 137, 233, 153, 167, 81, 229, 137, 229,
            158, 190, 214, 181, 211, 85, 93, 172, 161, 115, 74, 99, 165, 156,
        ],
        axcd: [
            29, 87, 205, 69, 139, 92, 201, 89, 248, 232, 221, 244, 67, 120, 231, 192, 229, 135,
            200, 178, 242, 207, 83, 145, 88, 83, 238, 30, 255, 54, 226, 31,
        ],
        axct: [
            188, 251, 66, 139, 217, 90, 175, 33, 93, 159, 193, 116, 124, 19, 113, 188, 195, 138,
            75, 212, 185, 133, 87, 115, 195, 93, 4, 189, 198, 152, 59, 190,
        ],
        axbm: [
            228, 142, 202, 253, 204, 232, 223, 220, 131, 162, 12, 252, 106, 74, 3, 180, 190, 71,
            230, 173, 146, 218, 209, 13, 101, 4, 43, 186, 70, 46, 196, 194,
        ],
        axci: [
            175, 56, 231, 224, 95, 58, 68, 216, 201, 155, 33, 50, 3, 124, 19, 157, 149, 107, 194,
            174, 170, 108, 34, 110, 128, 107, 240, 29, 11, 129, 67, 233,
        ],
    };

    #[test]
    fn sp_indirect_data() {
        let orig_indirect_data = [
            160, 130, 1, 8, 48, 130, 1, 4, // hash rest
            48, 53, // oid 1.3.6.1.4.1.311.2.1.30
            6, 10, 43, 6, 1, 4, 1, 130, 55, 2, 1, 30, // start sequence
            48, 39, // integer
            2, 4, 1, 1, 0, 0, // octet string tag
            4, 16, // octet string payload
            75, 223, 197, 10, 7, 206, 226, 77, 183, 110, 35, 200, 57, 160, 159, 209,
            // int 0
            2, 1, 0, // int 0
            2, 1, 0, // int 0
            2, 1, 0, // int 0
            2, 1, 0, // int 0
            2, 1, 0, // start sequence
            48, 129, 202, // start sequence
            48, 13, // oid 2.16.840.1.101.3.4.2.1
            6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, // null
            5, 0, // octet string tag
            4, 129, 184, // octet string bytes
            65, 80, 80, 88, // signature
            65, 88, 80, 67, // axpc signature
            37, 112, 168, 185, 134, 72, 152, 136, // axpc hash
            166, 55, 137, 233, 153, 167, 81, 229, 137, 229, 158, 190, 214, 181, 211, 85, 93, 172,
            161, 115, 74, 99, 165, 156, // end axpc hash
            65, 88, 67, 68, // axcd signature
            29, 87, 205, 69, 139, 92, 201, 89, // axcd hash
            248, 232, 221, 244, 67, 120, 231, 192, 229, 135, 200, 178, 242, 207, 83, 145, 88, 83,
            238, 30, 255, 54, 226, 31, // end axcd hash
            65, 88, 67, 84, // axct signature
            188, 251, 66, 139, 217, 90, 175, 33, // axct hash
            93, 159, 193, 116, 124, 19, 113, 188, 195, 138, 75, 212, 185, 133, 87, 115, 195, 93, 4,
            189, 198, 152, 59, 190, // end axct hash
            65, 88, 66, 77, // axbm signature
            228, 142, 202, 253, 204, 232, 223, 220, // axbm hash
            131, 162, 12, 252, 106, 74, 3, 180, 190, 71, 230, 173, 146, 218, 209, 13, 101, 4, 43,
            186, 70, 46, 196, 194, // end axbm hash
            65, 88, 67, 73, // axci signature
            175, 56, 231, 224, 95, 58, 68, 216, // axci hash
            201, 155, 33, 50, 3, 124, 19, 157, 149, 107, 194, 174, 170, 108, 34, 110, 128, 107,
            240, 29, 11, 129, 67, 233, // end axci hash
        ];
        let indirect_data = Payload::encode(&HASHES);
        let (rem, res) = der_parser::parse_der(&indirect_data).unwrap();
        assert!(rem.is_empty());
        println!("{:#?}", res);
        assert_eq!(indirect_data, orig_indirect_data);
    }

    #[test]
    #[ignore]
    fn payload_digest_and_sign() {
        let payload = Payload::encode(&HASHES);
        let encap_content_info = EncapsulatedContentInfo {
            content_type: SPC_INDIRECT_DATA_OBJID.into(),
            content: Any::new(payload),
        };
        let digest = Sha256::digest(&encap_content_info.content.as_bytes()[8..]);
        let orig_digest = [
            68, 234, 15, 167, 40, 66, 12, 133, 19, 239, 228, 168, 72, 147, 90, 139, 75, 131, 41,
            111, 247, 70, 28, 251, 130, 190, 57, 136, 200, 159, 93, 116,
        ];
        assert_eq!(&orig_digest[..], &digest[..]);
        let orig_signature = b"\x7f\x13uP\xc8m:\x99\xb6\x89u\x85y\xea\xfc\xd8Cw\x96w\x10>j\xa7Z\x8c\xa3\x1f\\\xf4\x82\\\xdf\x8eh;\x10\x16o/\"i\x89\xb9\xf1\x03\x9c\xb0)\x9f\xc4\xfe\xf1\x05\x93\xbeJ\xd2\xeb\xe3\xb1f\xb1rq\x89\xdf\x7f\xe4\xe1\n\xae\xa70\x8c|\xd3\xe6\xe6/\xad\x97\xcb1\xb6\xa0\xf9\x16z\x83R#\xe8n\r\xfdErJ\x01\xfb\xd4\xef\x05\xf9\xab\x08o\x16\xbc)C\xee\x03=$\x88>G\xa4\xba)\xbc\xf4n6\xaa\xfd\xa7e\x15\xb9,|\xd6\xf9\x9b>\xe8\x95\xf7\xc6\x08\n\t\x8a\xd5{j\x8a\xfe{,O\xf3\xd9\x8a\xc79\x9f\x80\xcd\x17k8\xf8\xb3\xc3\x96\xd8\x1a/\xa8\x14R\x14\xaf\x813\x91;>\x99\xd24\x86J\x12\x0e\x89\x0c\xb8?\xfa\xa8\x1dM\x98@vz'\xe6y\xab\xc0\xcb\xc5\xb3\xbeC'$\"\xd2\x15\xaf0\xa3\x05\xcbj\x18j\x11\xa2\xfd\xe7\xe6y\xcf\xadd\x99\xa9\xdc\xc4\xc2`\x1d\xb0\xe3\xdb\xfeC\xdc\xce\xe5@\xde;P\xfav\x8c\xff";
        let key = RsaPrivateKey::from_pkcs8_pem(crate::DEBUG_PEM).unwrap();
        let padding = PaddingScheme::new_pkcs1v15_sign::<sha2::Sha256>();
        let sig = key.sign(padding, &digest).unwrap();
        assert_eq!(sig.len(), orig_signature.len());
        assert_eq!(sig, orig_signature);
    }
}