Skip to content

Commit

Permalink
add common RollingTicketer data type
Browse files Browse the repository at this point in the history
  • Loading branch information
zh-jq-b committed Jul 10, 2024
1 parent 9bf1b4e commit 1bcedd7
Show file tree
Hide file tree
Showing 13 changed files with 500 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ rustls-pemfile = "2"
tokio-rustls = { version = "0.26", default-features = false, features = ["tls12", "ring"] }
quinn = { version = "0.11", default-features = false, features = ["runtime-tokio"] }
#
openssl = { package = "variant-ssl", version = "0.14.2" }
openssl-sys = { package = "variant-ssl-sys", version = "0.13.0" }
openssl = { package = "variant-ssl", version = "0.14.8" }
openssl-sys = { package = "variant-ssl-sys", version = "0.13.8" }
openssl-probe = "0.1"
#
flume = { version = "0.11", default-features = false }
Expand Down
4 changes: 3 additions & 1 deletion lib/g3-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ smol_str.workspace = true
memchr.workspace = true
url.workspace = true
num-traits.workspace = true
arc-swap.workspace = true
fastrand = { workspace = true, optional = true }
governor = { workspace = true, features = ["std", "jitter"] }
digest = { workspace = true, optional = true }
Expand All @@ -43,6 +44,7 @@ quinn = { workspace = true, optional = true }
webpki-roots = { version = "0.26", optional = true }
rustls-native-certs = { version = "0.7", optional = true }
rustls-pemfile = { workspace = true, optional = true }
ring = { version = "0.17", optional = true }
openssl = { workspace = true, optional = true }
lru = { workspace = true, optional = true }
bytes = { workspace = true, optional = true }
Expand All @@ -59,7 +61,7 @@ quic = []
auth-crypt = ["dep:digest", "dep:md-5", "dep:sha-1", "dep:blake3", "dep:hex"]
resolve = ["dep:ahash", "dep:radix_trie", "dep:fastrand"]
quinn = ["dep:quinn", "quic"]
rustls = ["dep:rustls", "dep:rustls-pki-types", "dep:webpki-roots", "dep:rustls-pemfile", "dep:rustls-native-certs", "dep:ahash", "dep:lru", "quinn?/rustls"]
rustls = ["dep:rustls", "dep:ring", "dep:rustls-pki-types", "dep:webpki-roots", "dep:rustls-pemfile", "dep:rustls-native-certs", "dep:ahash", "dep:lru", "quinn?/rustls"]
openssl = ["dep:openssl", "dep:ahash", "dep:lru", "dep:bytes"]
tongsuo = ["openssl", "openssl/tongsuo", "dep:brotli"]
aws-lc = ["openssl", "openssl/aws-lc", "rustls?/aws-lc-rs", "dep:brotli"]
Expand Down
5 changes: 5 additions & 0 deletions lib/g3-types/src/net/openssl/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ use crate::net::AlpnProtocol;
mod intercept;
pub use intercept::{OpensslInterceptionServerConfig, OpensslInterceptionServerConfigBuilder};

mod ticket_key;
pub use ticket_key::OpensslTicketKey;

mod ticketer;

mod session;
pub use session::{OpensslServerSessionCache, OpensslSessionIdContext};

Expand Down
90 changes: 90 additions & 0 deletions lib/g3-types/src/net/openssl/server/ticket_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2024 ByteDance and/or its affiliates.
*
* 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.
*/

use anyhow::anyhow;
use openssl::cipher::Cipher;
use openssl::cipher_ctx::CipherCtxRef;
use openssl::error::ErrorStack;
use openssl::hmac::HMacCtxRef;
use openssl::md::Md;
use openssl::rand;
use openssl::ssl::TicketKeyStatus;

use crate::net::{RollingTicketKey, TicketKeyName, TICKET_KEY_LENGTH, TICKET_KEY_NAME_LENGTH};

pub struct OpensslTicketKey {
name: TicketKeyName,
lifetime: u32,
key: [u8; TICKET_KEY_LENGTH],
}

impl OpensslTicketKey {
pub(super) fn encrypt_init(
&self,
key_name: &mut [u8],
iv: &[u8],
cipher_ctx: &mut CipherCtxRef,
hmac_ctx: &mut HMacCtxRef,
) -> Result<TicketKeyStatus, ErrorStack> {
if key_name.len() != TICKET_KEY_NAME_LENGTH {
return Ok(TicketKeyStatus::FAILED);
}
key_name.copy_from_slice(self.name.as_ref());

cipher_ctx.encrypt_init(Some(Cipher::aes_256_cbc()), Some(&self.key), Some(iv))?;
hmac_ctx.init_ex(Some(&self.key), Md::sha256())?;

Ok(TicketKeyStatus::SUCCESS)
}

pub(super) fn decrypt_init(
&self,
iv: &[u8],
cipher_ctx: &mut CipherCtxRef,
hmac_ctx: &mut HMacCtxRef,
) -> Result<(), ErrorStack> {
hmac_ctx.init_ex(Some(&self.key), Md::sha256())?;
cipher_ctx.decrypt_init(Some(Cipher::aes_256_cbc()), Some(&self.key), Some(iv))?;
Ok(())
}
}

impl RollingTicketKey for OpensslTicketKey {
fn new(lifetime: u32) -> anyhow::Result<Self> {
let mut key = [0u8; TICKET_KEY_LENGTH];
rand::rand_bytes(&mut key).map_err(|e| anyhow!("failed to generate random key: {e}"))?;

let mut key_name = [0u8; TICKET_KEY_NAME_LENGTH];
rand::rand_bytes(&mut key_name)
.map_err(|e| anyhow!("failed to generate random key name: {e}"))?;

Ok(OpensslTicketKey {
name: key_name.into(),
lifetime,
key,
})
}

#[inline]
fn name(&self) -> &TicketKeyName {
&self.name
}

#[inline]
fn lifetime(&self) -> u32 {
self.lifetime
}
}
57 changes: 57 additions & 0 deletions lib/g3-types/src/net/openssl/server/ticketer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2024 ByteDance and/or its affiliates.
*
* 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.
*/

use openssl::cipher_ctx::CipherCtxRef;
use openssl::error::ErrorStack;
use openssl::hmac::HMacCtxRef;
use openssl::ssl::TicketKeyStatus;

use super::OpensslTicketKey;
use crate::net::{RollingTicketKey, RollingTicketer};

impl RollingTicketer<OpensslTicketKey> {
pub fn encrypt_init(
&self,
key_name: &mut [u8],
iv: &[u8],
cipher_ctx: &mut CipherCtxRef,
hmac_ctx: &mut HMacCtxRef,
) -> Result<TicketKeyStatus, ErrorStack> {
self.enc_key
.load()
.encrypt_init(key_name, iv, cipher_ctx, hmac_ctx)
}

pub fn decrypt_init(
&self,
key_name: &[u8],
iv: &[u8],
cipher_ctx: &mut CipherCtxRef,
hmac_ctx: &mut HMacCtxRef,
) -> Result<TicketKeyStatus, ErrorStack> {
let Some(key) = self.get_decrypt_key(key_name) else {
return Ok(TicketKeyStatus::FAILED);
};

key.decrypt_init(iv, cipher_ctx, hmac_ctx)?;

if self.enc_key.load().name().constant_time_eq(key_name) {
Ok(TicketKeyStatus::SUCCESS)
} else {
Ok(TicketKeyStatus::SUCCESS_AND_RENEW)
}
}
}
15 changes: 15 additions & 0 deletions lib/g3-types/src/net/openssl/ticket_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright 2024 ByteDance and/or its affiliates.
*
* 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.
*/
3 changes: 3 additions & 0 deletions lib/g3-types/src/net/rustls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub use server::{RustlsServerConfig, RustlsServerConfigBuilder};
mod cache;
use cache::RustlsServerSessionCache;

mod ticket_key;
pub use ticket_key::RustlsTicketKey;

mod ticketer;
use ticketer::RustlsNoSessionTicketer;

Expand Down
147 changes: 147 additions & 0 deletions lib/g3-types/src/net/rustls/ticket_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright 2024 ByteDance and/or its affiliates.
*
* 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.
*/

use std::sync::atomic::{AtomicUsize, Ordering};

use anyhow::anyhow;
use ring::aead;
use ring::rand::{SecureRandom, SystemRandom};

use crate::net::{RollingTicketKey, TicketKeyName, TICKET_KEY_LENGTH, TICKET_KEY_NAME_LENGTH};

pub struct RustlsTicketKey {
name: TicketKeyName,
lifetime: u32,
key: aead::LessSafeKey,

/// Tracks the largest ciphertext produced by `encrypt`, and
/// uses it to early-reject `decrypt` queries that are too long.
///
/// Accepting excessively long ciphertexts means a "Partitioning
/// Oracle Attack" (see <https://eprint.iacr.org/2020/1491.pdf>)
/// can be more efficient, though also note that these are thought
/// to be cryptographically hard if the key is full-entropy (as it
/// is here).
maximum_ciphertext_len: AtomicUsize,
}

impl RustlsTicketKey {
/// Encrypt `message` and return the ciphertext.
pub(super) fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
// Random nonce, because a counter is a privacy leak.
let mut nonce_buf = [0u8; aead::NONCE_LEN];
SystemRandom::new().fill(&mut nonce_buf).ok()?;
let nonce = aead::Nonce::assume_unique_for_key(nonce_buf);
let aad = aead::Aad::from(&self.name);

// ciphertext structure is:
// key_name: [u8; 16]
// nonce: [u8; 12]
// message: [u8, _]
// tag: [u8; 16]

let mut ciphertext = Vec::with_capacity(
TICKET_KEY_NAME_LENGTH
+ nonce_buf.len()
+ message.len()
+ self.key.algorithm().tag_len(),
);
ciphertext.extend(self.name.as_ref());
ciphertext.extend(nonce_buf);
ciphertext.extend(message);
let ciphertext = self
.key
.seal_in_place_separate_tag(
nonce,
aad,
&mut ciphertext[TICKET_KEY_NAME_LENGTH + nonce_buf.len()..],
)
.map(|tag| {
ciphertext.extend(tag.as_ref());
ciphertext
})
.ok()?;

self.maximum_ciphertext_len
.fetch_max(ciphertext.len(), Ordering::SeqCst);
Some(ciphertext)
}

/// Decrypt `ciphertext` and recover the original message.
pub(super) fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
if ciphertext.len() > self.maximum_ciphertext_len.load(Ordering::SeqCst) {
return None;
}

let (alleged_key_name, ciphertext) = try_split_at(ciphertext, TICKET_KEY_NAME_LENGTH)?;
let (nonce, ciphertext) = try_split_at(ciphertext, aead::NONCE_LEN)?;

// This won't fail since `nonce` has the required length.
let nonce = aead::Nonce::try_assume_unique_for_key(nonce).ok()?;

let mut out = Vec::from(ciphertext);

let plain_len = self
.key
.open_in_place(nonce, aead::Aad::from(alleged_key_name), &mut out)
.ok()?
.len();
out.truncate(plain_len);

Some(out)
}
}

/// Non-panicking `let (nonce, ciphertext) = ciphertext.split_at(...)`.
fn try_split_at(slice: &[u8], mid: usize) -> Option<(&[u8], &[u8])> {
match mid > slice.len() {
true => None,
false => Some(slice.split_at(mid)),
}
}

impl RollingTicketKey for RustlsTicketKey {
fn new(lifetime: u32) -> anyhow::Result<Self> {
let mut key = [0u8; TICKET_KEY_LENGTH];
SystemRandom::new()
.fill(&mut key)
.map_err(|_| anyhow!("failed to generate random key"))?;

let key = aead::UnboundKey::new(&aead::AES_256_GCM, &key).unwrap();

let mut key_name = [0u8; TICKET_KEY_NAME_LENGTH];
SystemRandom::new()
.fill(&mut key_name)
.map_err(|_| anyhow!("failed to generate random key name"))?;

Ok(RustlsTicketKey {
name: key_name.into(),
lifetime,
key: aead::LessSafeKey::new(key),
maximum_ciphertext_len: AtomicUsize::new(0),
})
}

#[inline]
fn name(&self) -> &TicketKeyName {
&self.name
}

#[inline]
fn lifetime(&self) -> u32 {
self.lifetime
}
}
Loading

0 comments on commit 1bcedd7

Please sign in to comment.