clawft_kernel/workload_pkg/
trust.rs1use serde::{Deserialize, Serialize};
6
7use super::codec::{base64_decode, hex_decode_exact};
8use super::manifest::valid_token;
9
10pub const TRUST_FILE_SCHEMA: &str = "weftos.workload-trust.v1";
12
13pub const WEFTOS_PINNED_SIGNERS: &[(&str, &str)] = &[];
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum KeyOrigin {
24 Weftos,
26 Operator,
28 CognitumRelease,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct PinnedKey {
35 pub key_id: String,
37 pub public_key: [u8; 32],
39 pub origin: KeyOrigin,
41}
42
43#[derive(Debug, Clone, Default, PartialEq, Eq)]
45pub struct TrustAnchors {
46 pub signers: Vec<PinnedKey>,
48 pub cognitum: Vec<PinnedKey>,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(deny_unknown_fields)]
56pub struct TrustFileKey {
57 pub key_id: String,
59 pub public_key: String,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(deny_unknown_fields)]
66pub struct TrustFile {
67 pub schema: String,
69 #[serde(default)]
71 pub operator_keys: Vec<TrustFileKey>,
72 #[serde(default)]
74 pub cognitum_release_keys: Vec<TrustFileKey>,
75}
76
77impl TrustAnchors {
78 pub fn weftos_default() -> Result<Self, String> {
80 let mut anchors = Self::default();
81 for (id, hex) in WEFTOS_PINNED_SIGNERS {
82 anchors.push_signer(id, hex, KeyOrigin::Weftos)?;
83 }
84 Ok(anchors)
85 }
86
87 pub fn from_trust_file(file: &TrustFile) -> Result<Self, String> {
89 if file.schema != TRUST_FILE_SCHEMA {
90 return Err(format!("trust file schema must be {TRUST_FILE_SCHEMA}"));
91 }
92 let mut anchors = Self::weftos_default()?;
93 for k in &file.operator_keys {
94 anchors.push_signer(&k.key_id, &k.public_key, KeyOrigin::Operator)?;
95 }
96 for k in &file.cognitum_release_keys {
97 let key = parse_key(&k.key_id, &k.public_key, KeyOrigin::CognitumRelease)?;
98 anchors.cognitum.push(key);
99 }
100 Ok(anchors)
101 }
102
103 pub fn from_trust_json(bytes: &[u8]) -> Result<Self, String> {
105 if bytes.len() > 256 * 1024 {
106 return Err("trust file too large".into());
107 }
108 let file: TrustFile =
109 serde_json::from_slice(bytes).map_err(|e| format!("trust file: {e}"))?;
110 Self::from_trust_file(&file)
111 }
112
113 pub fn push_signer(
115 &mut self,
116 key_id: &str,
117 public_key: &str,
118 origin: KeyOrigin,
119 ) -> Result<(), String> {
120 let key = parse_key(key_id, public_key, origin)?;
121 if let Some(existing) = self.signers.iter().find(|k| k.public_key == key.public_key) {
122 if existing.key_id != key.key_id {
123 return Err(format!(
124 "public key pinned twice under {} and {}",
125 existing.key_id, key.key_id
126 ));
127 }
128 return Ok(());
129 }
130 if self.signers.iter().any(|k| k.key_id == key.key_id) {
131 return Err(format!(
132 "key id {} pinned to two different keys",
133 key.key_id
134 ));
135 }
136 self.signers.push(key);
137 Ok(())
138 }
139
140 pub fn signer(&self, public_key: &[u8; 32]) -> Option<&PinnedKey> {
142 self.signers.iter().find(|k| &k.public_key == public_key)
143 }
144}
145
146fn parse_key(key_id: &str, public_key: &str, origin: KeyOrigin) -> Result<PinnedKey, String> {
147 if !valid_token(key_id, 128) {
148 return Err(format!("bad key id {key_id:?}"));
149 }
150 let raw =
151 parse_public_key(public_key).ok_or_else(|| format!("{key_id}: bad Ed25519 public key"))?;
152 ed25519_dalek::VerifyingKey::from_bytes(&raw).map_err(|e| format!("{key_id}: {e}"))?;
154 Ok(PinnedKey {
155 key_id: key_id.to_string(),
156 public_key: raw,
157 origin,
158 })
159}
160
161const SPKI_PREFIX: [u8; 12] = [
163 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
164];
165
166pub fn parse_public_key(s: &str) -> Option<[u8; 32]> {
169 let s = s.trim();
170 if let Some(raw) = hex_decode_exact::<32>(s) {
171 return Some(raw);
172 }
173 let body = s
174 .strip_prefix("-----BEGIN PUBLIC KEY-----")?
175 .strip_suffix("-----END PUBLIC KEY-----")?;
176 let b64: String = body.split_whitespace().collect();
177 let der = base64_decode(&b64)?;
178 if der.len() != 44 || der[..12] != SPKI_PREFIX {
179 return None;
180 }
181 der[12..].try_into().ok()
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 const PK_HEX: &str = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
189
190 #[test]
191 fn parses_hex_and_pem() {
192 let raw = parse_public_key(PK_HEX).unwrap();
193 let mut der = SPKI_PREFIX.to_vec();
194 der.extend_from_slice(&raw);
195 let b64 = standard_b64(&der);
196 let pem = format!("-----BEGIN PUBLIC KEY-----\n{b64}\n-----END PUBLIC KEY-----\n");
197 assert_eq!(parse_public_key(&pem), Some(raw));
198 assert_eq!(parse_public_key("zz"), None);
199 }
200
201 #[test]
202 fn trust_file_rejects_wrong_schema_and_conflicting_pins() {
203 let bad = br#"{"schema":"nope","operator_keys":[]}"#;
204 assert!(TrustAnchors::from_trust_json(bad).is_err());
205 let conflict = format!(
206 r#"{{"schema":"{TRUST_FILE_SCHEMA}","operator_keys":[{{"key_id":"a","public_key":"{PK_HEX}"}},{{"key_id":"b","public_key":"{PK_HEX}"}}]}}"#
207 );
208 assert!(TrustAnchors::from_trust_json(conflict.as_bytes()).is_err());
209 let ok = format!(
210 r#"{{"schema":"{TRUST_FILE_SCHEMA}","operator_keys":[{{"key_id":"a","public_key":"{PK_HEX}"}}]}}"#
211 );
212 let anchors = TrustAnchors::from_trust_json(ok.as_bytes()).unwrap();
213 assert_eq!(anchors.signers.len(), 1);
214 assert_eq!(anchors.signers[0].origin, KeyOrigin::Operator);
215 }
216
217 fn standard_b64(bytes: &[u8]) -> String {
218 const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
219 let mut s = String::new();
220 for chunk in bytes.chunks(3) {
221 let n =
222 chunk.iter().fold(0u32, |acc, b| (acc << 8) | *b as u32) << (8 * (3 - chunk.len()));
223 for i in 0..=chunk.len() {
224 s.push(A[((n >> (18 - 6 * i)) & 63) as usize] as char);
225 }
226 }
227 while !s.len().is_multiple_of(4) {
228 s.push('=');
229 }
230 s
231 }
232}