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}