Skip to content

Commit c5af2fa

Browse files
committed
Add PCF-SIG signing/verification to CLI tooling
Introduce a generic `pcf-sig` tool (crate tools/pcf-sig, lib pcf_sig_cli + bin pcf-sig) for signing and verifying any PCF file with PCF-SIG (Ed25519): keygen, incremental sign, verify with optional trust check, and key listing. Signing is incremental by default — partitions already covered by a valid signature from the same key are skipped — with --resign to force. Wire PCF-SIG into the `pfs` CLI: keygen and verify-sig delegate to pcf_sig_cli, and every mutating command (mkfs/mkdir/put/mv/rm/create/update) accepts --key to auto-sign after its commit. Because PFS-MS is append-only with a backward-linked session chain, naively appending signature partitions corrupts the chain. Signing a PFS-MS file is therefore committed as a dedicated signature session (pfs-ms sign_archive), covering content and node records; the generic pcf-sig sign refuses PFS-MS files and points to `pfs sign`. Includes roundtrip/incremental/tamper/refusal tests for both crates, a dedicated CI workflow, release-binary packaging for pcf-sig, and README docs.
1 parent aa9e09a commit c5af2fa

16 files changed

Lines changed: 1831 additions & 40 deletions

File tree

.github/workflows/build-binaries.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ jobs:
8888
run: |
8989
cargo build --release --target ${{ matrix.target }} -p pcf-debug
9090
cargo build --release --target ${{ matrix.target }} -p pcf-compact
91+
cargo build --release --target ${{ matrix.target }} -p pcf-sig-cli --bin pcf-sig
9192
cargo build --release --target ${{ matrix.target }} -p pfs-ms --bin pfs
9293
9394
- name: Build (macOS universal2)
@@ -97,6 +98,7 @@ jobs:
9798
for t in ${{ matrix.macos-targets }}; do
9899
cargo build --release --target "$t" -p pcf-debug
99100
cargo build --release --target "$t" -p pcf-compact
101+
cargo build --release --target "$t" -p pcf-sig-cli --bin pcf-sig
100102
cargo build --release --target "$t" -p pfs-ms --bin pfs
101103
done
102104
mkdir -p target/universal2-apple-darwin/release
@@ -106,11 +108,15 @@ jobs:
106108
lipo -create -output target/universal2-apple-darwin/release/pcf-compact \
107109
target/x86_64-apple-darwin/release/pcf-compact \
108110
target/aarch64-apple-darwin/release/pcf-compact
111+
lipo -create -output target/universal2-apple-darwin/release/pcf-sig \
112+
target/x86_64-apple-darwin/release/pcf-sig \
113+
target/aarch64-apple-darwin/release/pcf-sig
109114
lipo -create -output target/universal2-apple-darwin/release/pfs \
110115
target/x86_64-apple-darwin/release/pfs \
111116
target/aarch64-apple-darwin/release/pfs
112117
file target/universal2-apple-darwin/release/pcf-debug
113118
file target/universal2-apple-darwin/release/pcf-compact
119+
file target/universal2-apple-darwin/release/pcf-sig
114120
file target/universal2-apple-darwin/release/pfs
115121
116122
- name: Stage and archive (unix)
@@ -120,7 +126,7 @@ jobs:
120126
VERSION='${{ needs.resolve-version.outputs.version }}'
121127
TARGET='${{ matrix.target }}'
122128
mkdir -p staging
123-
for bin in pcf-debug pcf-compact pfs; do
129+
for bin in pcf-debug pcf-compact pcf-sig pfs; do
124130
STAGE="staging/${bin}-${VERSION}-${TARGET}"
125131
mkdir -p "$STAGE"
126132
cp "target/${TARGET}/release/${bin}" "$STAGE/${bin}"
@@ -136,7 +142,7 @@ jobs:
136142
$version = '${{ needs.resolve-version.outputs.version }}'
137143
$target = '${{ matrix.target }}'
138144
New-Item -ItemType Directory -Force staging | Out-Null
139-
foreach ($bin in @('pcf-debug', 'pcf-compact', 'pfs')) {
145+
foreach ($bin in @('pcf-debug', 'pcf-compact', 'pcf-sig', 'pfs')) {
140146
$stage = "staging/$bin-$version-$target"
141147
New-Item -ItemType Directory -Force $stage | Out-Null
142148
Copy-Item "target/$target/release/$bin.exe" "$stage/$bin.exe"

.github/workflows/ci-pcf-sig.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: CI / Rust (pcf-sig CLI)
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
RUSTFLAGS: "-D warnings"
12+
13+
defaults:
14+
run:
15+
working-directory: tools/pcf-sig
16+
17+
jobs:
18+
fmt:
19+
name: rustfmt
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
- uses: dtolnay/rust-toolchain@stable
24+
with:
25+
components: rustfmt
26+
- run: cargo fmt --all -- --check
27+
28+
clippy:
29+
name: clippy
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
- uses: dtolnay/rust-toolchain@stable
34+
with:
35+
components: clippy
36+
- uses: Swatinem/rust-cache@v2
37+
with:
38+
workspaces: tools/pcf-sig
39+
- run: cargo clippy --all-targets --all-features -- -D warnings
40+
41+
test:
42+
name: test (${{ matrix.os }})
43+
runs-on: ${{ matrix.os }}
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
os: [ubuntu-latest, macos-latest, windows-latest]
48+
steps:
49+
- uses: actions/checkout@v4
50+
- uses: dtolnay/rust-toolchain@stable
51+
- uses: Swatinem/rust-cache@v2
52+
with:
53+
workspaces: tools/pcf-sig
54+
- run: cargo build --verbose
55+
- run: cargo test --all-targets --verbose

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ members = [
66
"reference/PCF-SIG-v1.0",
77
"tools/pcf-debug",
88
"tools/pcf-compact",
9+
"tools/pcf-sig",
910
]

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ for report in verify_all_with_recheck(&mut c)? {
7575
# Ok::<(), pcf_sig::Error>(())
7676
```
7777

78+
## Command-line tool
79+
80+
A ready-made CLI lives in [`tools/pcf-sig`](../../tools/pcf-sig) (`pcf-sig`),
81+
built on this crate:
82+
83+
```sh
84+
pcf-sig keygen id.key id.pub # 32-byte raw Ed25519 seed + public key
85+
pcf-sig sign file.pcf --key id.key # incremental by default; --resign to redo
86+
pcf-sig verify file.pcf --key id.pub # per-signature / per-partition report
87+
pcf-sig keys file.pcf # list embedded PCFSIG_KEY fingerprints
88+
```
89+
90+
A PFS-MS archive is a PCF file, so `pcf-sig verify`/`keys` work on it directly;
91+
to *sign* a PFS-MS file use `pfs sign`, which commits the signature as a PFS
92+
session (see [`reference/PFS-MS-v1.0`](../PFS-MS-v1.0)).
93+
7894
## Trust patterns
7995

8096
The profile describes one non-X.509 way for an application to express trust

reference/PFS-MS-v1.0/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,13 @@ flate2 = { version = "1", default-features = false, features = ["rust_backend"]
3838

3939
# Portable file modification-time setting for the directory-import/extract tools.
4040
filetime = "0.2"
41+
42+
# Shared PCF-SIG command-line logic, so `pfs` can run keygen / verify-sig using
43+
# exactly the same implementation as the standalone `pcf-sig` tool.
44+
pcf-sig-cli = { path = "../../tools/pcf-sig", version = "0.0.8" }
45+
46+
# The PCF-SIG signing primitives. Signing a PFS-MS file cannot simply append
47+
# partitions (that would break the backward-linked session chain); instead the
48+
# PCFSIG_KEY / PCFSIG_SIG partitions are committed as a dedicated PFS session
49+
# (see `src/sign.rs`), built from these primitives.
50+
pcf-sig = { path = "../PCF-SIG-v1.0", version = "0.0.8" }

reference/PFS-MS-v1.0/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,37 @@ on extract; pass `--no-metadata` (on either side) to skip this, and `--store` to
136136
disable compression. Symlinks and other non-regular files are skipped with a
137137
warning.
138138

139+
### Signing (PCF-SIG)
140+
141+
`pfs` can sign archives with [PCF-SIG](../PCF-SIG-v1.0) (Ed25519). Because
142+
PFS-MS is append-only with a backward-linked session chain, a signature is
143+
**committed as its own PFS session** carrying the `PCFSIG_KEY` / `PCFSIG_SIG`
144+
partitions — not appended out of band — so `verify` keeps working.
145+
146+
```
147+
# Generate a keypair (delegates to the pcf-sig tool).
148+
cargo run --bin pfs -- keygen id.key id.pub
149+
150+
# Sign content + node records not yet signed by this key (incremental).
151+
cargo run --bin pfs -- sign fs.pfs --key id.key # no-op if nothing new
152+
cargo run --bin pfs -- sign fs.pfs --key id.key --resign # re-sign everything
153+
154+
# Verify embedded signatures (optionally assert a trusted public key).
155+
cargo run --bin pfs -- verify-sig fs.pfs --key id.pub
156+
```
157+
158+
Every mutating command (`mkfs`, `mkdir`, `put`, `mv`, `rm`, `create`, `update`)
159+
also accepts `--key <priv>` to **auto-sign** right after its commit, so each
160+
operation adds one signature covering just the partitions it introduced:
161+
162+
```
163+
cargo run --bin pfs -- mkfs fs.pfs --key id.key
164+
echo hi | cargo run --bin pfs -- put fs.pfs hello.txt - --key id.key
165+
```
166+
167+
Signatures cover file content and node records; PFS-MS's own inter-session hash
168+
chain (checked by `verify`) already makes session records tamper-evident.
169+
139170
## Layout
140171

141172
```

0 commit comments

Comments
 (0)