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 }