From 9aab0bf049d9dc74c4c2ff58b7497a3e28a63808 Mon Sep 17 00:00:00 2001 From: Oliver Braunsdorf Date: Mon, 25 Sep 2023 17:50:10 +0200 Subject: [PATCH] Initial async commit --- .devcontainer/Dockerfile | 8 + .devcontainer/devcontainer.json | 40 + .github/actions-rs/grcov.yml | 6 + .github/workflows/ci.yml | 63 ++ .github/workflows/coverage.yml | 49 + .github/workflows/dependency-audit.yml | 14 + .gitignore | 4 + .idea/async-idscp2.iml | 18 + .vscode/launch.json | 20 + Cargo.toml | 7 + Notes.md | 2 + README.md | 10 + idscp2_core/Cargo.toml | 35 + idscp2_core/benches/tokio_idscp_connection.rs | 298 ++++++ idscp2_core/build.rs | 24 + idscp2_core/src/api/idscp2_config.rs | 21 + idscp2_core/src/api/mod.rs | 1 + idscp2_core/src/chunkvec.rs | 214 ++++ idscp2_core/src/driver/daps_driver.rs | 38 + idscp2_core/src/driver/mod.rs | 2 + idscp2_core/src/driver/ra_driver.rs | 570 +++++++++++ idscp2_core/src/fsm_spec/fsm.rs | 853 ++++++++++++++++ idscp2_core/src/fsm_spec/fsm_tests.rs | 563 +++++++++++ idscp2_core/src/fsm_spec/mod.rs | 4 + idscp2_core/src/lib.rs | 759 ++++++++++++++ idscp2_core/src/messages/.gitignore | 1 + .../src/messages/idscp_message_factory.rs | 122 +++ .../src/messages/idscpv2_messages.proto | 85 ++ idscp2_core/src/messages/mod.rs | 2 + idscp2_core/src/tokio_idscp_connection.rs | 934 ++++++++++++++++++ idscp2_core/src/util.rs | 30 + .../resources/openssl/out/test_client.crt | 21 + 32 files changed, 4818 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/actions-rs/grcov.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 .github/workflows/dependency-audit.yml create mode 100644 .gitignore create mode 100644 .idea/async-idscp2.iml create mode 100644 .vscode/launch.json create mode 100644 Cargo.toml create mode 100644 Notes.md create mode 100644 README.md create mode 100644 idscp2_core/Cargo.toml create mode 100644 idscp2_core/benches/tokio_idscp_connection.rs create mode 100644 idscp2_core/build.rs create mode 100644 idscp2_core/src/api/idscp2_config.rs create mode 100644 idscp2_core/src/api/mod.rs create mode 100644 idscp2_core/src/chunkvec.rs create mode 100644 idscp2_core/src/driver/daps_driver.rs create mode 100644 idscp2_core/src/driver/mod.rs create mode 100644 idscp2_core/src/driver/ra_driver.rs create mode 100644 idscp2_core/src/fsm_spec/fsm.rs create mode 100644 idscp2_core/src/fsm_spec/fsm_tests.rs create mode 100644 idscp2_core/src/fsm_spec/mod.rs create mode 100644 idscp2_core/src/lib.rs create mode 100644 idscp2_core/src/messages/.gitignore create mode 100644 idscp2_core/src/messages/idscp_message_factory.rs create mode 100644 idscp2_core/src/messages/idscpv2_messages.proto create mode 100644 idscp2_core/src/messages/mod.rs create mode 100644 idscp2_core/src/tokio_idscp_connection.rs create mode 100644 idscp2_core/src/util.rs create mode 100644 test_pki/resources/openssl/out/test_client.crt diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..5e0bb64 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,8 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.166.1/containers/rust/.devcontainer/base.Dockerfile + +FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1 + +# [Optional] Uncomment this section to install additional packages. + RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + protobuf-compiler diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..0cf8154 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,40 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.166.1/containers/rust +{ + "name": "Rust", + "build": { + "dockerfile": "Dockerfile" + }, + "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], + + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "lldb.executable": "/usr/bin/lldb", + // VS Code don't watch files under ./target + "files.watcherExclude": { + "**/target/**": true + } + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "rust-lang.rust", + "bungcip.better-toml", + "vadimcn.vscode-lldb", + "mutantdino.resourcemonitor" + ], + + "mounts": [ + "source=${localEnv:HOME}/.cargo/registry,target=/home/vscode/.cargo/registry,type=bind,consistency=cached" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "rustc --version", + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/.github/actions-rs/grcov.yml b/.github/actions-rs/grcov.yml new file mode 100644 index 0000000..667033d --- /dev/null +++ b/.github/actions-rs/grcov.yml @@ -0,0 +1,6 @@ +ignore-not-existing: true +llvm: true +output-type: lcov +output-path: ./lcov.info +ignore-dir: +ignore: \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2d68aa1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: CI + +on: + push: + branches: [ master, jacobsen/fsm_allow_one_side_attested ] + pull_request: + branches: [ master ] + schedule: + - cron: '0 0 1 * *' # trigger monthly + +env: + CARGO_TERM_COLOR: always + +jobs: + Build_and_Test: + runs-on: self-hosted + strategy: + matrix: + # Run on latest ubuntu and macos using latest stable and nightly toolchain + os: [ubuntu-latest] #, macos-latest] + rust: + - stable + - nightly + - beta + - 1.63.0 # Minimum supported rust version + steps: + - uses: actions/checkout@v2 + + - name: Install Linux Dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update -yq && sudo apt-get install -y protobuf-compiler && protoc --version + + - name: Install macOS Dependencies + if: matrix.os == 'macos-latest' + run: brew update && brew install protobuf && protoc --version + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + components: clippy, rustfmt + + - uses: actions-rs/cargo@v1 + with: + command: build + args: --all-features --verbose + + - uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --verbose + + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features # -- -D warnings # TODO use "-D warnings" again when clippy issue is fixed: https://github.com/rust-lang/rust-clippy/issues/11179 + diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..e24b0af --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,49 @@ +on: [push, pull_request] + +name: Code Coverage + +jobs: + grcov: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + + - name: Install Linux Dependencies + run: sudo apt-get update -yq && sudo apt-get install -y protobuf-compiler && protoc --version + + - name: Run Tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --no-fail-fast + env: + CARGO_INCREMENTAL: '0' + RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' + RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' + + - name: Gather Coverage Data + id: coverage + uses: actions-rs/grcov@v0.1 + + - name: Coveralls Upload + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel: true + path-to-lcov: lcov.info + + grcov_finalize: + runs-on: ubuntu-latest + needs: grcov + steps: + - name: Coveralls finalization + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true \ No newline at end of file diff --git a/.github/workflows/dependency-audit.yml b/.github/workflows/dependency-audit.yml new file mode 100644 index 0000000..ea312f1 --- /dev/null +++ b/.github/workflows/dependency-audit.yml @@ -0,0 +1,14 @@ +name: Dependency audit +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff0838b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +.idea/workspace.xml +idea/ \ No newline at end of file diff --git a/.idea/async-idscp2.iml b/.idea/async-idscp2.iml new file mode 100644 index 0000000..468a753 --- /dev/null +++ b/.idea/async-idscp2.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8f7f6e3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'async-idscp2'", + "cargo": { + "args": [ + "test", + ], + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..20966f6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +members = [ + "idscp2_core", +] + +[profile.bench] +debug = true diff --git a/Notes.md b/Notes.md new file mode 100644 index 0000000..da51a09 --- /dev/null +++ b/Notes.md @@ -0,0 +1,2 @@ +Für FSM: evtl match guards verwenden für nicht-statische werte in Match Expressions: https://doc.rust-lang.org/rust-by-example/flow_control/match/guard.html +API design: an Rust Guidelines halten: https://rust-lang.github.io/api-guidelines/checklist.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..f12a0bb --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ + +![Tests](https://github.com/obraunsdorf/async-idscp2/actions/workflows/ci.yml/badge.svg?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/obraunsdorf/async-idscp2/badge.svg?branch=master)](https://coveralls.io/github/obraunsdorf/async-idscp2?branch=master) + +# Async-Idscp2 + +The asynchronous implementation of the IDSCP2 protocol in Rust. + +# Notes +Make sure the APIs follow the Rust Guidelines: https://rust-lang.github.io/api-guidelines/checklist.html diff --git a/idscp2_core/Cargo.toml b/idscp2_core/Cargo.toml new file mode 100644 index 0000000..fc2e280 --- /dev/null +++ b/idscp2_core/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "idscp2_core" +version = "0.1.0" +authors = [ + "Oliver Braunsdorf ", + "Leon Beckmann " +] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tinyvec = "1.6" +thiserror = "1.0" +log = "0.4" +protobuf = { version = "2.27.1", features = ["with-bytes"] } +bytes = "1.1" +futures = "0.3" +async-trait = "0.1.57" +tokio = {version = "1.17", features = ["full"] } +openssl = "0.10.41" + +[build-dependencies] +protoc-rust = "2.27.1" + +[dev-dependencies] +env_logger = "0.10.0" +tokio-test = "0.4.2" +rand = "^0.8.5" +criterion = "=0.4.0" +lazy_static = "1.4.0" + +[[bench]] +name = "tokio_idscp_connection" +harness = false \ No newline at end of file diff --git a/idscp2_core/benches/tokio_idscp_connection.rs b/idscp2_core/benches/tokio_idscp_connection.rs new file mode 100644 index 0000000..9e597a7 --- /dev/null +++ b/idscp2_core/benches/tokio_idscp_connection.rs @@ -0,0 +1,298 @@ +use bytes::{Bytes, BytesMut}; +use idscp2_core::tokio_idscp_connection::{AsyncIdscpConnection, AsyncIdscpListener}; +use rand::{thread_rng, Fill}; +use std::fs; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; + +use async_trait::async_trait; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use idscp2_core::api::idscp2_config::{AttestationConfig, IdscpConfig}; +use idscp2_core::driver::daps_driver::DapsDriver; +use idscp2_core::driver::ra_driver::{ + DriverId, RaDriver, RaDriverInstance, RaMessage, RaProverType, RaRegistry, RaVerifierType, +}; +use idscp2_core::Certificate; +use lazy_static::lazy_static; +use tokio_test::assert_ok; + +pub(crate) const DUMMY_ID: DriverId = "dummy"; + +pub(crate) struct DummyVerifier {} + +impl RaDriver for DummyVerifier { + fn new_instance( + &self, + _cert: &Certificate, + ) -> Box<(dyn RaDriverInstance + 'static)> { + Box::new(DummyVerifierInstance { + state: DummyState::Idle, + }) + } + + fn get_id(&self) -> DriverId { + DUMMY_ID + } +} + +pub(crate) struct DummyVerifierInstance { + state: DummyState, +} + +pub(crate) enum DummyState { + Idle, + Ok, +} + +#[async_trait] +impl RaDriverInstance for DummyVerifierInstance { + fn get_id(&self) -> DriverId { + DUMMY_ID + } + + fn begin_attestation(&mut self) { + self.state = DummyState::Idle; + } + + fn send_msg(&mut self, _msg: Bytes) {} + + async fn recv_msg(&mut self) -> RaMessage { + loop { + match &self.state { + DummyState::Ok => { + self.state = DummyState::Idle; + return RaMessage::Ok(Bytes::new()); + } + _ => { + tokio::task::yield_now().await; + } + } + } + } +} + +pub(crate) struct DummyProver {} + +impl RaDriver for DummyProver { + fn new_instance( + &self, + _cert: &Certificate, + ) -> Box<(dyn RaDriverInstance + 'static)> { + Box::new(DummyProverInstance { + state: DummyState::Idle, + }) + } + + fn get_id(&self) -> DriverId { + DUMMY_ID + } +} + +pub(crate) struct DummyProverInstance { + state: DummyState, +} + +#[async_trait] +impl RaDriverInstance for DummyProverInstance { + fn get_id(&self) -> DriverId { + DUMMY_ID + } + + fn begin_attestation(&mut self) { + self.state = DummyState::Ok; + } + + fn send_msg(&mut self, _msg: Bytes) {} + + async fn recv_msg(&mut self) -> RaMessage { + loop { + match &self.state { + DummyState::Ok => { + self.state = DummyState::Idle; + return RaMessage::Ok(Bytes::new()); + } + _ => { + tokio::task::yield_now().await; + } + } + } + } +} + +pub(crate) fn get_test_cert() -> Certificate { + let local_client_cert = fs::read(PathBuf::from(format!( + "{}/../test_pki/resources/openssl/out/{}", + env!("CARGO_MANIFEST_DIR"), + "test_client.crt" + ))) + .unwrap(); + Certificate::from_pem(&local_client_cert).unwrap() +} + +lazy_static! { + pub(crate) static ref TEST_RA_VERIFIER_REGISTRY: RaRegistry = { + let mut registry = RaRegistry::new(); + let _ = registry.register_driver(Arc::new(DummyVerifier {})); + registry + }; + pub(crate) static ref TEST_RA_PROVER_REGISTRY: RaRegistry = { + let mut registry = RaRegistry::new(); + let _ = registry.register_driver(Arc::new(DummyProver {})); + registry + }; + pub(crate) static ref TEST_RA_CONFIG: AttestationConfig<'static> = AttestationConfig { + supported_provers: vec![DUMMY_ID], + expected_verifiers: vec![DUMMY_ID], + prover_registry: &TEST_RA_PROVER_REGISTRY, + ra_timeout: Duration::from_secs(20), + verifier_registry: &TEST_RA_VERIFIER_REGISTRY, + peer_cert: get_test_cert(), + }; + pub(crate) static ref TEST_CONFIG_ALICE: IdscpConfig<'static> = IdscpConfig { + id: "alice", + resend_timeout: Duration::from_secs(1), + ra_config: &TEST_RA_CONFIG, + }; + pub(crate) static ref TEST_CONFIG_BOB: IdscpConfig<'static> = IdscpConfig { + id: "bob", + resend_timeout: Duration::from_secs(1), + ra_config: &TEST_RA_CONFIG, + }; +} + +pub(crate) struct DummyDaps { + is_valid: bool, + timeout: Duration, +} + +impl Default for DummyDaps { + fn default() -> Self { + Self::with_timeout(Duration::from_secs(100000)) // practically forever + } +} + +impl DummyDaps { + pub fn with_timeout(timeout: Duration) -> Self { + Self { + is_valid: false, + timeout, + } + } +} + +impl DapsDriver for DummyDaps { + fn is_valid(&self) -> bool { + self.is_valid + } + + fn get_token(&self) -> String { + "valid".to_string() + } + + fn verify_token(&mut self, token_bytes: &[u8]) -> Option { + let token = String::from_utf8_lossy(token_bytes); + if token.eq("valid") { + self.is_valid = true; + Some(self.timeout) + } else { + None + } + } + + fn invalidate(&mut self) { + self.is_valid = false; + } +} + +fn random_data(size: usize) -> Vec { + let mut rng = thread_rng(); + + let mut data = vec![0u8; size]; + data.try_fill(&mut rng).unwrap(); + data +} + +async fn spawn_connection<'a>( + daps_driver_1: &'a mut dyn DapsDriver, + daps_driver_2: &'a mut dyn DapsDriver, + config_1: &'a IdscpConfig<'a>, + config_2: &'a IdscpConfig<'a>, +) -> (AsyncIdscpConnection<'a>, AsyncIdscpConnection<'a>) { + const ADDRESS: &str = "127.0.0.1:8080"; + let listener = AsyncIdscpListener::bind(ADDRESS).await.unwrap(); + let (connect_result, accept_result) = tokio::join!( + AsyncIdscpConnection::connect(ADDRESS, daps_driver_1, config_1), + listener.accept(daps_driver_2, config_2), + ); + + assert_ok!(&connect_result); + assert_ok!(&accept_result); + + (connect_result.unwrap(), accept_result.unwrap()) +} + +async fn transfer( + peer1: &mut AsyncIdscpConnection<'_>, + peer2: &mut AsyncIdscpConnection<'_>, + mut data: BytesMut, + chunk_size: usize, +) -> Result { + let mut cmp_data = data.clone(); + + tokio::try_join!( + async { + while !cmp_data.is_empty() { + let msg = peer2.recv(None).await.unwrap(); + assert_eq!(msg.len(), chunk_size); + let cmp_msg = cmp_data.split_to(chunk_size); + assert_eq!(std::convert::AsRef::as_ref(&msg), cmp_msg.as_ref()); + } + Ok::<(), std::io::Error>(()) + }, + async { + while !data.is_empty() { + let msg = data.split_to(chunk_size); + let n = peer1.send(msg.freeze(), None).await?; + assert!(n == chunk_size); + } + Ok::<(), std::io::Error>(()) + }, + )?; + + Ok(data.is_empty() && cmp_data.is_empty()) +} + +fn bench_transfer_size_1000(c: &mut Criterion) { + const TRANSMISSION_SIZE: usize = 200_000; + const FIXED_CHUNK_SIZE: usize = 1000; + + let data = BytesMut::from(random_data(TRANSMISSION_SIZE).as_slice()); + + c.bench_function("transfer_size_1000", |b| { + b.iter(|| { + tokio_test::block_on(async { + let mut daps_driver_1 = DummyDaps::default(); + let mut daps_driver_2 = DummyDaps::default(); + let (mut peer1, mut peer2) = spawn_connection( + &mut daps_driver_1, + &mut daps_driver_2, + &TEST_CONFIG_ALICE, + &TEST_CONFIG_BOB, + ) + .await; + transfer( + &mut peer1, + &mut peer2, + black_box(data.clone()), + FIXED_CHUNK_SIZE, + ) + .await + .unwrap() + }) + }) + }); +} + +criterion_group!(benches, bench_transfer_size_1000); +criterion_main!(benches); diff --git a/idscp2_core/build.rs b/idscp2_core/build.rs new file mode 100644 index 0000000..9aabcff --- /dev/null +++ b/idscp2_core/build.rs @@ -0,0 +1,24 @@ +use protoc_rust::Customize; +use std::path::Path; + +const PROTO_FILE_PATHS: &str = "src/messages/idscpv2_messages.proto"; +// const PROTO_INCLUDE_PATH: &Path = """; +const GEN_OUT_DIR: &str = "src/messages"; + +fn main() { + println!("cargo:rerun-if-changed=src/messages/idscpv2_messages.proto"); + std::fs::create_dir_all(GEN_OUT_DIR) + .unwrap_or_else(|_| panic!("could not create or find directory {}", GEN_OUT_DIR)); + + protoc_rust::Codegen::new() + .out_dir(GEN_OUT_DIR) + .input(Path::new(PROTO_FILE_PATHS)) + //.includes(PROTO_INCLUDE_PATH) + .customize(Customize { + carllerche_bytes_for_bytes: Some(true), + carllerche_bytes_for_string: Some(false), + ..Default::default() + }) + .run() + .expect("protoc"); +} diff --git a/idscp2_core/src/api/idscp2_config.rs b/idscp2_core/src/api/idscp2_config.rs new file mode 100644 index 0000000..72f80ce --- /dev/null +++ b/idscp2_core/src/api/idscp2_config.rs @@ -0,0 +1,21 @@ +use crate::driver::ra_driver::{DriverId, RaRegistry}; +use crate::{RaProverType, RaVerifierType}; +use openssl::x509::X509; +use std::time::Duration; + +pub struct IdscpConfig<'a> { + /// for logging purposes only + pub id: &'static str, + pub resend_timeout: Duration, + pub ra_config: &'a AttestationConfig<'a>, +} + +pub struct AttestationConfig<'a> { + // TODO integrate this with IDSCP2Configuration? + pub ra_timeout: Duration, + pub supported_provers: Vec, + pub expected_verifiers: Vec, + pub prover_registry: &'a RaRegistry, + pub verifier_registry: &'a RaRegistry, + pub peer_cert: X509, +} diff --git a/idscp2_core/src/api/mod.rs b/idscp2_core/src/api/mod.rs new file mode 100644 index 0000000..779db87 --- /dev/null +++ b/idscp2_core/src/api/mod.rs @@ -0,0 +1 @@ +pub mod idscp2_config; diff --git a/idscp2_core/src/chunkvec.rs b/idscp2_core/src/chunkvec.rs new file mode 100644 index 0000000..13dd394 --- /dev/null +++ b/idscp2_core/src/chunkvec.rs @@ -0,0 +1,214 @@ +/* +This has been copied from https://github.com/ctz/rustls/blob/main/rustls/src/vecbuf.rs + +I did not find sufficient alternative crates, possible candidates i looked at were: + - arraydeque (no chunked access) + - slicedeque (no chunked access, no fixed-size allocation) + - std::VecDeque (no chunked access, no fixed-size allocation) + - bytes (?) + + However, the ChunkVecBuffer also is no fixed-size allocation. +*/ + +use bytes::Buf; +use std::cmp; +use std::collections::VecDeque; +use std::io; +use std::io::Read; +use std::ops::Deref; + +/// This is a byte buffer that is built from a vector +/// of byte vectors. This avoids extra copies when +/// appending a new byte vector, at the expense of +/// more complexity when reading out. +#[allow(dead_code)] +pub struct ChunkVecBuffer { + chunks: VecDeque, + limit: usize, +} + +impl> ChunkVecBuffer { + pub fn new() -> ChunkVecBuffer { + ChunkVecBuffer { + chunks: VecDeque::new(), + limit: 0, + } + } + + /// Sets the upper limit on how many bytes this + /// object can store. + /// + /// Setting a lower limit than the currently stored + /// data is not an error. + /// + /// A zero limit is interpreted as no limit. + #[allow(dead_code)] + pub fn set_limit(&mut self, new_limit: usize) { + self.limit = new_limit; + } + + /// If we're empty + pub fn is_empty(&self) -> bool { + self.chunks.is_empty() + } + + /// How many bytes we're storing + pub fn len(&self) -> usize { + let mut len = 0; + for ch in &self.chunks { + len += ch.len(); + } + len + } + + /// For a proposed append of `len` bytes, how many + /// bytes should we actually append to adhere to the + /// currently set `limit`? + #[allow(dead_code)] + pub fn apply_limit(&self, len: usize) -> usize { + if self.limit == 0 { + len + } else { + let space = self.limit.saturating_sub(self.len()); + cmp::min(len, space) + } + } + + /// Take one of the chunks from this object. This + /// function panics if the object `is_empty`. + #[inline] + pub fn pop(&mut self) -> Option { + self.chunks.pop_front() + } +} + +impl ChunkVecBuffer> { + /// Append a copy of `bytes`, perhaps a prefix if + /// we're near the limit. + #[allow(dead_code)] + pub fn append_limited_copy(&mut self, bytes: &[u8]) -> usize { + let take = self.apply_limit(bytes.len()); + self.append(bytes[..take].to_vec()); + take + } + + /// Read data out of this object, writing it into `buf` + /// and returning how many bytes were written there. + #[allow(dead_code)] + pub fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut offs = 0; + + while offs < buf.len() && !self.is_empty() { + let used = self.chunks[0].as_slice().read(&mut buf[offs..])?; + + self.consume(used); + offs += used; + } + + Ok(offs) + } + + fn consume(&mut self, mut used: usize) { + while let Some(mut buf) = self.pop() { + if used < buf.len() { + self.insert_front(buf.split_off(used)); + break; + } else { + used -= buf.len(); + } + } + } + + /// Read data out of this object, passing it `wr` + #[allow(dead_code)] + pub fn write_to(&mut self, wr: &mut dyn io::Write) -> io::Result { + if self.is_empty() { + return Ok(0); + } + + let mut bufs = [io::IoSlice::new(&[]); 64]; + for (iov, chunk) in bufs.iter_mut().zip(self.chunks.iter()) { + *iov = io::IoSlice::new(chunk); + } + let len = cmp::min(bufs.len(), self.chunks.len()); + let used = wr.write_vectored(&bufs[..len])?; + self.consume(used); + Ok(used) + } +} + +impl> ChunkVecBuffer { + /// Take and append the given `bytes`. + pub fn append(&mut self, bytes: T) -> usize { + let len = bytes.len(); + + if !bytes.is_empty() { + self.chunks.push_back(bytes); + } + + len + } + + /// Take and insert the given `bytes` at the start of the queue. + pub fn insert_front(&mut self, bytes: T) -> usize { + let len = bytes.len(); + + if !bytes.is_empty() { + self.chunks.push_front(bytes); + } + + len + } +} + +impl + Buf> Buf for ChunkVecBuffer { + fn remaining(&self) -> usize { + self.len() + } + + fn chunk(&self) -> &[u8] { + self.chunks + .front() + .map(|x| x.deref()) + .unwrap_or_else(|| [0u8; 0].as_slice()) + } + + fn advance(&mut self, mut cnt: usize) { + while cnt > 0 { + let len = self.chunks.front().unwrap().len(); + if len < cnt { + self.chunks.pop_front(); + cnt -= len; + } else { + let front = self.chunks.front_mut().unwrap(); + front.advance(cnt); + break; + } + } + } +} + +impl> Default for ChunkVecBuffer { + fn default() -> Self { + ChunkVecBuffer::new() + } +} + +#[cfg(test)] +mod test { + use super::ChunkVecBuffer; + + #[test] + fn short_append_copy_with_limit() { + let mut cvb = ChunkVecBuffer::new(); + cvb.set_limit(12); + assert_eq!(cvb.append_limited_copy(b"hello"), 5); + assert_eq!(cvb.append_limited_copy(b"world"), 5); + assert_eq!(cvb.append_limited_copy(b"hello"), 2); + assert_eq!(cvb.append_limited_copy(b"world"), 0); + + let mut buf = [0u8; 12]; + assert_eq!(cvb.read(&mut buf).unwrap(), 12); + assert_eq!(buf.to_vec(), b"helloworldhe".to_vec()); + } +} diff --git a/idscp2_core/src/driver/daps_driver.rs b/idscp2_core/src/driver/daps_driver.rs new file mode 100644 index 0000000..7f5ea6f --- /dev/null +++ b/idscp2_core/src/driver/daps_driver.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2020, Fraunhofer AISEC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// public IDSCP2 API +// Daps Driver for accessing and verifying DynamicAttributeToken + +use std::time::Duration; + +pub trait DapsDriver { + //type for security requirement validation in the verify token method + //toDo type SecurityReq; + + fn is_valid(&self) -> bool; + + //get token as string + fn get_token(&self) -> String; + + //verify token and receive validity_period in seconds in an Option + //None if token is not valid + fn verify_token( + &mut self, + token: &[u8], + //toDo security_requirements: Option, + ) -> Option; //TODO return std::Duration + + fn invalidate(&mut self); +} diff --git a/idscp2_core/src/driver/mod.rs b/idscp2_core/src/driver/mod.rs new file mode 100644 index 0000000..824f635 --- /dev/null +++ b/idscp2_core/src/driver/mod.rs @@ -0,0 +1,2 @@ +pub mod daps_driver; +pub mod ra_driver; diff --git a/idscp2_core/src/driver/ra_driver.rs b/idscp2_core/src/driver/ra_driver.rs new file mode 100644 index 0000000..f4257a0 --- /dev/null +++ b/idscp2_core/src/driver/ra_driver.rs @@ -0,0 +1,570 @@ +use crate::Certificate; +use async_trait::async_trait; +use bytes::Bytes; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::sync::Arc; +use thiserror::Error; + +trait RaType {} +#[derive(Debug, PartialEq, Eq)] +pub struct RaProverType {} +impl RaType for RaProverType {} +#[derive(Debug, PartialEq, Eq)] +pub struct RaVerifierType {} +impl RaType for RaVerifierType {} + +#[derive(Debug)] +pub enum RaMessage { + Ok(Bytes), + Failed(), + RawData(Bytes, PhantomData), +} + +/// A `RaDriverInstance` represents the exclusive interface of an `IdscpConnection` to a driver. It +/// is owned by a connection and only exists for the lifetime of the connection. Therefore, it +/// should support multiple re-attestations. +/// +/// The `RaDriverInstance` trait combines all necessary functionalities required to interact with +/// the driver. It provides a synchronous common interface to which message bytes can be passed and +/// from which message bytes can be returned. These message bytes originate/are sent to another +/// compatible driver. +#[async_trait] +pub trait RaDriverInstance { + // TODO is this needed? + /// Returns the identifier of this driver + fn get_id(&self) -> DriverId; + + // TODO more async funcions? + + /// Signal to the `RaDriverInstance` that the start of a (re-)attestation is requested. + /// Trigger-function that is called whenever the connection requires attestation. + fn begin_attestation(&mut self); + + /// Synchronously triggers the processing of `msg`. Does not necessarily block until the message + /// has been processed. + fn send_msg(&mut self, msg: Bytes); + + /// Synchronously returns any output message if available. This message may be some `RawData` + /// bytes destined for the driver at the other end of the connection or a `ControlMessage` + /// indicating to the connection that some process has been completed. + async fn recv_msg(&mut self) -> RaMessage; +} + +/// Executable drivers capable of being started synchronously once and to process (re-)attestation +/// either as a Prover or Verifier. An `RaDriver` should hold all resources required for +/// (re-)attestation that are shared between multiple connections. +/// +/// Structs implementing this trait should provide their own interface for managing asynchronous +/// Prover or Verifier executables. This should include scheduling and communication channels. +/// +/// A RaDriver creates a launched instance with the `execute` function. +pub trait RaDriver { + /// Synchronously creates a new `RaDriverInstance` which holds all resources (such as + /// communication channels, nonces, or locks to shared resources) exclusive to one connection + /// and required to process multiple (re-)attestations. + fn new_instance(&self, cert: &Certificate) -> Box>; + + /// Returns the identifier of this driver + fn get_id(&self) -> DriverId; +} + +pub type DriverId = &'static str; + +/// The `RaRegistry` provides a set of available attestation drivers. +/// Each supported driver needs to be registered in the registry +#[derive(Clone)] +pub struct RaRegistry { + drivers: HashMap + Send + Sync>>, +} + +impl RaRegistry { + pub fn new() -> Self { + RaRegistry { + drivers: HashMap::new(), + } + } + + /// Registers a new driver. + /// Does not replace any already registered driver with the same id. + pub fn register_driver( + &mut self, + driver: Arc + Send + Sync>, + ) -> Result<&mut Arc + Send + Sync>, Arc + Send + Sync>> + { + let id = driver.get_id(); + match self.drivers.entry(id) { + Entry::Occupied(ref _entry) => Err(driver), + Entry::Vacant(entry) => Ok(entry.insert(driver)), + } + } + + pub fn unregister_driver(&mut self, id: &DriverId) { + self.drivers.remove(id); + } + + pub fn get_driver(&self, id: &DriverId) -> Option<&Arc + Send + Sync>> { + self.drivers.get(id) + } + + pub fn get_all_driver_ids(&self) -> Vec<&DriverId> { + self.drivers.keys().collect() + } +} + +/// Provides the connection with an interface to synchronously launch and interact with a type of +/// `RaDriver` (e.g., a Prover) +pub(crate) struct RaManager<'reg, RaType> { + registry: &'reg RaRegistry, + cert: &'reg Certificate, // ref? + // state + active_driver: Option>>, + active_instance: Option>>, + id: Option, // TODO redundant +} + +// TODO naming +pub enum RaManagerEvent { + SelectDriver(DriverId), + RunDriver, + RawData(Bytes, PhantomData), +} + +/// +#[derive(Debug, Error)] +pub(crate) enum RaManagerError { + #[error("The input could not be processed")] + DriverUnregistered, + // #[error("The input could not be processed")] + // DriverNotStarted, +} + +impl<'reg, RaType> RaManager<'reg, RaType> { + pub fn new(registry: &'reg RaRegistry, cert: &'reg Certificate) -> Self { + Self { + registry, + cert, + active_driver: None, + active_instance: None, + id: None, + } + } + + pub fn process_event(&mut self, event: RaManagerEvent) -> Result<(), RaManagerError> { + match event { + RaManagerEvent::SelectDriver(id) => self.select_driver(id), + RaManagerEvent::RunDriver => self.run_driver(), + RaManagerEvent::RawData(msg, _) => { + self.send_msg(msg); + Ok(()) + } + } + } + + /// Internally selects the driver with identifier `id` for running. + /// Returns an error, if `id` is not present in the registry. + pub fn select_driver(&mut self, id: DriverId) -> Result<(), RaManagerError> { + self.id = Some(id); + let driver = self + .registry + .get_driver(&id) + .ok_or(RaManagerError::DriverUnregistered)? + .clone(); + self.active_driver = Some(driver); + Ok(()) + } + + pub fn run_driver(&mut self) -> Result<(), RaManagerError> { + if self.active_instance.is_none() { + let driver = self + .active_driver + .as_ref() + .ok_or(RaManagerError::DriverUnregistered)?; + let instance = driver.new_instance(self.cert); + + self.active_instance = Some(instance); + } + self.active_instance.as_mut().unwrap().begin_attestation(); + Ok(()) + } + + pub fn recv_msg( + &mut self, + ) -> Option< + std::pin::Pin< + std::boxed::Box< + dyn futures::Future> + std::marker::Send + '_, + >, + >, + > { + self.active_instance + .as_mut() + .map(|instance| instance.recv_msg()) + } + + pub fn send_msg(&mut self, msg: Bytes) { + if let Some(instance) = &mut self.active_instance { + instance.send_msg(msg) + } + } +} + +impl Default for RaRegistry { + fn default() -> Self { + RaRegistry::new() + } +} + +#[cfg(test)] +pub(crate) mod tests { + use crate::driver::ra_driver::{ + DriverId, RaDriver, RaDriverInstance, RaMessage, RaProverType, RaVerifierType, + }; + use crate::Certificate; + use async_trait::async_trait; + use bytes::Bytes; + use std::fs; + use std::marker::PhantomData; + use std::path::PathBuf; + use tokio::select; + + pub(crate) const TEST_PROVER_ID: DriverId = "test.prover"; + pub(crate) const TEST_VERIFIER_ID: DriverId = "test.prover"; + + pub(crate) struct TestVerifier {} + + impl RaDriver for TestVerifier { + fn new_instance( + &self, + _cert: &Certificate, + ) -> Box<(dyn RaDriverInstance + 'static)> { + Box::new(TestVerifierInstance { + state: TestVerifierState::Idle, + }) + } + + fn get_id(&self) -> DriverId { + TEST_PROVER_ID + } + } + + pub(crate) struct TestVerifierInstance { + state: TestVerifierState, + } + + pub(crate) enum TestVerifierState { + Idle, + Begin(Bytes), + WaitingForReply, + Ok(Bytes), + Failed, + } + + #[async_trait] + impl RaDriverInstance for TestVerifierInstance { + fn get_id(&self) -> DriverId { + TEST_PROVER_ID + } + + fn begin_attestation(&mut self) { + let msg = Bytes::from("test_begin_attest"); + self.state = TestVerifierState::Begin(msg); + } + + fn send_msg(&mut self, msg: Bytes) { + if let TestVerifierState::WaitingForReply = &self.state { + self.state = if msg == *"test_report" { + TestVerifierState::Ok(Bytes::from("test_ok")) + } else { + TestVerifierState::Failed + }; + } + } + + async fn recv_msg(&mut self) -> RaMessage { + loop { + match &self.state { + TestVerifierState::Begin(msg) => { + let msg = msg.clone(); + self.state = TestVerifierState::WaitingForReply; + return RaMessage::RawData(msg, PhantomData); + } + TestVerifierState::Ok(msg) => { + let msg = msg.clone(); + self.state = TestVerifierState::Idle; + return RaMessage::Ok(msg); + } + TestVerifierState::Failed => { + self.state = TestVerifierState::Idle; + return RaMessage::Failed(); + } + _ => { + // println!("a"); + tokio::task::yield_now().await; + } + } + } + /* + loop { + let mut state = self.state.lock().await; + match state.deref() { + TestVerifierState::Begin(msg) => { + let msg = msg.clone(); + *state = TestVerifierState::WaitingForReply; + return RaMessage::RawData(msg, PhantomData); + } + TestVerifierState::Ok(msg) => { + let msg = msg.clone(); + *state = TestVerifierState::Idle; + return RaMessage::Ok(msg); + } + TestVerifierState::Failed => { + *state = TestVerifierState::Idle; + return RaMessage::Failed(); + } + _ => { + println!("a"); + }, + } + } + */ + } + } + + pub(crate) struct TestProver {} + + impl RaDriver for TestProver { + fn new_instance( + &self, + _cert: &Certificate, + ) -> Box<(dyn RaDriverInstance + 'static)> { + Box::new(TestProverInstance { + state: TestProverState::Idle, + }) + } + + fn get_id(&self) -> DriverId { + TEST_VERIFIER_ID + } + } + + pub(crate) struct TestProverInstance { + state: TestProverState, + } + + pub(crate) enum TestProverState { + Idle, + WaitingForBegin, + Reply(Bytes), + WaitingForResponse, + Ok, + Failed, + } + + #[async_trait] + impl RaDriverInstance for TestProverInstance { + fn get_id(&self) -> DriverId { + TEST_VERIFIER_ID + } + + fn begin_attestation(&mut self) { + self.state = TestProverState::WaitingForBegin; + } + + fn send_msg(&mut self, msg: Bytes) { + match &self.state { + TestProverState::WaitingForBegin => { + self.state = + TestProverState::Reply(Bytes::from(if msg == *"test_begin_attest" { + "test_report" + } else { + "test_failure" + })); + } + TestProverState::WaitingForResponse => { + self.state = if msg == *"test_ok" { + TestProverState::Ok + } else { + TestProverState::Failed + }; + } + _ => {} + } + } + + async fn recv_msg(&mut self) -> RaMessage { + loop { + match &self.state { + TestProverState::Reply(msg) => { + let msg = msg.clone(); + self.state = TestProverState::WaitingForResponse; + return RaMessage::RawData(msg, PhantomData); + } + TestProverState::Ok => { + self.state = TestProverState::Idle; + return RaMessage::Ok(Bytes::from("test_ok")); + } + TestProverState::Failed => { + self.state = TestProverState::Idle; + return RaMessage::Failed(); + } + _ => { + // println!("b"); + tokio::task::yield_now().await; + } + } + } + } + } + + /// TestProver implementation that never reports being done to the FSM + /// + /// Used for forcefully creating partially attested states + pub(crate) struct TestProverNeverDone {} + + impl RaDriver for TestProverNeverDone { + fn new_instance( + &self, + _cert: &Certificate, + ) -> Box<(dyn RaDriverInstance + 'static)> { + Box::new(TestProverNeverDoneInstance { + state: TestProverState::Idle, + }) + } + + fn get_id(&self) -> DriverId { + TEST_VERIFIER_ID + } + } + + pub(crate) struct TestProverNeverDoneInstance { + state: TestProverState, + } + + #[async_trait] + impl RaDriverInstance for TestProverNeverDoneInstance { + fn get_id(&self) -> DriverId { + TEST_VERIFIER_ID + } + + fn begin_attestation(&mut self) { + self.state = TestProverState::WaitingForBegin; + } + + fn send_msg(&mut self, msg: Bytes) { + match &self.state { + TestProverState::WaitingForBegin => { + self.state = + TestProverState::Reply(Bytes::from(if msg == *"test_begin_attest" { + "test_report" + } else { + "test_failure" + })); + } + TestProverState::WaitingForResponse => { + self.state = if msg == *"test_ok" { + TestProverState::Ok + } else { + TestProverState::Failed + }; + } + _ => {} + } + } + + async fn recv_msg(&mut self) -> RaMessage { + loop { + match &self.state { + TestProverState::Reply(msg) => { + let msg = msg.clone(); + self.state = TestProverState::WaitingForResponse; + return RaMessage::RawData(msg, PhantomData); + } + // no Ok/Failed state match on purpose + _ => { + // println!("b"); + tokio::task::yield_now().await; + } + } + } + } + } + + pub(crate) fn get_test_cert() -> Certificate { + let local_client_cert = fs::read(PathBuf::from(format!( + "{}/../test_pki/resources/openssl/out/{}", + env!("CARGO_MANIFEST_DIR"), + "test_client.crt" + ))) + .unwrap(); + Certificate::from_pem(&local_client_cert).unwrap() + } + + #[test] + fn async_ra_test_driver_communication() { + let verifier_driver = TestVerifier {}; + let prover_driver = TestProver {}; + + let cert = get_test_cert(); + let mut verifier_instance = verifier_driver.new_instance(&cert); + let mut prover_instance = prover_driver.new_instance(&cert); + + verifier_instance.begin_attestation(); + prover_instance.begin_attestation(); + + let mut verifier_ok = false; + let mut prover_ok = false; + + println!("Starting test"); + + tokio_test::block_on(async { + while !prover_ok || !verifier_ok { + let loop_idle; + + select! { + msg = verifier_instance.recv_msg() => { + match msg { + RaMessage::RawData(msg, ..) => { + loop_idle = false; + println!("Passing msg V->P {:?}", msg); + prover_instance.send_msg(msg); + } + RaMessage::Ok(msg) => { + loop_idle = false; + println!("Verifier Ok. Passing msg V->P {:?}", msg); + prover_instance.send_msg(msg); + verifier_ok = true; + } + RaMessage::Failed() => { + panic!("Test Verifier Failed"); + } + } + } + msg = prover_instance.recv_msg() => { + match msg { + RaMessage::RawData(msg, ..) => { + loop_idle = false; + println!("Passing msg P->V {:?}", msg); + verifier_instance.send_msg(msg); + } + RaMessage::Ok(_) => { + // panic!("Prover should never return"); + loop_idle = false; + println!("Prover Ok"); + prover_ok = true; + } + RaMessage::Failed() => { + // panic!("Prover should never return"); + panic!("Test Prover Failed"); + } + } + } + } + if loop_idle { + panic!("Prover and Verifier deadlocked."); + } + } + }); + } +} diff --git a/idscp2_core/src/fsm_spec/fsm.rs b/idscp2_core/src/fsm_spec/fsm.rs new file mode 100644 index 0000000..cf36e28 --- /dev/null +++ b/idscp2_core/src/fsm_spec/fsm.rs @@ -0,0 +1,853 @@ +use bytes::Bytes; +use std::marker::PhantomData; +use std::time::Duration; +use tinyvec::{array_vec, ArrayVec}; + +use crate::api::idscp2_config::IdscpConfig; +use crate::driver::daps_driver::DapsDriver; +use crate::driver::ra_driver::{DriverId, RaMessage}; +use crate::messages::idscp_message_factory; +use crate::messages::idscpv2_messages::{ + IdscpClose_CloseCause, IdscpData, IdscpMessage, IdscpMessage_oneof_message, +}; +use crate::{RaProverType, RaVerifierType}; + +#[derive(Debug, PartialEq, Eq)] +enum ProtocolState { + Closed, + WaitForHello, + Running, + Terminated, +} + +pub(crate) enum FsmAction { + None, // we use this to implement a meaningless Default for the safe ArrayVec + SecureChannelAction(SecureChannelAction), + NotifyUserData(Bytes), + SetDatTimeout(Duration), + SetRaTimeout(Duration), + SetResendDataTimeout(Duration), + StopDatTimeout, + StopRaTimeout, + StopResendDataTimeout, + StartProver(DriverId), + StartVerifier(DriverId), + RestartProver, + RestartVerifier, + ToProver(Bytes), + ToVerifier(Bytes), +} + +impl Default for FsmAction { + fn default() -> Self { + Self::None + } +} + +#[allow(clippy::enum_variant_names)] +#[derive(Debug)] +pub(crate) enum FsmEvent { + // USER EVENTS + FromUpper(UserEvent), + + // SECURE CHANNEL EVENTS + FromSecureChannel(SecureChannelEvent), + + // RA DRIVER EVENTS + FromRaProver(RaMessage), + FromRaVerifier(RaMessage), + + // TIMEOUT EVENTS + ResendTimout, + DatExpired, +} + +/*pub(crate) enum FsmIdscpMessageType { + Hello, + Close, + Data +} + +impl Into for &IdscpMessage { + fn into(self) -> FsmIdscpMessageType { + if match self.message + } +} +*/ + +#[derive(Debug)] +pub(crate) enum SecureChannelEvent { + Message(IdscpMessage_oneof_message), +} + +#[derive(Debug)] +pub(crate) enum SecureChannelAction { + Message(IdscpMessage), // TODO: make reference? +} + +#[derive(Debug, Clone)] +pub(crate) enum UserEvent { + StartHandshake, + #[allow(dead_code)] // FIXME use this in destructor/close + CloseConnection, + RequestReattestation(&'static str), //includes cause + Data(Bytes), // ref-counted +} + +#[derive(Debug, PartialEq)] +enum RaState { + Inactive(PhantomData), + Working, + Done, + Terminated, +} + +#[derive(Debug, PartialEq)] +enum TimeoutState { + Active, + Inactive, +} + +#[derive(PartialEq)] +enum AckBit { + Unset, + Set, +} + +impl AckBit { + /// this resembles the operiation : 1 - other + fn from_other_flipped(other: &AckBit) -> AckBit { + match other { + AckBit::Set => AckBit::Unset, + AckBit::Unset => AckBit::Set, + } + } +} + +impl From for bool { + fn from(x: AckBit) -> bool { + match x { + AckBit::Unset => false, + AckBit::Set => true, + } + } +} + +impl From for AckBit { + fn from(x: bool) -> AckBit { + match x { + false => AckBit::Unset, + true => AckBit::Set, + } + } +} + +struct ResendMetadata { + msg: Option, +} +impl ResendMetadata { + fn new() -> ResendMetadata { + ResendMetadata { msg: None } + } + + fn set(&mut self, msg: IdscpData) { + self.msg = Some(msg) + } + + fn get_ack_bit(&self) -> AckBit { + match &self.msg { + Some(msg) => msg.alternating_bit.into(), + None => AckBit::Unset, // In the Specification, ack bit is initialized to 0/false + } + } +} + +struct LastDataSent(ResendMetadata); +struct LastDataReceived(ResendMetadata); + +pub(crate) struct Fsm<'daps, 'config> { + /* all of the variables that make up the whole state space of the FSM */ + state: ProtocolState, + prover: RaState, + verifier: RaState, + daps_driver: &'daps mut dyn DapsDriver, + dat_timeout: TimeoutState, + ra_timeout: TimeoutState, + resend_timeout: TimeoutState, + last_ack_received: AckBit, + last_data_sent: LastDataSent, + last_data_received: LastDataReceived, + /* configs */ + config: &'config IdscpConfig<'config>, +} + +impl<'daps, 'config> Fsm<'daps, 'config> { + pub(crate) const EVENT_VEC_LEN: usize = 3; + + pub(crate) fn new( + daps_driver: &'daps mut dyn DapsDriver, + config: &'config IdscpConfig<'config>, + ) -> Fsm<'daps, 'config> { + Fsm { + state: ProtocolState::Closed, + prover: RaState::Inactive(PhantomData {}), + verifier: RaState::Inactive(PhantomData {}), + daps_driver, + dat_timeout: TimeoutState::Inactive, + ra_timeout: TimeoutState::Inactive, + resend_timeout: TimeoutState::Inactive, + last_ack_received: AckBit::Unset, + last_data_sent: LastDataSent(ResendMetadata::new()), + last_data_received: LastDataReceived(ResendMetadata::new()), + config, + } + } + + pub(crate) fn process_event( + &mut self, + event: FsmEvent, + ) -> ArrayVec<[FsmAction; Fsm::EVENT_VEC_LEN]> { + let dat = self.daps_driver.is_valid(); + match ( + &self.state, + &self.prover, + &self.verifier, + dat, + &self.dat_timeout, + &self.ra_timeout, + &self.resend_timeout, + event, + ) { + // TLA Action "Start" + ( + ProtocolState::Closed, + RaState::Inactive(_), // Prover state + RaState::Inactive(_), // Verifier state + false, // DAT is valid? + TimeoutState::Inactive, // Dat timeout + TimeoutState::Inactive, // Ra timeout + TimeoutState::Inactive, // Resend timeout + FsmEvent::FromUpper(UserEvent::StartHandshake), + ) => { + let hello_msg = idscp_message_factory::create_idscp_hello( + Bytes::from(self.daps_driver.get_token()), + &self.config.ra_config.expected_verifiers, + &self.config.ra_config.supported_provers, + ); + + self.state = ProtocolState::WaitForHello; + let action = + FsmAction::SecureChannelAction(SecureChannelAction::Message(hello_msg)); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action] + } + + // TLA Action "ReceiveHallo" + ( + ProtocolState::WaitForHello, + RaState::Inactive(_), // Prover state + RaState::Inactive(_), // Verifier state + false, // DAT is valid? + TimeoutState::Inactive, // Dat timeout + TimeoutState::Inactive, // Ra timeout + TimeoutState::Inactive, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpHello(hello_msg), + )), + ) => { + let dat = hello_msg.get_dynamicAttributeToken(); + let mut actions = ArrayVec::default(); + + let dat_timeout = match self.daps_driver.verify_token(dat.get_token()) { + Some(dat_timeout) => dat_timeout, + None => { + let action = FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_close( + IdscpClose_CloseCause::NO_VALID_DAT, + "", + ), + )); + self.cleanup(); + return array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action]; + } + }; + + self.state = ProtocolState::Running; + actions.push(FsmAction::SetDatTimeout(dat_timeout)); + self.dat_timeout = TimeoutState::Active; // TODO: make it one operation with SetDatTimeout + + match Self::find_ra_match_from_secondary( + hello_msg.expectedRaSuite.as_slice(), + &self.config.ra_config.supported_provers, + ) { + Some(prover) => actions.push(FsmAction::StartProver(prover)), + None => { + let action = FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_close( + IdscpClose_CloseCause::NO_RA_MECHANISM_MATCH_PROVER, + "", + ), + )); + self.cleanup(); + return array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action]; + } + } + + match Self::find_ra_match_from_primary( + &self.config.ra_config.expected_verifiers, + hello_msg.supportedRaSuite.as_slice(), + ) { + Some(verifier) => actions.push(FsmAction::StartVerifier(verifier)), + None => { + let action = FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_close( + IdscpClose_CloseCause::NO_RA_MECHANISM_MATCH_VERIFIER, + "", + ), + )); + self.cleanup(); + return array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action]; + } + } + + self.prover = RaState::Working; + self.verifier = RaState::Working; + + actions + } + + // TLA Action "SendProverMsg" + ( + ProtocolState::Running, + RaState::Working, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromRaProver(RaMessage::RawData(bytes, _)), + ) => { + let msg = idscp_message_factory::create_idscp_ra_prover(bytes); + let action = FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action] + } + + // TLA Action "SendVerifierMsg" + ( + ProtocolState::Running, + _, // Prover state + RaState::Working, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Inactive, // Ra timeout + _, // Resend timeout + FsmEvent::FromRaVerifier(RaMessage::RawData(bytes, _)), + ) => { + let msg = idscp_message_factory::create_idscp_ra_verifier(bytes); + let action = FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action] + } + + // TLA Action "ReceiveVerifierMsg" + ( + ProtocolState::Running, + RaState::Working, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpRaVerifier(msg), + )), + ) => { + let action = FsmAction::ToProver(msg.data); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action] + } + + // TLA Action "ReceiveProverMsg" + ( + ProtocolState::Running, + _, // Prover state + RaState::Working, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Inactive, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpRaProver(msg), + )), + ) => { + let action = FsmAction::ToVerifier(msg.data); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => action] + } + + // TLA Action "VerifierSuccess" + ( + ProtocolState::Running, + _, // Prover state + RaState::Working, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Inactive, // Ra timeout + _, // Resend timeout + FsmEvent::FromRaVerifier(RaMessage::Ok(msg)), + ) => { + self.verifier = RaState::Done; + let msg = idscp_message_factory::create_idscp_ra_verifier(msg); + // let msg = idscp_message_factory::create_idscp_ra_complete(msg); + let send_action = FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)); + let ra_timeout_action = FsmAction::SetRaTimeout(self.config.ra_config.ra_timeout); + self.ra_timeout = TimeoutState::Active; // TODO: make it one operation with SetRaTimeout + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => send_action, ra_timeout_action] + } + + // TLA Action "VerifierError" + ( + ProtocolState::Running, + _, // Prover state + RaState::Working, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Inactive, // Ra timeout + _, // Resend timeout + FsmEvent::FromRaVerifier(RaMessage::Failed()), + ) => { + let send_action = FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_close( + IdscpClose_CloseCause::RA_VERIFIER_FAILED, + "", + ), + )); + self.cleanup(); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => send_action] + } + + // TLA Action "ProverSuccess" + // We need an action "ProverSuccess" which is not obviously represented in the specification! + // Otherwhise prover_state is never set to "done" + ( + ProtocolState::Running, + RaState::Working, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromRaProver(RaMessage::Ok(_msg)), // TODO what is msg + ) => { + self.prover = RaState::Done; + ArrayVec::default() + } + + // TLA Action "ProverError" + // See above + ( + ProtocolState::Running, + RaState::Working, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromRaProver(RaMessage::Failed()), + ) => { + let send_action = FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_close( + IdscpClose_CloseCause::RA_VERIFIER_FAILED, + "", + ), + )); + self.cleanup(); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => send_action] + } + + // TLA Action DatExpired + ( + ProtocolState::Running, + _, // Prover state + _, // Verifier state + true, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::DatExpired, + ) => { + self.verifier = RaState::Inactive(PhantomData {}); + self.daps_driver.invalidate(); + self.ra_timeout = TimeoutState::Inactive; + self.dat_timeout = TimeoutState::Inactive; + let actions = array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => + FsmAction::StopRaTimeout, + FsmAction::StopDatTimeout, + FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_dat_exp(), + )), + ]; + actions + } + + // TLA Action ReceiveDatExpired + ( + ProtocolState::Running, + _, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpDatExpired(_), + )), + ) => { + let mut actions = ArrayVec::default(); + + self.prover = RaState::Working; + actions.push(FsmAction::RestartProver); + actions.push(FsmAction::SecureChannelAction( + SecureChannelAction::Message(idscp_message_factory::create_idscp_dat( + Bytes::from(self.daps_driver.get_token()), + )), + )); + actions + } + + // TLA Action ReceiveDat + ( + ProtocolState::Running, + _, // Prover state + RaState::Inactive(_), // Verifier state + false, // DAT is valid? + TimeoutState::Inactive, // Dat timeout + TimeoutState::Inactive, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpDat(dat), + )), + ) => { + let mut actions = ArrayVec::default(); + match self.daps_driver.verify_token(dat.get_token()) { + Some(dat_timeout) => { + self.dat_timeout = TimeoutState::Active; + actions.push(FsmAction::SetDatTimeout(dat_timeout)); + self.verifier = RaState::Working; + actions.push(FsmAction::RestartVerifier); + } + None => { + actions.push(FsmAction::SecureChannelAction( + SecureChannelAction::Message( + idscp_message_factory::create_idscp_close( + IdscpClose_CloseCause::NO_VALID_DAT, + "", + ), + ), + )); + self.cleanup(); + } + } + actions + } + + // TLA Action SendData + ( + ProtocolState::Running, + RaState::Done, // Prover state + RaState::Done, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Active, // Ra timeout + TimeoutState::Inactive, // Resend timeout + FsmEvent::FromUpper(UserEvent::Data(data)), + ) => { + if self.last_ack_received == self.last_data_sent.0.get_ack_bit() { + let msg = idscp_message_factory::create_idscp_data( + data, + AckBit::from_other_flipped(&self.last_ack_received).into(), + ); + self.last_data_sent.0.set(msg.get_idscpData().clone()); // zero-copy clone + self.resend_timeout = TimeoutState::Active; + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)), + FsmAction::SetResendDataTimeout(self.config.resend_timeout), + ] + } else { + self.no_matching_event() + } + } + + // TLA Action ResendData + ( + ProtocolState::Running, + RaState::Done, // Prover state + RaState::Done, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Active, // Ra timeout + TimeoutState::Active, // Resend timeout + FsmEvent::ResendTimout, + ) => { + if let Some(resend_msg) = &self.last_data_sent.0.msg { + if self.last_ack_received != self.last_data_sent.0.get_ack_bit() { + let mut msg = IdscpMessage::new(); + msg.set_idscpData(resend_msg.clone()); // zero-copy clone + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)), + FsmAction::SetResendDataTimeout(self.config.resend_timeout), // Resetting the timout + ] + } else { + self.no_matching_event() + } + } else { + self.no_matching_event() + } + } + + // TLA Action ReceiveData + ( + ProtocolState::Running, + _, // Prover state + RaState::Done, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Active, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpData(idscp_data), + )), + ) => { + if AckBit::from(idscp_data.get_alternating_bit()) + != self.last_data_received.0.get_ack_bit() + { + self.last_data_received.0.set(idscp_data.clone()); + } + + let ack_msg = idscp_message_factory::create_idscp_ack(bool::from( + self.last_data_received.0.get_ack_bit(), + )); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => + FsmAction::NotifyUserData(idscp_data.data), + FsmAction::SecureChannelAction(SecureChannelAction::Message(ack_msg)), + ] + } + + // TLA Action ReceiveAck + ( + ProtocolState::Running, + _, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpAck(idscp_ack), + )), + ) => { + let received_ack = AckBit::from(idscp_ack.alternating_bit); + if self.last_ack_received != received_ack { + // can be simplified in the Spec. + self.last_ack_received = received_ack; + self.resend_timeout = TimeoutState::Inactive; + } + + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => FsmAction::StopResendDataTimeout] + } + + // TLA Action CloseConnection + ( + ProtocolState::Running | ProtocolState::WaitForHello, + _, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromUpper(UserEvent::CloseConnection), + ) => { + let msg = FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_close( + IdscpClose_CloseCause::USER_SHUTDOWN, + "", + ), + )); + self.cleanup(); + array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => msg] + } + + // TLA Action ReceiveClose + ( + ProtocolState::Running | ProtocolState::WaitForHello, + _, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpClose(_), + )), + ) => { + self.cleanup(); + // TODO: The other actors should probably be notified about that + ArrayVec::default() + } + + // TLA Action RequestReattestation + ( + ProtocolState::Running, + _, // Prover state + RaState::Done, // Verifier state + true, // DAT is valid? + TimeoutState::Active, // Dat timeout + TimeoutState::Active, // Ra timeout + _, // Resend timeout + FsmEvent::FromUpper(UserEvent::RequestReattestation(cause)), + ) => { + self.verifier = RaState::Working; + self.ra_timeout = TimeoutState::Inactive; + let actions = array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => + FsmAction::StopRaTimeout, + FsmAction::SecureChannelAction(SecureChannelAction::Message( + idscp_message_factory::create_idscp_re_rat(cause), + )), + FsmAction::RestartVerifier, + ]; + actions + } + + // TLA Action ReceiveReattestation + ( + ProtocolState::Running, + _, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + IdscpMessage_oneof_message::idscpReRa(_), + )), + ) => { + self.prover = RaState::Working; + let actions = array_vec![[FsmAction; Fsm::EVENT_VEC_LEN] => + FsmAction::RestartProver, + ]; + actions + } + + // Ignore messages in any remaining states + ( + _, // FSM state + _, // Prover state + _, // Verifier state + _, // DAT is valid? + _, // Dat timeout + _, // Ra timeout + _, // Resend timeout + FsmEvent::FromSecureChannel(SecureChannelEvent::Message(_)), // TODO only match on messages? + ) => { + // nothing to process + ArrayVec::default() + } + + // All other states should not be possible under the IDSCP2 specification. + // The FSM is private and can only be accessed via IdscpConnection. + // We guarantee that the IdscpConnection only enters valid FSM states. + (state, prover, verifier, dat, dat_timeout, ra_timeout, resend_timeout, event) => { + panic!( + "\n(\ + state: {:?},\n\ + prover: {:?},\n\ + verifier: {:?},\n\ + dat: {:?},\n\ + dat_timeout: {:?},\n\ + ra_timeout: {:?},\n\ + resend_timeout: {:?},\n\ + event: {:?},\n\ + )", + state, prover, verifier, dat, dat_timeout, ra_timeout, resend_timeout, event + ) + } + } + } + + /// This function is needed because Rust's match statement cannot express all Condtions encoded in the IDSCP2 Specification. + /// Rust's match statement can only compare a variable to static value of the same type, e.g. protocol_state == ProtocolState::Running. + /// It CANNOT compare two variables (e.g. lastAckReceived == lastDataSent.ack). + /// Therefore we need to make this check within the body of the match arm. + /// If this check does not succeed, we call this method to have a common place to ignore events that do not trigger + /// an action in the current FSM state. + fn no_matching_event(&self) -> ArrayVec<[FsmAction; Fsm::EVENT_VEC_LEN]> { + unimplemented!() + } + + /// Returns `true` if a connection to another peer is set up and not closed. + /// Does not check if the connection has been validated. + pub(crate) fn is_open(&self) -> bool { + self.state == ProtocolState::Running + } + + /// Returns `true` if the connection is verified and can be used to receive data. + pub(crate) fn is_partially_attested(&self) -> bool { + self.is_open() + && self.dat_timeout == TimeoutState::Active + && self.verifier == RaState::Done + && self.daps_driver.is_valid() + } + + /// Returns `true` if the connection is verified and can be used to send and receive data. + pub(crate) fn is_fully_attested(&self) -> bool { + self.is_partially_attested() && self.prover == RaState::Done + } + + /// Returns `true` if data can be sent in the current state. + /// This is the case, if the connection is verified and all sent data has been acknowledged by the connected peer. + pub(crate) fn is_ready_to_send(&self) -> bool { + self.is_fully_attested() && self.resend_timeout == TimeoutState::Inactive + } + + // workaround + + /// Finds the attestation id match between two capability lists + fn find_ra_match_from_primary>( + primary: &[DriverId], + secondary: &[T], + ) -> Option { + for p in primary { + for s in secondary { + if *p == s.as_ref() { + // no check against registry at this point anymore + return Some(p); + } + } + } + None + } + + /// Finds the attestation id match between two capability lists + fn find_ra_match_from_secondary>( + primary: &[T], + secondary: &[DriverId], + ) -> Option { + for p in primary { + for s in secondary { + if p.as_ref() == *s { + // no check against registry at this point anymore + return Some(s); + } + } + } + None + } + + // Implements the TLA spec's action "Close" + fn cleanup(&mut self) { + self.state = ProtocolState::Terminated; + self.verifier = RaState::Terminated; + self.prover = RaState::Terminated; + self.dat_timeout = TimeoutState::Inactive; + self.ra_timeout = TimeoutState::Inactive; + self.resend_timeout = TimeoutState::Inactive; + + //TODO do we need to implement the other cleanup steps from the spec? + } +} diff --git a/idscp2_core/src/fsm_spec/fsm_tests.rs b/idscp2_core/src/fsm_spec/fsm_tests.rs new file mode 100644 index 0000000..6ac7b51 --- /dev/null +++ b/idscp2_core/src/fsm_spec/fsm_tests.rs @@ -0,0 +1,563 @@ +use crate::driver::ra_driver::tests::get_test_cert; +use crate::messages::idscpv2_messages::IdscpClose_CloseCause; +use crate::msg_factory::create_idscp_hello; +use crate::{ + api::idscp2_config::{AttestationConfig, IdscpConfig}, + driver::daps_driver::DapsDriver, + messages::{idscp_message_factory, idscpv2_messages::IdscpMessage_oneof_message}, + RaMessage, +}; +use bytes::Bytes; +use std::{marker::PhantomData, time::Duration, vec}; + +use super::fsm::*; + +pub(crate) struct TestDaps { + is_valid: bool, + timeout: Duration, +} + +impl Default for TestDaps { + fn default() -> Self { + Self { + is_valid: false, + timeout: Duration::from_secs(1), + } + } +} + +impl TestDaps { + pub fn with_timeout(timeout: Duration) -> Self { + Self { + is_valid: false, + timeout, + } + } +} + +impl DapsDriver for TestDaps { + fn is_valid(&self) -> bool { + self.is_valid + } + + fn get_token(&self) -> String { + "valid".to_string() + } + + fn verify_token(&mut self, token_bytes: &[u8]) -> Option { + let token = String::from_utf8_lossy(token_bytes); + if token.eq("valid") { + self.is_valid = true; + Some(self.timeout) + } else { + None + } + } + + fn invalidate(&mut self) { + self.is_valid = false; + } +} + +#[test] +#[allow(clippy::bool_assert_comparison)] +fn normal_sequence() { + let mut daps_driver = TestDaps::default(); + let ra_config = AttestationConfig { + supported_provers: vec!["TestRatProver"], + expected_verifiers: vec!["TestRatProver"], + prover_registry: &Default::default(), + ra_timeout: Duration::from_secs(30), + verifier_registry: &Default::default(), + peer_cert: get_test_cert(), + }; + let config = IdscpConfig { + id: "", + ra_config: &ra_config, + resend_timeout: Duration::from_secs(5), + }; + let mut fsm = Fsm::new(&mut daps_driver, &config); + + // TLA Action Start + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::StartHandshake)); + assert_eq!(actions.len(), 1); + let hello_msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + + // TLA Action ReceiveHello + let idscp_hello = hello_msg.clone().message.unwrap(); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + idscp_hello, + ))); + assert_eq!(actions.len(), 3); + assert!(matches!(&actions[0], FsmAction::SetDatTimeout(_))); + assert!(matches!( + &actions[1], + FsmAction::StartProver("TestRatProver") + )); + assert!(matches!( + &actions[2], + FsmAction::StartVerifier("TestRatProver") + )); + + // TLA Action SendVerifierMsg + let actions = fsm.process_event(FsmEvent::FromRaVerifier(RaMessage::RawData( + Bytes::from("nonce"), + PhantomData, + ))); + assert_eq!(actions.len(), 1); + let verif_msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(verif_msg)) => verif_msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(verif_msg.has_idscpRaVerifier()); + + // TLA Action ReceiveVerifierMsg + let verif_msg = verif_msg.clone().message.unwrap(); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + verif_msg, + ))); + assert_eq!(actions.len(), 1); + assert!(matches!(&actions[0], FsmAction::ToProver(_))); + + // TLA Action SendProverMsg + let actions = fsm.process_event(FsmEvent::FromRaProver(RaMessage::RawData( + Bytes::from("attestation_report"), + PhantomData, + ))); + assert_eq!(actions.len(), 1); + let prover_msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(prover_msg)) => prover_msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(prover_msg.has_idscpRaProver()); + + // TLA Action ReceiveProverMsg + let prover_msg = prover_msg.clone().message.unwrap(); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + prover_msg, + ))); + assert_eq!(actions.len(), 1); + assert!(matches!(&actions[0], FsmAction::ToVerifier(_))); + + // TLA Action Verifier Success + let actions = fsm.process_event(FsmEvent::FromRaVerifier(RaMessage::Ok(Bytes::from( + "attestation successful", + )))); + assert_eq!(actions.len(), 2); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpRaVerifier(_) + )); + assert!(matches!(&actions[1], FsmAction::SetRaTimeout(_))); + + // TLA Action ReceiveData (before ProverSuccess) + let msg = idscp_message_factory::create_idscp_data(Bytes::from("foo bar"), true); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + msg.message.unwrap(), + ))); + assert_eq!(actions.len(), 2); + assert!(matches!(&actions[0], FsmAction::NotifyUserData(_))); + let msg = match &actions[1] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + match msg.message.as_ref().unwrap() { + IdscpMessage_oneof_message::idscpAck(idscp_ack) => { + assert_eq!(idscp_ack.alternating_bit, true) + } + _ => panic!("expected IdscpAck"), + } + + // Action ProverSuccess (which is only implicitly defined in TLA Spec) + let actions = fsm.process_event(FsmEvent::FromRaProver(RaMessage::Ok(Bytes::from( + "attestation successful", + )))); + assert_eq!(actions.len(), 0); + // let msg = match &actions[0] { + // FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + // _ => panic!("expected Secure Channel message"), + // }; + // assert!(matches!( + // msg.clone().message.unwrap(), + // IdscpMessage_oneof_message::idscpRaProver(_) + // )); + + // TLA Action SendData + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::Data(Bytes::from( + "hello world!", + )))); + assert_eq!(actions.len(), 2); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + match msg.message.as_ref().unwrap() { + IdscpMessage_oneof_message::idscpData(data) => { + assert_eq!(data.alternating_bit, true) + } + _ => panic!("expected IdscpData"), + } + assert!(matches!(&actions[1], FsmAction::SetResendDataTimeout(_))); + + // TLA Action ReceiveData (after ProverSuccess) + let msg = idscp_message_factory::create_idscp_data(Bytes::from("foo bar"), true); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + msg.message.unwrap(), + ))); + assert_eq!(actions.len(), 2); + assert!(matches!(&actions[0], FsmAction::NotifyUserData(_))); + let msg = match &actions[1] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + match msg.message.as_ref().unwrap() { + IdscpMessage_oneof_message::idscpAck(idscp_ack) => { + assert_eq!(idscp_ack.alternating_bit, true) + } + _ => panic!("expected IdscpAck"), + } + + // TLA Action ResendData + let actions = fsm.process_event(FsmEvent::ResendTimout); + assert_eq!(actions.len(), 2); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + match msg.message.as_ref().unwrap() { + IdscpMessage_oneof_message::idscpData(data) => { + assert_eq!(data.alternating_bit, true) + } + _ => panic!("expected IdscpData"), + } + assert!(matches!(&actions[1], FsmAction::SetResendDataTimeout(_))); + + // TLA Action ReceiveAck + let msg = idscp_message_factory::create_idscp_ack(true); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + msg.message.unwrap(), + ))); + assert!(matches!(&actions[0], FsmAction::StopResendDataTimeout)); + + // TLA Action DatExpired + let actions = fsm.process_event(FsmEvent::DatExpired); + assert_eq!(actions.len(), 3); + assert!(matches!(actions[0], FsmAction::StopRaTimeout)); + assert!(matches!(actions[1], FsmAction::StopDatTimeout)); + let dat_exp_msg = match &actions[2] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + dat_exp_msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpDatExpired(_) + )); + + // TLA Action ReceiveDatExpired + let msg = dat_exp_msg.clone().message.unwrap(); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + msg, + ))); + assert_eq!(actions.len(), 2); + assert!(matches!(actions[0], FsmAction::RestartProver)); + let dat_msg = match &actions[1] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + dat_msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpDat(_) + )); + + // TLA Action ReceiveDat + let msg = dat_msg.clone().message.unwrap(); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + msg, + ))); + assert_eq!(actions.len(), 2); + assert!(matches!(actions[0], FsmAction::SetDatTimeout(_))); + assert!(matches!(actions[1], FsmAction::RestartVerifier)); + + // TLA Action Verifier Success + let actions = fsm.process_event(FsmEvent::FromRaVerifier(RaMessage::Ok(Bytes::from( + "attestation successful", + )))); + assert_eq!(actions.len(), 2); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpRaVerifier(_) + )); + assert!(matches!(&actions[1], FsmAction::SetRaTimeout(_))); + + // Action ProverSuccess (which is only implicitly defined in TLA Spec) + let actions = fsm.process_event(FsmEvent::FromRaProver(RaMessage::Ok(Bytes::from( + "attestation successful", + )))); + assert_eq!(actions.len(), 0); + // let msg = match &actions[0] { + // FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + // _ => panic!("expected Secure Channel message"), + // }; + // assert!(matches!( + // msg.clone().message.unwrap(), + // IdscpMessage_oneof_message::idscpRaProver(_) + // )); + + // TLA Action RequestReattestation + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::RequestReattestation(""))); + assert_eq!(actions.len(), 3); + assert!(matches!(actions[0], FsmAction::StopRaTimeout)); + let re_ra_msg = match &actions[1] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + re_ra_msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpReRa(_) + )); + assert!(matches!(actions[2], FsmAction::RestartVerifier)); + + // TLA Action ReceiveReattestation + let msg = re_ra_msg.clone().message.unwrap(); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + msg, + ))); + assert_eq!(actions.len(), 1); + assert!(matches!(actions[0], FsmAction::RestartProver)); + + // TLA Action CloseConnection + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::CloseConnection)); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpClose(_) + )); +} + +#[test] +#[should_panic] +fn send_data_before_prover_ok_error_sequence() { + let mut daps_driver = TestDaps::default(); + let ra_config = AttestationConfig { + supported_provers: vec!["TestRatProver"], + expected_verifiers: vec!["TestRatProver"], + prover_registry: &Default::default(), + ra_timeout: Duration::from_secs(30), + verifier_registry: &Default::default(), + peer_cert: get_test_cert(), + }; + let config = IdscpConfig { + id: "", + ra_config: &ra_config, + resend_timeout: Duration::from_secs(5), + }; + let mut fsm = Fsm::new(&mut daps_driver, &config); + + // TLA Action Start + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::StartHandshake)); + assert_eq!(actions.len(), 1); + let hello_msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + + // TLA Action ReceiveHello + let idscp_hello = hello_msg.clone().message.unwrap(); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + idscp_hello, + ))); + assert_eq!(actions.len(), 3); + assert!(matches!(&actions[0], FsmAction::SetDatTimeout(_))); + assert!(matches!( + &actions[1], + FsmAction::StartProver("TestRatProver") + )); + assert!(matches!( + &actions[2], + FsmAction::StartVerifier("TestRatProver") + )); + + // TLA Action Verifier Success + let actions = fsm.process_event(FsmEvent::FromRaVerifier(RaMessage::Ok(Bytes::from( + "attestation successful", + )))); + assert_eq!(actions.len(), 2); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpRaVerifier(_) + )); + assert!(matches!(&actions[1], FsmAction::SetRaTimeout(_))); + + // TLA Action SendData + // this should panic + fsm.process_event(FsmEvent::FromUpper(UserEvent::Data(Bytes::from( + "hello world!", + )))); +} + +#[test] +fn ra_driver_match_complex_sequence() { + let mut daps_driver = TestDaps::default(); + let ra_config = AttestationConfig { + supported_provers: vec!["Unmatched1", "Unmatched2", "TestRatProver", "Unmatched3"], + expected_verifiers: vec!["TestRatProver", "Unmatched1", "Unmatched2"], + prover_registry: &Default::default(), + ra_timeout: Duration::from_secs(30), + verifier_registry: &Default::default(), + peer_cert: get_test_cert(), + }; + let config = IdscpConfig { + id: "", + ra_config: &ra_config, + resend_timeout: Duration::from_secs(5), + }; + let mut fsm = Fsm::new(&mut daps_driver, &config); + + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::StartHandshake)); + let hello_msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + + // set the supported/expected fields + let expected_drivers = vec!["TestRatProver", "Unmatched1"]; + let supported_drivers = vec!["Unmatched1", "TestRatProver"]; + let dat = if let Some(IdscpMessage_oneof_message::idscpHello(hello)) = &hello_msg.message { + hello.dynamicAttributeToken.as_ref().unwrap().token.clone() + } else { + panic!("expected IdscpHello") + }; + + let idscp_hello = create_idscp_hello(dat, &expected_drivers, &supported_drivers); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + idscp_hello.message.unwrap(), + ))); + + if let FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) = &actions[0] { + if let IdscpMessage_oneof_message::idscpClose(close_msg) = msg.message.as_ref().unwrap() { + panic!("{:?}", close_msg.cause_code); + } + } + assert_eq!(actions.len(), 3); + assert!(matches!( + actions[1], + FsmAction::StartProver("TestRatProver"), + )); + assert!(matches!( + actions[2], + FsmAction::StartVerifier("TestRatProver"), + )); +} + +#[test] +fn ra_driver_match_error_sequence() { + let mut daps_driver = TestDaps::default(); + let ra_config = AttestationConfig { + supported_provers: vec!["Unmatched3", "TestRatProver", "Unmatched4", "Unmatched5"], + expected_verifiers: vec!["Unmatched3", "TestRatProver", "Unmatched4", "Unmatched5"], + prover_registry: &Default::default(), + ra_timeout: Duration::from_secs(30), + verifier_registry: &Default::default(), + peer_cert: get_test_cert(), + }; + let config = IdscpConfig { + id: "", + ra_config: &ra_config, + resend_timeout: Duration::from_secs(5), + }; + let mut fsm = Fsm::new(&mut daps_driver, &config); + + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::StartHandshake)); + let hello_msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + + // set the supported/expected fields + let expected_drivers = vec!["TestRatProver", "Unmatched1"]; + let supported_drivers = vec!["Unmatched1", "Unmatched2"]; + let dat = if let Some(IdscpMessage_oneof_message::idscpHello(hello)) = &hello_msg.message { + hello.dynamicAttributeToken.as_ref().unwrap().token.clone() + } else { + panic!("expected IdscpHello") + }; + + let idscp_hello = create_idscp_hello(dat, &expected_drivers, &supported_drivers); + let actions = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + idscp_hello.message.unwrap(), + ))); + + assert_eq!(actions.len(), 1); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + let cause_code = match msg.clone().message.unwrap() { + IdscpMessage_oneof_message::idscpClose(close_msg) => close_msg.cause_code, + _ => panic!("expected IdscpClose"), + }; + assert_eq!( + cause_code, + IdscpClose_CloseCause::NO_RA_MECHANISM_MATCH_VERIFIER, + ); +} + +#[test] +fn verifier_error_sequence() { + let mut daps_driver = TestDaps::default(); + let ra_config = AttestationConfig { + supported_provers: vec!["TestRatProver"], + expected_verifiers: vec!["TestRatProver"], + prover_registry: &Default::default(), + ra_timeout: Duration::from_secs(30), + verifier_registry: &Default::default(), + peer_cert: get_test_cert(), + }; + let config = IdscpConfig { + id: "", + ra_config: &ra_config, + resend_timeout: Duration::from_secs(5), + }; + let mut fsm = Fsm::new(&mut daps_driver, &config); + + let actions = fsm.process_event(FsmEvent::FromUpper(UserEvent::StartHandshake)); + let hello_msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + + let idscp_hello = hello_msg.clone().message.unwrap(); + let _ = fsm.process_event(FsmEvent::FromSecureChannel(SecureChannelEvent::Message( + idscp_hello, + ))); + + // TLA Action VerifierError + let actions = fsm.process_event(FsmEvent::FromRaVerifier(RaMessage::Failed())); + assert_eq!(actions.len(), 1); + let msg = match &actions[0] { + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => msg, + _ => panic!("expected Secure Channel message"), + }; + assert!(matches!( + msg.clone().message.unwrap(), + IdscpMessage_oneof_message::idscpClose(_) + )); +} diff --git a/idscp2_core/src/fsm_spec/mod.rs b/idscp2_core/src/fsm_spec/mod.rs new file mode 100644 index 0000000..95b8888 --- /dev/null +++ b/idscp2_core/src/fsm_spec/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod fsm; + +#[cfg(test)] +pub(crate) mod fsm_tests; diff --git a/idscp2_core/src/lib.rs b/idscp2_core/src/lib.rs new file mode 100644 index 0000000..1649577 --- /dev/null +++ b/idscp2_core/src/lib.rs @@ -0,0 +1,759 @@ +extern crate core; + +use std::collections::VecDeque; +use std::convert::TryFrom; +use std::io::Write; +use std::marker::PhantomData; +use std::time::Instant; + +use bytes::{Buf, Bytes, BytesMut}; +use log::{error, info, trace, warn}; +use protobuf::{CodedOutputStream, Message}; + +use crate::api::idscp2_config::IdscpConfig; +use crate::driver::daps_driver::DapsDriver; +use crate::driver::ra_driver::{ + RaManager, RaManagerEvent, RaMessage, RaProverType, RaVerifierType, +}; +use crate::messages::idscpv2_messages::IdscpMessage_oneof_message; +use crate::UserEvent::RequestReattestation; +use chunkvec::ChunkVecBuffer; +use fsm_spec::fsm::*; +use messages::{idscp_message_factory as msg_factory, idscpv2_messages::IdscpMessage}; +use openssl::x509::X509; +use thiserror::Error; + +pub mod api; +mod chunkvec; +pub mod driver; +mod fsm_spec; +mod messages; +pub mod tokio_idscp_connection; +mod util; + +type LengthPrefix = u32; + +const LENGTH_PREFIX_SIZE: usize = std::mem::size_of::(); + +/// The maximum frame size (including length prefix) supported by this implementation +/// +/// This number impacts internal buffer sizes and restricts the maximum data message size. +const MAX_FRAME_SIZE: usize = 4096; + +/// A certificate TODO +pub type Certificate = X509; + +#[derive(Error, Debug)] +pub enum IdscpConnectionError { + // TODO name + #[error("The input could not be processed")] + MalformedInput, + #[error("Action cannot be performed currently, since the connection is in an invalid state")] + NotReady, +} + +pub struct IdscpConnection<'fsm> { + /// debug identifier + id: &'fsm str, + /// state machine + fsm: Fsm<'fsm, 'fsm>, + + // Buffers + /// buffer of partial bytes received from out_conn + read_out_conn_queue: ChunkVecBuffer, + /// length of the next expected, but partially received frame from out_conn + read_out_conn_partial_len: Option, + /// buffer for messages to be sent to out_conn + write_out_conn_queue: Vec, + /// buffer of payload parsed from the connected peer destined for the user + write_in_user_queue: ChunkVecBuffer, + /// buffer of messages destined for the verifier manager + write_ra_verifier_queue: VecDeque>, // TODO ArrayDeque? + /// buffer of messages destined for the prover manager + write_ra_prover_queue: VecDeque>, + + // Timeouts + dat_timeout: Option, + ra_timeout: Option, + reset_data_timeout: Option, +} + +impl<'fsm> IdscpConnection<'fsm> { + /// Returns a new initialized `IdscpConnection` ready to communicate with another peer + pub fn connect(daps_driver: &'fsm mut dyn DapsDriver, config: &'fsm IdscpConfig<'fsm>) -> Self { + let fsm = Fsm::new(daps_driver, config); + let mut conn = Self { + id: config.id, + fsm, + read_out_conn_queue: Default::default(), + read_out_conn_partial_len: None, + write_out_conn_queue: vec![], + write_in_user_queue: Default::default(), + write_ra_verifier_queue: Default::default(), + write_ra_prover_queue: Default::default(), + dat_timeout: None, + ra_timeout: None, + reset_data_timeout: None, + }; + conn.process_event(FsmEvent::FromUpper(UserEvent::StartHandshake)); + info!("{}: Created connection", conn.id); + + conn + } + + /* + pub fn check_drivers(&mut self) { + while let Some(msg) = self.ra_verifier_manager.recv_msg() { + self.process_event(FsmEvent::FromRaVerifier(msg)); + } + while let Some(msg) = self.ra_prover_manager.recv_msg() { + self.process_event(FsmEvent::FromRaProver(msg)); + } + } + */ + + /// Returns `true`, if the connection is in an active communication with another peer, i.e., + /// initialized and not closed. + pub fn is_open(&self) -> bool { + self.fsm.is_open() + } + + // TODO naming partially <> fully + + /// Returns `true`, if the connection is connected and attestation of the connected peer has + /// been completed and not timed out. + /// + /// If true, incoming data from the connected peer can be trusted, but no data can be sent due + /// to the missing attestation of this peer to the connected peer. + pub fn is_partially_attested(&self) -> bool { + self.fsm.is_partially_attested() + } + + /// Returns `true`, if the connection is connected and attestation of both peers to each other + /// has been completed and not timed out. + /// + /// If true, data can be both received and queued for sending. Sending may not succeed due to + /// the connection being blocked. See [IdscpConnection::is_ready_to_send](IdscpConnection::is_ready_to_send). + pub fn is_fully_attested(&self) -> bool { + self.fsm.is_fully_attested() + } + + /// Returns `true`, if the connection is connected, attested, and available for sending data. + /// This may not be the case, if previously sent data has not been acknowledged by the connected + /// peer. + pub fn is_ready_to_send(&self) -> bool { + self.fsm.is_ready_to_send() + } + + /// Checks internal attestation timeouts and issues all actions required to return the internal + /// state machine to a state where data can be transferred again. + fn check_attestation_timeouts(&mut self, now: &Instant) { + // Note: the order is important, since some timeouts may be cancelled out by other timeouts. + // unwrap() is called since we expect the fsm to never fail expired timouts, though this may + // be changed. + if let Some(timeout) = &self.dat_timeout { + if timeout < now { + trace!("{}: check_attestation_timeouts: DAT timed out.", self.id); + self.process_event(FsmEvent::DatExpired); + } + } + if let Some(timeout) = &self.ra_timeout { + if timeout < now { + trace!("{}: check_attestation_timeouts: DAT timed out.", self.id); + self.process_event(FsmEvent::FromUpper(RequestReattestation("timeout"))); + } + } + } + + /// Checks internal resend timeout and issues all actions required to return the internal + /// state machine to a state where data can be transferred again. + fn check_resend_timeout(&mut self, now: &Instant) { + if let Some(timeout) = &self.reset_data_timeout { + if timeout < now { + self.process_event(FsmEvent::ResendTimout); + } + } + } + + /// Passes an event to the internal state machine and executes all actions commanded by the + /// state machine. + fn process_event(&mut self, event: FsmEvent) { + let actions = self.fsm.process_event(event); + + for action in actions { + match action { + // Outgoing messages + FsmAction::SecureChannelAction(SecureChannelAction::Message(msg)) => { + trace!( + "{}: FsmAction SecureChannel: Pushing message to send queue", + self.id + ); + self.write_out_conn_queue.push(msg); + } + FsmAction::NotifyUserData(data) => { + trace!( + "{}: FsmAction NotifyUserData: Pushing message to receive queue", + self.id + ); + self.write_in_user_queue.append(data); + } + + // Timeouts + FsmAction::SetDatTimeout(timeout) => { + trace!( + "{}: FsmAction SetDatTimeout: Set timeout to {}s", + self.id, + timeout.as_secs() + ); + self.dat_timeout = Some(Instant::now() + timeout); + } + FsmAction::StopDatTimeout => { + self.dat_timeout = None; + } + FsmAction::SetRaTimeout(timeout) => { + trace!( + "{}: FsmAction SetRaTimeout: Set timeout to {}s", + self.id, + timeout.as_secs() + ); + self.ra_timeout = Some(Instant::now() + timeout); + } + FsmAction::StopRaTimeout => { + self.ra_timeout = None; + } + FsmAction::SetResendDataTimeout(timeout) => { + self.reset_data_timeout = Some(Instant::now() + timeout); + } + FsmAction::StopResendDataTimeout => { + self.reset_data_timeout = None; + } + + // Prover/Verifier communication + // TODO error management + FsmAction::StartVerifier(id) => { + trace!( + "{}: FsmAction StartVerifier: Starting selected Verifier {}", + self.id, + id + ); + self.write_ra_verifier_queue + .push_back(RaManagerEvent::SelectDriver(id)); + self.write_ra_verifier_queue + .push_back(RaManagerEvent::RunDriver); + } + FsmAction::StartProver(id) => { + trace!( + "{}: FsmAction StartProver: Starting selected Prover {}", + self.id, + id + ); + self.write_ra_prover_queue + .push_back(RaManagerEvent::SelectDriver(id)); + self.write_ra_prover_queue + .push_back(RaManagerEvent::RunDriver); + } + FsmAction::RestartVerifier => { + trace!("{}: FsmAction RestartVerifier", self.id); + self.write_ra_verifier_queue + .push_back(RaManagerEvent::RunDriver); + } + FsmAction::RestartProver => { + trace!("{}: FsmAction RestartProver", self.id); + self.write_ra_prover_queue + .push_back(RaManagerEvent::RunDriver); + } + FsmAction::ToVerifier(msg) => { + trace!("{}: FsmAction ToVerifier", self.id); + self.write_ra_verifier_queue + .push_back(RaManagerEvent::RawData(msg, PhantomData)); + } + FsmAction::ToProver(msg) => { + trace!("{}: FsmAction ToProver", self.id); + self.write_ra_prover_queue + .push_back(RaManagerEvent::RawData(msg, PhantomData)); + } + + FsmAction::None => { + #[cfg(debug_assertions)] + unimplemented!("Tasked to perform FsmAction::None"); + } + } + } + } + + /// Consumes `buf` and attempts to parse at least one message frame from out_conn. + /// Remaining bytes belonging to partial message frames are cached and parsed in a future call to `read`. + /// This function checks potential channel timeouts during processing. + /// + /// # Return + /// + /// The number of bytes parsed in total. + /// This may include bytes parsed from previous calls to `read`. + /// + /// # Errors + /// + /// If this function encounters an error during parsing, an [`IdscpConnectionError::MalformedInput`] error is returned. + /// If the state of the secure channel can no longer be trusted and messages received, + /// + pub fn read_out_conn(&mut self, buf: BytesMut) -> Result { + trace!( + "{}: read_out_conn: Appending {} byte to read_out_queue", + self.id, + buf.len() + ); + self.read_out_conn_queue.append(buf); + self.parse_read_out_queue(0) + } + + /// Recursively parses and processes messages from the internal read buffer + fn parse_read_out_queue(&mut self, parsed_bytes: usize) -> Result { + let msg_length: LengthPrefix = match self.read_out_conn_partial_len.take() { + Some(len) => len, + None => { + if self.read_out_conn_queue.len() >= LENGTH_PREFIX_SIZE { + self.read_out_conn_queue.get_u32() + } else { + return Ok(parsed_bytes); + } + } + }; + + if self.read_out_conn_queue.remaining() < msg_length as usize { + // not enough bytes available to parse message + self.read_out_conn_partial_len = Some(msg_length); + return Ok(parsed_bytes); + } + + let mut frame = self.read_out_conn_queue.pop().unwrap(); + + // extend frame, can be zero-copy if the original buffer was contiguous + while frame.len() < msg_length as usize { + let b = self.read_out_conn_queue.pop().unwrap(); + frame.unsplit(b); + } + + // reinsert the remainder to the read queue + if frame.len() > msg_length as usize { + let rem = frame.split_off(msg_length as usize); + self.read_out_conn_queue.insert_front(rem); + } + + if let Ok(msg) = IdscpMessage::parse_from_carllerche_bytes(&frame.freeze()) { + let m = msg.compute_size(); + assert_eq!(m, msg_length); + let msg_len = usize::try_from(m).unwrap(); + + // match if data? + let msg = match msg.message.unwrap() { + IdscpMessage_oneof_message::idscpData(data_msg) => { + trace!( + "{}: read_out_conn: Parsed {} byte data message with {} byte payload", + self.id, + msg_len, + data_msg.data.len() + ); + // check timeouts and continue only when ready + let now = Instant::now(); + self.check_attestation_timeouts(&now); + if !self.is_partially_attested() { + warn!( + "{}: read_out_conn: Connection not partially attested, discarding data", + self.id + ); + return Err(IdscpConnectionError::NotReady); + } + IdscpMessage_oneof_message::idscpData(data_msg) + } + msg => { + trace!( + "{}: read_out_conn: Parsed {} byte non-data message", + self.id, + msg_len + ); + msg + } + }; + + let event = FsmEvent::FromSecureChannel(SecureChannelEvent::Message(msg)); + + self.process_event(event); + self.parse_read_out_queue(LENGTH_PREFIX_SIZE + msg_len) + } else { + Err(IdscpConnectionError::MalformedInput) + } + } + + /// Synchronously returns whether the connection has buffered message frames to be sent to + /// the connected peer. + pub fn wants_write_out_conn(&self) -> bool { + !self.write_out_conn_queue.is_empty() + } + + /// Writes the buffered message frames destined to the connected peer to `out`. + pub fn write_out_conn(&mut self, out: &mut dyn Write) -> std::io::Result { + let mut written = 0usize; + + if self.wants_write_out_conn() { + // Workaround: serialize without the wrapper method of IdscpMessage to initialize and + // use only a single buffer for length delimiters and messages + let mut os = CodedOutputStream::new(out); + + for msg in self.write_out_conn_queue.drain(..) { + let msg_length: u32 = msg.compute_size(); + os.write_raw_bytes(msg_length.to_be_bytes().as_slice())?; + written += 4; + msg.check_initialized()?; + msg.write_to_with_cached_sizes(&mut os)?; + written += msg_length as usize; + } + os.flush()?; + } + trace!( + "{}: write_out_conn: Write {} byte to out_conn", + self.id, + written + ); + Ok(written) + } + + /// Reads data from in_user destined to out_conn. + /// This function checks potential channel timeouts during processing. + /// + /// # Return + /// + /// The number of bytes to be sent in total. + /// This includes message frame headers. + /// + /// # Errors + /// + /// If `data` is too large to be sent, an [`IdscpConnectionError::MalformedInput`] error is returned. + /// Returns [`IdscpConnectionError::NotReady`], if the state of the secure channel can no longer be trusted and messages not sent. + /// + pub fn read_in_user(&mut self, data: Bytes) -> Result { + // the empty vector would not have worked, since the protobuf-encoded len of data also has + // variable length that needs to be accounted for + // no copy here, since data is ref-counted and dropped + let msg: IdscpMessage = msg_factory::create_idscp_data(data.clone(), true); // create empty package to determine size overhead of protobuf encapsulation + let frame_size = usize::try_from(msg.compute_size()).unwrap(); + let buffer_space: usize = MAX_FRAME_SIZE; + + // TODO maybe split data here to MAX_FRAME_SIZE + if frame_size > buffer_space { + error!("{}: read_in_user: Malformed input by in_user", self.id); + return Err(IdscpConnectionError::MalformedInput); + } + + let n = data.len(); + + // check timeouts and continue only when ready + let now = Instant::now(); + warn!("{}: attested {}", self.id, self.is_fully_attested()); + self.check_attestation_timeouts(&now); + if !self.is_fully_attested() { + warn!( + "{}: read_in_user: Connection not attested, discarding data", + self.id + ); + return Err(IdscpConnectionError::NotReady); + } + self.check_resend_timeout(&now); + if !self.is_ready_to_send() { + warn!( + "{}: read_in_user: Connection attested, but not ready to send, discarding data", + self.id + ); + return Err(IdscpConnectionError::NotReady); + } + + trace!("{}: read_in_user: Parsing {} byte data", self.id, n); + self.process_event(FsmEvent::FromUpper(UserEvent::Data(data))); + Ok(n) + } + + /// Returns optional data received from the out_conn + pub fn write_in_user(&mut self) -> Option { + trace!( + "{}: write_in_user: Returning message: {}", + self.id, + !self.write_in_user_queue.is_empty() + ); + self.write_in_user_queue.pop() + } + + /// Reads a message from the verifier + pub fn read_ra_verifier_manager( + &mut self, + msg: RaMessage, + ) -> Result<(), IdscpConnectionError> { + trace!("{}: read_ra_verifier_manager: Reading message", self.id); + self.process_event(FsmEvent::FromRaVerifier(msg)); + Ok(()) + } + + pub fn wants_write_ra_verifier_manager(&self) -> bool { + !self.write_ra_verifier_queue.is_empty() + } + + /// Returns instructions for the verifier manager + pub fn write_ra_verifier_manager(&mut self) -> Option> { + trace!( + "{}: write_ra_verifier_manager: Returning event: {}", + self.id, + !self.write_ra_verifier_queue.is_empty() + ); + self.write_ra_verifier_queue.pop_front() + } + + /// Reads a message from the prover + pub fn read_ra_prover_manager( + &mut self, + msg: RaMessage, + ) -> Result<(), IdscpConnectionError> { + trace!("{}: read_ra_prover_manager: Reading message", self.id); + self.process_event(FsmEvent::FromRaProver(msg)); + Ok(()) + } + + pub fn wants_write_ra_prover_manager(&self) -> bool { + !self.write_ra_prover_queue.is_empty() + } + + /// Returns instructions for the prover manager + pub fn write_ra_prover_manager(&mut self) -> Option> { + trace!( + "{}: write_ra_prover_manager: Returning event: {}", + self.id, + !self.write_ra_prover_queue.is_empty() + ); + self.write_ra_prover_queue.pop_front() + } +} + +#[cfg(test)] +mod tests { + use std::ops::Deref; + use std::sync::Arc; + use std::time::Duration; + + use crate::api::idscp2_config::AttestationConfig; + use crate::driver::ra_driver::tests::{ + get_test_cert, TestProver, TestProverNeverDone, TestVerifier, TEST_PROVER_ID, + TEST_VERIFIER_ID, + }; + use crate::driver::ra_driver::{RaProverType, RaRegistry, RaVerifierType}; + use bytes::{BufMut, BytesMut}; + use fsm_spec::fsm_tests::TestDaps; + use lazy_static::lazy_static; + + #[macro_export] + macro_rules! test_begin { + () => { + let _ = env_logger::builder().is_test(true).try_init(); + println!("Test started."); + }; + } + + #[macro_export] + macro_rules! test_finalize { + () => { + println!("Test done.") + }; + } + + use super::*; + + lazy_static! { + pub(crate) static ref TEST_RA_VERIFIER_REGISTRY: RaRegistry = { + let mut registry = RaRegistry::new(); + let _ = registry.register_driver(Arc::new(TestVerifier {})); + registry + }; + pub(crate) static ref TEST_RA_PROVER_REGISTRY: RaRegistry = { + let mut registry = RaRegistry::new(); + let _ = registry.register_driver(Arc::new(TestProver {})); + registry + }; + pub(crate) static ref TEST_RA_PROVER_NEVER_DONE_REGISTRY: RaRegistry = { + let mut registry = RaRegistry::new(); + let _ = registry.register_driver(Arc::new(TestProverNeverDone {})); + registry + }; + pub(crate) static ref TEST_RA_CONFIG: AttestationConfig<'static> = AttestationConfig { + supported_provers: vec![TEST_PROVER_ID], + expected_verifiers: vec![TEST_VERIFIER_ID], + prover_registry: &TEST_RA_PROVER_REGISTRY, + ra_timeout: Duration::from_secs(20), + verifier_registry: &TEST_RA_VERIFIER_REGISTRY, + peer_cert: get_test_cert(), + }; + pub(crate) static ref TEST_RA_PROVER_NEVER_DONE_CONFIG: AttestationConfig<'static> = + AttestationConfig { + supported_provers: vec![TEST_PROVER_ID], + expected_verifiers: vec![TEST_VERIFIER_ID], + prover_registry: &TEST_RA_PROVER_NEVER_DONE_REGISTRY, + ra_timeout: Duration::from_secs(20), + verifier_registry: &TEST_RA_VERIFIER_REGISTRY, + peer_cert: get_test_cert(), + }; + pub(crate) static ref TEST_CONFIG_ALICE: IdscpConfig<'static> = IdscpConfig { + id: "alice", + resend_timeout: Duration::from_secs(1), + ra_config: &TEST_RA_CONFIG, + }; + pub(crate) static ref TEST_CONFIG_BOB: IdscpConfig<'static> = IdscpConfig { + id: "bob", + resend_timeout: Duration::from_secs(1), + ra_config: &TEST_RA_CONFIG, + }; + pub(crate) static ref TEST_PROVER_NEVER_DONE_CONFIG_BOB: IdscpConfig<'static> = + IdscpConfig { + id: "bob", + resend_timeout: Duration::from_secs(1), + ra_config: &TEST_RA_PROVER_NEVER_DONE_CONFIG, + }; + } + + pub(crate) struct IdscpConnectionHandle<'a> { + connection: IdscpConnection<'a>, + out_conn_channel: BytesMut, + } + + fn read_channel(peer2: &mut IdscpConnection, in_channel: &mut BytesMut) { + let mut chunk_start = 0; + while chunk_start < in_channel.len() { + match peer2.read_out_conn(in_channel.split_to(in_channel.len())) { + Ok(n) => chunk_start += n, + Err(e) => { + panic!("{:?}", e) + } + } + } + } + + fn emulate_ra_manager(event: RaManagerEvent) -> Option> { + match event { + RaManagerEvent::RunDriver => Some(RaMessage::Ok(Bytes::from(""))), + _ => None, + } + } + + fn spawn_peers<'a>( + daps_driver_1: &'a mut dyn DapsDriver, + daps_driver_2: &'a mut dyn DapsDriver, + ) -> std::io::Result<(IdscpConnectionHandle<'a>, IdscpConnectionHandle<'a>)> { + let mut connection_1 = IdscpConnection::connect(daps_driver_1, &TEST_CONFIG_ALICE); + let mut connection_2 = IdscpConnection::connect(daps_driver_2, &TEST_CONFIG_BOB); + + const MTU: usize = 1500; // Maximum Transmission Unit + let mut channel_1_2 = BytesMut::with_capacity(MTU); + let mut channel_2_1 = BytesMut::with_capacity(MTU); + + while !connection_1.is_ready_to_send() || !connection_2.is_ready_to_send() { + while connection_1.wants_write_out_conn() { + let mut writer = channel_1_2.writer(); + connection_1.write_out_conn(&mut writer).unwrap(); + channel_1_2 = writer.into_inner(); + } + while let Some(event) = connection_1.write_ra_verifier_manager() { + if let Some(msg) = emulate_ra_manager(event) { + connection_1.read_ra_verifier_manager(msg).unwrap(); + } + } + while let Some(event) = connection_1.write_ra_prover_manager() { + if let Some(msg) = emulate_ra_manager(event) { + connection_1.read_ra_prover_manager(msg).unwrap(); + } + } + + read_channel(&mut connection_2, &mut channel_1_2); + channel_2_1.reserve(MTU); + + while connection_2.wants_write_out_conn() { + let mut writer = channel_2_1.writer(); + connection_2.write_out_conn(&mut writer).unwrap(); + channel_2_1 = writer.into_inner(); + } + while let Some(event) = connection_2.write_ra_verifier_manager() { + if let Some(msg) = emulate_ra_manager(event) { + connection_2.read_ra_verifier_manager(msg).unwrap(); + } + } + while let Some(event) = connection_2.write_ra_prover_manager() { + if let Some(msg) = emulate_ra_manager(event) { + connection_2.read_ra_prover_manager(msg).unwrap(); + } + } + + read_channel(&mut connection_1, &mut channel_2_1); + channel_2_1.reserve(MTU); + } + + Ok(( + IdscpConnectionHandle { + connection: connection_1, + out_conn_channel: channel_1_2, + }, + IdscpConnectionHandle { + connection: connection_2, + out_conn_channel: channel_2_1, + }, + )) + } + + #[test] + fn establish_connection() { + test_begin!(); + let mut daps_driver_1 = TestDaps::default(); + let mut daps_driver_2 = TestDaps::default(); + let (peer1, peer2) = spawn_peers(&mut daps_driver_1, &mut daps_driver_2).unwrap(); + + assert!(peer1.connection.is_ready_to_send() && peer1.out_conn_channel.is_empty()); + assert!(peer2.connection.is_ready_to_send() && peer2.out_conn_channel.is_empty()); + test_finalize!(); + } + + #[test] + fn transmit_data() { + test_begin!(); + let mut daps_driver_1 = TestDaps::default(); + let mut daps_driver_2 = TestDaps::default(); + let (mut peer1, mut peer2) = spawn_peers(&mut daps_driver_1, &mut daps_driver_2).unwrap(); + + const MSG: &[u8; 11] = b"hello world"; + + // send message from peer1 to peer2 + let n = peer1 + .connection + .read_in_user(Bytes::from(MSG.as_slice())) + .unwrap(); + assert!(n == 11 && peer1.connection.wants_write_out_conn()); + let mut n = 0; + while peer1.connection.wants_write_out_conn() { + let mut writer = peer1.out_conn_channel.writer(); + n += peer1.connection.write_out_conn(&mut writer).unwrap(); + peer1.out_conn_channel = writer.into_inner(); + } + assert!(n > 0 && n == peer1.out_conn_channel.len()); + + // peer2 reads from channel + read_channel(&mut peer2.connection, &mut peer1.out_conn_channel); + + // receive msg and compare from peer2 + let recv_msg = peer2.connection.write_in_user().unwrap(); + assert_eq!(MSG, recv_msg.deref()); + + // peer2 must reply with an ack + let mut n = 0; + while peer2.connection.wants_write_out_conn() { + let mut writer = peer2.out_conn_channel.writer(); + n += peer2.connection.write_out_conn(&mut writer).unwrap(); + peer2.out_conn_channel = writer.into_inner(); + } + assert!(n > 0 && n == peer2.out_conn_channel.len()); + + // peer2 reads from channel + read_channel(&mut peer1.connection, &mut peer2.out_conn_channel); + + test_finalize!(); + } +} diff --git a/idscp2_core/src/messages/.gitignore b/idscp2_core/src/messages/.gitignore new file mode 100644 index 0000000..c719406 --- /dev/null +++ b/idscp2_core/src/messages/.gitignore @@ -0,0 +1 @@ +idscpv2_messages.rs diff --git a/idscp2_core/src/messages/idscp_message_factory.rs b/idscp2_core/src/messages/idscp_message_factory.rs new file mode 100644 index 0000000..212f9f4 --- /dev/null +++ b/idscp2_core/src/messages/idscp_message_factory.rs @@ -0,0 +1,122 @@ +use super::idscpv2_messages::{ + IdscpAck, IdscpClose, IdscpClose_CloseCause, IdscpDat, IdscpDatExpired, IdscpData, IdscpHello, + IdscpMessage, IdscpRaProver, IdscpRaVerifier, IdscpReRa, +}; +use crate::driver::ra_driver::DriverId; +use bytes::Bytes; +use protobuf::SingularPtrField; +use std::iter::FromIterator; + +pub(crate) fn create_idscp_hello( + dat: Bytes, + expected_rat_suite: &[DriverId], + supported_rat_suite: &[DriverId], +) -> IdscpMessage { + let mut idscp_dat = IdscpDat::new(); + idscp_dat.token = dat; + + let mut hello = IdscpHello::new(); + hello.version = 2; + hello.dynamicAttributeToken = SingularPtrField::some(idscp_dat); + hello.expectedRaSuite = + protobuf::RepeatedField::from_iter(expected_rat_suite.iter().map(|&id| String::from(id))); + hello.supportedRaSuite = + protobuf::RepeatedField::from_iter(supported_rat_suite.iter().map(|&id| String::from(id))); + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpHello(hello); + idscp +} + +pub(crate) fn create_idscp_close(code: IdscpClose_CloseCause, msg: &'static str) -> IdscpMessage { + let mut close = IdscpClose::new(); + close.cause_code = code; + close.cause_msg = String::from(msg); + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpClose(close); + idscp +} + +pub(crate) fn create_idscp_ra_prover(data: Bytes) -> IdscpMessage { + let mut idscp_p = IdscpRaProver::new(); + idscp_p.data = data; + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpRaProver(idscp_p); + idscp +} + +pub(crate) fn create_idscp_ra_verifier(data: Bytes) -> IdscpMessage { + let mut idscp_v = IdscpRaVerifier::new(); + idscp_v.data = data; + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpRaVerifier(idscp_v); + idscp +} + +pub(crate) fn create_idscp_dat_exp() -> IdscpMessage { + let mut idscp = IdscpMessage::new(); + idscp.set_idscpDatExpired(IdscpDatExpired::new()); + idscp +} + +pub(crate) fn create_idscp_dat(dat: Bytes) -> IdscpMessage { + let mut idscp_dat = IdscpDat::new(); + idscp_dat.token = dat; + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpDat(idscp_dat); + idscp +} + +pub(crate) fn create_idscp_re_rat(cause: &'static str) -> IdscpMessage { + let mut idscp_rerat = IdscpReRa::new(); + idscp_rerat.cause = String::from(cause); + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpReRa(idscp_rerat); + idscp +} + +/* just commenting out because i dont need it right now +pub(crate) fn create_idscp_rat_prover(data: Bytes) -> IdscpMessage { + let mut idscp_p = IdscpRatProver::new(); + idscp_p.data = Bytes::from(data); + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpRatProver(idscp_p); + idscp +} + +pub(crate) fn create_idscp_rat_verifier(data: Vec) -> IdscpMessage { + let mut idscp_v = IdscpRatVerifier::new(); + idscp_v.data = Bytes::from(data); + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpRatVerifier(idscp_v); + idscp +} + */ + +pub(crate) fn create_idscp_data(data: Bytes, ack_bit: bool) -> IdscpMessage { + let mut idscp_data = IdscpData::new(); + idscp_data.data = data; + idscp_data.alternating_bit = ack_bit; + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpData(idscp_data); + + idscp +} + +pub(crate) fn create_idscp_ack(ack_bit: bool) -> IdscpMessage { + let mut idscp_ack = IdscpAck::new(); + idscp_ack.alternating_bit = ack_bit; + + let mut idscp = IdscpMessage::new(); + idscp.set_idscpAck(idscp_ack); + + idscp +} diff --git a/idscp2_core/src/messages/idscpv2_messages.proto b/idscp2_core/src/messages/idscpv2_messages.proto new file mode 100644 index 0000000..6b0e8a4 --- /dev/null +++ b/idscp2_core/src/messages/idscpv2_messages.proto @@ -0,0 +1,85 @@ +// Copyright (c) 2020, Fraunhofer AISEC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +//IDSCP message frame +message IdscpMessage { + // One of the following will be filled in. + oneof message { + IdscpHello idscpHello = 1; + IdscpClose idscpClose = 2; + IdscpDatExpired idscpDatExpired = 3; + IdscpDat idscpDat = 4; + IdscpReRa idscpReRa = 5; + IdscpRaProver idscpRaProver = 6; + IdscpRaVerifier idscpRaVerifier = 7; + IdscpData idscpData = 8; + IdscpAck idscpAck = 9; + } +} + + +//IDSCP messages +message IdscpHello { + int32 version = 1; //IDSCP protocol version + IdscpDat dynamicAttributeToken = 2; //initial dynamicAttributeToken + repeated string supportedRaSuite = 3; //RemoteAttestationCipher prover + repeated string expectedRaSuite = 4; //RemoteAttestationCipher verifier +} + +message IdscpClose { + + enum CloseCause { + USER_SHUTDOWN = 0; + TIMEOUT = 1; + ERROR = 2; + NO_VALID_DAT = 3; + NO_RA_MECHANISM_MATCH_PROVER = 4; + NO_RA_MECHANISM_MATCH_VERIFIER = 5; + RA_PROVER_FAILED = 6; + RA_VERIFIER_FAILED = 7; + } + + CloseCause cause_code = 1; + string cause_msg = 2; +} + +message IdscpDatExpired { //request new dynamicAttributeToken +} + +message IdscpDat { + bytes token = 1; +} + +message IdscpReRa { //request new remoteAttestation + string cause = 1; //optional +} + +message IdscpRaProver { + bytes data = 1; +} + +message IdscpRaVerifier { + bytes data = 1; +} + +message IdscpData { + bytes data = 1; + bool alternating_bit = 2; +} + +message IdscpAck { + bool alternating_bit = 1; +} \ No newline at end of file diff --git a/idscp2_core/src/messages/mod.rs b/idscp2_core/src/messages/mod.rs new file mode 100644 index 0000000..7144d74 --- /dev/null +++ b/idscp2_core/src/messages/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod idscp_message_factory; +pub(crate) mod idscpv2_messages; diff --git a/idscp2_core/src/tokio_idscp_connection.rs b/idscp2_core/src/tokio_idscp_connection.rs new file mode 100644 index 0000000..2cf42fe --- /dev/null +++ b/idscp2_core/src/tokio_idscp_connection.rs @@ -0,0 +1,934 @@ +use super::IdscpConnection; +use crate::{ + DapsDriver, IdscpConfig, IdscpConnectionError, RaManager, RaProverType, RaVerifierType, + MAX_FRAME_SIZE, +}; +use bytes::{Bytes, BytesMut}; +use log::Level::Warn; +use log::{info, log_enabled, trace, warn}; +use std::io; +use std::io::ErrorKind; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::select; + +impl From for std::io::Error { + fn from(e: IdscpConnectionError) -> Self { + match e { + IdscpConnectionError::MalformedInput => { + io::Error::new(ErrorKind::InvalidData, "Malformed input from out_conn") + } + IdscpConnectionError::NotReady => { + io::Error::new(ErrorKind::WouldBlock, "IdscpConnection not ready") + } + } + } +} + +pub struct AsyncIdscpListener { + tcp_listener: TcpListener, +} + +impl AsyncIdscpListener { + pub async fn bind(addr: &'static str) -> std::io::Result { + let tcp_listener = TcpListener::bind(addr).await?; + info!("Binding Listener to {}", addr); + Ok(AsyncIdscpListener { tcp_listener }) + } + + pub async fn accept<'a>( + &self, + daps_driver: &'a mut dyn DapsDriver, + config: &'a IdscpConfig<'a>, + ) -> std::io::Result> { + let (mut tcp_stream, _) = self.tcp_listener.accept().await.unwrap(); + info!("Accepted incoming TCP connection, initializing connection"); + let mut ra_prover_manager = RaManager::new( + config.ra_config.prover_registry, + &config.ra_config.peer_cert, + ); + let mut ra_verifier_manager = RaManager::new( + config.ra_config.verifier_registry, + &config.ra_config.peer_cert, + ); + + let mut connection = IdscpConnection::connect(daps_driver, config); + info!("{}: Starting handshake", connection.id); + AsyncIdscpConnection::start_handshake( + &mut connection, + &mut ra_verifier_manager, + &mut ra_prover_manager, + &mut tcp_stream, + ) + .await?; + + Ok(AsyncIdscpConnection { + tcp_stream, + connection, + ra_verifier_manager, + ra_prover_manager, + }) + } +} + +pub struct AsyncIdscpConnection<'fsm> { + tcp_stream: TcpStream, + connection: IdscpConnection<'fsm>, + /// attestation manager launching and communicating with the ra_verifier + ra_verifier_manager: RaManager<'fsm, RaVerifierType>, + /// attestation manager launching and communicating with the ra_prover + ra_prover_manager: RaManager<'fsm, RaProverType>, +} + +macro_rules! timed_out { + ($opt_timeout:expr, $future:expr) => { + if let Some(duration) = $opt_timeout { + tokio::time::timeout(duration, $future) + .await + .unwrap_or_else(|_| { + Err(std::io::Error::new( + ErrorKind::Interrupted, + "action timed out", + )) + }) + } else { + $future.await + } + }; +} + +impl<'fsm> AsyncIdscpConnection<'fsm> { + pub async fn connect( + addr: &'static str, + daps_driver: &'fsm mut dyn DapsDriver, + config: &'fsm IdscpConfig<'fsm>, + ) -> std::io::Result> { + let mut tcp_stream = TcpStream::connect(addr).await?; + + let mut connection = IdscpConnection::connect(daps_driver, config); + + let mut ra_prover_manager = RaManager::new( + config.ra_config.prover_registry, + &config.ra_config.peer_cert, + ); + let mut ra_verifier_manager = RaManager::new( + config.ra_config.verifier_registry, + &config.ra_config.peer_cert, + ); + + Self::start_handshake( + &mut connection, + &mut ra_verifier_manager, + &mut ra_prover_manager, + &mut tcp_stream, + ) + .await?; + + Ok(AsyncIdscpConnection { + tcp_stream, + connection, + ra_prover_manager, + ra_verifier_manager, + }) + } + + // TODO unify with recover + async fn start_handshake<'a>( + connection: &mut IdscpConnection<'a>, + ra_verifier_manager: &mut RaManager<'a, RaVerifierType>, + ra_prover_manager: &mut RaManager<'a, RaProverType>, + stream: &mut TcpStream, + ) -> std::io::Result<()> { + let (mut reader, mut writer) = stream.split(); + let res = Self::recover( + connection, + ra_verifier_manager, + ra_prover_manager, + &mut reader, + &mut writer, + ) + .await; + if log_enabled!(Warn) { + if connection.is_ready_to_send() { + info!( + "{}: Connection established, ready to send data.", + connection.id + ) + } else { + warn!( + "{}: Connection established, not ready to send data!", + connection.id + ); + } + } + res + } + + async fn read<'a, R: AsyncReadExt + Unpin>( + connection: &mut IdscpConnection<'a>, + reader: &mut R, + ) -> std::io::Result> { + // TODO no nontransparent allocation during read function, maybe replace with arena? + let mut buf: BytesMut = BytesMut::with_capacity(MAX_FRAME_SIZE); + reader.read_buf(&mut buf).await?; // initial read "Copy" + // TODO error type + if buf.is_empty() { + trace!( + "{}: read: Read {} bytes from io::Reader, skipping connection", + connection.id, + buf.len() + ); + Ok(Ok(0)) + } else { + trace!( + "{}: read: Read {} bytes from io::Reader, passing to connection", + connection.id, + buf.len() + ); + Ok(connection.read_out_conn(buf)) + } + } + + async fn write<'a, W: AsyncWriteExt + Unpin>( + connection: &mut IdscpConnection<'a>, + writer: &mut W, + ) -> std::io::Result<()> { + /* FIXME this is triple-buffered: messages buffered in the connection are written via an + internal buffer to buf which implements std::io::Write and from there to the actual + async writer + */ + let mut buf = Vec::new(); // TODO: use a statically sized array here? + let _n = connection.write_out_conn(&mut buf)?; + writer.write_all(&buf).await + } + + pub fn is_connected(&self) -> bool { + self.connection.is_open() + } + + async fn recover<'a, R: AsyncReadExt + Unpin, W: AsyncWriteExt + Unpin>( + connection: &mut IdscpConnection<'a>, + ra_verifier_manager: &mut RaManager<'a, RaVerifierType>, + ra_prover_manager: &mut RaManager<'a, RaProverType>, + reader: &mut R, + writer: &mut W, + ) -> std::io::Result<()> { + loop { + // check outgoing channels + while connection.wants_write_out_conn() { + Self::write(connection, writer).await?; + } + while let Some(event) = connection.write_ra_verifier_manager() { + ra_verifier_manager.process_event(event).unwrap(); + } + while let Some(event) = connection.write_ra_prover_manager() { + ra_prover_manager.process_event(event).unwrap(); + } + + // break if ready + if connection.is_ready_to_send() { + break; + } + + // check incoming channels + /* + let msg_from_ra_verifier = ra_verifier_manager.recv_msg(); + let msg_from_ra_prover = ra_prover_manager.recv_msg(); + select! { + _ = Self::read(connection, reader) => { + // read completed + } + msg = msg_from_ra_verifier.unwrap(), if msg_from_ra_verifier.is_some() => { + connection.read_ra_verifier_manager(msg).unwrap(); + } + msg = msg_from_ra_prover.unwrap(), if msg_from_ra_prover.is_some() => { + connection.read_ra_prover_manager(msg).unwrap(); + } + }; + */ + + // since guards don't seem to allow lazy execution, use this workaround: + // TODO process result + match (ra_verifier_manager.recv_msg(), ra_prover_manager.recv_msg()) { + (Some(msg_from_ra_verifier), Some(msg_from_ra_prover)) => { + select! { + _ = Self::read(connection, reader) => { + // read completed + } + msg = msg_from_ra_verifier => { + connection.read_ra_verifier_manager(msg).unwrap(); + } + msg = msg_from_ra_prover => { + connection.read_ra_prover_manager(msg).unwrap(); + } + }; + } + (Some(msg_from_ra_verifier), None) => { + select! { + _ = Self::read(connection, reader) => { + // read completed + } + msg = msg_from_ra_verifier => { + connection.read_ra_verifier_manager(msg).unwrap(); + } + }; + } + (None, Some(msg_from_ra_prover)) => { + select! { + _ = Self::read(connection, reader) => { + // read completed + } + msg = msg_from_ra_prover => { + connection.read_ra_prover_manager(msg).unwrap(); + } + }; + } + (None, None) => { + Self::read(connection, reader).await?.unwrap(); // TODO + } + } + } + Ok(()) + } + + #[inline] + async fn do_send_to<'a, R: AsyncReadExt + Unpin, W: AsyncWriteExt + Unpin>( + connection: &mut IdscpConnection<'a>, + ra_verifier_manager: &mut RaManager<'a, RaVerifierType>, + ra_prover_manager: &mut RaManager<'a, RaProverType>, + reader: &mut R, + writer: &mut W, + data: Bytes, + ) -> std::io::Result { + let res = Self::do_try_send_to( + connection, + ra_verifier_manager, + ra_prover_manager, + reader, + writer, + data, + ) + .await?; + trace!("{}: send: Recovering", connection.id,); + Self::recover( + connection, + ra_verifier_manager, + ra_prover_manager, + reader, + writer, + ) + .await?; + Ok(res) + } + + /// Sends `data` to the connected peer and awaits acknowledgement. + /// If `timeout` is supplied, the operation may terminate during operation if the timeout elapses. + /// In this case, there is no guarantee that the data has been delivered, but the connection + /// should be able to recover. + pub async fn send(&mut self, data: Bytes, timeout: Option) -> std::io::Result { + let (mut reader, mut writer) = self.tcp_stream.split(); + timed_out!( + timeout, + Self::do_send_to( + &mut self.connection, + &mut self.ra_verifier_manager, + &mut self.ra_prover_manager, + &mut reader, + &mut writer, + data + ) + ) + } + + #[allow(dead_code)] + pub(crate) async fn send_to( + &mut self, + reader: &mut R, + writer: &mut W, + data: Bytes, + timeout: Option, + ) -> std::io::Result { + timed_out!( + timeout, + Self::do_send_to( + &mut self.connection, + &mut self.ra_verifier_manager, + &mut self.ra_prover_manager, + reader, + writer, + data + ) + ) + } + + #[inline] + async fn do_try_send_to<'a, R: AsyncReadExt + Unpin, W: AsyncWriteExt + Unpin>( + connection: &mut IdscpConnection<'a>, + ra_verifier_manager: &mut RaManager<'a, RaVerifierType>, + ra_prover_manager: &mut RaManager<'a, RaProverType>, + reader: &mut R, + writer: &mut W, + data: Bytes, + ) -> std::io::Result { + trace!( + "{}: send: Sending {} byte of payload.", + connection.id, + data.len() + ); + loop { + trace!("{}: send: Looping read_in_user", connection.id); + match connection.read_in_user(data.clone()) { + Ok(n) => { + // anticipative write + Self::write(connection, writer).await?; + break Ok(n); + } + Err(IdscpConnectionError::MalformedInput) => { + break Err(std::io::Error::new( + ErrorKind::OutOfMemory, + "data too large", + )); + } + Err(IdscpConnectionError::NotReady) => { + Self::recover( + connection, + ra_verifier_manager, + ra_prover_manager, + reader, + writer, + ) + .await?; + } + } + } + } + + /// Sends `data` to the connected peer without awaiting acknowledgement. + /// If `timeout` is supplied, the operation may terminate during operation if the timeout elapses. + /// In this case, there is no guarantee that the data has been delivered, but the connection + /// should be able to recover. + pub async fn try_send( + &mut self, + data: Bytes, + timeout: Option, + ) -> std::io::Result { + let (mut reader, mut writer) = self.tcp_stream.split(); + timed_out!( + timeout, + Self::do_try_send_to( + &mut self.connection, + &mut self.ra_verifier_manager, + &mut self.ra_prover_manager, + &mut reader, + &mut writer, + data + ) + ) + } + + #[allow(dead_code)] + pub(crate) async fn try_send_to( + &mut self, + reader: &mut R, + writer: &mut W, + data: Bytes, + timeout: Option, + ) -> std::io::Result { + timed_out!( + timeout, + Self::do_try_send_to( + &mut self.connection, + &mut self.ra_verifier_manager, + &mut self.ra_prover_manager, + reader, + writer, + data + ) + ) + } + + /* + #[inline] + async fn do_try_send_to<'a, W: AsyncWriteExt + Unpin>( + connection: &mut IdscpConnection<'a>, + writer: &mut W, + data: Bytes, + ) -> std::io::Result { + trace!( + "{}: try_send: Sending {} byte of payload.", + connection.id, + data.len() + ); + // TODO error-type + let n = connection + .read_in_user(data) + .map_err(|_e| std::io::Error::new(ErrorKind::Other, ""))?; + Self::write(connection, writer).await?; + Ok(n) + } + + pub async fn try_send(&mut self, data: Bytes) -> std::io::Result { + Self::do_try_send_to(&mut self.connection, &mut self.tcp_stream, data).await + } + + pub(crate) async fn try_send_to( + &mut self, + writer: &mut W, + data: Bytes, + ) -> std::io::Result { + Self::do_try_send_to(&mut self.connection, writer, data).await + } + */ + + async fn do_recv_from<'a, R: AsyncReadExt + Unpin, W: AsyncWriteExt + Unpin>( + connection: &mut IdscpConnection<'a>, + ra_verifier_manager: &mut RaManager<'a, RaVerifierType>, + ra_prover_manager: &mut RaManager<'a, RaProverType>, + reader: &mut R, + writer: &mut W, + ) -> std::io::Result { + trace!("{}: recv: Requesting data from connection.", connection.id); + match connection.write_in_user() { + Some(msg) => { + // fast path + Ok(msg) + } + None => { + // slow path + trace!( + "{}: recv: No cached messages, entering slow path.", + connection.id + ); + let mut res: Option = None; + + /* + The loop is structured that it first checks all outgoing channels of the + IdscpConnection for any pending messages that need to be delivered, then reading on + the underlying socket using Self::read. + Any read messages are cached in the `res` variable. + The loop will wrap at least once, such that the outgoing channels can again be + checked for any acknowledgements resulting of the read operation before attempting + to return some cached message. + */ + loop { + // check outgoing channels + while connection.wants_write_out_conn() { + Self::write(connection, writer).await?; + } + while let Some(event) = connection.write_ra_verifier_manager() { + ra_verifier_manager.process_event(event).unwrap(); + } + while let Some(event) = connection.write_ra_prover_manager() { + ra_prover_manager.process_event(event).unwrap(); + } + + // loop exit condition only triggered after second loop iteration + if let Some(msg) = res { + return Ok(msg); + } + + // check incoming channels + // since guards don't seem to allow lazy execution, use this workaround: + match (ra_verifier_manager.recv_msg(), ra_prover_manager.recv_msg()) { + (Some(msg_from_ra_verifier), Some(msg_from_ra_prover)) => { + select! { + msg = msg_from_ra_verifier => { + connection.read_ra_verifier_manager(msg).unwrap(); + } + msg = msg_from_ra_prover => { + connection.read_ra_prover_manager(msg).unwrap(); + } + read_res = Self::read(connection, reader) => { + if read_res?? == 0 { // TODO evaluate if fix is correct + res = Some(Bytes::new()); + } + if let Some(msg) = connection.write_in_user() { + res = Some(msg); + } + } + }; + } + (Some(msg_from_ra_verifier), None) => { + select! { + msg = msg_from_ra_verifier => { + connection.read_ra_verifier_manager(msg).unwrap(); + } + read_res = Self::read(connection, reader) => { + if read_res?? == 0 { // TODO evaluate if fix is correct + res = Some(Bytes::new()); + } + if let Some(msg) = connection.write_in_user() { + res = Some(msg); + } + } + }; + } + (None, Some(msg_from_ra_prover)) => { + select! { + msg = msg_from_ra_prover => { + connection.read_ra_prover_manager(msg).unwrap(); + } + read_res = Self::read(connection, reader) => { + if read_res?? == 0 { // TODO evaluate if fix is correct + res = Some(Bytes::new()); + } + if let Some(msg) = connection.write_in_user() { + res = Some(msg); + } + } + }; + } + (None, None) => { + let read_res = Self::read(connection, reader).await; + if read_res?? == 0 { + // TODO evaluate if fix is correct + res = Some(Bytes::new()); + } + if let Some(msg) = connection.write_in_user() { + res = Some(msg); + } + } + } + // end of loop + } + } + } + } + + /// Receives data from the connected peer and replies with an acknowledgement. + /// If `timeout` is supplied, the operation may terminate during operation if the timeout elapses. + /// In this case, there is no guarantee that the data has been delivered, but the connection + /// should be able to recover. + pub async fn recv(&mut self, timeout: Option) -> std::io::Result { + let (mut reader, mut writer) = self.tcp_stream.split(); + timed_out!( + timeout, + Self::do_recv_from( + &mut self.connection, + &mut self.ra_verifier_manager, + &mut self.ra_prover_manager, + &mut reader, + &mut writer + ) + ) + } + + pub async fn recv_from( + &mut self, + reader: &mut R, + writer: &mut W, + timeout: Option, + ) -> std::io::Result { + timed_out!( + timeout, + Self::do_recv_from( + &mut self.connection, + &mut self.ra_verifier_manager, + &mut self.ra_prover_manager, + reader, + writer + ) + ) + } + + /// Gracefully waits for acknowledgement of all transferred data and issues close message to the + /// connected peer if one of the following conditions is met: + /// - all messages have been acknowledged and a close message is sent + /// - the attestation or dynamic attribute tokens time out + /// - the channel is externally closed, either by a close message or by protocol violation + /// - an io::Error occurred + pub async fn close(self) -> std::io::Result<()> { + todo!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::fsm_spec::fsm_tests::TestDaps; + use crate::tests::{TEST_CONFIG_ALICE, TEST_CONFIG_BOB, TEST_PROVER_NEVER_DONE_CONFIG_BOB}; + use crate::util::test::spawn_listener; + use crate::{test_begin, test_finalize}; + use bytes::BytesMut; + use futures::lock::MutexGuard; + use rand::{thread_rng, Fill}; + use std::ops::Deref; + use std::thread::sleep; + use std::time::Duration; + use tokio_test::assert_ok; + + #[test] + fn async_establish_connection() { + test_begin!(); + tokio_test::block_on(async { + let (listener, _guard, address) = spawn_listener().await; + let (connect_result, accept_result) = tokio::join!( + async { + let mut daps_driver = TestDaps::default(); + let _connection = AsyncIdscpConnection::connect( + address, + &mut daps_driver, + &TEST_CONFIG_ALICE, + ) + .await?; + Ok::<(), std::io::Error>(()) + }, + async { + let mut daps_driver = TestDaps::default(); + let _connection = listener.accept(&mut daps_driver, &TEST_CONFIG_BOB).await?; + Ok::<(), std::io::Error>(()) + } + ); + + assert!(connect_result.is_ok()); + assert!(accept_result.is_ok()); + + //tokio::spawn(task)tokio::spawn(TcpStream::connect("127.0.0.1:8080")); + }); + test_finalize!(); + } + + #[test] + fn async_transmit_data() { + test_begin!(); + const MSG: &[u8; 4] = &[1, 2, 3, 4]; + + tokio_test::block_on(async { + let (listener, _guard, address) = spawn_listener().await; + let (connect_result, accept_result) = tokio::join!( + async { + let mut daps_driver = TestDaps::default(); + let mut connection = AsyncIdscpConnection::connect( + address, + &mut daps_driver, + &TEST_CONFIG_ALICE, + ) + .await?; + let n = connection.send(Bytes::from(MSG.as_slice()), None).await?; + assert!(n == 4); + Ok::<(), std::io::Error>(()) + }, + async { + let mut daps_driver = TestDaps::default(); + let mut connection = + listener.accept(&mut daps_driver, &TEST_CONFIG_BOB).await?; + let msg = connection.recv(None).await?; + assert_eq!(msg.deref(), MSG); + Ok::<(), std::io::Error>(()) + } + ); + + assert!(connect_result.is_ok()); + assert!(accept_result.is_ok()); + }); + test_finalize!(); + } + + #[test] + fn async_transmit_data_no_full_attestation() { + test_begin!(); + const MSG: &[u8; 4] = &[1, 2, 3, 4]; + + tokio_test::block_on(async { + let (listener, _guard, address) = spawn_listener().await; + tokio::select!( + connect_result = async { + let mut daps_driver = TestDaps::default(); + let mut connection = AsyncIdscpConnection::connect( + address, + &mut daps_driver, + &TEST_CONFIG_ALICE, + ) + .await?; + let n = connection.send(Bytes::from(MSG.as_slice()), None).await?; + assert!(n == 4); + Ok::<(), std::io::Error>(()) + } => { + assert!(connect_result.is_ok()); + }, + _ = async { + let mut daps_driver = TestDaps::default(); + let mut connection = + listener.accept(&mut daps_driver, &TEST_PROVER_NEVER_DONE_CONFIG_BOB).await?; + println!("accepted"); + let msg = connection.recv(None).await?; + assert_eq!(msg.deref(), MSG); + Ok::<(), std::io::Error>(()) + } => { + panic!("Bob should not terminate!"); + }, + ); + }); + test_finalize!(); + } + + fn random_data(size: usize) -> Vec { + let mut rng = thread_rng(); + + let mut data = vec![0u8; size]; + data.try_fill(&mut rng).unwrap(); + data + } + + async fn spawn_connection<'a>( + daps_driver_1: &'a mut dyn DapsDriver, + daps_driver_2: &'a mut dyn DapsDriver, + config_1: &'a IdscpConfig<'a>, + config_2: &'a IdscpConfig<'a>, + ) -> ( + AsyncIdscpConnection<'a>, + AsyncIdscpConnection<'a>, + MutexGuard<'static, ()>, + ) { + let (listener, guard, address) = spawn_listener().await; + let (connect_result, accept_result) = tokio::join!( + AsyncIdscpConnection::connect(address, daps_driver_1, config_1), + listener.accept(daps_driver_2, config_2), + ); + + assert_ok!(&connect_result); + assert_ok!(&accept_result); + + (connect_result.unwrap(), accept_result.unwrap(), guard) + } + + async fn transfer( + peer1: &mut AsyncIdscpConnection<'_>, + peer2: &mut AsyncIdscpConnection<'_>, + transmission_size: usize, + chunk_size: usize, + send_delay: Option, + ) -> Result { + let mut data = BytesMut::from(random_data(transmission_size).as_slice()); + let mut cmp_data = data.clone(); + + tokio::try_join!( + async { + while !cmp_data.is_empty() { + let msg = peer2.recv(None).await.unwrap(); // TODO right place to unwrap? + assert_eq!(msg.len(), chunk_size); + let cmp_msg = cmp_data.split_to(chunk_size); + assert_eq!(std::convert::AsRef::as_ref(&msg), cmp_msg.as_ref()); + } + Ok::<(), std::io::Error>(()) + }, + async { + while !data.is_empty() { + let msg = data.split_to(chunk_size); + let n = peer1.send(msg.freeze(), None).await?; + if let Some(delay) = send_delay { + sleep(delay); + } + assert!(n == chunk_size); + } + Ok::<(), std::io::Error>(()) + }, + )?; + + Ok(data.is_empty() && cmp_data.is_empty()) + } + + #[test] + fn test_transfer_size_1000() { + test_begin!(); + const TRANSMISSION_SIZE: usize = 10000; + const FIXED_CHUNK_SIZE: usize = 1000; + + let res = tokio_test::block_on(async { + let mut daps_driver_1 = TestDaps::default(); + let mut daps_driver_2 = TestDaps::default(); + let (mut peer1, mut peer2, _guard) = spawn_connection( + &mut daps_driver_1, + &mut daps_driver_2, + &TEST_CONFIG_ALICE, + &TEST_CONFIG_BOB, + ) + .await; + transfer( + &mut peer1, + &mut peer2, + TRANSMISSION_SIZE, + FIXED_CHUNK_SIZE, + None, + ) + .await + }); + + assert_ok!(res); + test_finalize!(); + } + + #[test] + fn test_transfer_dat_expired() { + test_begin!(); + const TRANSMISSION_SIZE: usize = 20_000; + const FIXED_CHUNK_SIZE: usize = 1000; + + let res = tokio_test::block_on(async { + let mut daps_driver_1 = TestDaps::default(); + let mut daps_driver_2 = TestDaps::with_timeout(Duration::from_secs(4)); + let (mut peer1, mut peer2, _guard) = spawn_connection( + &mut daps_driver_1, + &mut daps_driver_2, + &TEST_CONFIG_ALICE, + &TEST_CONFIG_BOB, + ) + .await; + transfer( + &mut peer1, + &mut peer2, + TRANSMISSION_SIZE, + FIXED_CHUNK_SIZE, + Some(Duration::from_millis(100)), + ) + .await + }); + + assert_ok!(res); + test_finalize!(); + } + + /* + async fn send( + peer1: &mut AsyncIdscpConnection<'_>, + transmission_size: usize, + chunk_size: usize, + ) -> Result { + let mut data = BytesMut::from(random_data(transmission_size).as_slice()); + let mut sink = tokio::io::sink(); + + while !data.is_empty() { + let msg = data.split_to(chunk_size); + let n = peer1.try_send_to(&mut sink, msg.freeze()).await?; + assert_eq!(n, chunk_size); + } + + Ok(data.is_empty()) + } + + #[test] + fn test_send_size_1000() { + test_begin!(); + const TRANSMISSION_SIZE: usize = 200_000; + const FIXED_CHUNK_SIZE: usize = 1000; + + let res = tokio_test::block_on(async { + let mut daps_driver_1 = TestDaps::default(); + let mut daps_driver_2 = TestDaps::default(); + let (mut peer1, mut _peer2, _guard) = spawn_connection( + &mut daps_driver_1, + &mut daps_driver_2, + &TEST_CONFIG_ALICE, + &TEST_CONFIG_BOB, + ) + .await; + send(&mut peer1, TRANSMISSION_SIZE, FIXED_CHUNK_SIZE).await + }); + + // This test fails now, since no acknowledgements are received + assert_err!(res); + test_finalize!(); + } + */ +} diff --git a/idscp2_core/src/util.rs b/idscp2_core/src/util.rs new file mode 100644 index 0000000..5adac31 --- /dev/null +++ b/idscp2_core/src/util.rs @@ -0,0 +1,30 @@ +#[cfg(test)] +pub(crate) mod test { + use crate::tokio_idscp_connection::AsyncIdscpListener; + use futures::lock::{Mutex, MutexGuard}; + use lazy_static::lazy_static; + use std::thread::sleep; + use std::time::Duration; + + struct GuardedAddress<'a> { + mutex: Mutex<()>, + address: &'a str, + } + + const TEST_ADDRESS_STRING: &str = "127.0.0.1:8080"; + + lazy_static! { + static ref TEST_ADDRESS: GuardedAddress<'static> = GuardedAddress { + mutex: Mutex::new(()), + address: TEST_ADDRESS_STRING, + }; + } + + pub(crate) async fn spawn_listener( + ) -> (AsyncIdscpListener, MutexGuard<'static, ()>, &'static str) { + let guard = TEST_ADDRESS.mutex.lock().await; + sleep(Duration::from_millis(100)); // give the OS some time to make the address available + let listener = AsyncIdscpListener::bind("127.0.0.1:8080").await.unwrap(); + (listener, guard, TEST_ADDRESS.address) + } +} diff --git a/test_pki/resources/openssl/out/test_client.crt b/test_pki/resources/openssl/out/test_client.crt new file mode 100644 index 0000000..969d537 --- /dev/null +++ b/test_pki/resources/openssl/out/test_client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdzCCAl8CFAxtF6KsLlYJtOSmap8jjF9QRxt+MA0GCSqGSIb3DQEBCwUAMHgx +CzAJBgNVBAYTAkRFMRAwDgYDVQQIDAdCYXZhcmlhMQ8wDQYDVQQHDAZNdW5pY2gx +GTAXBgNVBAoMEEZyYXVuaG9mZXIgQUlTRUMxDDAKBgNVBAsMA1NPUzEdMBsGA1UE +AwwUTWF4aW1pbGFuIE11c3Rlcm1hbm4wHhcNMjIwNzE4MTQwNzQwWhcNMjMwNzE4 +MTQwNzQwWjB4MQswCQYDVQQGEwJERTEQMA4GA1UECAwHQmF2YXJpYTEPMA0GA1UE +BwwGTXVuaWNoMRkwFwYDVQQKDBBGcmF1bmhvZmVyIEFJU0VDMQwwCgYDVQQLDANT +T1MxHTAbBgNVBAMMFE1heGltaWxhbiBNdXN0ZXJtYW5uMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAn0rQ4ovCBnRkPcS5g03vMSiXTIcIaJ48HGAg2VfE +NXU1zW7O06Yue/EPW4Onmf1Y/Glv2MoRfx3jQKqQBsuqCFojcHsxPkLjc8b2sdpp +V16Z4wg2v4p6U1WXTqvuk5g3RRmDb13hcPX1azJpXcnaMvOnnCofpHJopmey09i3 +NXrdfZ+o3AIdop0S56ZQgRhs9dBbEijqI0yx12dV8HnuDebgyxi2Gulldwr8deY8 +HV4Uff0IHs5jKj9sp4WBOKQ1Ff0q5KQnN2aOe7vPxOB7lieS7GskeTFvY/9pg+n3 ++50YV9qJDx8MSsKTxT8AIELWpk+r8Co0k2qQTWlTI1T/BQIDAQABMA0GCSqGSIb3 +DQEBCwUAA4IBAQBww/sjjUiVD81TtsyRrjfigOJ89pMlUCAJOqPciVcbvnvEIBcB +2k7AKtOiSms001zrI91wmIeems0Oez9B+vtDW0oE5UCJxu/10w/D3gmMFEhSkxmJ +hBsV9hn8WSZdUeu370qSWb9WKiP7SETvpCMprJhYlnHH6HR9qsDH1hJXugsIfA6Y +NPuLEHY2utUUE4Y75CCBryX7OgCZ2E1hjfpY29RBeFdFX67r5mcR04heCbdmD/Wh +srS3UZJD1n9Co4q0WWxWBCCZYeR1fsKLJke772WHloP3kSN1hsW17DQ8JdBTi30w +AjYrb4dHVVepo7AZ1b58GYrHVDuYVTuB0+L4 +-----END CERTIFICATE-----