mirror of
https://github.com/zerotier/zssp.git
synced 2026-05-22 16:28:40 -07:00
Performance repo merged
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
perf*.data
|
||||
perf*.old
|
||||
*.svg
|
||||
Generated
+315
@@ -0,0 +1,315 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"digest",
|
||||
"ff",
|
||||
"generic-array",
|
||||
"group",
|
||||
"hkdf",
|
||||
"rand_core",
|
||||
"sec1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "pqc_kyber"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5dd33c0b42d244b01ab4f6cabaeb03c3b875017780fb3903b53b9c91fb6663"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||
|
||||
[[package]]
|
||||
name = "zssp"
|
||||
version = "0.0.3"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"hmac",
|
||||
"openssl-sys",
|
||||
"p384",
|
||||
"pqc_kyber",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
authors = ["ZeroTier, Inc. <contact@zerotier.com>", "Adam Ierymenko <adam.ierymenko@zerotier.com>", "Monica Moniot <monica.moniot@zerotier.com>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
name = "zssp"
|
||||
version = "0.0.3"
|
||||
|
||||
[lib]
|
||||
name = "zssp"
|
||||
path = "src/lib.rs"
|
||||
doc = true
|
||||
|
||||
[profile.bench]
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
rand_core = { version = "0.6.4" }
|
||||
zeroize = { version = "1.6.0" }
|
||||
arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] }
|
||||
|
||||
pqc_kyber = { version = "0.7.0", default-features = false, features = ["kyber1024", "std"], optional = true }
|
||||
p384 = { version = "0.13.0", default-features = false, features = ["ecdh"], optional = true }
|
||||
sha2 = { version = "0.10.7", default-features = false, optional = true }
|
||||
hmac = { version = "0.12.1", default-features = false, optional = true }
|
||||
openssl-sys = { version = "0.9.91", default-features = false, optional = true }
|
||||
|
||||
[features]
|
||||
default = ["debug", "default-crypto"]
|
||||
default-crypto = ["p384", "sha2", "pqc_kyber", "openssl-sys", "rand_core/getrandom"]
|
||||
sha2 = ["dep:sha2", "dep:hmac"]
|
||||
logging = []
|
||||
debug = ["logging"]
|
||||
@@ -0,0 +1,60 @@
|
||||
ZeroTier Secure Sessions Protocol
|
||||
======
|
||||
|
||||
## Introduction
|
||||
|
||||
ZeroTier Secure Socket Protocol (ZSSP) is a [Noise](http://noiseprotocol.org) protocol implementation using NIST/FIPS/CfSC compliant cryptographic primitives plus post-quantum forward secrecy via [Kyber1024](https://pq-crystals.org/kyber/). It also includes built-in support for fragmentation and defragmentation of large messages with strong resistance against denial of service attacks targeted against the fragmentation protocol.
|
||||
|
||||
Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the session. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secret key compromise.
|
||||
|
||||
Hybrid post-quantum forward secrecy using Kyber1024 is performed alongside Noise with the result being mixed in alongside an optional pre-shared key at the end of session negotiation.
|
||||
|
||||
ZSSP is designed for use in ZeroTier but is payload-agnostic and could easily be adapted for use in other projects.
|
||||
|
||||
Further information can be found in the ZSSP whitepaper (pending official release).
|
||||
|
||||
## Cryptographic Primitives Used
|
||||
|
||||
- **NIST P-384 ECDH**: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session
|
||||
- **Kyber1024**: Quantum attack resistant lattice-based key exchange during initial handshake
|
||||
- **SHA-512**: Used to construct KBKDF, also used in a proof of work and IP ownership DOS mitigation scheme
|
||||
- **KBKDF**: Key mixing, sub-key derivation
|
||||
- **AES-256**: 128-bit PRP for AES-256-GCM and for authenticated encryption of header to harden fragmentation against DOS (see section on header protection)
|
||||
- **AES-256-GCM**: Authenticated encryption
|
||||
|
||||
## Security Properties
|
||||
|
||||
| | Persistent ZSSP | Opportunistic ZSSP| WireGuard | ZeroTier Legacy Transport |
|
||||
| --- | --- | --- | --- | --- |
|
||||
|**Construction**|Noise\_XKhfs+psk2|Noise\_XKhfs+psk2|Noise\_IKpsk2|Static Diffie-Helman|
|
||||
|**Perfect Forward Secrecy**|Yes|Yes|Yes|No|
|
||||
|**Forward Secret Identity Hiding**|Yes|Yes|No|No|
|
||||
|**Quantum Forward Secret**|Yes|Yes|No|No|
|
||||
|**Ratcheted Forward Secrecy**|Yes|Yes|No|No|
|
||||
|**Silence is a Virtue**|Yes|No|Yes|No|
|
||||
|**Key-Compromise Impersonation**|Resistant|Resistant|Resistant|Vulnerable|
|
||||
|**Compromise-and-Impersonate**|Resistant|Detectable|Vulnerable|Vulnerable|
|
||||
|**Single Key-Compromise MitM**|Resistant|Resistant|Resistant|Vulnerable|
|
||||
|**Double Key-Compromise MitM**|Resistant|Detectable|Vulnerable|Vulnerable|
|
||||
|**DOS Mitigation**|Yes|Yes|Yes|No|
|
||||
|**Supports Fragmentation**|Yes|Yes|No|Yes|
|
||||
|**FIPS Compliant**|Yes|Yes|No|No|
|
||||
|**Small Code Footprint**|Yes|Yes|Yes|No|
|
||||
|**RTT**|2|2|1|1|
|
||||
|
||||
### Definitions
|
||||
|
||||
* **Construction**: The mathematical construction the protocol is based upon.
|
||||
* **Perfect Forward Secrecy**: An attacker with the static private keys of both party cannot decrypt recordings of messages sent between those parties.
|
||||
* **Forward Secret Identity Hiding**: An attacker with the static private key of one or more parties cannot determine the identity of everyone they have previously communicated with.
|
||||
* **Quantum Forward Secret**: A quantum computer powerful enough to break Elliptic-curve cryptography is not sufficient in order to decrypt recordings of messages sent between parties.
|
||||
* **Ratcheted Forward Secrecy**: In order to break forward secrecy an attacker must record and break every single key exchange two parties perform, in order, starting from the first time they began communicating. Improves secrecy under weak or compromised RNG.
|
||||
* **Silence is a Virtue**: A server running the protocol can be configured in such a way that it will not respond to an unauthenticated, anonymous or replayed message.
|
||||
* **Key-Compromise Impersonation**: The attacker has a memory image of a single party, and attempts to create a brand new session with that party, pretending to be someone else.
|
||||
* **Compromise-and-Impersonate**: The attacker has a memory image of a single party, and attempts to impersonate them on a brand new session with the other party.
|
||||
* **Single Key-Compromise MitM**: The attacker has a memory image of a single party, and attempts to become a Man-in-the-Middle between them and any other party.
|
||||
* **Double Key-Compromise MitM**: The attacker has a memory image of both parties, and attempts to become a Man-in-the-Middle between them.
|
||||
* **Supports Fragmentation**: Transmission data can be fragmented into smaller units to support jumbo-sized data or MTU discovery.
|
||||
* **FIPS Compliant**: The protocol uses FIPS approved cryptographic algorithms.
|
||||
* **Small Code Footprint**: The Codebase implementing the protocol can be easily audited by anyone on the internet.
|
||||
* **RTT**: "Round-Trip-Time" - How many round trips from initiator to responder it takes to establish a session.
|
||||
@@ -0,0 +1,406 @@
|
||||
use std::collections::HashMap;
|
||||
use std::iter::ExactSizeIterator;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rand_core::OsRng;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use zssp::application::{
|
||||
AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate,
|
||||
Settings, RATCHET_SIZE,
|
||||
};
|
||||
use zssp::crypto::P384KeyPair;
|
||||
use zssp::crypto_impl::*;
|
||||
use zssp::result::ReceiveError;
|
||||
use zssp::Session;
|
||||
|
||||
const TEST_MTU: usize = 1500;
|
||||
|
||||
struct TestApplication {
|
||||
time: Instant,
|
||||
name: &'static str,
|
||||
ratchets: Mutex<Ratchets>,
|
||||
}
|
||||
|
||||
struct Ratchets {
|
||||
rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>,
|
||||
peer_map: HashMap<u128, RatchetStates>,
|
||||
}
|
||||
impl Ratchets {
|
||||
fn new() -> Self {
|
||||
Self { rf_map: HashMap::new(), peer_map: HashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl CryptoLayer for TestApplication {
|
||||
const SETTINGS: Settings = Settings {
|
||||
initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS,
|
||||
rekey_timeout: 60 * 1000,
|
||||
rekey_after_time: 3000,
|
||||
rekey_time_max_jitter: 1000,
|
||||
rekey_after_key_uses: Settings::REKEY_AFTER_KEY_USES,
|
||||
resend_time: 250,
|
||||
fragment_assembly_timeout: Settings::FRAGMENT_ASSEMBLY_TIMEOUT_MS,
|
||||
};
|
||||
|
||||
type Rng = OsRng;
|
||||
type PrpEnc = OpenSSLAes256Enc;
|
||||
type PrpDec = OpenSSLAes256Dec;
|
||||
type Aead = OpenSSLAesGcm;
|
||||
type AeadPool = OpenSSLAesGcmPool;
|
||||
type Hash = CrateSha512;
|
||||
type Hmac = CrateHmacSha512;
|
||||
type PublicKey = CrateP384PublicKey;
|
||||
type KeyPair = CrateP384KeyPair;
|
||||
type Kem = CrateKyber1024PrivateKey;
|
||||
|
||||
type SessionData = u128;
|
||||
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl ApplicationLayer for &TestApplication {
|
||||
type Crypto = TestApplication;
|
||||
|
||||
fn incoming_session(&mut self) -> IncomingSessionAction {
|
||||
IncomingSessionAction::Challenge
|
||||
}
|
||||
|
||||
fn hello_requires_recognized_ratchet(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<TestApplication>>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn check_accept_session(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
identity: &[u8],
|
||||
) -> AcceptAction<TestApplication> {
|
||||
AcceptAction {
|
||||
session_data: Some(1),
|
||||
responder_disallows_downgrade: true,
|
||||
responder_silently_rejects: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result<Option<RatchetState>, ()> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned())
|
||||
}
|
||||
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &u128,
|
||||
) -> Result<Option<RatchetStates>, ()> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.peer_map.get(session_data).cloned())
|
||||
}
|
||||
|
||||
fn save_ratchet_state(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &u128,
|
||||
update_data: RatchetUpdate<'_>,
|
||||
) -> Result<(), ()> {
|
||||
let mut ratchets = self.ratchets.lock().unwrap();
|
||||
ratchets.peer_map.insert(*session_data, update_data.to_states());
|
||||
|
||||
if let Some(rf) = update_data.added_fingerprint() {
|
||||
ratchets.rf_map.insert(*rf, update_data.state1.clone());
|
||||
println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len);
|
||||
}
|
||||
if let Some(rf) = update_data.deleted_fingerprint1() {
|
||||
ratchets.rf_map.remove(rf);
|
||||
}
|
||||
if let Some(rf) = update_data.deleted_fingerprint2() {
|
||||
ratchets.rf_map.remove(rf);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn time(&mut self) -> i64 {
|
||||
self.time.elapsed().as_millis() as i64
|
||||
}
|
||||
|
||||
fn event_log(&mut self, event: zssp::LogEvent<TestApplication>) {
|
||||
println!(">[{}] {:?}", self.name, event);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn alice_main(
|
||||
run: &AtomicBool,
|
||||
packet_success_rate: u32,
|
||||
alice_app: &TestApplication,
|
||||
alice_out: mpsc::SyncSender<Vec<u8>>,
|
||||
alice_in: mpsc::Receiver<Vec<u8>>,
|
||||
recursive_out: mpsc::SyncSender<Vec<u8>>,
|
||||
alice_keypair: CrateP384KeyPair,
|
||||
bob_pubkey: CrateP384PublicKey,
|
||||
) {
|
||||
let startup_time = std::time::Instant::now();
|
||||
let context = zssp::Context::<TestApplication>::new(alice_keypair, OsRng);
|
||||
let mut next_service = startup_time.elapsed().as_millis() as i64 + 500;
|
||||
let test_data = [1u8; TEST_MTU * 10];
|
||||
let mut up = false;
|
||||
let mut alice_session = None;
|
||||
|
||||
while run.load(Ordering::Relaxed) {
|
||||
if alice_session.is_none() {
|
||||
up = false;
|
||||
alice_session = Some(
|
||||
context
|
||||
.open(
|
||||
alice_app,
|
||||
|b| alice_out.send(b.to_vec()).is_ok(),
|
||||
TEST_MTU,
|
||||
bob_pubkey.clone(),
|
||||
0,
|
||||
&[],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
println!("[alice] opening session");
|
||||
}
|
||||
let current_time = startup_time.elapsed().as_millis() as i64;
|
||||
loop {
|
||||
let pkt = alice_in.try_recv();
|
||||
if let Ok(pkt) = pkt {
|
||||
if OsRng.next_u32() <= packet_success_rate {
|
||||
use zssp::result::ReceiveOk::*;
|
||||
use zssp::result::SessionEvent::*;
|
||||
let mut output_data = Vec::new();
|
||||
match context.receive(
|
||||
alice_app,
|
||||
|b| alice_out.send(b.to_vec()).is_ok(),
|
||||
TEST_MTU,
|
||||
|_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)),
|
||||
&0,
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok(Unassociated) => {
|
||||
//println!("[alice] ok");
|
||||
}
|
||||
Ok(Session(_, event)) => match event {
|
||||
Established => {
|
||||
up = true;
|
||||
}
|
||||
Data => {
|
||||
assert!(!output_data.is_empty());
|
||||
//println!("[alice] received {}", data.len());
|
||||
}
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Err(e) => {
|
||||
println!("[alice] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
|
||||
assert!(!unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
//} else if OsRng.next_u32() | 1 > 0 {
|
||||
// let _ = recursive_out.send(pkt);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if up {
|
||||
context
|
||||
.send(
|
||||
alice_session.as_ref().unwrap(),
|
||||
|b| alice_out.send(b.to_vec()).is_ok(),
|
||||
&mut [0u8; TEST_MTU],
|
||||
&test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))],
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
// TODO: we need to more comprehensively test if re-opening the session works
|
||||
if OsRng.next_u32() <= ((u32::MAX as f64) * 0.0000005) as u32 {
|
||||
alice_session = None;
|
||||
}
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service = current_time
|
||||
+ context.service(alice_app, |_| {
|
||||
Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bob_main(
|
||||
run: &AtomicBool,
|
||||
packet_success_rate: u32,
|
||||
bob_app: &TestApplication,
|
||||
bob_out: mpsc::SyncSender<Vec<u8>>,
|
||||
bob_in: mpsc::Receiver<Vec<u8>>,
|
||||
recursive_out: mpsc::SyncSender<Vec<u8>>,
|
||||
bob_keypair: CrateP384KeyPair,
|
||||
) {
|
||||
let startup_time = std::time::Instant::now();
|
||||
let context = zssp::Context::<TestApplication>::new(bob_keypair, OsRng);
|
||||
let mut last_speed_metric = startup_time.elapsed().as_millis() as i64;
|
||||
let mut next_service = last_speed_metric + 500;
|
||||
let mut transferred = 0u64;
|
||||
|
||||
let mut bob_session = None;
|
||||
|
||||
while run.load(Ordering::Relaxed) {
|
||||
let pkt = bob_in.recv_timeout(Duration::from_millis(100));
|
||||
let current_time = startup_time.elapsed().as_millis() as i64;
|
||||
|
||||
if let Ok(pkt) = pkt {
|
||||
if OsRng.next_u32() <= packet_success_rate {
|
||||
use zssp::result::ReceiveOk::*;
|
||||
use zssp::result::SessionEvent::*;
|
||||
let mut output_data = Vec::new();
|
||||
match context.receive(
|
||||
bob_app,
|
||||
|b| bob_out.send(b.to_vec()).is_ok(),
|
||||
TEST_MTU,
|
||||
|_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)),
|
||||
&0,
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok(Unassociated) => {}
|
||||
Ok(Session(s, event)) => match event {
|
||||
NewSession | NewDowngradedSession => {
|
||||
println!("[bob] new session, took {}s", current_time as f32 / 1000.0);
|
||||
let _ = bob_session.replace(s);
|
||||
}
|
||||
Data => {
|
||||
assert!(!output_data.is_empty());
|
||||
//println!("[bob] received {}", output_data.len());
|
||||
transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back
|
||||
context
|
||||
.send(
|
||||
&s,
|
||||
|b| bob_out.send(b.to_vec()).is_ok(),
|
||||
&mut [0u8; TEST_MTU],
|
||||
&output_data,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Err(e) => {
|
||||
println!("[bob] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
|
||||
assert!(!unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
//} else if OsRng.next_u32() | 1 > 0 {
|
||||
// let _ = recursive_out.try_send(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
let speed_metric_elapsed = current_time - last_speed_metric;
|
||||
if speed_metric_elapsed >= 10000 {
|
||||
last_speed_metric = current_time;
|
||||
println!(
|
||||
"[bob] throughput: {} MiB/sec (combined input and output)",
|
||||
((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0)
|
||||
);
|
||||
transferred = 0;
|
||||
}
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service = current_time
|
||||
+ context.service(bob_app, |_| {
|
||||
Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn core(time: u64, packet_success_rate: u32) {
|
||||
let run = &AtomicBool::new(true);
|
||||
|
||||
let alice_keypair = CrateP384KeyPair::generate(&mut OsRng);
|
||||
let alice_app = TestApplication {
|
||||
time: Instant::now(),
|
||||
name: "alice",
|
||||
ratchets: Mutex::new(Ratchets::new()),
|
||||
};
|
||||
let bob_keypair = CrateP384KeyPair::generate(&mut OsRng);
|
||||
let bob_pubkey = bob_keypair.public_key();
|
||||
let bob_app = TestApplication {
|
||||
time: Instant::now(),
|
||||
name: "bob",
|
||||
ratchets: Mutex::new(Ratchets::new()),
|
||||
};
|
||||
|
||||
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(256);
|
||||
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(256);
|
||||
|
||||
thread::scope(|ts| {
|
||||
{
|
||||
let alice_out = alice_out.clone();
|
||||
let bob_out = bob_out.clone();
|
||||
ts.spawn(move || {
|
||||
alice_main(
|
||||
run,
|
||||
packet_success_rate,
|
||||
&alice_app,
|
||||
alice_out,
|
||||
alice_in,
|
||||
bob_out,
|
||||
alice_keypair,
|
||||
bob_pubkey,
|
||||
)
|
||||
});
|
||||
}
|
||||
ts.spawn(move || {
|
||||
bob_main(
|
||||
run,
|
||||
packet_success_rate,
|
||||
&bob_app,
|
||||
bob_out,
|
||||
bob_in,
|
||||
alice_out,
|
||||
bob_keypair,
|
||||
)
|
||||
});
|
||||
|
||||
thread::sleep(Duration::from_secs(time));
|
||||
|
||||
run.store(false, Ordering::SeqCst);
|
||||
println!("finished");
|
||||
});
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args();
|
||||
let packet_success_rate = if args.len() <= 1 {
|
||||
let default_success_rate = 1.0;
|
||||
((u32::MAX as f64) * default_success_rate) as u32
|
||||
} else {
|
||||
((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32
|
||||
};
|
||||
|
||||
core(60 * 60, packet_success_rate)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_main() {
|
||||
core(2, u32::MAX / 2)
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use rand_core::OsRng;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use zssp::application::{
|
||||
AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE,
|
||||
};
|
||||
use zssp::crypto::P384KeyPair;
|
||||
use zssp::crypto_impl::*;
|
||||
use zssp::result::ReceiveError;
|
||||
use zssp::Session;
|
||||
|
||||
const TEST_MTU: usize = 1500;
|
||||
|
||||
struct TestApplication {
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
/// We have to pool allocations or else variations in the speed of the memory allocator will bias
|
||||
/// our performance stats.
|
||||
struct PooledVec(Vec<u8>);
|
||||
static POOL: Mutex<Vec<Vec<u8>>> = Mutex::new(Vec::new());
|
||||
fn alloc(b: &[u8]) -> PooledVec {
|
||||
let mut p = POOL.lock().unwrap();
|
||||
let mut v = p.pop().unwrap_or_default();
|
||||
v.extend(b);
|
||||
PooledVec(v)
|
||||
}
|
||||
impl Drop for PooledVec {
|
||||
fn drop(&mut self) {
|
||||
let mut p = POOL.lock().unwrap();
|
||||
let mut v = Vec::new();
|
||||
std::mem::swap(&mut self.0, &mut v);
|
||||
v.clear();
|
||||
p.push(v);
|
||||
}
|
||||
}
|
||||
impl AsMut<[u8]> for PooledVec {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.0.as_mut()
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for PooledVec {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl DefaultCrypto for TestApplication {
|
||||
type SessionData = ();
|
||||
type IncomingPacketBuffer = PooledVec;
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl ApplicationLayer for &TestApplication {
|
||||
type Crypto = TestApplication;
|
||||
|
||||
fn incoming_session(&mut self) -> IncomingSessionAction {
|
||||
IncomingSessionAction::Allow
|
||||
}
|
||||
|
||||
fn hello_requires_recognized_ratchet(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<TestApplication>>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn check_accept_session(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
identity: &[u8],
|
||||
) -> AcceptAction<TestApplication> {
|
||||
AcceptAction {
|
||||
session_data: Some(()),
|
||||
responder_disallows_downgrade: true,
|
||||
responder_silently_rejects: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result<Option<RatchetState>, ()> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &(),
|
||||
) -> Result<Option<RatchetStates>, ()> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn save_ratchet_state(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &(),
|
||||
update_data: RatchetUpdate<'_>,
|
||||
) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn time(&mut self) -> i64 {
|
||||
self.time.elapsed().as_millis() as i64
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn alice_main(
|
||||
run: &AtomicBool,
|
||||
alice_app: &TestApplication,
|
||||
alice_out: mpsc::SyncSender<PooledVec>,
|
||||
alice_in: mpsc::Receiver<PooledVec>,
|
||||
alice_keypair: CrateP384KeyPair,
|
||||
bob_pubkey: CrateP384PublicKey,
|
||||
) {
|
||||
let startup_time = std::time::Instant::now();
|
||||
let context = zssp::Context::<TestApplication>::new(alice_keypair, OsRng);
|
||||
let mut next_service = startup_time.elapsed().as_millis() as i64 + 500;
|
||||
let test_data = [1u8; TEST_MTU * 10];
|
||||
let mut up = false;
|
||||
let mut output_data = ArrayVec::<u8, 15000>::new();
|
||||
|
||||
let alice_session = Some(
|
||||
context
|
||||
.open(
|
||||
alice_app,
|
||||
|b| alice_out.send(alloc(b)).is_ok(),
|
||||
TEST_MTU,
|
||||
bob_pubkey.clone(),
|
||||
(),
|
||||
&[],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
println!("[alice] opening session");
|
||||
while run.load(Ordering::Relaxed) {
|
||||
let current_time = startup_time.elapsed().as_millis() as i64;
|
||||
loop {
|
||||
let pkt = alice_in.try_recv();
|
||||
if let Ok(pkt) = pkt {
|
||||
use zssp::result::ReceiveOk::*;
|
||||
use zssp::result::SessionEvent::*;
|
||||
output_data.clear();
|
||||
match context.receive(
|
||||
alice_app,
|
||||
|b| alice_out.send(alloc(b)).is_ok(),
|
||||
TEST_MTU,
|
||||
|_| Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU)),
|
||||
&0,
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok(Unassociated) => {
|
||||
//println!("[alice] ok");
|
||||
}
|
||||
Ok(Session(_, event)) => match event {
|
||||
Established => {
|
||||
up = true;
|
||||
}
|
||||
Data => {
|
||||
assert!(!output_data.is_empty());
|
||||
//println!("[alice] received {}", data.len());
|
||||
}
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Err(e) => {
|
||||
println!("[alice] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
|
||||
assert!(!unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if up {
|
||||
context
|
||||
.send(
|
||||
alice_session.as_ref().unwrap(),
|
||||
|b| alice_out.send(alloc(b)).is_ok(),
|
||||
&mut [0u8; TEST_MTU],
|
||||
&test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))],
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service = current_time
|
||||
+ context.service(alice_app, |_| {
|
||||
Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bob_main(
|
||||
run: &AtomicBool,
|
||||
bob_app: &TestApplication,
|
||||
bob_out: mpsc::SyncSender<PooledVec>,
|
||||
bob_in: mpsc::Receiver<PooledVec>,
|
||||
bob_keypair: CrateP384KeyPair,
|
||||
) {
|
||||
let startup_time = std::time::Instant::now();
|
||||
let context = zssp::Context::<TestApplication>::new(bob_keypair, OsRng);
|
||||
let mut last_speed_metric = startup_time.elapsed().as_millis() as i64;
|
||||
let mut next_service = last_speed_metric + 500;
|
||||
let mut transferred = 0u64;
|
||||
let mut output_data = ArrayVec::<u8, 15000>::new();
|
||||
|
||||
let mut bob_session = None;
|
||||
|
||||
while run.load(Ordering::Relaxed) {
|
||||
let pkt = bob_in.recv_timeout(Duration::from_millis(100));
|
||||
let current_time = startup_time.elapsed().as_millis() as i64;
|
||||
|
||||
if let Ok(pkt) = pkt {
|
||||
use zssp::result::ReceiveOk::*;
|
||||
use zssp::result::SessionEvent::*;
|
||||
output_data.clear();
|
||||
match context.receive(
|
||||
bob_app,
|
||||
|b| bob_out.send(alloc(b)).is_ok(),
|
||||
TEST_MTU,
|
||||
|_| Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU)),
|
||||
&0,
|
||||
pkt,
|
||||
&mut output_data,
|
||||
) {
|
||||
Ok(Unassociated) => {}
|
||||
Ok(Session(s, event)) => match event {
|
||||
NewSession | NewDowngradedSession => {
|
||||
println!("[bob] new session, took {}s", current_time as f32 / 1000.0);
|
||||
let _ = bob_session.replace(s);
|
||||
}
|
||||
Data => {
|
||||
assert!(!output_data.is_empty());
|
||||
//println!("[bob] received {}", output_data.len());
|
||||
transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back
|
||||
context
|
||||
.send(
|
||||
&s,
|
||||
|b| bob_out.send(alloc(b)).is_ok(),
|
||||
&mut [0u8; TEST_MTU],
|
||||
&output_data,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Control => (),
|
||||
_ => panic!(),
|
||||
},
|
||||
Err(e) => {
|
||||
println!("[bob] ERROR {:?}", e);
|
||||
if let ReceiveError::ByzantineFault { unnatural, .. } = e {
|
||||
assert!(!unnatural)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let speed_metric_elapsed = current_time - last_speed_metric;
|
||||
if speed_metric_elapsed >= 10000 {
|
||||
last_speed_metric = current_time;
|
||||
println!(
|
||||
"[bob] throughput: {} MiB/sec (combined input and output)",
|
||||
((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0)
|
||||
);
|
||||
transferred = 0;
|
||||
}
|
||||
|
||||
if current_time >= next_service {
|
||||
next_service = current_time
|
||||
+ context.service(bob_app, |_| {
|
||||
Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn core(time: u64) {
|
||||
let run = &AtomicBool::new(true);
|
||||
|
||||
let alice_keypair = CrateP384KeyPair::generate(&mut OsRng);
|
||||
let alice_app = TestApplication { time: Instant::now() };
|
||||
let bob_keypair = CrateP384KeyPair::generate(&mut OsRng);
|
||||
let bob_pubkey = bob_keypair.public_key();
|
||||
let bob_app = TestApplication { time: Instant::now() };
|
||||
|
||||
let (alice_out, bob_in) = mpsc::sync_channel::<PooledVec>(256);
|
||||
let (bob_out, alice_in) = mpsc::sync_channel::<PooledVec>(256);
|
||||
|
||||
thread::scope(|ts| {
|
||||
{
|
||||
let alice_out = alice_out.clone();
|
||||
ts.spawn(move || alice_main(run, &alice_app, alice_out, alice_in, alice_keypair, bob_pubkey));
|
||||
}
|
||||
ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair));
|
||||
|
||||
thread::sleep(Duration::from_secs(time));
|
||||
|
||||
run.store(false, Ordering::SeqCst);
|
||||
println!("finished");
|
||||
});
|
||||
}
|
||||
|
||||
fn main() {
|
||||
core(20)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_main() {
|
||||
core(2)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
max_width = 120
|
||||
edition = "2021"
|
||||
newline_style = "Unix"
|
||||
struct_lit_width = 60
|
||||
tab_spaces = 4
|
||||
use_small_heuristics = "Default"
|
||||
single_line_if_else_max_width = 0
|
||||
use_try_shorthand = true
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
pub struct Window<const L: usize, const MAX: u64>([AtomicU64; L]);
|
||||
|
||||
impl<const L: usize, const MAX: u64> Window<L, MAX> {
|
||||
pub fn new() -> Self {
|
||||
Self(std::array::from_fn(|_| AtomicU64::new(0)))
|
||||
}
|
||||
/// Check the window without mutating state.
|
||||
pub fn check(&self, counter: u64) -> bool {
|
||||
let slot = &self.0[(counter as usize) % self.0.len()];
|
||||
let counter = counter.wrapping_add(1);
|
||||
let prev_counter = slot.load(Ordering::Relaxed);
|
||||
prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX
|
||||
}
|
||||
/// Update the window, returning true if the packet is still valid.
|
||||
/// This should only be called after the packet is authenticated.
|
||||
pub fn update(&self, counter: u64) -> bool {
|
||||
let slot = &self.0[(counter as usize) % self.0.len()];
|
||||
let counter = counter.wrapping_add(1);
|
||||
let prev_counter = slot.fetch_max(counter, Ordering::Relaxed);
|
||||
prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::crypto::*;
|
||||
use crate::zeta::Session;
|
||||
|
||||
pub use crate::proto::RATCHET_SIZE;
|
||||
pub use crate::ratchet_state::*;
|
||||
|
||||
/// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings.
|
||||
/// If the user wishes to measure time in units other than milliseconds for some reason, then they can
|
||||
/// create an adjusted version of this struct with those units, and use it instead of the default.
|
||||
pub struct Settings {
|
||||
/// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake
|
||||
/// was completed successfully. The handshake attempt will be assumed as failed and
|
||||
/// restarted if Bob does not respond by this cut-off.
|
||||
pub initial_offer_timeout: u64,
|
||||
/// Timeout for how long ZSSP should wait before expiring and closing a session when it has
|
||||
/// lingered in certain states for too long, primarily the rekeying states.
|
||||
/// If a remote peer does not send the correct information to rekey a session before this
|
||||
/// timeout then the session will close.
|
||||
pub rekey_timeout: u64,
|
||||
/// How long until rekeying should occur for each new session key.
|
||||
pub rekey_after_time: u64,
|
||||
/// Maximum random jitter to subtract from the rekey after time timer.
|
||||
/// Must be greater than 0.
|
||||
/// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder.
|
||||
pub rekey_time_max_jitter: u64,
|
||||
/// How many key uses may occur before the session starts attempting to rekey.
|
||||
/// The session will forceably close at 2^32 key uses so it is recommended this value be smaller.
|
||||
pub rekey_after_key_uses: u64,
|
||||
/// Retry interval for outgoing connection initiation or rekey attempts.
|
||||
///
|
||||
/// Retry attempts will be no more often than this, but the delay may end up being
|
||||
/// slightly more in some cases based on the rate of calls to `service`.
|
||||
pub resend_time: u64,
|
||||
/// How long fragments are allowed to linger in the defragmentation buffer before they are dropped.
|
||||
/// This implementation of a defrag buffer only bounds memory consumption based on this value.
|
||||
pub fragment_assembly_timeout: u64,
|
||||
}
|
||||
impl Settings {
|
||||
/// Default value for the `initial_offer_timeout`.
|
||||
/// The default value is 10 seconds in ms.
|
||||
pub const INITIAL_OFFER_TIMEOUT_MS: u64 = 10 * 1000;
|
||||
/// Default value for the `rekey_timeout`.
|
||||
/// The default value is 1 minute in ms.
|
||||
pub const REKEY_TIMEOUT_MS: u64 = 60 * 1000;
|
||||
/// Default value for the `rekey_after_time`.
|
||||
/// The default value is 1 hour in ms.
|
||||
pub const REKEY_AFTER_TIME_MS: u64 = 60 * 60 * 1000;
|
||||
/// Default value for the `rekey_time_max_jitter`.
|
||||
/// The default is 10 minutes in ms.
|
||||
pub const REKEY_AFTER_TIME_MAX_JITTER_MS: u64 = 10 * 60 * 1000;
|
||||
/// Default value for the `rekey_after_key_uses`.
|
||||
/// The default is 2^30.
|
||||
pub const REKEY_AFTER_KEY_USES: u64 = 1 << 30;
|
||||
/// Default value for the `resend_time`.
|
||||
/// The default is 1 second in ms.
|
||||
pub const RESEND_TIME: u64 = 1000;
|
||||
/// Default value for the `fragment_assembly_timeout`.
|
||||
/// The default is 5 seconds in ms.
|
||||
pub const FRAGMENT_ASSEMBLY_TIMEOUT_MS: u64 = 5 * 1000;
|
||||
/// Create an instance of Settings with all default values.
|
||||
/// These defaults are in units of milliseconds, so if these defaults are used, `App::time`
|
||||
/// must return timestamps in unts of milliseconds as well.
|
||||
pub const fn new_ms() -> Self {
|
||||
Self {
|
||||
initial_offer_timeout: Self::INITIAL_OFFER_TIMEOUT_MS,
|
||||
rekey_timeout: Self::REKEY_TIMEOUT_MS,
|
||||
rekey_after_time: Self::REKEY_AFTER_TIME_MS,
|
||||
rekey_time_max_jitter: Self::REKEY_AFTER_TIME_MAX_JITTER_MS,
|
||||
rekey_after_key_uses: Self::REKEY_AFTER_KEY_USES,
|
||||
resend_time: Self::RESEND_TIME,
|
||||
fragment_assembly_timeout: Self::FRAGMENT_ASSEMBLY_TIMEOUT_MS,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self::new_ms()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to implement to integrate the session into an application.
|
||||
///
|
||||
/// Templating the session on this trait lets the code here be almost entirely transport, OS,
|
||||
/// and use case independent.
|
||||
///
|
||||
/// The constants exposed in this trait can be redefined from their defaults to change rekey
|
||||
/// and negotiation timeout behavior. Both sides of a ZSSP session **must** have these constants
|
||||
/// set to the same values. Changing these constants is generally discouraged unless you know
|
||||
/// what you are doing.
|
||||
pub trait CryptoLayer: Sized {
|
||||
/// These are constants that can be redefined from their defaults to change rekey
|
||||
/// and negotiation timeout behavior. If two sides of a ZSSP session have different constants,
|
||||
/// the protocol will tend to default to the smaller constants.
|
||||
const SETTINGS: Settings = Settings::new_ms();
|
||||
|
||||
type Rng: CryptoRng + RngCore;
|
||||
|
||||
/// The implementation of AES-256 Encryption that ZSSP should use.
|
||||
///
|
||||
/// FIPS compliance requires use of a FIPS certified implementation.
|
||||
type PrpEnc: Aes256Enc;
|
||||
/// The implementation of AES-256 Decryption that ZSSP should use.
|
||||
///
|
||||
/// FIPS compliance requires use of a FIPS certified implementation.
|
||||
type PrpDec: Aes256Dec;
|
||||
|
||||
type Aead: LowThroughputAesGcm;
|
||||
type AeadPool: HighThroughputAesGcmPool;
|
||||
|
||||
/// The implementation of SHA-512 that ZSSP should use.
|
||||
///
|
||||
/// FIPS compliance requires use of a FIPS certified implementation.
|
||||
type Hash: Sha512Hash;
|
||||
type Hmac: Sha512Hmac;
|
||||
/// The implementation of P-384 public keys that ZSSP should use.
|
||||
///
|
||||
/// FIPS compliance requires a FIPS certified implementation.
|
||||
type PublicKey: P384PublicKey;
|
||||
/// The implementation of P-384 private keys that ZSSP should use.
|
||||
///
|
||||
/// FIPS compliance requires use of a FIPS certified implementation.
|
||||
type KeyPair: P384KeyPair<Self::Rng, PublicKey = Self::PublicKey>;
|
||||
/// The implementation of Kyber1024 that ZSSP should use.
|
||||
///
|
||||
/// No implementation of Kyber1024 can be FIPS certified, but this is not required
|
||||
/// for ZSSP to achieve FIPS compliance.
|
||||
type Kem: Kyber1024PrivateKey<Self::Rng>;
|
||||
|
||||
/// Type for arbitrary opaque object for use by the application that is attached to
|
||||
/// each session.
|
||||
type SessionData;
|
||||
|
||||
/// Data type for incoming packet buffers.
|
||||
///
|
||||
/// This can be something like `Vec<u8>` or `Box<[u8]>` or it can be something like a pooled
|
||||
/// reusable buffer that automatically returns to its pool when ZSSP is done with it. ZSSP may
|
||||
/// hold these for a short period of time when assembling fragmented packets on the receive
|
||||
/// path.
|
||||
type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>;
|
||||
}
|
||||
|
||||
pub trait ApplicationLayer: Sized {
|
||||
type Crypto: CryptoLayer;
|
||||
|
||||
/// Should return the current time in milliseconds. Does not have to be monotonic, nor synced
|
||||
/// with remote peers (although both of these properties would help reliability slightly).
|
||||
/// Used to determine if any current handshakes should be resent or timed-out, or if a session
|
||||
/// should rekey.
|
||||
fn time(&mut self) -> i64;
|
||||
|
||||
fn incoming_session(&mut self) -> IncomingSessionAction;
|
||||
/// This function will be called whenever Alice's initial Hello packet contains the empty ratchet
|
||||
/// fingerprint. Brand new peers will always connect to Bob with the empty ratchet, but from
|
||||
/// then on they should be using non-empty ratchet states.
|
||||
///
|
||||
/// If this returns false, we will attempt to connect to Alice with the empty ratchet state.
|
||||
/// If this returns true, Alice's connection will be silently dropped.
|
||||
/// If this function is configured to always return true, it means peers will not be able to
|
||||
/// connect to us unless they had a prior-established ratchet key with us. This is the best way
|
||||
/// for the paranoid to enforce a manual allow-list.
|
||||
fn hello_requires_recognized_ratchet(&mut self) -> bool;
|
||||
/// This function is called if we, as Alice, attempted to open a session with Bob using a
|
||||
/// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade
|
||||
/// to the zero ratchet key.
|
||||
///
|
||||
/// If it returns true Alice will downgrade their ratchet state to emtpy, potentially ending
|
||||
/// their current ratchet chain.
|
||||
/// If it returns false then we will consider Bob as having failed authentication, and this
|
||||
/// packet will be dropped. The session will continue attempting to connect to Bob.
|
||||
///
|
||||
/// This function must deterministically return either true or false for a given session.
|
||||
///
|
||||
/// It is a bad sign that Bob has somehow forgotten Alice's ratchet key, it either means at
|
||||
/// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has
|
||||
/// been compromised and is being impersonated. An attacker must at least have Bob's private
|
||||
/// static key to be able to ask Alice to downgrade.
|
||||
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<Self::Crypto>>) -> bool;
|
||||
/// Function to accept sessions after final negotiation.
|
||||
/// The second argument is the identity that the remote peer sent us. The application
|
||||
/// must verify this identity is associated with the remote peer's static key.
|
||||
/// To prevent desync, if this function specifies that we should connect, no other open session
|
||||
/// with the same remote peer must exist. Drop or call expire on any pre-existing sessions
|
||||
/// before returning.
|
||||
fn check_accept_session(
|
||||
&mut self,
|
||||
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
|
||||
identity: &[u8],
|
||||
) -> AcceptAction<Self::Crypto>;
|
||||
|
||||
/// Lookup a specific ratchet state based on its ratchet fingerprint.
|
||||
/// This function will be called whenever Alice attempts to connect to us with a non-empty
|
||||
/// ratchet fingerprint.
|
||||
///
|
||||
/// If a ratchet state with a matching fingerprint could not be found, this function should
|
||||
/// return `Ok(None)`.
|
||||
fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result<Option<RatchetState>, ()>;
|
||||
/// Lookup the specific ratchet states based on the identity of the peer being communicated with.
|
||||
/// This function will be called whenever Alice attempts to open a session, or Bob attempts
|
||||
/// to verify Alice's identity.
|
||||
///
|
||||
/// If the peer's ratchet states could not be could, this function should return
|
||||
/// `RatchetState::new_initial_states()`.
|
||||
///
|
||||
/// If a one-time-password has been pre-shared with this peer, `RatchetState::new_otp_states(...)`
|
||||
/// should be pre-saved to the storage backend as if it is a normal ratchet state.
|
||||
/// This is to ensure it can both be restored and eventually deleted when it is used.
|
||||
///
|
||||
/// This function is not responsible for deciding whether or not to connect to this remote peer.
|
||||
/// Filtering peers should be done by the caller to `Context::open` as well as by the
|
||||
/// function `ApplicationLayer::check_accept_session`.
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
|
||||
session_data: &<Self::Crypto as CryptoLayer>::SessionData,
|
||||
) -> Result<Option<RatchetStates>, ()>;
|
||||
/// Atomically commit the update specified by `update_data` to storage, or return an error if
|
||||
/// the update could not be made.
|
||||
/// The implementor is free to choose how to apply these updates to storage.
|
||||
///
|
||||
/// If this returns `Err(IoError)`, the packet which triggered this function to be called will be
|
||||
/// dropped, and no session state will be mutated, preserving synchronization. The remote peer
|
||||
/// will eventually resend that packet and so this function will be called again.
|
||||
///
|
||||
/// If persistent storage is supported, this function should not return until the ratchet state
|
||||
/// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local
|
||||
/// machine to put our ratchet state out of sync with the remote peer. If this happens the only
|
||||
/// fix is to reset both ratchet keys to empty.
|
||||
///
|
||||
/// This function may also save state to volatile storage, in which case all peers which connect
|
||||
/// to us will have to allow downgrade across the board.
|
||||
/// Otherwise, when we restart, we will not be allowed to reconnect.
|
||||
fn save_ratchet_state(
|
||||
&mut self,
|
||||
remote_static_key: &<Self::Crypto as CryptoLayer>::PublicKey,
|
||||
session_data: &<Self::Crypto as CryptoLayer>::SessionData,
|
||||
update_data: RatchetUpdate<'_>,
|
||||
) -> Result<(), ()>;
|
||||
|
||||
/// Receives a stream of events that occur during an execution of ZSSP.
|
||||
/// These are provided for debugging, logging or metrics purposes, and must be used for
|
||||
/// nothing else. Do not base protocol-level decisions upon the events passed to this function.
|
||||
#[cfg(feature = "logging")]
|
||||
#[allow(unused)]
|
||||
fn event_log(&mut self, event: crate::LogEvent<'_, Self::Crypto>) {}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum IncomingSessionAction {
|
||||
Allow,
|
||||
Challenge,
|
||||
Drop,
|
||||
}
|
||||
|
||||
/// A collection of fields specifying how to complete the key exchange with a specific remote peer,
|
||||
/// used by Bob, the responder, at the very last stage of the key exchange.
|
||||
///
|
||||
/// Corresponds to the *Accept* callback of Transition Algorithm 4.
|
||||
pub struct AcceptAction<Crypto: CryptoLayer> {
|
||||
/// The data object to be attached to the session if we successfully connect.
|
||||
/// If this field is None then we will not connect to this remote peer.
|
||||
pub session_data: Option<Crypto::SessionData>,
|
||||
/// Whether or not we will accept a connection with the remote peer when they do not have a
|
||||
/// ratchet key that we think they should have.
|
||||
pub responder_disallows_downgrade: bool,
|
||||
/// Whether or not to send an explicit rejection packet to the remote peer if we do not create
|
||||
/// a session with them.
|
||||
///
|
||||
/// This field will not be used if `session_data` is `Some` and the remote peer passes all other
|
||||
/// authentication checks.
|
||||
pub responder_silently_rejects: bool,
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use std::hash::Hasher;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use crate::antireplay::Window;
|
||||
use crate::crypto::*;
|
||||
use crate::proto::*;
|
||||
|
||||
pub struct ChallengeContext {
|
||||
counter: AtomicU64,
|
||||
antireplay_window: Window<CHALLENGE_COUNTER_WINDOW_MAX_OOO, { u64::MAX }>,
|
||||
salt: [u8; SALT_SIZE],
|
||||
}
|
||||
|
||||
/// Corresponds to Algorithm 11 found in Section 5.
|
||||
pub fn gen_null_response(rng: &mut impl RngCore) -> [u8; CHALLENGE_SIZE] {
|
||||
let mut response = [0u8; CHALLENGE_SIZE];
|
||||
response[POW_START..].copy_from_slice(&rng.next_u64().to_ne_bytes());
|
||||
response
|
||||
}
|
||||
/// Corresponds to Algorithm 13 found in Section 5.
|
||||
pub fn respond_to_challenge_in_place(
|
||||
rng: &mut impl RngCore,
|
||||
hash: &mut impl Sha512Hash,
|
||||
challenge: &[u8; CHALLENGE_SIZE],
|
||||
pre_response: &mut [u8; CHALLENGE_SIZE],
|
||||
) {
|
||||
if &challenge[POW_START..] == &pre_response[POW_START..] {
|
||||
pre_response.copy_from_slice(challenge);
|
||||
let mut pow = rng.next_u64();
|
||||
let mut work_buf = [0u8; SHA512_HASH_SIZE];
|
||||
loop {
|
||||
pre_response[POW_START..].copy_from_slice(&pow.to_ne_bytes());
|
||||
if verify_pow(hash, pre_response, &mut work_buf) {
|
||||
return;
|
||||
}
|
||||
pow = pow.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChallengeContext {
|
||||
pub fn new<Rng: RngCore + CryptoRng>(rng: &mut Rng) -> Self {
|
||||
let mut salt = [0u8; SALT_SIZE];
|
||||
rng.fill_bytes(&mut salt);
|
||||
Self {
|
||||
counter: AtomicU64::new(0),
|
||||
antireplay_window: Window::new(),
|
||||
salt,
|
||||
}
|
||||
}
|
||||
/// Corresponds to Algorithm 12 found in Section 5.
|
||||
pub fn process_hello(
|
||||
&self,
|
||||
hash: &mut impl Sha512Hash,
|
||||
addr: &impl std::hash::Hash,
|
||||
response: &[u8; CHALLENGE_SIZE],
|
||||
) -> Result<(), [u8; CHALLENGE_SIZE]> {
|
||||
let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap());
|
||||
let mut work_buf = [0u8; SHA512_HASH_SIZE];
|
||||
if self.antireplay_window.check(c)
|
||||
&& secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac(hash, c, addr))
|
||||
&& verify_pow(hash, response, &mut work_buf)
|
||||
{
|
||||
self.antireplay_window.update(c);
|
||||
Ok(())
|
||||
} else {
|
||||
let mut challenge = [0u8; CHALLENGE_SIZE];
|
||||
let d = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||
challenge[..COUNTER_SIZE].copy_from_slice(&d.to_be_bytes());
|
||||
challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac(hash, d, addr));
|
||||
challenge[POW_START..].copy_from_slice(&response[POW_START..]);
|
||||
Err(challenge)
|
||||
}
|
||||
}
|
||||
fn create_mac(&self, hash: &mut impl Sha512Hash, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] {
|
||||
let mut hasher = ShaHasher(hash);
|
||||
hasher.write(&c.to_be_bytes());
|
||||
addr.hash(&mut hasher);
|
||||
hasher.write(&self.salt);
|
||||
drop(hasher);
|
||||
|
||||
let mut mac = [0u8; SHA512_HASH_SIZE];
|
||||
hash.finish_and_reset(&mut mac);
|
||||
mac[..MAC_SIZE].try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trick rust into letting us use a hasher that returns more than 64 bits.
|
||||
struct ShaHasher<'a, ShaImpl: Sha512Hash>(&'a mut ShaImpl);
|
||||
impl<'a, ShaImpl: Sha512Hash> Hasher for ShaHasher<'a, ShaImpl> {
|
||||
fn finish(&self) -> u64 {
|
||||
unimplemented!()
|
||||
}
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
self.0.update(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the proof of work attached to the first message contains the correct number of leading
|
||||
/// zeros.
|
||||
fn verify_pow(hash: &mut impl Sha512Hash, response: &[u8], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool {
|
||||
hash.update(response);
|
||||
hash.finish_and_reset(work_buf);
|
||||
let n = u32::from_be_bytes(work_buf[..4].try_into().unwrap());
|
||||
n.leading_zeros() >= DIFFICULTY
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
pub const AES_256_KEY_SIZE: usize = 32;
|
||||
pub const AES_256_BLOCK_SIZE: usize = 16;
|
||||
pub const AES_GCM_TAG_SIZE: usize = 16;
|
||||
pub const AES_GCM_NONCE_SIZE: usize = 12;
|
||||
|
||||
/// A trait for encrypting individual blocks of plaintext using AES-256.
|
||||
/// It is used for header authentication, for which we have a standard model proof that our
|
||||
/// algorithm is secure.
|
||||
///
|
||||
/// Instances must securely delete their keys when dropped or reset.
|
||||
pub trait Aes256Enc: Sized + Send + Sync {
|
||||
fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self;
|
||||
|
||||
/// Change the encryption key to `key` so that all future encryption is performed with it.
|
||||
/// This function is very rarely called so it does not have to be particularly efficient.
|
||||
fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) {
|
||||
*self = Self::new(key);
|
||||
}
|
||||
|
||||
/// Decrypt the given `block` of plaintext directly using the AES block cipher
|
||||
/// (i.e. AES-256 in zero-padding ECB mode).
|
||||
/// The ciphertext should be written directly back out to `block`.
|
||||
fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]);
|
||||
}
|
||||
|
||||
/// A trait for decrypting individual blocks of plaintext using AES-256.
|
||||
///
|
||||
/// Instances must securely delete their keys when dropped or reset.
|
||||
pub trait Aes256Dec: Sized + Send + Sync {
|
||||
fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self;
|
||||
|
||||
/// Change the decryption key to `key` so that all future decryption is performed with it.
|
||||
/// This function is very rarely called so it does not have to be particularly efficient.
|
||||
fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) {
|
||||
*self = Self::new(key);
|
||||
}
|
||||
|
||||
/// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher
|
||||
/// (i.e. AES-256 in zero-padding ECB mode).
|
||||
/// The plaintext should be written directly back out to `block`.
|
||||
fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]);
|
||||
}
|
||||
|
||||
pub trait AesGcmEncContext {
|
||||
fn encrypt(&mut self, input: &[u8], output: &mut [u8]);
|
||||
|
||||
fn finish(self) -> [u8; AES_GCM_TAG_SIZE];
|
||||
}
|
||||
|
||||
pub trait AesGcmDecContext {
|
||||
fn decrypt_in_place(&mut self, data: &mut [u8]);
|
||||
|
||||
#[must_use]
|
||||
fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool;
|
||||
}
|
||||
|
||||
pub trait HighThroughputAesGcmPool: Send + Sync {
|
||||
type EncContext<'a>: AesGcmEncContext
|
||||
where
|
||||
Self: 'a;
|
||||
type DecContext<'a>: AesGcmDecContext
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self;
|
||||
|
||||
fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>;
|
||||
fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>;
|
||||
}
|
||||
|
||||
pub trait LowThroughputAesGcm {
|
||||
fn encrypt_in_place(
|
||||
key: &[u8; AES_256_KEY_SIZE],
|
||||
nonce: &[u8; AES_GCM_NONCE_SIZE],
|
||||
aad: &[u8],
|
||||
data: &mut [u8],
|
||||
) -> [u8; AES_GCM_TAG_SIZE];
|
||||
#[must_use]
|
||||
fn decrypt_in_place(
|
||||
key: &[u8; AES_256_KEY_SIZE],
|
||||
nonce: &[u8; AES_GCM_NONCE_SIZE],
|
||||
aad: &[u8],
|
||||
data: &mut [u8],
|
||||
tag: &[u8; AES_GCM_TAG_SIZE],
|
||||
) -> bool;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// The size of a Kyber1024 public key, which is 1568 bytes.
|
||||
pub const KYBER_PUBLIC_KEY_SIZE: usize = 1568;
|
||||
/// The size of a Kyber1024 KEM ciphertext, which is 1568 bytes.
|
||||
pub const KYBER_CIPHERTEXT_SIZE: usize = 1568;
|
||||
/// The size of a Kyber1024 KEM plaintext, which is 32 bytes.
|
||||
pub const KYBER_PLAINTEXT_SIZE: usize = 32;
|
||||
|
||||
/// Instances must securely delete the private key when dropped.
|
||||
pub trait Kyber1024PrivateKey<Rng: RngCore + CryptoRng>: Sized + Send + Sync {
|
||||
/// Generate a Kyber1024 private key and public key pair, and return the raw bytes of the public
|
||||
/// key.
|
||||
/// The private key will be temporarily held in memory but the public key will be immediately
|
||||
/// sent to the remote peer.
|
||||
///
|
||||
/// This function may use the provided RNG or its own, so long as the output is cryptographically random.
|
||||
fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]);
|
||||
/// Generate a Kyber1024 key encapsulation based on the given `public_key`, and return the
|
||||
/// raw bytes of the generated ciphertext and plaintext. The ciphertext is immediately sent to
|
||||
/// the remote peer and the plaintext is immediately hashed, both are quickly deleted.
|
||||
///
|
||||
/// This function may use the provided RNG or its own, so long as the output is cryptographically random.
|
||||
///
|
||||
/// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way
|
||||
/// according to the Kyber1024 spec.
|
||||
#[must_use]
|
||||
fn encapsulate(
|
||||
rng: &mut Rng,
|
||||
public_key: &[u8; KYBER_PUBLIC_KEY_SIZE],
|
||||
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
|
||||
) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]>;
|
||||
/// Decapsulate a Kyber1024 `ciphertext` received from the remote peer, retreiving
|
||||
/// the raw bytes of the original plaintext. This plaintext is immediately hashed and deleted.
|
||||
///
|
||||
/// **CRITICAL**: This must return `None` if the given `ciphertext` is invalid in any way
|
||||
/// according to the Kyber1024 spec.
|
||||
#[must_use]
|
||||
fn decapsulate(
|
||||
&self,
|
||||
ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE],
|
||||
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
|
||||
) -> bool;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
mod aes;
|
||||
pub use self::aes::*;
|
||||
|
||||
mod p384;
|
||||
pub use self::p384::*;
|
||||
|
||||
mod sha512;
|
||||
pub use sha512::*;
|
||||
|
||||
mod kyber1024;
|
||||
pub use kyber1024::*;
|
||||
|
||||
// We re-export our dependencies so it is less of a headache for the implementor to use the same
|
||||
// exact version of them.
|
||||
pub use arrayvec;
|
||||
pub use rand_core;
|
||||
pub use zeroize;
|
||||
|
||||
/// Constant time byte slice equality.
|
||||
pub fn secure_eq<A: AsRef<[u8]> + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool {
|
||||
let (a, b) = (a.as_ref(), b.as_ref());
|
||||
if a.len() == b.len() {
|
||||
let mut x = 0u8;
|
||||
for (aa, bb) in a.iter().zip(b.iter()) {
|
||||
x |= *aa ^ *bb;
|
||||
}
|
||||
x == 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// The size in bytes of a P-384 public key when in compressed SEC1-encoded format.
|
||||
pub const P384_PUBLIC_KEY_SIZE: usize = 49;
|
||||
/// The size in bytes of the raw output of ECDH between a P-384 public and private key.
|
||||
pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48;
|
||||
|
||||
/// A NIST P-384 ECDH/ECDSA public key.
|
||||
pub trait P384PublicKey: Sized + Send + Sync {
|
||||
/// Create a P-384 public key from raw bytes.
|
||||
///
|
||||
/// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P-384
|
||||
/// curve, or if it breaks the P-384 spec in any other way.
|
||||
fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option<Self>;
|
||||
|
||||
/// Get the raw bytes that uniquely define the public key.
|
||||
///
|
||||
/// This must output the compressed SEC1 NIST encoding of P-384 public keys.
|
||||
fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE];
|
||||
}
|
||||
|
||||
/// A NIST P-384 ECDH/ECDSA public/private key pair.
|
||||
///
|
||||
/// Instances must securely delete the private key when dropped.
|
||||
pub trait P384KeyPair<Rng: RngCore + CryptoRng> {
|
||||
/// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation.
|
||||
type PublicKey: P384PublicKey;
|
||||
/// Randomly generate a new P-384 keypair.
|
||||
///
|
||||
/// This function may use the provided RNG or its own, so long as the output is cryptographically random.
|
||||
fn generate(rng: &mut Rng) -> Self;
|
||||
|
||||
/// Get the raw bytes that uniquely define the public key.
|
||||
///
|
||||
/// This must output the compressed SEC1 NIST encoding of P-384 public keys.
|
||||
fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE];
|
||||
|
||||
/// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `ecdh_out`.
|
||||
///
|
||||
/// **CRITICAL**: This function must return `false` if key agreement between this private key and
|
||||
/// the input `public_key` key would result in an invalid, non-standard or predictable ECDH secret.
|
||||
/// Please refer to the NIST spec for P-384 ECDH key agreement, or better yet use a peer reviewed
|
||||
/// library that has already implemented this correctly.
|
||||
#[must_use]
|
||||
fn agree(&self, public_key: &Self::PublicKey, ecdh_out: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
pub const SHA512_HASH_SIZE: usize = 64;
|
||||
|
||||
/// A SHA-512 implementation.
|
||||
pub trait Sha512Hash {
|
||||
/// Create a new instance of SHA-512 for streaming data to.
|
||||
fn new() -> Self;
|
||||
/// Update the instance of SHA-512 with input `data`.
|
||||
/// This must update the state of SHA-512 as if `data` was appended to the previous input.
|
||||
fn update(&mut self, data: &[u8]);
|
||||
/// Finish streaming input and output the final hash.
|
||||
fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]);
|
||||
}
|
||||
|
||||
/// Opaque HMAC-SHA-512 implementation.
|
||||
/// Does not need to be threadsafe.
|
||||
pub trait Sha512Hmac {
|
||||
/// Allocate space on the stack or heap for repeated Hmac invocations.
|
||||
///
|
||||
/// Many FIPS compliant libraries, namely OpenSSL, require initializing an Hmac context on the
|
||||
/// heap before operating on it.
|
||||
/// If you are using a more sane library feel free to make this return an empty type.
|
||||
fn new() -> Self;
|
||||
/// Pure function for computing a single HMAC Hash. Repeat invocations of this function should
|
||||
/// have no effect on each other.
|
||||
fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::crypto::*;
|
||||
|
||||
/// A wrapper for a buffer the size of a pqc_kyber secret key.
|
||||
/// The crate `pqc_kyber` is low level and operates directly on buffers of bytes.
|
||||
pub type CrateKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>;
|
||||
impl<Rng: RngCore + CryptoRng> Kyber1024PrivateKey<Rng> for CrateKyber1024PrivateKey {
|
||||
fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) {
|
||||
// According to the source code this can only fail if the RNG fails.
|
||||
// Idk why rust allows RNG to fail.
|
||||
let keypair = pqc_kyber::keypair(rng).unwrap();
|
||||
(Zeroizing::new(keypair.secret), keypair.public)
|
||||
}
|
||||
|
||||
fn encapsulate(
|
||||
rng: &mut Rng,
|
||||
public_key: &[u8; KYBER_PUBLIC_KEY_SIZE],
|
||||
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
|
||||
) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]> {
|
||||
let ret;
|
||||
(ret, *plaintext_out) = pqc_kyber::encapsulate(public_key, rng).ok()?;
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
fn decapsulate(
|
||||
&self,
|
||||
ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE],
|
||||
plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE],
|
||||
) -> bool {
|
||||
if let Ok(result) = pqc_kyber::decapsulate(ciphertext, self.as_ref()) {
|
||||
*plaintext_out = result;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#[cfg(feature = "pqc_kyber")]
|
||||
mod kyber1024;
|
||||
#[cfg(feature = "pqc_kyber")]
|
||||
pub use kyber1024::*;
|
||||
#[cfg(feature = "pqc_kyber")]
|
||||
pub use pqc_kyber;
|
||||
|
||||
#[cfg(feature = "p384")]
|
||||
mod p384_impl;
|
||||
#[cfg(feature = "p384")]
|
||||
pub use p384;
|
||||
#[cfg(feature = "p384")]
|
||||
pub use p384_impl::*;
|
||||
|
||||
#[cfg(feature = "sha2")]
|
||||
mod sha512;
|
||||
#[cfg(feature = "sha2")]
|
||||
pub use hmac;
|
||||
#[cfg(feature = "sha2")]
|
||||
pub use sha2;
|
||||
#[cfg(feature = "sha2")]
|
||||
pub use sha512::*;
|
||||
|
||||
#[cfg(feature = "openssl-sys")]
|
||||
mod openssl;
|
||||
#[cfg(feature = "openssl-sys")]
|
||||
pub use openssl::*;
|
||||
#[cfg(feature = "openssl-sys")]
|
||||
pub use openssl_sys;
|
||||
|
||||
#[cfg(feature = "default-crypto")]
|
||||
pub trait DefaultCrypto {
|
||||
type SessionData;
|
||||
type IncomingPacketBuffer: AsMut<[u8]> + AsRef<[u8]>;
|
||||
}
|
||||
#[cfg(feature = "default-crypto")]
|
||||
impl<C: DefaultCrypto> crate::application::CryptoLayer for C {
|
||||
type Rng = rand_core::OsRng;
|
||||
type PrpEnc = OpenSSLAes256Enc;
|
||||
type PrpDec = OpenSSLAes256Dec;
|
||||
type Aead = OpenSSLAesGcm;
|
||||
type AeadPool = OpenSSLAesGcmPool;
|
||||
type Hash = CrateSha512;
|
||||
type Hmac = CrateHmacSha512;
|
||||
type PublicKey = CrateP384PublicKey;
|
||||
type KeyPair = CrateP384KeyPair;
|
||||
type Kem = CrateKyber1024PrivateKey;
|
||||
|
||||
type SessionData = C::SessionData;
|
||||
type IncomingPacketBuffer = C::IncomingPacketBuffer;
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
use std::{
|
||||
ptr::{self, NonNull},
|
||||
sync::{Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use openssl_sys::*;
|
||||
|
||||
use crate::crypto::*;
|
||||
|
||||
pub struct OpenSSLCtx(NonNull<openssl_sys::EVP_CIPHER_CTX>);
|
||||
impl Drop for OpenSSLCtx {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
EVP_CIPHER_CTX_free(self.0.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
impl OpenSSLCtx {
|
||||
/// Creates a new context.
|
||||
pub fn new() -> Option<Self> {
|
||||
unsafe { Some(OpenSSLCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) }
|
||||
}
|
||||
|
||||
pub unsafe fn cipher_init<const ENCRYPT: bool>(
|
||||
&self,
|
||||
t: *const openssl_sys::EVP_CIPHER,
|
||||
key: *const u8,
|
||||
iv: *const u8,
|
||||
) -> bool {
|
||||
let evp_f = if ENCRYPT {
|
||||
EVP_EncryptInit_ex
|
||||
} else {
|
||||
EVP_DecryptInit_ex
|
||||
};
|
||||
|
||||
// OpenSSL will usually leak a static amount of memory per cipher given here.
|
||||
evp_f(self.0.as_ptr(), t, ptr::null_mut(), key, iv) > 0
|
||||
}
|
||||
|
||||
pub unsafe fn update<const ENCRYPT: bool>(&self, input: &[u8], output: *mut u8) -> bool {
|
||||
let evp_f = if ENCRYPT {
|
||||
EVP_EncryptUpdate
|
||||
} else {
|
||||
EVP_DecryptUpdate
|
||||
};
|
||||
|
||||
let mut outlen = 0;
|
||||
|
||||
evp_f(
|
||||
self.0.as_ptr(),
|
||||
output,
|
||||
&mut outlen,
|
||||
input.as_ptr(),
|
||||
input.len() as c_int,
|
||||
) > 0
|
||||
}
|
||||
|
||||
pub unsafe fn finalize<const ENCRYPT: bool>(&self) -> bool {
|
||||
let evp_f = if ENCRYPT {
|
||||
EVP_EncryptFinal_ex
|
||||
} else {
|
||||
EVP_DecryptFinal_ex
|
||||
};
|
||||
let mut outl = 0;
|
||||
|
||||
evp_f(self.0.as_ptr(), ptr::null_mut(), &mut outl) > 0
|
||||
}
|
||||
|
||||
pub unsafe fn get_tag(&self, tag: &mut [u8]) -> bool {
|
||||
EVP_CIPHER_CTX_ctrl(
|
||||
self.0.as_ptr(),
|
||||
openssl_sys::EVP_CTRL_GCM_GET_TAG,
|
||||
tag.len() as c_int,
|
||||
tag.as_mut_ptr() as *mut _,
|
||||
) > 0
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub unsafe fn set_tag(&self, tag: &[u8]) -> bool {
|
||||
EVP_CIPHER_CTX_ctrl(
|
||||
self.0.as_ptr(),
|
||||
openssl_sys::EVP_CTRL_GCM_SET_TAG,
|
||||
tag.len() as c_int,
|
||||
tag.as_ptr() as *mut _,
|
||||
) > 0
|
||||
}
|
||||
pub fn as_ptr(&self) -> *mut openssl_sys::EVP_CIPHER_CTX {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenSSLAes256Enc(Mutex<OpenSSLCtx>);
|
||||
unsafe impl Send for OpenSSLAes256Enc {}
|
||||
unsafe impl Sync for OpenSSLAes256Enc {}
|
||||
|
||||
impl Aes256Enc for OpenSSLAes256Enc {
|
||||
fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self {
|
||||
let ctx = OpenSSLCtx::new().unwrap();
|
||||
unsafe {
|
||||
let t = openssl_sys::EVP_aes_256_ecb();
|
||||
assert!(ctx.cipher_init::<true>(t, key.as_ptr(), ptr::null()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
}
|
||||
Self(Mutex::new(ctx))
|
||||
}
|
||||
|
||||
fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) {
|
||||
let ctx = self.0.lock().unwrap();
|
||||
unsafe {
|
||||
let t = openssl_sys::EVP_aes_256_ecb();
|
||||
assert!(ctx.cipher_init::<true>(t, key.as_ptr(), ptr::null()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]) {
|
||||
let ptr = block.as_mut_ptr();
|
||||
let ctx = self.0.lock().unwrap();
|
||||
unsafe { assert!(ctx.update::<true>(block, ptr)) }
|
||||
}
|
||||
}
|
||||
pub struct OpenSSLAes256Dec(Mutex<OpenSSLCtx>);
|
||||
unsafe impl Send for OpenSSLAes256Dec {}
|
||||
unsafe impl Sync for OpenSSLAes256Dec {}
|
||||
|
||||
impl Aes256Dec for OpenSSLAes256Dec {
|
||||
fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self {
|
||||
let ctx = OpenSSLCtx::new().unwrap();
|
||||
unsafe {
|
||||
let t = openssl_sys::EVP_aes_256_ecb();
|
||||
assert!(ctx.cipher_init::<false>(t, key.as_ptr(), ptr::null()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
}
|
||||
Self(Mutex::new(ctx))
|
||||
}
|
||||
|
||||
fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) {
|
||||
let ctx = self.0.lock().unwrap();
|
||||
unsafe {
|
||||
let t = openssl_sys::EVP_aes_256_ecb();
|
||||
assert!(ctx.cipher_init::<false>(t, key.as_ptr(), ptr::null()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]) {
|
||||
let ptr = block.as_mut_ptr();
|
||||
let ctx = self.0.lock().unwrap();
|
||||
unsafe { assert!(ctx.update::<false>(block, ptr)) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenSSLAesGcmEnc<'a>(MutexGuard<'a, OpenSSLCtx>);
|
||||
impl<'a> AesGcmEncContext for OpenSSLAesGcmEnc<'a> {
|
||||
fn encrypt(&mut self, input: &[u8], output: &mut [u8]) {
|
||||
unsafe { assert!(self.0.update::<true>(input, output.as_mut_ptr())) };
|
||||
}
|
||||
|
||||
fn finish(self) -> [u8; AES_GCM_TAG_SIZE] {
|
||||
let mut output = [0u8; AES_GCM_TAG_SIZE];
|
||||
unsafe {
|
||||
assert!(self.0.finalize::<true>());
|
||||
assert!(self.0.get_tag(&mut output));
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenSSLAesGcmDec<'a>(MutexGuard<'a, OpenSSLCtx>);
|
||||
impl<'a> AesGcmDecContext for OpenSSLAesGcmDec<'a> {
|
||||
fn decrypt_in_place(&mut self, data: &mut [u8]) {
|
||||
let p = data.as_mut_ptr();
|
||||
unsafe { assert!(self.0.update::<false>(data, p)) };
|
||||
}
|
||||
|
||||
fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool {
|
||||
unsafe { self.0.set_tag(tag) && self.0.finalize::<false>() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenSSLAesGcmPool {
|
||||
enc: [Mutex<OpenSSLCtx>; 8],
|
||||
dec: [Mutex<OpenSSLCtx>; 8],
|
||||
}
|
||||
unsafe impl Send for OpenSSLAesGcmPool {}
|
||||
unsafe impl Sync for OpenSSLAesGcmPool {}
|
||||
|
||||
impl HighThroughputAesGcmPool for OpenSSLAesGcmPool {
|
||||
type EncContext<'a> = OpenSSLAesGcmEnc<'a>;
|
||||
|
||||
type DecContext<'a> = OpenSSLAesGcmDec<'a>;
|
||||
|
||||
fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self {
|
||||
unsafe {
|
||||
OpenSSLAesGcmPool {
|
||||
enc: std::array::from_fn(|_| {
|
||||
let ctx = OpenSSLCtx::new().unwrap();
|
||||
let t = openssl_sys::EVP_aes_256_gcm();
|
||||
assert!(ctx.cipher_init::<true>(t, encrypt_key.as_ptr(), ptr::null()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
Mutex::new(ctx)
|
||||
}),
|
||||
dec: std::array::from_fn(|_| {
|
||||
let ctx = OpenSSLCtx::new().unwrap();
|
||||
let t = openssl_sys::EVP_aes_256_gcm();
|
||||
assert!(ctx.cipher_init::<false>(t, decrypt_key.as_ptr(), ptr::null()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
Mutex::new(ctx)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc {
|
||||
let i = u64::from_be_bytes(nonce[4..].try_into().unwrap());
|
||||
let g = self.enc[(i as usize) % self.enc.len()].lock().unwrap();
|
||||
unsafe {
|
||||
assert!(g.cipher_init::<true>(ptr::null(), ptr::null(), nonce.as_ptr()));
|
||||
}
|
||||
OpenSSLAesGcmEnc(g)
|
||||
}
|
||||
|
||||
fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec {
|
||||
let i = u64::from_be_bytes(nonce[4..].try_into().unwrap());
|
||||
let g = self.dec[(i as usize) % self.enc.len()].lock().unwrap();
|
||||
unsafe {
|
||||
assert!(g.cipher_init::<false>(ptr::null(), ptr::null(), nonce.as_ptr()));
|
||||
}
|
||||
OpenSSLAesGcmDec(g)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenSSLAesGcm;
|
||||
impl LowThroughputAesGcm for OpenSSLAesGcm {
|
||||
fn encrypt_in_place(
|
||||
key: &[u8; AES_256_KEY_SIZE],
|
||||
nonce: &[u8; AES_GCM_NONCE_SIZE],
|
||||
aad: &[u8],
|
||||
data: &mut [u8],
|
||||
) -> [u8; AES_GCM_TAG_SIZE] {
|
||||
let mut output = [0u8; AES_GCM_TAG_SIZE];
|
||||
let ctx = OpenSSLCtx::new().unwrap();
|
||||
unsafe {
|
||||
let t = openssl_sys::EVP_aes_256_gcm();
|
||||
assert!(ctx.cipher_init::<true>(t, key.as_ptr(), nonce.as_ptr()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
|
||||
assert!(ctx.update::<true>(aad, ptr::null_mut()));
|
||||
let p = data.as_mut_ptr();
|
||||
assert!(ctx.update::<true>(data, p));
|
||||
|
||||
assert!(ctx.finalize::<true>());
|
||||
assert!(ctx.get_tag(&mut output));
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn decrypt_in_place(
|
||||
key: &[u8; AES_256_KEY_SIZE],
|
||||
nonce: &[u8; AES_GCM_NONCE_SIZE],
|
||||
aad: &[u8],
|
||||
data: &mut [u8],
|
||||
tag: &[u8; AES_GCM_TAG_SIZE],
|
||||
) -> bool {
|
||||
let ctx = OpenSSLCtx::new().unwrap();
|
||||
unsafe {
|
||||
let t = openssl_sys::EVP_aes_256_gcm();
|
||||
assert!(ctx.cipher_init::<false>(t, key.as_ptr(), nonce.as_ptr()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
|
||||
assert!(ctx.update::<false>(aad, ptr::null_mut()));
|
||||
let p = data.as_mut_ptr();
|
||||
assert!(ctx.update::<false>(data, p));
|
||||
|
||||
ctx.set_tag(tag) && ctx.finalize::<false>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn aes_128_ecb() {
|
||||
let key = [1u8; 16];
|
||||
let ctx = OpenSSLCtx::new().unwrap();
|
||||
unsafe {
|
||||
assert!(ctx.cipher_init::<true>(openssl_sys::EVP_aes_128_ecb(), key.as_ptr(), ptr::null()));
|
||||
openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0);
|
||||
assert_eq!(openssl_sys::EVP_CIPHER_CTX_get_block_size(ctx.as_ptr()) as usize, 16);
|
||||
|
||||
let origin = [2u8; 16];
|
||||
let mut val = origin;
|
||||
let p = val.as_mut_ptr();
|
||||
|
||||
assert!(ctx.update::<true>(&val, p));
|
||||
assert!(ctx.cipher_init::<false>(ptr::null(), key.as_ptr(), ptr::null()));
|
||||
assert!(ctx.update::<false>(&val, p));
|
||||
|
||||
assert_eq!(val, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use p384::{ecdh::EphemeralSecret, CompressedPoint, PublicKey};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use crate::crypto::*;
|
||||
|
||||
pub type CrateP384PublicKey = PublicKey;
|
||||
impl P384PublicKey for CrateP384PublicKey {
|
||||
fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option<Self> {
|
||||
PublicKey::from_sec1_bytes(raw_key).ok()
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE] {
|
||||
let k = CompressedPoint::from(self);
|
||||
k.as_slice().try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub type CrateP384KeyPair = EphemeralSecret;
|
||||
impl<Rng: RngCore + CryptoRng> P384KeyPair<Rng> for CrateP384KeyPair {
|
||||
type PublicKey = PublicKey;
|
||||
|
||||
fn generate(rng: &mut Rng) -> Self {
|
||||
EphemeralSecret::random(rng)
|
||||
}
|
||||
|
||||
fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE] {
|
||||
CompressedPoint::from(self.public_key()).as_slice().try_into().unwrap()
|
||||
}
|
||||
|
||||
fn agree(&self, public_key: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool {
|
||||
*output = self
|
||||
.diffie_hellman(public_key)
|
||||
.raw_secret_bytes()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use hmac::{Hmac, Mac};
|
||||
use sha2::{Digest, Sha512};
|
||||
|
||||
use crate::crypto::*;
|
||||
|
||||
pub type CrateSha512 = Sha512;
|
||||
impl Sha512Hash for CrateSha512 {
|
||||
fn new() -> Self {
|
||||
Digest::new()
|
||||
}
|
||||
|
||||
fn update(&mut self, data: &[u8]) {
|
||||
Digest::update(self, data)
|
||||
}
|
||||
|
||||
fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]) {
|
||||
let mut hasher = Digest::new();
|
||||
std::mem::swap(self, &mut hasher);
|
||||
*output = hasher.finalize().into();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CrateHmacSha512;
|
||||
impl Sha512Hmac for CrateHmacSha512 {
|
||||
fn new() -> Self {
|
||||
CrateHmacSha512
|
||||
}
|
||||
|
||||
fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]) {
|
||||
let mut hm = Hmac::<Sha512>::new_from_slice(key).unwrap();
|
||||
hm.update(full_input);
|
||||
*output = hm.finalize().into_bytes().into()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user