Skip to content

Commit

Permalink
Add demonstration of custom crypto
Browse files Browse the repository at this point in the history
This is an example that builds a mostly-unchanged rustls example
(simpleclient), but only using crypto from the rust-crypto project
and elsewhere.

This is intended to be minimalistic, and not a complete replacement
for *ring*.

It implements:

- TLS1.3 TLS13_CHACHA20_POLY1305_SHA256 cipher suite.
- TLS1.2 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 cipher suite.
- X25519 key exchange.
- RSA-PSS-SHA256 and RSA-PKCS1-SHA256 signature verification for
  verifying the server, integrated into the webpki crate.
- random generation using `rand_core`.

This means it can fetch www.rust-lang.org.

TLS1.2 is not strictly necessary for this server, but serves to
demonstrate that part of the API.
  • Loading branch information
ctz committed Aug 31, 2023
1 parent e2bcdfe commit 5b8a7e5
Show file tree
Hide file tree
Showing 12 changed files with 538 additions and 3 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
env:
RUST_BACKTRACE: 1

- name: cargo build (debug; rustls-provider-example)
run: cargo build -p rustls-provider-example

msrv:
name: MSRV
runs-on: ubuntu-20.04
Expand Down Expand Up @@ -168,8 +171,8 @@ jobs:
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@nightly

- name: cargo doc (all features)
run: cargo doc --all-features --no-deps --document-private-items --workspace
- name: cargo doc (rustls; all features)
run: cargo doc --all-features --no-deps --document-private-items --package rustls
env:
RUSTDOCFLAGS: -Dwarnings

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/connect-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ jobs:
- run: cargo run --bin limitedclient

- run: cargo run --bin simple_0rtt_client

- run: cargo run -p rustls-provider-example --example client
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ members = [
"examples",
# the main library and tests
"rustls",
# example of custom provider
"provider-example",
]
default-members = [
"examples",
Expand Down
23 changes: 23 additions & 0 deletions provider-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "rustls-provider-example"
version = "0.0.1"
edition = "2021"
rust-version = "1.60"
license = "Apache-2.0 OR ISC OR MIT"
description = "Example of rustls with custom crypto provider."
publish = false

[dependencies]
chacha20poly1305 = "0.10.0"
der = "0.7.0"
env_logger = "0.10"
hmac = "0.12.0"
rand_core = "0.6.0"
rustls = { path = "../rustls", default-features = false, features = [ "logging", "dangerous_configuration", "webpki", "tls12" ]}
rsa = { version = "0.9.0", features = [ "sha2" ] }
sha2 = "0.10.0"
webpki = { package = "rustls-webpki", version = "0.102.0-alpha.0", default-features = false, features = ["alloc", "std"] }
webpki-roots = "0.25.0"
x25519-dalek = "2"

[dev-dependencies]
57 changes: 57 additions & 0 deletions provider-example/examples/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::sync::Arc;

use std::io::{stdout, Read, Write};
use std::net::TcpStream;

use rustls_provider_example::Provider;

fn main() {
env_logger::init();

let mut root_store = rustls::RootCertStore::empty();
root_store.add_trust_anchors(
webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(|ta| {
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
}),
);

let config = rustls::ClientConfig::<Provider>::builder()
.with_safe_defaults()
.with_custom_certificate_verifier(Provider::certificate_verifier(root_store))
.with_no_client_auth();

let server_name = "www.rust-lang.org".try_into().unwrap();
let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap();
let mut sock = TcpStream::connect("www.rust-lang.org:443").unwrap();
let mut tls = rustls::Stream::new(&mut conn, &mut sock);
tls.write_all(
concat!(
"GET / HTTP/1.1\r\n",
"Host: www.rust-lang.org\r\n",
"Connection: close\r\n",
"Accept-Encoding: identity\r\n",
"\r\n"
)
.as_bytes(),
)
.unwrap();
let ciphersuite = tls
.conn
.negotiated_cipher_suite()
.unwrap();
writeln!(
&mut std::io::stderr(),
"Current ciphersuite: {:?}",
ciphersuite.suite()
)
.unwrap();
let mut plaintext = Vec::new();
tls.read_to_end(&mut plaintext).unwrap();
stdout().write_all(&plaintext).unwrap();
}
158 changes: 158 additions & 0 deletions provider-example/src/aead.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use chacha20poly1305::{AeadInPlace, KeyInit, KeySizeUser};
use rustls::crypto::cipher;
use rustls::internal::msgs::base::Payload; // FIXME
use rustls::{ContentType, ProtocolVersion};

pub struct Chacha20Poly1305;

impl cipher::Tls13AeadAlgorithm for Chacha20Poly1305 {
fn encrypter(&self, key: cipher::AeadKey, iv: cipher::Iv) -> Box<dyn cipher::MessageEncrypter> {
Box::new(Tls13Cipher(
chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(),
iv,
))
}

fn decrypter(&self, key: cipher::AeadKey, iv: cipher::Iv) -> Box<dyn cipher::MessageDecrypter> {
Box::new(Tls13Cipher(
chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(),
iv,
))
}

fn key_len(&self) -> usize {
chacha20poly1305::ChaCha20Poly1305::key_size()
}
}

impl cipher::Tls12AeadAlgorithm for Chacha20Poly1305 {
fn encrypter(
&self,
key: cipher::AeadKey,
iv: &[u8],
_: &[u8],
) -> Box<dyn cipher::MessageEncrypter> {
Box::new(Tls12Cipher(
chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(),
cipher::Iv::copy(iv),
))
}

fn decrypter(&self, key: cipher::AeadKey, iv: &[u8]) -> Box<dyn cipher::MessageDecrypter> {
Box::new(Tls12Cipher(
chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()).unwrap(),
cipher::Iv::copy(iv),
))
}

fn key_block_shape(&self) -> cipher::KeyBlockShape {
cipher::KeyBlockShape {
enc_key_len: 32,
fixed_iv_len: 12,
explicit_nonce_len: 0,
}
}
}

struct Tls13Cipher(chacha20poly1305::ChaCha20Poly1305, cipher::Iv);

impl cipher::MessageEncrypter for Tls13Cipher {
fn encrypt(
&self,
m: cipher::BorrowedPlainMessage,
seq: u64,
) -> Result<cipher::OpaqueMessage, rustls::Error> {
let total_len = m.payload.len() + 1 + CHACHAPOLY1305_OVERHEAD;

// construct a TLSInnerPlaintext
let mut payload = Vec::with_capacity(total_len);
payload.extend_from_slice(m.payload);
payload.push(m.typ.get_u8());

let nonce = chacha20poly1305::Nonce::from(cipher::Nonce::new(&self.1, seq).0);
let aad = cipher::make_tls13_aad(total_len);

self.0
.encrypt_in_place(&nonce, &aad, &mut payload)
.map_err(|_| rustls::Error::EncryptError)
.and_then(|_| {
Ok(cipher::OpaqueMessage {
typ: ContentType::ApplicationData,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(payload),
})
})
}
}

impl cipher::MessageDecrypter for Tls13Cipher {
fn decrypt(
&self,
mut m: cipher::OpaqueMessage,
seq: u64,
) -> Result<cipher::PlainMessage, rustls::Error> {
let payload = &mut m.payload.0;
let nonce = chacha20poly1305::Nonce::from(cipher::Nonce::new(&self.1, seq).0);
let aad = cipher::make_tls13_aad(payload.len());

self.0
.decrypt_in_place(&nonce, &aad, payload)
.map_err(|_| rustls::Error::DecryptError)?;

m.into_tls13_unpadded_message()
}
}

struct Tls12Cipher(chacha20poly1305::ChaCha20Poly1305, cipher::Iv);

impl cipher::MessageEncrypter for Tls12Cipher {
fn encrypt(
&self,
m: cipher::BorrowedPlainMessage,
seq: u64,
) -> Result<cipher::OpaqueMessage, rustls::Error> {
let total_len = m.payload.len() + CHACHAPOLY1305_OVERHEAD;

let mut payload = Vec::with_capacity(total_len);
payload.extend_from_slice(m.payload);

let nonce = chacha20poly1305::Nonce::from(cipher::Nonce::new(&self.1, seq).0);
let aad = cipher::make_tls12_aad(seq, m.typ, m.version, payload.len());

self.0
.encrypt_in_place(&nonce, &aad, &mut payload)
.map_err(|_| rustls::Error::EncryptError)
.and_then(|_| {
Ok(cipher::OpaqueMessage {
typ: m.typ,
version: m.version,
payload: Payload::new(payload),
})
})
}
}

impl cipher::MessageDecrypter for Tls12Cipher {
fn decrypt(
&self,
mut m: cipher::OpaqueMessage,
seq: u64,
) -> Result<cipher::PlainMessage, rustls::Error> {
let payload = &mut m.payload.0;
let nonce = chacha20poly1305::Nonce::from(cipher::Nonce::new(&self.1, seq).0);
let aad = cipher::make_tls12_aad(
seq,
m.typ,
m.version,
payload.len() - CHACHAPOLY1305_OVERHEAD,
);

self.0
.decrypt_in_place(&nonce, &aad, payload)
.map_err(|_| rustls::Error::DecryptError)?;

Ok(m.into_plain_message())
}
}

const CHACHAPOLY1305_OVERHEAD: usize = 16;
42 changes: 42 additions & 0 deletions provider-example/src/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use rustls::crypto::hash;
use sha2::Digest;

pub struct SHA256;

impl hash::Hash for SHA256 {
fn start(&self) -> Box<dyn hash::Context> {
Box::new(SHA256Context(sha2::Sha256::new()))
}

fn hash(&self, data: &[u8]) -> hash::Output {
hash::Output::new(&sha2::Sha256::digest(data)[..])
}

fn algorithm(&self) -> hash::HashAlgorithm {
hash::HashAlgorithm::SHA256
}

fn output_len(&self) -> usize {
32
}
}

struct SHA256Context(sha2::Sha256);

impl hash::Context for SHA256Context {
fn fork_finish(&self) -> hash::Output {
hash::Output::new(&self.0.clone().finalize()[..])
}

fn fork(&self) -> Box<dyn hash::Context> {
Box::new(SHA256Context(self.0.clone()))
}

fn finish(self: Box<Self>) -> hash::Output {
hash::Output::new(&self.0.finalize()[..])
}

fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
}
33 changes: 33 additions & 0 deletions provider-example/src/hmac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use hmac::{Hmac, Mac};
use rustls::crypto;
use sha2::{Digest, Sha256};

pub struct Sha256Hmac;

impl crypto::hmac::Hmac for Sha256Hmac {
fn with_key(&self, key: &[u8]) -> Box<dyn crypto::hmac::Key> {
Box::new(Sha256HmacKey(Hmac::<Sha256>::new_from_slice(key).unwrap()))
}

fn hash_output_len(&self) -> usize {
Sha256::output_size()
}
}

struct Sha256HmacKey(Hmac<Sha256>);

impl crypto::hmac::Key for Sha256HmacKey {
fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag {
let mut ctx = self.0.clone();
ctx.update(first);
for m in middle {
ctx.update(m);
}
ctx.update(last);
crypto::hmac::Tag::new(&ctx.finalize().into_bytes()[..])
}

fn tag_len(&self) -> usize {
Sha256::output_size()
}
}

0 comments on commit 5b8a7e5

Please sign in to comment.