canokey_protocol/lib.rs
1//! Transport-free APDU conversations and caller-owned operations.
2//!
3//! This crate is the wire-format foundation of the CanoKey host library. It
4//! provides encoders and parsers for command and response APDUs ([`apdu`]),
5//! bounded definite-length BER TLV codecs ([`tlv`]), typed protocol errors
6//! with command context ([`error`]), and the owned, caller-driven
7//! [`Operation`] state machine ([`operation`]) that drives an exchange of
8//! complete APDUs step by step. There is no I/O, transport trait, runtime,
9//! threading, or mutable global state: the library builds bytes and parses
10//! bytes, and the caller performs every transmit.
11//!
12//! Most applications should not depend on this crate directly; the `canokey`
13//! facade crate re-exports everything needed to talk to a device. Depend on
14//! `canokey-protocol` when you are building a new applet binding on top of the
15//! operation model, or when you need the APDU/TLV codecs for other low-level
16//! work. The item-level examples in [`apdu`], [`tlv`], and [`operation`] are
17//! runnable offline and show the intended usage.
18//!
19//! Full API contracts (ownership, execution rules, error taxonomy) live in
20//! `docs/design/api-design.md` in the repository.
21#![deny(missing_docs)]
22#![forbid(unsafe_code)]
23/// Physical command and response APDU codecs.
24pub mod apdu;
25/// Typed protocol errors and command context.
26pub mod error;
27/// Caller-driven operations, resource limits, and conversation policies.
28pub mod operation;
29/// Bounded definite-length BER TLV codecs.
30pub mod tlv;
31pub use apdu::{ApduEncoding, ApduHeader, CommandApdu, ExpectedLength, ResponseApdu, StatusWord};
32pub use error::{Error, ErrorKind, Phase, SecretReference};
33pub use operation::{
34 ExchangeOptions, Operation, OperationLimits, OperationOptions, OperationState, Step,
35};
36use std::fmt;
37use zeroize::Zeroizing;
38
39/// Owned sensitive bytes. Debug is redacted; the allocation is zeroized on drop.
40#[derive(Clone, Default)]
41pub struct SecretBytes(Zeroizing<Vec<u8>>);
42impl SecretBytes {
43 /// Take ownership of a byte allocation without copying.
44 ///
45 /// Previously created copies remain the caller's responsibility.
46 pub fn new(bytes: Vec<u8>) -> Self {
47 Self(Zeroizing::new(bytes))
48 }
49 /// Borrow the secret bytes. Avoid logging or making unmanaged copies.
50 pub fn as_bytes(&self) -> &[u8] {
51 &self.0
52 }
53 /// Return the number of initialized bytes; this reveals the length.
54 pub fn len(&self) -> usize {
55 self.0.len()
56 }
57 /// Return whether the buffer contains no bytes.
58 pub fn is_empty(&self) -> bool {
59 self.0.is_empty()
60 }
61 /// Append bytes, wiping the old allocation if growth requires replacement.
62 pub fn extend(&mut self, data: &[u8]) {
63 if data.len() > self.0.capacity() - self.0.len() {
64 // Vec::reserve would free a previous allocation without wiping it.
65 let mut next = Zeroizing::new(Vec::with_capacity(self.0.len() + data.len()));
66 next.extend_from_slice(&self.0);
67 next.extend_from_slice(data);
68 self.0 = next;
69 } else {
70 self.0.extend_from_slice(data);
71 }
72 }
73}
74impl fmt::Debug for SecretBytes {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 f.write_str("SecretBytes([REDACTED])")
77 }
78}