Skip to main content

config_generator/
main.rs

1// Copyright (c) 2026 Tuloup Simon
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Generator for the ATECC608B configuration zone blob used by the
17//! mini-HSM.
18//!
19//! Produces a 128-byte binary file matching the specification documented
20//! in `docs/config-zone-layout.md`. The bytes 0 to 15 (factory area) are
21//! placeholders. They are not transmitted to the chip during the Write
22//! command. Bytes 16 to 127 are the writable portion, which is what the
23//! firmware writes to the chip during provisioning.
24//!
25//! See the documentation for the bit-by-bit justification of every value.
26
27use std::fs;
28use std::path::PathBuf;
29
30use anyhow::{Context, Result};
31use clap::Parser;
32
33mod annotate;
34mod blob;
35mod counter_encoding;
36mod crc;
37
38/// Expected CRC-16 of the writable portion (bytes 16 to 127) of the
39/// generated blob. This is an internal sanity check: if `blob::build()`
40/// ever returns a different value, the generator has drifted from the
41/// specification in `docs/config-zone-layout.md`.
42///
43/// **Not the argument of the `Lock(config)` command.** The chip computes
44/// its CRC over the full 128-byte configuration zone, factory area
45/// included. That value is per-chip and is computed at lock time by the
46/// host CLI directly from a `ReadConfigZone` of the chip. See
47/// `tools/hsm-host/src/main.rs::cmd_lock_config_dangerous`.
48const EXPECTED_CRC: u16 = 0xC92D;
49
50#[derive(Parser, Debug)]
51#[command(author, version, about)]
52struct Cli
53{
54    /// Path of the binary output. Defaults to `config_zone.bin` in the
55    /// current directory.
56    #[arg(short, long, default_value = "config_zone.bin")]
57    output: PathBuf,
58
59    /// Also write a human-readable annotation file next to the binary,
60    /// with the same basename and the `.txt` extension.
61    #[arg(long)]
62    annotate: bool,
63
64    /// Print the CRC-16 of the writable portion of the blob and exit
65    /// without writing any file.
66    #[arg(long, conflicts_with = "annotate")]
67    crc_only: bool,
68}
69
70fn main() -> Result<()>
71{
72    let cli = Cli::parse();
73
74    let blob = blob::build();
75
76    let writable = &blob[16..128];
77    let crc = crc::crc16(writable);
78
79    if crc != EXPECTED_CRC
80    {
81        anyhow::bail!
82        (
83            "internal error: generated blob CRC is 0x{crc:04X} but spec says 0x{EXPECTED_CRC:04X}. \
84             The generator is out of sync with docs/config-zone-layout.md.",
85        );
86    }
87
88    if cli.crc_only
89    {
90        println!("0x{crc:04X}");
91        return Ok(());
92    }
93
94    fs::write(&cli.output, blob)
95        .with_context(|| format!("failed to write {}", cli.output.display()))?;
96    println!("Wrote {} bytes to {}", blob.len(), cli.output.display());
97    println!("CRC-16 of writable portion: 0x{crc:04X}");
98
99    if cli.annotate
100    {
101        let annotation_path = cli.output.with_extension("txt");
102        let annotation = annotate::format(&blob, crc);
103        fs::write(&annotation_path, annotation)
104            .with_context(|| format!("failed to write {}", annotation_path.display()))?;
105        println!("Wrote annotation to {}", annotation_path.display());
106    }
107
108    Ok(())
109}