lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

rshr_202_step_298_platform.rs (6617B)


      1 use std::fs;
      2 use std::path::Path;
      3 use std::process::Command;
      4 
      5 use serde_json::{Value, json};
      6 use sha2::{Digest, Sha256};
      7 
      8 const APPLE_TOOLCHAIN_IDENTITY_SHA256: &str =
      9     "fd9bb9af273d0a834c2abff36910edf25f3e5b60c36fcc23b45b738c5c8b2d08";
     10 const PROBE_SOURCE_PATH: &str =
     11     "tools/radroots_scripts/src/radroots_scripts/verify/rshr_200_series.py";
     12 const PROBE_SOURCE_SHA256: &str =
     13     "add949c6c20a037123808230625dfd09dd6fa6c5afe5a856400227191f5de5b5";
     14 const REQUEST_PATH: &str = ".git/rshr-step-298-platform-request-sha256";
     15 
     16 fn root() -> &'static Path {
     17     Path::new(env!("CARGO_MANIFEST_DIR"))
     18         .parent()
     19         .and_then(Path::parent)
     20         .expect("xtask must remain under tools/xtask")
     21 }
     22 
     23 fn canonical(value: &Value) -> Result<Vec<u8>, String> {
     24     serde_json::to_vec(value).map_err(|_| "Step 298 platform JSON encoding failed".to_owned())
     25 }
     26 
     27 fn sha256(bytes: &[u8]) -> String {
     28     hex::encode(Sha256::digest(bytes))
     29 }
     30 
     31 fn uname(flag: &str) -> Result<String, String> {
     32     let output = Command::new("/usr/bin/uname")
     33         .arg(flag)
     34         .output()
     35         .map_err(|_| "Step 298 platform probe could not start uname".to_owned())?;
     36     if !output.status.success() || !output.stderr.is_empty() {
     37         return Err("Step 298 platform probe uname failed".to_owned());
     38     }
     39     parse_uname(&output.stdout)
     40 }
     41 
     42 fn parse_uname(stdout: &[u8]) -> Result<String, String> {
     43     let value = std::str::from_utf8(stdout)
     44         .map_err(|_| "Step 298 platform probe uname output is not UTF-8".to_owned())?
     45         .strip_suffix('\n')
     46         .ok_or_else(|| "Step 298 platform probe uname output differs".to_owned())?;
     47     if value.is_empty() || value.contains('\n') || value.contains('\r') {
     48         return Err("Step 298 platform probe uname output differs".to_owned());
     49     }
     50     Ok(value.to_owned())
     51 }
     52 
     53 pub(crate) fn run() -> Result<(), String> {
     54     let request_bytes = fs::read(root().join(REQUEST_PATH))
     55         .map_err(|_| "Step 298 platform execution request is unavailable".to_owned())?;
     56     let execution_request_sha256 = request_digest(&request_bytes)?;
     57     let bytes = result_bytes(
     58         execution_request_sha256,
     59         &uname("-s")?,
     60         &uname("-r")?,
     61         &uname("-v")?,
     62         std::env::consts::ARCH,
     63     )?;
     64     std::io::Write::write_all(&mut std::io::stdout().lock(), &bytes)
     65         .map_err(|_| "Step 298 platform result write failed".to_owned())
     66 }
     67 
     68 fn request_digest(request_bytes: &[u8]) -> Result<&str, String> {
     69     let raw_request = std::str::from_utf8(request_bytes)
     70         .map_err(|_| "Step 298 platform execution request is not UTF-8".to_owned())?;
     71     let execution_request_sha256 = raw_request.strip_suffix('\n').unwrap_or(raw_request);
     72     if execution_request_sha256.len() != 64
     73         || !execution_request_sha256
     74             .bytes()
     75             .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
     76     {
     77         return Err("Step 298 platform execution request differs".to_owned());
     78     }
     79     Ok(execution_request_sha256)
     80 }
     81 
     82 fn result_bytes(
     83     execution_request_sha256: &str,
     84     kernel_name: &str,
     85     kernel_release: &str,
     86     kernel_version: &str,
     87     architecture: &str,
     88 ) -> Result<Vec<u8>, String> {
     89     if kernel_name != "Darwin" || architecture != "aarch64" {
     90         return Err("Step 298 platform identity differs".to_owned());
     91     }
     92 
     93     let os_build = json!({
     94         "kernel_name": kernel_name,
     95         "kernel_release": kernel_release,
     96         "kernel_version": kernel_version,
     97     });
     98     let result = json!({
     99         "schema": "radroots.services-hardening.rshr-200-platform-result.v1",
    100         "platform": "macos_aarch64",
    101         "system": "aarch64-darwin",
    102         "os_family": "macos",
    103         "architecture": "aarch64",
    104         "kernel_name": os_build["kernel_name"],
    105         "kernel_release": os_build["kernel_release"],
    106         "os_build_sha256": sha256(&canonical(&os_build)?),
    107         "runner_kind": "host",
    108         "runner_image_sha256": "none",
    109         "apple_toolchain_identity_sha256": APPLE_TOOLCHAIN_IDENTITY_SHA256,
    110         "probe_source_path": PROBE_SOURCE_PATH,
    111         "probe_source_sha256": PROBE_SOURCE_SHA256,
    112         "execution_request_sha256": execution_request_sha256,
    113         "assertion": [
    114             {"id": "os_family", "result": "pass"},
    115             {"id": "architecture", "result": "pass"},
    116             {"id": "kernel_identity", "result": "pass"},
    117             {"id": "runner_identity", "result": "pass"},
    118             {"id": "apple_identity", "result": "pass"},
    119         ],
    120         "result": "available",
    121     });
    122     let mut bytes = canonical(&result)?;
    123     bytes.push(b'\n');
    124     Ok(bytes)
    125 }
    126 
    127 #[cfg(test)]
    128 mod tests {
    129     use super::*;
    130 
    131     #[test]
    132     fn request_digest_rejects_noncanonical_or_unbound_values() {
    133         let digest = "0123456789abcdef".repeat(4);
    134         assert_eq!(request_digest(digest.as_bytes()).unwrap(), digest);
    135         assert_eq!(
    136             request_digest(format!("{digest}\n").as_bytes()).unwrap(),
    137             digest
    138         );
    139         for bytes in [
    140             vec![],
    141             vec![0xff],
    142             vec![b'0'; 63],
    143             vec![b'0'; 65],
    144             vec![b'G'; 64],
    145             vec![b'A'; 64],
    146             format!("{digest}\n\n").into_bytes(),
    147         ] {
    148             assert!(request_digest(&bytes).is_err());
    149         }
    150     }
    151 
    152     #[test]
    153     fn uname_output_requires_one_nonempty_utf8_line() {
    154         assert_eq!(parse_uname(b"Darwin\n").unwrap(), "Darwin");
    155         for bytes in [
    156             b"".as_slice(),
    157             b"\n",
    158             b"Darwin",
    159             b"Dar\nwin\n",
    160             b"Darwin\r\n",
    161             &[0xff],
    162         ] {
    163             assert!(parse_uname(bytes).is_err());
    164         }
    165     }
    166 
    167     #[test]
    168     fn encoded_platform_fixture_binds_request_and_kernel_without_qualifying_host() {
    169         let request = "a".repeat(64);
    170         let bytes = result_bytes(
    171             &request,
    172             "Darwin",
    173             "fixture-release",
    174             "fixture-version",
    175             "aarch64",
    176         )
    177         .unwrap();
    178         let result: Value = serde_json::from_slice(&bytes).unwrap();
    179         assert_eq!(result["execution_request_sha256"], request);
    180         assert_eq!(result["kernel_release"], "fixture-release");
    181         assert_eq!(result["os_build_sha256"], sha256(&canonical(&json!({"kernel_name":"Darwin", "kernel_release":"fixture-release", "kernel_version":"fixture-version"})).unwrap()));
    182         assert!(bytes.ends_with(b"\n"));
    183         assert!(result_bytes(&request, "Linux", "fixture", "fixture", "aarch64").is_err());
    184         assert!(result_bytes(&request, "Darwin", "fixture", "fixture", "x86_64").is_err());
    185     }
    186 }