Skip to content

Commit be99cae

Browse files
committed
Add PCF-SIG v1.0: cryptographic signatures profile
PCF-SIG is an application-level profile that adds digital signatures to PCF v1.0 without changing the byte container. Two new partition types are defined: - PCFSIG_KEY (0xAAAB0001): one signer's public key or X.509 cert chain, identified by a 32-byte SHA-256 fingerprint of the key bytes. - PCFSIG_SIG (0xAAAB0002): one Manifest enumerating signed partitions by uid + protected fields, followed by the signature over the Manifest. A Manifest binds only the fields needed to identify a partition's contents (uid, partition_type, label, used_bytes, data_hash_algo_id, data_hash) and NOT physical placement (start_offset, max_length). This makes signatures stable across PCF compaction, reservation growth, and Table Block chain reorganisation -- the relocation-stability property. The reference Rust crate implements Ed25519 as the MUST-support baseline. RSA-PSS, ECDSA, and X.509 are registered in the algorithm registry and recognised at parse time (returning Unverifiable rather than Malformed for unsupported ids), so language ports can add full implementations without changing the on-disk format. Includes 68 tests across 5 integration files (roundtrip, relocation, multi_signer, tamper, spec_compliance) plus 25 unit tests, a gen_testvector example that produces a 966-byte canonical container, and the full normative specification under specs/PCF-SIG-spec-v1.0.txt. https://claude.ai/code/session_01ST4PcjqvobURus32WuyEi5
1 parent 58161bf commit be99cae

20 files changed

Lines changed: 4762 additions & 1 deletion

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
[workspace]
22
resolver = "2"
3-
members = ["reference/PCF-v1.0", "reference/PFS-MS-v1.0", "tools/pcf-debug"]
3+
members = [
4+
"reference/PCF-v1.0",
5+
"reference/PFS-MS-v1.0",
6+
"reference/PCF-SIG-v1.0",
7+
"tools/pcf-debug",
8+
]

reference/PCF-SIG-v1.0/Cargo.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "pcf-sig"
3+
version = "0.0.1"
4+
edition = "2021"
5+
description = "Reference implementation of PCF-SIG v1.0, the PCF Cryptographic Signatures profile"
6+
license = "MIT OR Apache-2.0"
7+
repository = "https://github.com/kduma-OSS/Partitioned-Container-Format"
8+
homepage = "https://github.com/kduma-OSS/Partitioned-Container-Format"
9+
readme = "README.md"
10+
keywords = ["pcf", "signature", "ed25519", "cryptography", "container"]
11+
categories = ["cryptography", "encoding"]
12+
13+
# This crate is a *reference* implementation of the PCF-SIG profile. Like the
14+
# `pcf` crate it builds on, it favours a direct, auditable mapping onto the
15+
# written specification (`specs/PCF-SIG-spec-v1.0.txt`) over raw performance.
16+
17+
[dependencies]
18+
# The PCF-SIG profile is layered strictly above PCF v1.0; every byte container
19+
# operation goes through the reference PCF crate.
20+
pcf = { path = "../PCF-v1.0", version = "0.0.1" }
21+
22+
# SHA-256 for key fingerprints and for the optional independent re-hash check
23+
# during verification. Pinned by the PCF crate already; we re-use it here.
24+
sha2 = "0.10"
25+
26+
# Ed25519 is the MUST-support baseline algorithm (spec Section 8). The pure-Rust
27+
# `ed25519-dalek` 2.x line implements RFC 8032 verification and signing without
28+
# C dependencies. We disable randomized signing because PCF-SIG signs
29+
# deterministically over a serialised manifest.
30+
ed25519-dalek = { version = "=2.1.1", default-features = false, features = ["std"] }
31+
32+
# --- MSRV pins (this environment ships rustc 1.75) -------------------------
33+
# The latest releases of these transitive crates require edition2024, which
34+
# rustc 1.75 cannot build. Constrain them to the last 1.75-compatible line.
35+
cpufeatures = "=0.2.12"

reference/PCF-SIG-v1.0/README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# pcf-sig — PCF Cryptographic Signatures (reference implementation)
2+
3+
Reference reader/writer for **PCF-SIG v1.0**, an application-level profile
4+
that adds digital signatures to the [Partitioned Container Format](../PCF-v1.0)
5+
without modifying the PCF byte container.
6+
7+
This crate mirrors the written specification (`specs/PCF-SIG-spec-v1.0.txt`)
8+
field-for-field and is intended as the *normative* implementation against
9+
which language ports are checked. It favours auditability over performance.
10+
11+
## Model at a glance
12+
13+
PCF-SIG defines two new PCF partition types:
14+
15+
| Type | Name | Holds |
16+
|--------------|--------------|----------------------------------------------------------|
17+
| `0xAAAB0001` | `PCFSIG_KEY` | One signer's public key or X.509 cert, identified by a 32-byte SHA-256 fingerprint of the key bytes |
18+
| `0xAAAB0002` | `PCFSIG_SIG` | One Manifest enumerating signed partitions + the signature over the Manifest |
19+
20+
A **Manifest** binds the *protected fields* of each covered partition:
21+
`uid`, `partition_type`, `label`, `used_bytes`, `data_hash_algo_id`,
22+
`data_hash`. It does NOT bind `start_offset` or `max_length`, so PCF
23+
compaction and other relocations preserve signature validity as long as
24+
partition bytes do not change.
25+
26+
```
27+
PCFSIG_SIG partition data:
28+
[ Manifest (60 + 218 * N bytes) | u32 sig_len | sig_bytes | u32 trailer_len=0 ]
29+
```
30+
31+
## Algorithm support
32+
33+
| `sig_algo_id` | Algorithm | This crate v1.0 |
34+
|---------------|---------------------|------------------|
35+
| 1 | Ed25519 (RFC 8032) | implemented (MUST) |
36+
| 2, 4, 5, 7 | RSA-PSS / PKCS1v15 | registry only |
37+
| 16, 18 | ECDSA P-256 / P-521 | registry only |
38+
| 32 | X.509 chain | registry only |
39+
40+
Algorithms in *registry only* are recognised at parse time and reported as
41+
`Unverifiable` rather than `Malformed`. Adding a full implementation for any
42+
of them is a pure addition that does not touch the on-disk format.
43+
44+
Hash algorithm constraint: signed partitions MUST use a cryptographic
45+
`data_hash_algo_id` (16 SHA-256, 17 SHA-512, 18 BLAKE3). The Writer refuses
46+
to sign weakly-hashed partitions; the Verifier rejects them per entry.
47+
48+
## Usage
49+
50+
```rust
51+
use std::io::Cursor;
52+
use pcf::{Container, HashAlgo};
53+
use pcf_sig::{sign_partitions, verify_all_with_recheck, ManifestVerdict, SigningMaterial};
54+
55+
let mut c = Container::create(Cursor::new(Vec::new()))?;
56+
let alpha = [0x11u8; 16];
57+
c.add_partition(0x10, alpha, "alpha", b"Hello, PCF-SIG!", 0, HashAlgo::Sha256)?;
58+
59+
let signer = SigningMaterial::ed25519_from_seed(&[0x42u8; 32]);
60+
sign_partitions(
61+
&mut c, &signer,
62+
&[alpha],
63+
[0x33u8; 16], // PCFSIG_SIG uid
64+
[0x22u8; 16], // PCFSIG_KEY uid (reused if a key with the same fingerprint already exists)
65+
0, // signed_at_unix_seconds (0 = unspecified)
66+
"pcfsig", "pcfkey",
67+
)?;
68+
69+
for report in verify_all_with_recheck(&mut c)? {
70+
assert!(matches!(report.verdict, ManifestVerdict::Valid));
71+
for entry in &report.entries {
72+
println!("covered uid {:?} verdict {:?}", entry.uid, entry.verdict);
73+
}
74+
}
75+
# Ok::<(), pcf_sig::Error>(())
76+
```
77+
78+
## Relocation stability
79+
80+
The central property: a PCFSIG_SIG signature remains valid across any
81+
operation that touches only the unprotected fields. `tests/relocation.rs`
82+
exercises this end-to-end:
83+
84+
- PCF compaction (full rewrite, every `start_offset` and `max_length`
85+
changes) — signature still verifies.
86+
- Table Block chain growth (extra blocks inserted, chain re-linked) —
87+
signature still verifies.
88+
- In-place update of a sibling UNSIGNED partition — signature still verifies.
89+
90+
## Tests
91+
92+
```
93+
reference/PCF-SIG-v1.0/
94+
├── Cargo.toml
95+
├── README.md
96+
├── src/ # library sources
97+
│ ├── lib.rs
98+
│ ├── consts.rs # magics, type ids, byte-layout constants
99+
│ ├── algo.rs # SigAlgo + KeyFormat registries
100+
│ ├── error.rs
101+
│ ├── key.rs # PCFSIG_KEY record (Key Record + TLV metadata)
102+
│ ├── manifest.rs # Manifest + SignedEntry layout
103+
│ ├── sig.rs # PCFSIG_SIG payload framing (manifest|sig|trailer)
104+
│ ├── sign.rs # high-level Writer API
105+
│ └── verify.rs # high-level Verifier API
106+
├── tests/
107+
│ ├── roundtrip.rs # sign → write → reopen → verify
108+
│ ├── relocation.rs # compaction + chain growth + sibling update
109+
│ ├── multi_signer.rs # independent signatures, key deduplication
110+
│ ├── tamper.rs # protected-field changes invalidate signatures
111+
│ └── spec_compliance.rs # one test per normative MUST/SHALL clause
112+
├── examples/
113+
│ └── gen_testvector.rs # produces a deterministic byte-exact vector
114+
└── testdata/
115+
└── canonical.bin # 966-byte canonical PCF-SIG container
116+
```
117+
118+
Run from this directory:
119+
120+
```
121+
cargo test
122+
cargo run --example gen_testvector # writes pcfsig_testvector.bin
123+
```
124+
125+
The canonical test vector is 966 bytes; its SHA-256 is printed on stderr
126+
when the example runs. Ports are expected to reproduce the same bytes.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! Generates the canonical PCF-SIG v1.0 test-vector file used in spec
2+
//! section 19.
3+
//!
4+
//! Run with: `cargo run --example gen_testvector -- <output-path>`
5+
//! (defaults to ./pcfsig_testvector.bin).
6+
//!
7+
//! The Ed25519 keypair is generated deterministically from a fixed 32-byte
8+
//! seed of 0x00..0x1F, so independent implementations can reproduce the file
9+
//! byte-for-byte.
10+
11+
use std::io::Cursor;
12+
13+
use pcf::{Container, HashAlgo};
14+
use pcf_sig::{sign_partitions, verify_all, DataRecheck, ManifestVerdict, SigningMaterial};
15+
use sha2::{Digest, Sha256};
16+
17+
fn main() {
18+
let path = std::env::args()
19+
.nth(1)
20+
.unwrap_or_else(|| "pcfsig_testvector.bin".to_string());
21+
22+
let seed: [u8; 32] = std::array::from_fn(|i| i as u8);
23+
let signer = SigningMaterial::ed25519_from_seed(&seed);
24+
25+
let mut c = Container::create_with(Cursor::new(Vec::new()), 8, HashAlgo::Sha256).unwrap();
26+
27+
// Partition "alpha": the partition to be signed.
28+
c.add_partition(
29+
0x0000_0010,
30+
[0x11u8; 16],
31+
"alpha",
32+
b"Hello, PCF-SIG!",
33+
0,
34+
HashAlgo::Sha256,
35+
)
36+
.unwrap();
37+
38+
// Sign it. This adds a PCFSIG_KEY partition (uid = 0x22..) and a
39+
// PCFSIG_SIG partition (uid = 0x33..).
40+
sign_partitions(
41+
&mut c,
42+
&signer,
43+
&[[0x11u8; 16]],
44+
[0x33u8; 16], // sig partition uid
45+
[0x22u8; 16], // key partition uid
46+
0, // signed_at = unspecified
47+
"pcfsig",
48+
"pcfkey",
49+
)
50+
.unwrap();
51+
52+
// Compact to the canonical layout and re-verify.
53+
let image = c.compacted_image().unwrap();
54+
std::fs::write(&path, &image).unwrap();
55+
56+
let mut v = Container::open(Cursor::new(image.clone())).unwrap();
57+
v.verify().unwrap();
58+
let reports = verify_all(&mut v, DataRecheck::Recompute).unwrap();
59+
assert_eq!(reports.len(), 1);
60+
assert!(matches!(reports[0].verdict, ManifestVerdict::Valid));
61+
62+
let digest = Sha256::digest(&image);
63+
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
64+
eprintln!("wrote {} ({} bytes)", path, image.len());
65+
eprintln!("sha256 = {hex}");
66+
eprintln!(
67+
"signer fingerprint = {}",
68+
signer
69+
.fingerprint()
70+
.iter()
71+
.map(|b| format!("{b:02x}"))
72+
.collect::<String>()
73+
);
74+
}

0 commit comments

Comments
 (0)