Skip to main content

atecc608b/command/
privwrite.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//! `PrivWrite` command.
17//!
18//! Writes a P-256 private key from the host into a slot.
19//!
20//! # Project policy
21//!
22//! **This command is intentionally NOT used for the user identity key.**
23//! User identity keys (slots 0..=4 in this project) are generated on-chip
24//! via [`crate::AteccChannel::genkey_create`] so that the private
25//! material never traverses the host or the USB bus. `PrivWrite` exists
26//! here for bring-up and for the V3 attestation slot (slot 7) only, both
27//! controlled by a privileged path in `tools/hsm-host`.
28//!
29//! # Modes
30//!
31//! Two modes exist:
32//!
33//! - **Cleartext** (`param1 = 0x00`). Only accepted before the data zone is
34//!   locked. Used during bring-up to load known test keys.
35//! - **Encrypted** (`param1 = 0x40`). Required after data zone lock. The
36//!   data field carries ciphertext plus a 32-byte MAC. The driver does not
37//!   currently expose the encrypted path: the orchestration is
38//!   service-layer work that depends on
39//!   [`crate::AteccChannel::nonce_random`] +
40//!   [`crate::AteccChannel::gendig`] and the matching host-side
41//!   key derivation. It will be added when that orchestration lands.
42//!
43//! Reference: `CryptoAuthLib` `lib/calib/calib_priv_write.c`, constants
44//! `PRIV_WRITE_MODE_ENCRYPT` (0x40).
45//!
46//! # Data layout
47//!
48//! Cleartext: 4-byte zero padding then the 32-byte raw private scalar
49//! (big-endian, the natural P-256 byte order).
50//!
51//! ```text
52//! [00 00 00 00] [P-256 scalar, 32 bytes BE]
53//! ```
54
55use crate::driver::AteccChannel;
56use crate::error::AteccError;
57use crate::hal::AteccHal;
58use crate::opcodes::{EXEC_TIME_PRIVWRITE_MS, OP_PRIVWRITE};
59use crate::slot::Slot;
60
61/// Cleartext `PrivWrite` payload size (4 padding + 32 scalar).
62pub const PRIVWRITE_CLEARTEXT_SIZE: usize = 36;
63
64/// `param1` mode for cleartext `PrivWrite` (data zone unlocked only).
65const PRIVWRITE_MODE_CLEARTEXT: u8 = 0x00;
66
67impl<H> AteccChannel<'_, H>
68where
69    H: AteccHal,
70{
71    /// Write a 32-byte P-256 private scalar into `slot` in cleartext.
72    ///
73    /// **Only valid while the data zone is unlocked**, which on this project
74    /// means before the irreversible data-zone Lock has been performed.
75    /// Calling this after lock returns a chip error.
76    ///
77    /// **Not for the user identity key.** The user identity key is created
78    /// on-chip via `genkey_create`. This entry point exists for bring-up
79    /// helpers (loading a known test key into a scratch slot) and for the
80    /// attestation slot if used.
81    ///
82    /// `private_key` is the raw 32-byte scalar in big-endian form, the
83    /// natural P-256 byte order matching the output of standard libraries
84    /// (`p256`, OpenSSL, etc.).
85    ///
86    /// # Errors
87    /// See [`AteccChannel::execute_command_status`]. Returns a chip error if
88    /// the data zone is already locked.
89    pub async fn privwrite_cleartext
90    (
91        &mut self,
92        slot: Slot,
93        private_key: &[u8; 32],
94    ) -> Result<(), AteccError<H::Error>>
95    {
96        let mut data = [0u8; PRIVWRITE_CLEARTEXT_SIZE];
97        // First 4 bytes are zero padding as required by the chip.
98        data[4..].copy_from_slice(private_key);
99
100        self.execute_command_status
101        (
102            OP_PRIVWRITE,
103            PRIVWRITE_MODE_CLEARTEXT,
104            u16::from(slot.as_u8()),
105            &data,
106            EXEC_TIME_PRIVWRITE_MS,
107        )
108        .await
109    }
110}
111
112#[cfg(test)]
113mod tests
114{
115    use super::*;
116
117    #[test]
118    fn cleartext_payload_size_is_36()
119    {
120        assert_eq!(PRIVWRITE_CLEARTEXT_SIZE, 36);
121    }
122
123    #[test]
124    fn cleartext_mode_is_zero()
125    {
126        assert_eq!(PRIVWRITE_MODE_CLEARTEXT, 0x00);
127    }
128}