Skip to main content

pamoja_session/
lib.rs

1#![no_std]
2
3//! Encrypted, authenticated sessions for the pamoja SDK.
4//!
5//! [`pamoja-security`](https://docs.rs/pamoja-security) proves a payload came from a
6//! device and was not altered. This crate adds the other half a networked link needs:
7//! confidentiality and a fresh, ordered, replay-protected channel, so a reading is
8//! not just trustworthy but private, and a captured message cannot be replayed to
9//! reopen a valve or re-trigger an alarm.
10//!
11//! Two devices that each hold the other's authenticated public key agree a session
12//! key with [`Session::establish`] and then exchange messages with [`Session::seal`]
13//! and [`Session::open`]. The whole exchange is built from published standards and
14//! the tests are pinned to their reference vectors:
15//!
16//! - X25519 key agreement, RFC 7748, so neither side ever sends the key.
17//! - HKDF-SHA256, RFC 5869, to derive a per-session key bound to both public keys.
18//! - ChaCha20-Poly1305, RFC 8439, to encrypt and authenticate each message; chosen
19//!   because the cheap hardware this SDK targets rarely has AES acceleration.
20//!
21//! Establishing a session is deterministic given the keys and salt, and every
22//! operation works in place on caller-owned buffers, so the crate is `no_std` and
23//! allocation-free and runs unchanged on a microcontroller. It is the secured-channel
24//! groundwork the security pillar builds on, ahead of full transport TLS/DTLS.
25//!
26//! # Authenticating the peer
27//!
28//! Key agreement gives a private channel; it does not by itself say who is on the
29//! other end. The peer's [`AgreementPublicKey`] must be authenticated out of band,
30//! by pinning it at provisioning time or by having it signed with the peer's
31//! `pamoja-security` identity. Without that, the channel is confidential but open to
32//! a man in the middle.
33//!
34//! # Examples
35//!
36//! ```
37//! use pamoja_session::{AgreementKey, Role, Session};
38//!
39//! // Each device holds its own seed and the other's authenticated public key.
40//! let sensor = AgreementKey::from_seed(&[1u8; 32]);
41//! let gateway = AgreementKey::from_seed(&[2u8; 32]);
42//!
43//! // A fresh salt is exchanged in the clear to start the session.
44//! let salt = [42u8; 16];
45//! let mut a = Session::establish(&sensor, &gateway.public(), &salt, Role::Initiator);
46//! let mut b = Session::establish(&gateway, &sensor.public(), &salt, Role::Responder);
47//!
48//! // Seal a reading; the device id rides along as authenticated-but-readable data.
49//! let mut reading = *b"tank: 18%";
50//! let sealed = a.seal(&mut reading, b"well-3");
51//!
52//! // The gateway opens it, recovering the reading and proving it is authentic.
53//! b.open(&sealed, &mut reading, b"well-3").expect("authentic and fresh");
54//! assert_eq!(&reading, b"tank: 18%");
55//!
56//! // A replay of the same message is refused.
57//! assert!(b.open(&sealed, &mut reading.clone(), b"well-3").is_err());
58//! ```
59
60mod aead;
61mod error;
62mod kdf;
63mod kex;
64mod session;
65
66pub use error::SessionError;
67pub use kdf::{hkdf_sha256, hmac_sha256};
68pub use kex::{AgreementKey, AgreementPublicKey};
69pub use session::{Role, Sealed, Session};