Skip to main content

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}