Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 8 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ dilithium = ["dep:pqcrypto-mldsa"]
saber = ["pqcrypto-saber"]

coap = []
coap-std = ["coap", "dep:coap-lite"]
coap-std = ["std", "coap", "dep:coap-lite"]

config = ["serde", "toml", "dep:serde_json"]
config = ["std", "serde", "toml", "dep:serde_json"]
default = [
"std",
"config",
Expand All @@ -55,10 +55,10 @@ default = [

embedded = ["no-std", "heapless", "serde"]
heapless = []
mqtt = ["rumqttc", "tokio", "dep:rand", "dep:serde_json", "serde_json/std"]
mqtt = ["std", "rumqttc", "tokio", "dep:rand", "dep:serde_json", "serde_json/std"]
# ...
no-std = []
std = []
std = ["rand_core/getrandom", "log/std"]
alloc = []

# Profile-specific features
Expand All @@ -74,7 +74,6 @@ profile-saber-dilithium = []
[dependencies]
cfg-if = "1.0"
coap-lite = {version = "0.9", optional = true}
getrandom = { version = "0.2", default-features = false, features = ["custom"] }
# heapless 0.7 pulls `atomic-polyfill` on some targets (RUSTSEC-2023-0089). 0.9 moved to
# `portable-atomic`, reducing exposure to unmaintained crates.
heapless = { version = "0.9.2", default-features = false }
Expand Down Expand Up @@ -103,11 +102,11 @@ base64 = { version = "0.22", default-features = false, features = ["alloc"] }
rand = { version = "0.8", optional = true, default-features = false, features = ["std", "std_rng"] } # std_rng for higher quality on OS
serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] }
sha2 = { version = "0.10", default-features = false }
hkdf = "0.12"
hmac = "0.12"
log = "0.4.29"
hkdf = { version = "0.12", default-features = false }
hmac = { version = "0.12", default-features = false }
log = { version = "0.4.29", default-features = false }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
x25519-dalek = { version = "2.0.1", features = ["static_secrets"] }
x25519-dalek = { version = "2.0.1", default-features = false, features = ["static_secrets", "zeroize"] }

[dev-dependencies]
criterion = "0.5"
Expand Down
2 changes: 1 addition & 1 deletion docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [Introduction](index.md)
- [Security Architecture](architecture.md)
- [Threat Model](security/threat_model.md)
- [Security Invariants](security/invariants.md)
- [FIPS 140-3 Compliance](security/compliance.md)
- [Usage Guide](usage.md)
- [MQTT Integration](usage/mqtt.md)
Expand Down Expand Up @@ -32,4 +33,3 @@
- [Deep IIoT Scenarios](usage/deep_iiot_scenarios.md)
- [Abyssal Threat Model (Math & Lattice)](architecture/threat_model_abyssal.md)
- [Space-Grade Physics (TMR & Hybrid)](architecture/space_grade_physics.md)

197 changes: 197 additions & 0 deletions docs/src/security/invariants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Security Invariants (Contract)

This document is the *contract* for security-relevant behavior in PQC-IIoT. The intent is to make the system’s trust boundaries, assumptions, and “must-hold” properties explicit and regression-testable.

If a change violates an invariant, it is a **security bug**, even if unit tests still pass.

## Scope

This contract covers the reference protocol surfaces implemented in this repository:

- MQTT key announcements and encrypted payload delivery (`src/mqtt_secure.rs`)
- MQTT authenticated sessions + symmetric ratchet (forward secrecy building block) (`src/mqtt_secure.rs`)
- Provisioning-backed identity (`src/provisioning.rs`)
- Signed audit logging (`src/security/audit.rs`)
- CoAP payload authenticity shim + session-based secure mode (`src/coap_secure.rs`)

Out of scope (by design, today):

- OSCORE / DTLS transport security for CoAP (required for confidentiality + replay protection)
- Secure time / monotonic counters backed by TPM/TEE/HSM (we only implement a best-effort monotonic floor)
- Strong post-compromise security (PCS) for MQTT/CoAP (needs a DH ratchet; periodic re-handshake exists but is not PCS)

## Trust Boundaries

### 1) `SecurityProvider` boundary

Long-term secrets live behind `SecurityProvider` (`src/security/provider.rs`). Anything outside that boundary must be treated as hostile input:

- MQTT broker and network traffic
- local filesystem state (identity, keystore, audit log) unless sealed in a rollback-resistant provider

Critical deployment note:
`SecureMqttClient::new()` uses an exportable software identity for demos/tests. For production fleets,
use `SecureMqttClient::new_with_provider()` with a TPM/HSM/TEE-backed `SecurityProvider` so:

- identity keys are non-exportable, and
- sealed state (time floor, keystore anti-rollback counters, revocation sequence) is rollback-resistant.

### 2) MQTT broker is *not trusted*

Assume the broker can:

- reorder, retain, replay, and duplicate publishes
- inject arbitrary topics/payloads
- drop packets (liveness loss)

Therefore:

- no TOFU-by-accident in strict mode
- message authenticity cannot depend on broker ordering
- replay protection must be bounded and deterministic

## Invariants

### I0 — Peer identifiers are bounded and sanitized

Peer IDs appear:

- as MQTT topic suffixes (`pqc/keys/<peer_id>`)
- inside encrypted packet prefixes (`[id_len][peer_id]...`)
- as keystore hashmap keys and log/metric dimensions

**Invariant**:

- `peer_id` MUST be ASCII `[A-Za-z0-9_.-]` and `len(peer_id) <= 128`.
- Invalid IDs MUST be dropped *before* any expensive operation or state insertion.

**Enforced in**: `src/mqtt_secure.rs` (wire ID validation + early drops).

### I1 — Strict mode eliminates TOFU

**Invariant**:

- With `strict_mode=true` (default), a peer MUST NOT become trusted/ready unless:
- an `OperationalCertificate` is present, and
- the certificate verifies under a pinned CA public key, and
- the certificate subject binds to `peer_id` (topic suffix), and
- the announced keys match the certificate.

**Enforced in**: `SecureMqttClient::handle_key_exchange`.

**Regression tests**: `tests/integration_tests.rs::test_strict_mode`.

### I2 — Key announcements are identity-bound and non-malleable

**Invariant**:

- Key announcements MUST include a detached `key_signature`.
- The signature MUST verify over a canonical payload with explicit domain separation and `peer_id` binding.
- `key_epoch` MUST be monotonic per peer (anti-rollback).
- For the same `key_epoch`, `key_id` MUST be identical (epoch collision rejection).

**Enforced in**:

- canonical payload: `key_announcement_payload()` in `src/mqtt_secure.rs`
- anti-rollback: `handle_key_exchange` epoch/key_id checks

**Regression tests**:

- `tests/integration_tests.rs::test_key_announcement_binds_peer_id`
- `tests/integration_tests.rs::test_malicious_key_announcement_rejected`

### I3 — Encrypted MQTT messages have explicit domain separation and topic binding

**Invariant**:

- For encrypted MQTT packets, the signature MUST cover a digest of:
- a protocol domain tag (`pqc-iiot:mqtt-msg:v1`)
- `sender_id`
- MQTT `topic`
- `encrypted_blob`

This prevents cross-protocol confusion and topic re-routing (semantic confusion) attacks.

**Enforced in**:

- sender: `SecureMqttClient::publish_encrypted`
- receiver: `SecureMqttClient::process_notification`

**Regression tests**:

- `tests/mqtt_invariants.rs::mqtt_signature_binds_topic`

### I4 — Replay protection is bounded and supports limited reordering

MQTT delivery can be duplicated and out-of-order in real deployments. Strict monotonic sequencing is not availability-safe.

**Invariant**:

- Each peer maintains a sliding replay window (64-bit bitmap) relative to `last_sequence`.
- A sequence number MUST be accepted iff:
- it is within the window, and
- it has not been seen before.
- Messages older than the window MUST be rejected deterministically.

**Enforced in**: `replay_window_accept()` in `src/mqtt_secure.rs` (persisted in `PeerKeys`).

**Regression tests**:

- `tests/mqtt_invariants.rs::mqtt_replay_window_accepts_out_of_order_within_window`

### I5 — Input size limits exist before parsing / crypto

**Invariant**:

- Untrusted payloads MUST be rejected by size before any parsing or expensive cryptography.
- Limits MUST be explicit and configurable, not “implicit by broker defaults”.

**Enforced in**: `src/mqtt_secure.rs` per-message-type limits:

- key announcements
- attestation challenge/quote
- encrypted packets

### I6 — Audit log is signed if it claims tamper-evidence

A pure hash chain is *not* tamper-evident against an attacker with filesystem write access: they can rewrite the file and recompute the chain.

**Invariant**:

- If the audit log is used as evidence, each chained entry MUST be signed with a device identity key that is non-exportable in production (TPM/HSM-backed).
- If a non-exportable signer is not available, the audit log MUST be treated as best-effort observability, not forensics-grade evidence.

**Enforced in**:

- signing: `ChainedAuditLogger::new_signed()` in `src/security/audit.rs`
- consumers: `SecureMqttClient` uses the signed logger by default

### I7 — Distributed revocation updates are authenticated and monotonic

**Invariant**:

- Revocation updates MUST verify under a pinned CA signature key and be bound to the configured revocation topic.
- Updates MUST enforce monotonic `seq` to prevent rollback/replay.
- A revoked `(peer_id, key_id)` MUST NOT be able to:
- complete a key exchange in strict mode, or
- send encrypted messages that are accepted by receivers.

**Enforced in**:

- message format + verification: `src/security/revocation.rs`
- receiver application: `src/mqtt_secure.rs` (`handle_revocation_update`, key exchange gating, and pre-decrypt key_id checks)

**Regression tests**:

- `tests/integration_tests.rs::test_distributed_revocation_blocks_peer`

## What this contract does *not* guarantee

This project intentionally does not yet provide:

- a trusted secure time source (we only enforce a best-effort monotonic floor; validity windows remain weak without TPM/TEE/HSM)
- standardized CoAP transport security (OSCORE/DTLS); the session-based secure CoAP mode is application-level and not OSCORE
- strong post-compromise security (PCS) for MQTT/CoAP (no DH ratchet; only periodic re-handshake)
- revocation removal / unrevocation semantics (revocation is monotonic and additive)

For critical IIoT deployments, treat these as **blockers**, not “nice-to-haves”.
96 changes: 76 additions & 20 deletions docs/src/security/threat_model.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,88 @@
# Threat Model

This document outlines the threats PQC-IIoT is designed to mitigate.
This document defines what PQC-IIoT assumes about adversaries and what security properties the current codebase aims to enforce.

## 1. "Store Now, Decrypt Later" (SNDL)
This is a *systems* threat model: it explicitly treats the network, the broker, and local persistence as attacker-controlled unless proven otherwise.

**Threat**: Attackers record encrypted traffic today, intending to decrypt it years later when a sufficiently powerful quantum computer becomes available.
**Mitigation**: Use of Kyber (KEM) ensures that session keys cannot be retroactively recovered by quantum algorithms like Shor's algorithm.
## Actors and Capabilities

## 2. Identity Impersonation
### A1 — Network attacker (remote)

**Threat**: An attacker attempts to masquerade as a legitimate sensor or controller to inject false data or commands.
**Mitigation**: Falcon digital signatures provide strong, quantum-resistant authentication. Strict Allow-listing of public keys prevents unauthorized devices from joining the network.
Assume an attacker can:

## 3. Replay Attacks
- observe, replay, reorder, and drop traffic
- inject arbitrary packets
- attempt downgrade/TOFU by racing “first contact” messages
- exploit cost asymmetry (force expensive signature/KEM work)

**Threat**: An attacker captures a valid command (e.g., "Open Valve") and re-transmits it later.
**Mitigation**: Encrypted packets contain a sequential counter. The receiver tracks the `last_seen` counter for each peer and rejects duplicates or out-of-order packets.
### A2 — MQTT broker attacker (malicious broker)

## 4. Key Extraction from Memory
Treat the broker as untrusted infrastructure. It can:

**Threat**: Malware or physical access allows an attacker to dump device RAM and extract private keys.
**Mitigation**:
- **Hardware**: Integration with TPM 2.0 ensures keys are non-exportable and operations happen inside the secure element.
- **Software**: The `software` provider uses the `zeroize` crate to wipe keys from memory immediately after use or on drop.
- publish arbitrary topics/payloads
- replay retained messages indefinitely
- rewrite key announcements
- act as a cardinality amplifier (many peer IDs, many topics)

## 5. Side-Channel Attacks (SCA)
### A3 — Local persistence attacker (filesystem write)

**Threat**: Analyzing power consumption or timing to deduce private keys.
**Mitigation**:
- Underlying libraries (`pqcrypto-*`) utilize constant-time implementations where available.
- Hardware offloading (TPM) provides physical resistance to SCA.
If an attacker can modify local files (keystore, audit log, identity), assume they can:

- truncate or rewrite logs
- roll back state to bypass replay protection
- poison keystore entries to block liveness (availability attack)

Unless the `SecurityProvider` is backed by a TPM/HSM, filesystem security is best-effort.

## Primary Threats and Current Mitigations

### T1 — Harvest-now, decrypt-later (HN-DL)

**Threat**: capture ciphertext today; attempt decryption in the future with quantum capability.

**Mitigation**: hybrid KEM uses Kyber/ML-KEM as the PQ component. The goal is to remove reliance on classical DH alone. (Note: no primitive has a proof of “quantum resistance”; security is based on current cryptanalytic consensus and conservative parameterization.)

### T2 — Identity impersonation / key announcement rewriting

**Threat**: broker or MITM republishes a valid announcement under a different peer ID, or injects forged keys to impersonate a peer.

**Mitigations**:

- strict mode (default) requires `OperationalCertificate` verification under a pinned CA key
- key announcements are signed over a canonical payload with explicit domain separation and peer-id binding
- epoch/key-id checks provide anti-rollback and collision detection

### T3 — Replay and reordering

**Threat**: attacker replays a valid encrypted command; or induces out-of-order delivery (common in field networks).

**Mitigation**: per-peer sliding replay window (bitmap) rejects duplicates while tolerating bounded reordering.

### T4 — Cross-topic / cross-protocol confusion

**Threat**: attacker re-routes a valid encrypted blob into a different MQTT topic and changes the semantic meaning at the application layer.

**Mitigation**: encrypted MQTT packets are signed over a digest that binds `sender_id + topic + encrypted_blob` under an explicit domain tag.

### T5 — Parsing and allocation DoS

**Threat**: attacker forces large allocations or pathological parsing via oversized JSON/key announcements.

**Mitigation**: hard byte limits are enforced *before* parsing/crypto for key announcements, attestation messages, and encrypted packets.

### T6 — Audit log rewriting / truncation

**Threat**: attacker with filesystem write access rewrites the audit log and recomputes any unkeyed hash chain.

**Mitigation**: audit entries are hash-chained and can be additionally signed via a `SecurityProvider` signer (meaningful only when backed by non-exportable keys in TPM/HSM).

## Known Gaps (Critical for real IIoT deployments)

These are not paper cuts; they are architectural blockers for safety/security-critical systems:

- **Secure time / monotonic counters**: without a trusted monotonic source, time-window enforcement and replay state rollback are weak.
- **Post-compromise security (PCS)**: MQTT sessions provide forward secrecy, but there is no DH ratchet / periodic re-key to recover after compromise of a current chain key.
- **CoAP transport standardization**: session-based secure CoAP exists, but OSCORE/DTLS are not implemented; full CoAP option/method binding and standardized replay context are still missing.
- **Distributed policy under partitions**: CA-signed policy/revocation updates exist, but guaranteed catch-up semantics are not implemented; fleets must define TTL/fail-closed behavior under long partitions.

The concrete invariants enforced by the codebase are specified in `security/invariants.md`.
Loading
Loading