More work on ciphers and hkdf
Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
parent
238a0a5516
commit
a39767d32b
43
Cargo.toml
43
Cargo.toml
|
@ -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]
|
||||
|
||||
|
|
|
@ -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,
|
||||
})))
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
pub mod handshake;
|
||||
mod packet;
|
||||
|
||||
use ::num_traits::FromPrimitive;
|
||||
use ::std::vec::Vec;
|
||||
|
||||
pub use handshake::Handshake;
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod asym;
|
||||
mod errors;
|
||||
pub mod hkdf;
|
||||
pub mod sym;
|
||||
|
||||
pub use errors::Error;
|
||||
|
|
|
@ -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)]
|
||||
|
|
37
src/lib.rs
37
src/lib.rs
|
@ -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())),
|
||||
}),
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue