More work on ciphers and hkdf

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2023-02-17 14:59:02 +01:00
parent 238a0a5516
commit a39767d32b
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
9 changed files with 270 additions and 56 deletions

View File

@ -10,7 +10,7 @@ rust-version = "1.67.0"
homepage = "https://git.runesauth.com/RunesAuth/libFenrir"
repository = "https://git.runesauth.com/RunesAuth/libFenrir"
license = "Apache-2.0 WITH LLVM-exception"
license-file = "LICENSE"
#license-file = "LICENSE"
keywords = [ "Fenrir", "libFenrir", "authentication" ]
categories = [ "authentication", "cryptography", "network-programming" ]
@ -26,26 +26,33 @@ crate_type = [ "lib", "cdylib", "staticlib" ]
[dependencies]
# please keep these in alphabetical order
arc-swap = { version = "^1.6" }
arc-swap = { version = "1.6" }
# base85 repo has no tags, fix on a commit. v1.1.1 points to older, wrong version
base85 = { git = "https://gitlab.com/darkwyrm/base85", rev = "d98efbfd171dd9ba48e30a5c88f94db92fc7b3c6" }
futures = { version = "^0.3" }
libc = { version = "^0.2" }
num-traits = { version = "^0.2" }
num-derive = { version = "^0.3" }
ring = { version = "^0.16" }
bincode = { version = "^1.3" }
strum = { version = "^0.24" }
strum_macros = { version = "^0.24" }
thiserror = { version = "^1.0" }
tokio = { version = "^1", features = ["full"] }
futures = { version = "0.3" }
hkdf = { version = "0.12" }
libc = { version = "0.2" }
num-traits = { version = "0.2" }
num-derive = { version = "0.3" }
ring = { version = "0.16" }
bincode = { version = "1.3" }
sha3 = { version = "0.10" }
strum = { version = "0.24" }
strum_macros = { version = "0.24" }
thiserror = { version = "1.0" }
tokio = { version = "1", features = ["full"] }
# PERF: todo linux-only, behind "iouring" feature
#tokio-uring = { version = "^0.4" }
tracing = { version = "^0.1" }
trust-dns-resolver = { version = "^0.22", features = [ "dnssec-ring" ] }
trust-dns-client = { version = "^0.22", features = [ "dnssec" ] }
trust-dns-proto = { version = "^0.22" }
x25519-dalek = { version = "^1.2", features = [ "serde" ] }
#tokio-uring = { version = "0.4" }
tracing = { version = "0.1" }
trust-dns-resolver = { version = "0.22", features = [ "dnssec-ring" ] }
trust-dns-client = { version = "0.22", features = [ "dnssec" ] }
trust-dns-proto = { version = "0.22" }
x25519-dalek = { version = "1.2", features = [ "serde" ] }
# NOTE: can not force newer versions of zeroize:
# * zeroize is a dependency of x25519-dalek
# * zeroize is not *pure* rust, but uses `alloc`
# * rust can have multiple versions as deps, but only if the are pure rust
zeroize = { version = "1" }
[profile.dev]

View File

@ -11,7 +11,10 @@
use super::{Error, HandshakeData};
use crate::{
connection::ID,
enc::asym::{ExchangePubKey, KeyID},
enc::{
asym::{ExchangePubKey, KeyExchange, KeyID},
sym::CipherKind,
},
};
use ::std::vec::Vec;
@ -29,6 +32,10 @@ pub enum DirSync {
pub struct Req {
/// Id of the server key used for the key exchange
pub key_id: KeyID,
/// Selected key exchange
pub exchange: KeyExchange,
/// Selected cipher
pub cipher: CipherKind,
/// Client ephemeral public key used for key exchanges
pub exchange_key: ExchangePubKey,
/// encrypted data
@ -37,19 +44,30 @@ pub struct Req {
impl super::HandshakeParsing for Req {
fn parse(raw: &[u8]) -> Result<HandshakeData, Error> {
const MIN_PKT_LEN: usize = 8;
const MIN_PKT_LEN: usize = 10;
if raw.len() < MIN_PKT_LEN {
return Err(Error::NotEnoughData);
}
let key_id: KeyID =
KeyID(u16::from_le_bytes(raw[0..1].try_into().unwrap()));
let (exchange_key, len) = match ExchangePubKey::from_slice(&raw[2..]) {
use ::num_traits::FromPrimitive;
let exchange: KeyExchange = match KeyExchange::from_u8(raw[2]) {
Some(exchange) => exchange,
None => return Err(Error::Parsing),
};
let cipher: CipherKind = match CipherKind::from_u8(raw[3]) {
Some(cipher) => cipher,
None => return Err(Error::Parsing),
};
let (exchange_key, len) = match ExchangePubKey::from_slice(&raw[4..]) {
Ok(exchange_key) => exchange_key,
Err(e) => return Err(e.into()),
};
let enc = raw[(2 + len)..].to_vec();
let enc = raw[(4 + len)..].to_vec();
Ok(HandshakeData::DirSync(DirSync::Req(Self {
key_id,
exchange,
cipher,
exchange_key,
enc,
})))

View File

@ -3,7 +3,6 @@
pub mod handshake;
mod packet;
use ::num_traits::FromPrimitive;
use ::std::vec::Vec;
pub use handshake::Handshake;

View File

@ -4,11 +4,36 @@ use ::num_traits::FromPrimitive;
use ::std::vec::Vec;
use super::Error;
use crate::enc::sym::Secret;
/// Public key ID
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct KeyID(pub u16);
/// Kind of key used in the handshake
#[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)]
#[repr(u8)]
pub enum Key {
/// X25519 Public key
X25519 = 0,
}
impl Key {
fn pub_len(&self) -> usize {
match self {
// FIXME: 99% wrong size
Key::X25519 => ::ring::signature::ED25519_PUBLIC_KEY_LEN,
}
}
}
/// Kind of key exchange
#[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)]
#[repr(u8)]
pub enum KeyExchange {
/// X25519 Public key
X25519DiffieHellman = 0,
}
/// Kind of key in the handshake
#[derive(Clone)]
#[allow(missing_debug_implementations)]
@ -28,28 +53,29 @@ pub enum ExchangePrivKey {
}
impl ExchangePrivKey {
/// Get the kind of key
pub fn kind(&self) -> Key {
match self {
ExchangePrivKey::X25519(_) => Key::X25519,
}
}
/// Run the key exchange between two keys of the same kind
pub fn key_exchange(
&self,
exchange: KeyExchange,
pub_key: ExchangePubKey,
) -> Result<[u8; 32], Error> {
todo!();
}
}
/// Kind of key in the key exchange
#[derive(Debug, Copy, Clone, ::num_derive::FromPrimitive)]
#[repr(u8)]
pub enum ExchangePubKeyKind {
/// X25519 Public key
X25519 = 0,
}
impl ExchangePubKeyKind {
fn len(&self) -> usize {
) -> Result<Secret, Error> {
match self {
// FIXME: 99% wrong size
ExchangePubKeyKind::X25519 => {
::ring::signature::ED25519_PUBLIC_KEY_LEN
ExchangePrivKey::X25519(priv_key) => {
if exchange != KeyExchange::X25519DiffieHellman {
return Err(Error::UnsupportedKeyExchange);
}
if let ExchangePubKey::X25519(inner_pub_key) = pub_key {
let shared_secret = priv_key.diffie_hellman(&inner_pub_key);
Ok(shared_secret.into())
} else {
Err(Error::UnsupportedKeyExchange)
}
}
}
}
@ -72,16 +98,17 @@ impl ExchangePubKey {
if raw.len() < 1 + MIN_KEY_SIZE {
return Err(Error::NotEnoughData);
}
match ExchangePubKeyKind::from_u8(raw[0]) {
match Key::from_u8(raw[0]) {
Some(kind) => match kind {
ExchangePubKeyKind::X25519 => {
Key::X25519 => {
let pub_key: ::x25519_dalek::PublicKey =
match ::bincode::deserialize(&raw[1..(1 + kind.len())])
{
match ::bincode::deserialize(
&raw[1..(1 + kind.pub_len())],
) {
Ok(pub_key) => pub_key,
Err(_) => return Err(Error::Parsing),
};
Ok((ExchangePubKey::X25519(pub_key), kind.len()))
Ok((ExchangePubKey::X25519(pub_key), kind.pub_len()))
}
},
None => {

View File

@ -13,4 +13,10 @@ pub enum Error {
/// You might have passed rsa keys where x25519 was expected
#[error("wrong key type")]
WrongKey,
/// Unsupported key exchange for this key
#[error("unsupported key exchange")]
UnsupportedKeyExchange,
/// Unsupported cipher
#[error("unsupported cipher")]
UnsupportedCipher,
}

47
src/enc/hkdf.rs Normal file
View File

@ -0,0 +1,47 @@
//! Hash-based Key Derivation Function
//! We just repackage other crates
use ::hkdf::Hkdf;
use ::sha3::Sha3_256;
use ::zeroize::Zeroize;
use crate::enc::sym::Secret;
// Hack & tricks:
// HKDF are pretty important, but they don't zero out the data.
// we can't user #[derive(Zeroing)] either.
// So we craete a union with a Zeroing object, and drop manually both.
#[derive(Zeroize)]
#[zeroize(drop)]
struct Zeroable([u8; ::core::mem::size_of::<Hkdf<Sha3_256>>()]);
union HkdfInner {
hkdf: ::core::mem::ManuallyDrop<Hkdf<Sha3_256>>,
zeroable: ::core::mem::ManuallyDrop<Zeroable>,
}
impl Drop for HkdfInner {
fn drop(&mut self) {
#[allow(unsafe_code)]
unsafe {
drop(&mut self.hkdf);
drop(&mut self.zeroable);
}
}
}
/// Sha3 based HKDF
#[allow(missing_debug_implementations)]
pub struct HkdfSha3 {
_inner: Hkdf<Sha3_256>,
}
impl HkdfSha3 {
/// Instantiate a new HKDF with Sha3-256
pub fn new(salt: Option<&[u8]>, key: Secret) -> Self {
Self {
_inner: Hkdf::<Sha3_256>::new(salt, key.as_ref()),
}
}
}

View File

@ -2,6 +2,7 @@
pub mod asym;
mod errors;
pub mod hkdf;
pub mod sym;
pub use errors::Error;

View File

@ -1,5 +1,96 @@
//! Symmetric cypher stuff
use ::zeroize::Zeroize;
/// Secret, used for keys.
/// Grants that on drop() we will zero out memory
#[derive(Zeroize)]
#[zeroize(drop)]
#[allow(missing_debug_implementations)]
pub struct Secret {
secret: [u8; 32],
}
impl Secret {
/// return a reference to the secret
pub fn as_ref(&self) -> &[u8; 32] {
&self.secret
}
}
impl From<::x25519_dalek::SharedSecret> for Secret {
fn from(shared_secret: ::x25519_dalek::SharedSecret) -> Self {
Self {
secret: shared_secret.to_bytes(),
}
}
}
/// List of possible Ciphers
#[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)]
#[repr(u8)]
pub enum CipherKind {
/// Chaha20_Poly1305
Chacha20Poly1305 = 0,
}
impl CipherKind {
/// required length of the nonce
pub fn nonce_len(&self) -> usize {
::ring::aead::CHACHA20_POLY1305.nonce_len()
}
/// required length of the key
pub fn key_len(&self) -> usize {
::ring::aead::CHACHA20_POLY1305.key_len()
}
/// Length of the authentication tag
pub fn tag_len(&self) -> usize {
::ring::aead::CHACHA20_POLY1305.tag_len()
}
}
/// actual ciphers
#[derive(Debug)]
pub enum Cipher {
/// Cipher Chaha20_Poly1305
Chacha20Poly1305(Chacha20Poly1305),
}
impl Cipher {
/// Build a new Cipher
pub fn new(kind: CipherKind, secret: Secret) -> Self {
match kind {
CipherKind::Chacha20Poly1305 => {
Self::Chacha20Poly1305(Chacha20Poly1305::new(secret))
}
}
}
}
/// Chacha20Poly1305 cipher
#[derive(Debug)]
pub struct Chacha20Poly1305 {
cipher: ::ring::aead::OpeningKey<Nonce>,
}
impl Chacha20Poly1305 {
/// Initialize the cipher with the given nonce
pub fn with_nonce(key: Secret, nonce: Nonce) -> Self {
let unbound_key = ::ring::aead::UnboundKey::new(
&::ring::aead::CHACHA20_POLY1305,
key.as_ref(),
)
.unwrap();
use ::ring::aead::BoundKey;
Self {
cipher: ::ring::aead::OpeningKey::<Nonce>::new(unbound_key, nonce),
}
}
/// Initialize a new ChachaPoly20 cipher with a random nonce
pub fn new(key: Secret) -> Self {
Self::with_nonce(key, Nonce::new())
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
#[allow(missing_debug_implementations)]
@ -11,6 +102,8 @@ struct NonceNum {
#[repr(C)]
pub union Nonce {
num: NonceNum,
// ring::aead::Nonce does not implement 'Copy'
// even though it's just [u8;12]
raw: ::core::mem::ManuallyDrop<::ring::aead::Nonce>,
easy_from: [u8; 12],
}
@ -48,7 +141,6 @@ impl Nonce {
}
}
//impl Copy for Nonce {}
impl Clone for Nonce {
fn clone(&self) -> Self {
#[allow(unsafe_code)]

View File

@ -22,6 +22,7 @@ use ::arc_swap::{ArcSwap, ArcSwapAny};
use ::std::{net::SocketAddr, sync::Arc};
use ::tokio::{net::UdpSocket, task::JoinHandle};
use crate::enc::{asym, sym::CipherKind};
pub use config::Config;
use connection::handshake::{self, Handshake, HandshakeKey};
@ -40,9 +41,14 @@ pub enum Error {
/// Handshake errors
#[error("Handshake: {0:?}")]
Handshake(#[from] handshake::Error),
/// Key error
#[error("key: {0:?}")]
Key(#[from] crate::enc::Error),
}
struct FenrirInner {
key_exchanges: ArcSwapAny<Arc<Vec<(asym::Key, asym::KeyExchange)>>>,
ciphers: ArcSwapAny<Arc<Vec<CipherKind>>>,
keys: ArcSwapAny<Arc<Vec<HandshakeKey>>>,
}
@ -76,21 +82,30 @@ impl FenrirInner {
return Err(handshake::Error::UnknownKeyID.into());
}
let ephemeral_key = ephemeral_key.unwrap();
{
let exchanges = self.key_exchanges.load();
if None
== exchanges.iter().find(|&x| {
*x == (ephemeral_key.kind(), req.exchange)
})
{
return Err(
enc::Error::UnsupportedKeyExchange.into()
);
}
}
{
let ciphers = self.ciphers.load();
if None == ciphers.iter().find(|&x| *x == req.cipher) {
return Err(enc::Error::UnsupportedCipher.into());
}
}
let shared_key = match ephemeral_key
.key_exchange(req.exchange_key)
.key_exchange(req.exchange, req.exchange_key)
{
Ok(shared_key) => shared_key,
Err(e) => return Err(handshake::Error::Key(e).into()),
};
use crate::enc::sym::Nonce;
use ::ring::aead::{self, BoundKey};
let alg = aead::UnboundKey::new(
&aead::CHACHA20_POLY1305,
&shared_key,
)
.unwrap();
let nonce = Nonce::new();
let chacha = aead::OpeningKey::new(alg, nonce);
todo!();
}
DirSync::Resp(resp) => {
@ -135,6 +150,8 @@ impl Fenrir {
dnssec: None,
stop_working: sender,
_inner: Arc::new(FenrirInner {
ciphers: ArcSwapAny::new(Arc::new(Vec::new())),
key_exchanges: ArcSwapAny::new(Arc::new(Vec::new())),
keys: ArcSwapAny::new(Arc::new(Vec::new())),
}),
};