1use pcf::HashAlgo;
9use pcf_sig::{
10 compute_fingerprint, is_crypto_hash, KeyFormat, SigAlgo, FINGERPRINT_SIZE, KEY_MAGIC,
11 KEY_PREFIX_SIZE, MANIFEST_PREFIX_SIZE, SIGNED_ENTRY_SIZE, SIG_MAGIC, TYPE_PCFSIG_KEY,
12 TYPE_PCFSIG_SIG,
13};
14
15use super::{
16 le_u16, le_u32, le_u64, uid_at, Decoded, FieldNode, FieldValue, PartitionDecoder, PartitionMeta,
17};
18
19fn magic8(b: &[u8]) -> String {
21 b.iter()
22 .map(|&c| {
23 if c == 0 {
24 "\\0".to_string()
25 } else if (0x20..0x7f).contains(&c) {
26 (c as char).to_string()
27 } else {
28 format!("\\x{c:02x}")
29 }
30 })
31 .collect()
32}
33
34fn sig_algo_name(id: u8) -> &'static str {
35 match SigAlgo::from_id(id) {
36 Ok(SigAlgo::Ed25519) => "Ed25519",
37 Ok(SigAlgo::RsaPssSha256) => "RSA-PSS-SHA-256",
38 Ok(SigAlgo::RsaPssSha512) => "RSA-PSS-SHA-512",
39 Ok(SigAlgo::RsaPkcs1v15Sha256) => "RSA-PKCS1v15-SHA-256",
40 Ok(SigAlgo::RsaPkcs1v15Sha512) => "RSA-PKCS1v15-SHA-512",
41 Ok(SigAlgo::EcdsaP256Sha256) => "ECDSA-P256-SHA-256",
42 Ok(SigAlgo::EcdsaP521Sha512) => "ECDSA-P521-SHA-512",
43 Ok(SigAlgo::X509Chain) => "X.509 chain",
44 Err(_) => "unknown",
45 }
46}
47
48fn key_format_name(id: u8) -> &'static str {
49 match KeyFormat::from_id(id) {
50 Ok(KeyFormat::Ed25519Raw) => "Ed25519 raw",
51 Ok(KeyFormat::RsaSpkiDer) => "RSA SPKI DER",
52 Ok(KeyFormat::EcdsaSpkiDer) => "ECDSA SPKI DER",
53 Ok(KeyFormat::X509Cert) => "X.509 certificate",
54 Ok(KeyFormat::X509Chain) => "X.509 certificate chain",
55 Err(_) => "unknown",
56 }
57}
58
59fn metadata_tag_name(tag: u16) -> &'static str {
60 match tag {
61 0x0000 => "reserved",
62 0x0001 => "subject_dn",
63 0x0002 => "not_before",
64 0x0003 => "not_after",
65 0x0004 => "issuer_dn",
66 0x0005 => "comment",
67 t if t >= 0x8000 => "application-private",
68 _ => "reserved (future)",
69 }
70}
71
72pub struct PcfSigKeyDecoder;
77
78impl PartitionDecoder for PcfSigKeyDecoder {
79 fn name(&self) -> &'static str {
80 "pcfsig-key"
81 }
82
83 fn matches(&self, meta: &PartitionMeta, data: &[u8]) -> bool {
84 meta.partition_type == TYPE_PCFSIG_KEY || data.get(0..8) == Some(&KEY_MAGIC)
85 }
86
87 fn decode(&self, _meta: &PartitionMeta, data: &[u8]) -> Decoded {
88 let mut warnings = Vec::new();
89 let mut fields = Vec::new();
90
91 if data.len() < KEY_PREFIX_SIZE {
92 warnings.push(format!(
93 "record is {} bytes; PCFSIG_KEY needs at least a {KEY_PREFIX_SIZE}-byte prefix",
94 data.len()
95 ));
96 }
97
98 let magic_ok = data.get(0..8) == Some(&KEY_MAGIC);
99 if !magic_ok {
100 warnings.push("record_magic is not \"PCFKEY\\0\\0\"".into());
101 }
102 fields.push(
103 FieldNode::leaf(
104 "record_magic",
105 FieldValue::Text(magic8(data.get(0..8).unwrap_or(&[]))),
106 (0, 8),
107 )
108 .with_note(if magic_ok {
109 "magic OK"
110 } else {
111 "expected \"PCFKEY\\0\\0\""
112 }),
113 );
114
115 let version_major = le_u16(data, 8).unwrap_or(0);
116 let version_minor = le_u16(data, 10).unwrap_or(0);
117 if version_major != 1 {
118 warnings.push(format!(
119 "record_version_major is {version_major} (v1.0 reader expects 1)"
120 ));
121 }
122 fields.push(FieldNode::leaf(
123 "record_version_major",
124 FieldValue::U64(version_major as u64),
125 (8, 10),
126 ));
127 fields.push(FieldNode::leaf(
128 "record_version_minor",
129 FieldValue::U64(version_minor as u64),
130 (10, 12),
131 ));
132
133 let key_format_id = data.get(12).copied().unwrap_or(0);
134 if key_format_id == 0 {
135 warnings.push("key_format_id is 0 (reserved)".into());
136 } else if KeyFormat::from_id(key_format_id).is_err() {
137 warnings.push(format!("key_format_id {key_format_id} is unknown"));
138 }
139 fields.push(FieldNode::leaf(
140 "key_format_id",
141 FieldValue::Enum {
142 raw: key_format_id as u64,
143 name: key_format_name(key_format_id).into(),
144 },
145 (12, 13),
146 ));
147
148 let reserved = data.get(13..16).unwrap_or(&[]);
149 if reserved.iter().any(|&b| b != 0) {
150 warnings.push("reserved bytes (offset 13..16) must be 0".into());
151 }
152 fields.push(FieldNode::leaf(
153 "reserved",
154 FieldValue::Bytes(reserved.to_vec()),
155 (13, 16),
156 ));
157
158 let fingerprint_stored = data.get(16..16 + FINGERPRINT_SIZE).unwrap_or(&[]);
159 fields.push(FieldNode::leaf(
160 "fingerprint",
161 FieldValue::Bytes(fingerprint_stored.to_vec()),
162 (16, 16 + FINGERPRINT_SIZE as u64),
163 ));
164
165 let key_data_length = le_u32(data, 48).unwrap_or(0) as usize;
166 if key_data_length == 0 {
167 warnings.push("key_data_length is 0".into());
168 }
169 fields.push(FieldNode::leaf(
170 "key_data_length",
171 FieldValue::U64(key_data_length as u64),
172 (48, 52),
173 ));
174
175 let key_end = KEY_PREFIX_SIZE.saturating_add(key_data_length);
176 if key_end > data.len() {
177 warnings.push(format!(
178 "key_data runs past end of record ({key_end} > {})",
179 data.len()
180 ));
181 }
182 let key_data = data
183 .get(KEY_PREFIX_SIZE..key_end.min(data.len()))
184 .unwrap_or(&[]);
185 fields.push(FieldNode::leaf(
186 "key_data",
187 FieldValue::Bytes(key_data.to_vec()),
188 (KEY_PREFIX_SIZE as u64, key_end as u64),
189 ));
190
191 if !key_data.is_empty() && fingerprint_stored.len() == FINGERPRINT_SIZE {
193 let recomputed = compute_fingerprint(key_data);
194 if recomputed.as_slice() != fingerprint_stored {
195 warnings.push(
196 "stored fingerprint does not equal SHA-256(key_data) (spec Section 6.3)".into(),
197 );
198 }
199 }
200
201 if key_end < data.len() {
203 let mut tlv_group = FieldNode::group("optional_metadata");
204 let mut cur = key_end;
205 let mut entry_idx = 0usize;
206 while cur < data.len() {
207 if data.len() - cur < 6 {
208 warnings.push(format!(
209 "metadata TLV entry {entry_idx} is truncated ({} bytes left)",
210 data.len() - cur
211 ));
212 break;
213 }
214 let tag = le_u16(data, cur).unwrap_or(0);
215 let len = le_u32(data, cur + 2).unwrap_or(0) as usize;
216 let value_start = cur + 6;
217 let value_end = value_start.saturating_add(len);
218 let mut entry = FieldNode::group(format!("entry[{entry_idx}]"));
219 entry.push(FieldNode::leaf(
220 "tag",
221 FieldValue::Enum {
222 raw: tag as u64,
223 name: metadata_tag_name(tag).into(),
224 },
225 (cur as u64, cur as u64 + 2),
226 ));
227 entry.push(FieldNode::leaf(
228 "length",
229 FieldValue::U64(len as u64),
230 (cur as u64 + 2, cur as u64 + 6),
231 ));
232 if value_end > data.len() {
233 warnings.push(format!(
234 "metadata TLV entry {entry_idx} value ({len} bytes) runs past end of record"
235 ));
236 entry.push(FieldNode::leaf(
237 "value",
238 FieldValue::Bytes(data.get(value_start..).unwrap_or(&[]).to_vec()),
239 (value_start as u64, data.len() as u64),
240 ));
241 tlv_group.push(entry);
242 break;
243 }
244 let value = &data[value_start..value_end];
245 entry.push(FieldNode::leaf(
246 "value",
247 FieldValue::Bytes(value.to_vec()),
248 (value_start as u64, value_end as u64),
249 ));
250 tlv_group.push(entry);
251 cur = value_end;
252 entry_idx += 1;
253 }
254 if entry_idx > 0 {
255 fields.push(tlv_group);
256 }
257 }
258
259 Decoded {
260 format_name: "PCFSIG_KEY".into(),
261 fields,
262 warnings,
263 }
264 }
265}
266
267pub struct PcfSigSignatureDecoder;
272
273impl PartitionDecoder for PcfSigSignatureDecoder {
274 fn name(&self) -> &'static str {
275 "pcfsig-sig"
276 }
277
278 fn matches(&self, meta: &PartitionMeta, data: &[u8]) -> bool {
279 meta.partition_type == TYPE_PCFSIG_SIG || data.get(0..8) == Some(&SIG_MAGIC)
280 }
281
282 fn decode(&self, _meta: &PartitionMeta, data: &[u8]) -> Decoded {
283 let mut warnings = Vec::new();
284 let mut fields = Vec::new();
285
286 if data.len() < MANIFEST_PREFIX_SIZE {
287 warnings.push(format!(
288 "record is {} bytes; PCFSIG_SIG manifest needs at least {MANIFEST_PREFIX_SIZE}",
289 data.len()
290 ));
291 }
292
293 let mut manifest = FieldNode::group("manifest");
295
296 let magic_ok = data.get(0..8) == Some(&SIG_MAGIC);
297 if !magic_ok {
298 warnings.push("manifest_magic is not \"PCFSIG\\0\\0\"".into());
299 }
300 manifest.push(
301 FieldNode::leaf(
302 "manifest_magic",
303 FieldValue::Text(magic8(data.get(0..8).unwrap_or(&[]))),
304 (0, 8),
305 )
306 .with_note(if magic_ok {
307 "magic OK"
308 } else {
309 "expected \"PCFSIG\\0\\0\""
310 }),
311 );
312
313 let version_major = le_u16(data, 8).unwrap_or(0);
314 let version_minor = le_u16(data, 10).unwrap_or(0);
315 if version_major != 1 {
316 warnings.push(format!(
317 "manifest_version_major is {version_major} (v1.0 reader expects 1)"
318 ));
319 }
320 manifest.push(FieldNode::leaf(
321 "manifest_version_major",
322 FieldValue::U64(version_major as u64),
323 (8, 10),
324 ));
325 manifest.push(FieldNode::leaf(
326 "manifest_version_minor",
327 FieldValue::U64(version_minor as u64),
328 (10, 12),
329 ));
330
331 let sig_algo_id = data.get(12).copied().unwrap_or(0);
332 if sig_algo_id == 0 {
333 warnings.push("sig_algo_id is 0 (reserved)".into());
334 } else if SigAlgo::from_id(sig_algo_id).is_err() {
335 warnings.push(format!("sig_algo_id {sig_algo_id} is unknown"));
336 }
337 manifest.push(FieldNode::leaf(
338 "sig_algo_id",
339 FieldValue::Enum {
340 raw: sig_algo_id as u64,
341 name: sig_algo_name(sig_algo_id).into(),
342 },
343 (12, 13),
344 ));
345
346 let manifest_hash_id = data.get(13).copied().unwrap_or(0);
347 let (manifest_hash_name, hash_is_crypto) = match HashAlgo::from_id(manifest_hash_id) {
348 Ok(a) => (crate::model::algo_name(a), is_crypto_hash(a)),
349 Err(_) => ("unknown", false),
350 };
351 if !hash_is_crypto {
352 warnings.push(format!(
353 "manifest_hash_algo_id {manifest_hash_id} is not cryptographic (spec Section 9)"
354 ));
355 }
356 manifest.push(FieldNode::leaf(
357 "manifest_hash_algo_id",
358 FieldValue::Enum {
359 raw: manifest_hash_id as u64,
360 name: manifest_hash_name.into(),
361 },
362 (13, 14),
363 ));
364
365 let flags = le_u16(data, 14).unwrap_or(0);
366 if flags != 0 {
367 warnings.push(format!("flags is {flags:#06x}; v1.0 readers require 0"));
368 }
369 manifest.push(FieldNode::leaf(
370 "flags",
371 FieldValue::U64(flags as u64),
372 (14, 16),
373 ));
374
375 let signer_fp = data.get(16..16 + FINGERPRINT_SIZE).unwrap_or(&[]);
376 manifest.push(FieldNode::leaf(
377 "signer_key_fingerprint",
378 FieldValue::Bytes(signer_fp.to_vec()),
379 (16, 16 + FINGERPRINT_SIZE as u64),
380 ));
381
382 let signed_at = le_u64(data, 48).unwrap_or(0);
383 manifest.push(FieldNode::leaf(
384 "signed_at_unix_seconds",
385 FieldValue::U64(signed_at),
386 (48, 56),
387 ));
388
389 let signed_count = le_u32(data, 56).unwrap_or(0) as usize;
390 if signed_count == 0 {
391 warnings.push("signed_count is 0 (manifest must have at least 1 entry)".into());
392 }
393 manifest.push(FieldNode::leaf(
394 "signed_count",
395 FieldValue::U64(signed_count as u64),
396 (56, 60),
397 ));
398
399 let mut entries_group = FieldNode::group("signed_entries");
401 for i in 0..signed_count {
402 let off = MANIFEST_PREFIX_SIZE + i * SIGNED_ENTRY_SIZE;
403 if off + SIGNED_ENTRY_SIZE > data.len() {
404 warnings.push(format!(
405 "signed_entry[{i}] runs past end of record (offset {off}, len {})",
406 data.len()
407 ));
408 break;
409 }
410 let mut entry = FieldNode::group(format!("entry[{i}]"));
411
412 let uid = uid_at(data, off).unwrap_or([0; 16]);
413 if uid == [0u8; 16] {
414 warnings.push(format!("signed_entry[{i}].uid is NIL"));
415 }
416 entry.push(FieldNode::leaf(
417 "uid",
418 FieldValue::Uid(uid),
419 (off as u64, off as u64 + 16),
420 ));
421
422 let ptype = le_u32(data, off + 16).unwrap_or(0);
423 if ptype == 0 {
424 warnings.push(format!("signed_entry[{i}].partition_type is 0 (reserved)"));
425 }
426 entry.push(FieldNode::leaf(
427 "partition_type",
428 FieldValue::U64(ptype as u64),
429 (off as u64 + 16, off as u64 + 20),
430 ));
431
432 let label_bytes = data.get(off + 20..off + 52).unwrap_or(&[]);
433 let label_end = label_bytes.iter().position(|&b| b == 0).unwrap_or(32);
434 let label_str =
435 String::from_utf8_lossy(&label_bytes[..label_end.min(label_bytes.len())])
436 .into_owned();
437 entry.push(FieldNode::leaf(
438 "label",
439 FieldValue::Text(label_str),
440 (off as u64 + 20, off as u64 + 52),
441 ));
442
443 let used_bytes = le_u64(data, off + 52).unwrap_or(0);
444 entry.push(FieldNode::leaf(
445 "used_bytes",
446 FieldValue::U64(used_bytes),
447 (off as u64 + 52, off as u64 + 60),
448 ));
449
450 let entry_hash_id = data.get(off + 60).copied().unwrap_or(0);
451 let (entry_hash_name, entry_is_crypto) = match HashAlgo::from_id(entry_hash_id) {
452 Ok(a) => (crate::model::algo_name(a), is_crypto_hash(a)),
453 Err(_) => ("unknown", false),
454 };
455 if !entry_is_crypto {
456 warnings.push(format!(
457 "signed_entry[{i}].data_hash_algo_id {entry_hash_id} is not cryptographic"
458 ));
459 }
460 entry.push(FieldNode::leaf(
461 "data_hash_algo_id",
462 FieldValue::Enum {
463 raw: entry_hash_id as u64,
464 name: entry_hash_name.into(),
465 },
466 (off as u64 + 60, off as u64 + 61),
467 ));
468
469 let reserved1 = data.get(off + 61).copied().unwrap_or(0);
470 if reserved1 != 0 {
471 warnings.push(format!(
472 "signed_entry[{i}] reserved byte at offset 61 is {reserved1:#04x} (must be 0)"
473 ));
474 }
475 entry.push(FieldNode::leaf(
476 "reserved (1 B)",
477 FieldValue::U64(reserved1 as u64),
478 (off as u64 + 61, off as u64 + 62),
479 ));
480
481 let data_hash = data.get(off + 62..off + 126).unwrap_or(&[]);
482 entry.push(FieldNode::leaf(
483 "data_hash",
484 FieldValue::Bytes(data_hash.to_vec()),
485 (off as u64 + 62, off as u64 + 126),
486 ));
487
488 let reserved2 = data.get(off + 126..off + 218).unwrap_or(&[]);
489 if reserved2.iter().any(|&b| b != 0) {
490 warnings.push(format!(
491 "signed_entry[{i}] reserved tail (92 B at offset 126) must be all-zero"
492 ));
493 }
494 entry.push(FieldNode::leaf(
495 "reserved (92 B)",
496 FieldValue::Bytes(reserved2.to_vec()),
497 (off as u64 + 126, off as u64 + 218),
498 ));
499
500 entries_group.push(entry);
501 }
502 manifest.push(entries_group);
503 fields.push(manifest);
504
505 let manifest_len = MANIFEST_PREFIX_SIZE + signed_count * SIGNED_ENTRY_SIZE;
507 if data.len() >= manifest_len + 4 {
508 let sig_length = le_u32(data, manifest_len).unwrap_or(0) as usize;
509 fields.push(FieldNode::leaf(
510 "sig_length",
511 FieldValue::U64(sig_length as u64),
512 (manifest_len as u64, manifest_len as u64 + 4),
513 ));
514
515 let sig_start = manifest_len + 4;
516 let sig_end = sig_start.saturating_add(sig_length);
517 if sig_end > data.len() {
518 warnings.push(format!(
519 "sig_bytes ({sig_length} bytes) runs past end of record"
520 ));
521 }
522 let sig_bytes = data.get(sig_start..sig_end.min(data.len())).unwrap_or(&[]);
523 fields.push(FieldNode::leaf(
524 "sig_bytes",
525 FieldValue::Bytes(sig_bytes.to_vec()),
526 (sig_start as u64, sig_end as u64),
527 ));
528
529 if data.len() >= sig_end + 4 {
530 let trailer_length = le_u32(data, sig_end).unwrap_or(0) as usize;
531 if trailer_length != 0 {
532 warnings.push(format!(
533 "trailer_length is {trailer_length}; v1.0 readers require 0"
534 ));
535 }
536 fields.push(FieldNode::leaf(
537 "trailer_length",
538 FieldValue::U64(trailer_length as u64),
539 (sig_end as u64, sig_end as u64 + 4),
540 ));
541 if trailer_length > 0 {
542 let trailer_bytes = data
543 .get(sig_end + 4..(sig_end + 4 + trailer_length).min(data.len()))
544 .unwrap_or(&[]);
545 fields.push(FieldNode::leaf(
546 "trailer_bytes",
547 FieldValue::Bytes(trailer_bytes.to_vec()),
548 (sig_end as u64 + 4, (sig_end + 4 + trailer_length) as u64),
549 ));
550 }
551 } else {
552 warnings.push("trailer_length field missing (record is truncated)".into());
553 }
554 } else {
555 warnings.push("sig_length field missing (record is truncated)".into());
556 }
557
558 Decoded {
559 format_name: "PCFSIG_SIG".into(),
560 fields,
561 warnings,
562 }
563 }
564}