atecc608x (1.0.1)
Installation
[registries.forgejo]
index = "sparse+ " # Sparse index
# index = " " # Git
[net]
git-fetch-with-cli = truecargo add atecc608x@1.0.1 --registry forgejoAbout this package
ATECC608B Driver
no_std driver for the Microchip ATECC608B Secure Element.
Description
This crate provides a Rust interface for the ATECC608B Secure Element, supporting:
- P-256 ECDSA key pair generation
- P-256 ECDSA digital signatures
- P-256 ECDH key exchange
- Secure key storage (private keys never exposed to the MCU)
Features
- ✅
no_stdcompatible - ✅ Generic allocation-free
AteccTransportboundary - ✅ Hardware-accelerated cryptographic operations
- ✅ Private keys never exposed to the MCU
- ✅ Fail-closed protocol framing and mutation retry policy
Cargo features
| Feature | Purpose |
|---|---|
| (none, default) | Runtime driver: discovery, cryptography, counters and Data-zone access |
provisioning |
Generic manufacturing surface: typed Config image, slot profiles, verified Config writes and verified irreversible locks |
The provisioning feature does not contain a product slot map, expected serial
number, certificate policy, or any configAtecc constant. Those choices remain
the responsibility of the provisioning application.
Usage
Adding to your project
Add the dependency from the Forgejo registry:
[dependencies]
atecc608x = { version = "1.0.1", registry = "forgejo" }
embedded-hal = "1.0.0"
For a provisioning executable such as configAtecc, enable the dedicated
surface explicitly:
[dependencies]
atecc608x = { version = "1.0.1", registry = "forgejo", features = ["provisioning"] }
Configure forgejo / forgejorc as in this crate’s .cargo/config.toml. Publish / release flow: see .forgejo/README.md.
Basic example
use atecc608x::{Atecc608b, KeySlot, P256PublicKey};
// `transport` implements AteccTransport and reports actual received lengths.
let mut atecc = Atecc608b::new(&mut delay, &mut transport, 0x60)?;
let slot = KeySlot::new(0)?;
// Generate a P-256 key pair in slot 0
let public_key = atecc.generate_keypair(&mut delay, slot)?;
// Sign a message hash
let message_hash = [0u8; 32]; // SHA-256 hash
let signature = atecc.sign(&mut delay, slot, &message_hash)?;
// Perform ECDH key exchange
let peer_public_key = P256PublicKey::from_bytes(&peer_sec1_bytes)?;
let shared_secret = atecc.ecdh(&mut delay, slot, peer_public_key)?;
There is intentionally no blanket adapter for embedded_hal::I2c: its read
method does not report how many bytes the controller actually received. A
platform adapter must satisfy the contract documented by AteccTransport.
Generic provisioning
With features = ["provisioning"], a host tool can read the device-specific
128-byte image, verify its revision against its own allowlist, replace complete
documented fields from the corresponding template, and apply the supported
Config words with read-back verification:
use atecc608x::{KeySlot, ProvisioningConfig, SlotProfile};
let mut target = atecc.read_provisioning_config(&mut delay)?;
verify_revision_and_protected_fields(&target)?;
target.set_count_match(0x00);
target.set_chip_options(expected_complete_chip_options);
target.set_slot_profile(SlotProfile {
slot: KeySlot::new(slot_number)?,
slot_config,
key_config,
});
atecc.apply_provisioning_config(&mut delay, &target)?;
let observed = atecc.read_provisioning_config(&mut delay)?;
assert_eq!(observed, target);
atecc.lock_config_with_crc(&mut delay, target.as_bytes())?;
apply_provisioning_config deliberately refuses changes to silicon identity,
counter storage, and the UserExtra/lock word. It is a non-atomic multi-word
operation: after any interruption, re-read and compare the complete image
before retrying or locking. Runtime counter values continue to use
read_counter/increment_counter and are not tied to this feature.
Ephemeral ECDH with TempKey (no EEPROM writes)
For ephemeral P-256 key exchange without writing to any key slot, use the TempKey APIs. The private key is generated in the device's volatile TempKey region and immediately consumed by ECDH — it never touches EEPROM and never leaves the device.
use atecc608x::{Atecc608b, P256PublicKey};
// peer_pubkey: construct P256PublicKey from 65-byte uncompressed SEC1 bytes
let peer_pubkey = P256PublicKey::from_bytes(&peer_sec1_bytes)?;
// Atomic API: one call, safe lifecycle (GenKey → Idle → ECDH → Sleep).
let (ephemeral_key, shared_secret) =
atecc.ephemeral_ecdh_tempkey(&mut delay, peer_pubkey)?;
// ephemeral_key: validated P256PublicKey in uncompressed SEC1 form
// shared_secret: zeroizing handle; borrow it only while feeding a KDF.
Requirements:
ChipOptions.ECDHPROT == 0b00(clear ECDH output allowed). Unlocked Config is read on every call; a policy observed after Config lock is cached for this driver instance.- No ephemeral EEPROM slot is required — this flow uses only volatile TempKey.
Error handling:
| Error | Cause |
|---|---|
InvalidPublicKey |
Peer key does not start with 0x04 |
OutputProtectionRequired |
ECDHPROT forbids clear output |
TempKeyInvalid |
ECDH without valid TempKey (consumed/Slept) |
Status / InvalidResponse |
Device/protocol fault; cleanup is attempted and failures are surfaced |
The public API exposes the complete atomic flow rather than split TempKey primitives. Internally:
- Idle preserves TempKey (use between GenKey and ECDH).
- Sleep / power loss / watchdog expiry invalidates TempKey.
- ECDH consumes TempKey on success; the public API exposes only the complete
ephemeral_ecdh_tempkeysequence.
Threat model
- Private keys remain inside the secure element, but clear-output ECDH sends the resulting shared secret over I2C. A physical probe, compromised controller, or malicious same-bus peripheral can observe that value.
- Protocol CRC detects accidental corruption; it does not authenticate the device or host. A bus attacker can observe, inject, suppress, or replay traffic. Slot configuration limits commands the device accepts but does not authenticate all host traffic.
- Provisioning and irreversible lock operations require an exclusive trusted bus, stable power, and a stable physical endpoint. Hot-swap or address rerouting requires dropping and reconstructing the driver before further operations.
- Raw ECDH output must enter an approved KDF and a consuming protocol that provides peer authentication and key confirmation. ECDH alone does not authenticate a peer.
- Systems with an adversarial bus must add physical protection or use a separately qualified protected-output/authenticated design. This crate does not currently implement encrypted ECDH output, MAC/CheckMac, or PrivWrite.
Performance budgets
These are configured device-delay budgets and exclude physical I2C transfer time. They are deterministic transaction counts, not host wall-clock benchmarks.
apply_provisioning_configuses8 + 2Wcommands forWchanged writable Config words. The maximum 23-word path is 54 commands and 2,970 ms, while preserving immediate containing-block verification and a final exact-image read.apply_slot_profilesuses8 + 4Pcommands forPchanged profiles. Sixteen profiles use 72 commands and 3,960 ms without coalescing ordered writes.- A locked Config ECDH policy is read once and then cached.
AteccTransportimplementations must keep the same physical endpoint for a live driver; hot-swap, address rerouting, or replacement requires reconstructing the driver. Unlocked policy is re-read for every agreement. - Retry-eligible wake-started commands remain bounded to three attempts;
NeverAfterAcceptoperations are never retransmitted after possible acceptance.
Project structure
atecc608x/
├── src/
│ ├── lib.rs # Entry point and re-exports
│ ├── driver/ # Main driver implementation
│ ├── error.rs # Error types
│ ├── types/ # Types (KeySlot, P256PublicKey, P256Signature)
│ ├── status/ # Status handling
│ └── setup/ # Configuration profiles
├── scripts/
│ └── test.sh # Run tests
├── Cargo.toml
└── README.md
Dependencies
| ID | Version |
|---|---|
| embedded-hal | ^1.0.0 |
| heapless | ^0.9.2 |
| p256 | ^0.13 |
| sha2 | ^0.10 |
| zeroize | ^1 |
| embedded-hal-mock | ^0.11.1 |