Compare commits

1 Commits

Author SHA1 Message Date
d39384c388 Use libjade as crypto backend 2024-05-16 14:58:05 +02:00
16 changed files with 147 additions and 132 deletions

1
.envrc
View File

@@ -1 +0,0 @@
use nix

1
.gitignore vendored
View File

@@ -3,4 +3,3 @@
proptest-regressions/
.idea/
result
.direnv/

33
Cargo.lock generated
View File

@@ -26,16 +26,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "agent_lib"
version = "0.1.0"
source = "git+https://gitea.rixxc.de/rixxc/agent_lib.git#2ed3949ee12bebca2d19ec788688cc59e5378ded"
dependencies = [
"anyhow",
"libc",
"shared_memory_heap",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -45,12 +35,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
[[package]]
name = "arraydeque"
version = "0.4.5"
@@ -973,32 +957,24 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.202"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.202"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "shared_memory_heap"
version = "0.1.0"
source = "git+https://gitea.rixxc.de/rixxc/shared_memory_heap.git#9ec0b728c2936c57ef8b0e6b015cdcc0474060d4"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.9"
@@ -1392,7 +1368,6 @@ name = "wireguard-rs"
version = "0.1.4"
dependencies = [
"aead",
"agent_lib",
"arraydeque",
"blake2",
"byteorder",

View File

@@ -29,7 +29,6 @@ rand_core = "^0.5"
ring = "0.16"
spin = "0.7"
zerocopy = "0.3"
agent_lib = { git = "https://gitea.rixxc.de/rixxc/agent_lib.git" }
[dependencies.treebitmap]
package = "ip_network_table-deps-treebitmap"

3
build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-search=/home/rixxc/Work/Jasmin/libjade/src/");
}

View File

@@ -1,32 +1,59 @@
{ pkgs ? import <nixpkgs> { } }:
{ pkgs ? import <nixpkgs> {
overlays = [
(import (fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"))
];
}
}:
with pkgs;
let
jasmin-src = fetchFromGitHub {
owner = "Rixxc";
repo = "jasmin";
rev = "783aea97836f5ddf7b62de24ab94768cb606adf8";
hash = "sha256-5XXZ2IYXCixJHaswdYkG8ivh3fIftaibOgkpz2TKGMI=";
};
jasmin-drv = callPackage "${jasmin-src}/default.nix" { inherit pkgs; };
jasminc = jasmin-drv.overrideAttrs {
name = "jasmin with syscalls";
buildPhase = ''
make -C compiler/ CIL
make -C compiler/
'';
};
agent = callPackage "${fetchgit {
url = "https://gitea.rixxc.de/rixxc/agent_harness.git";
rev = "d2154ade95b88fe90709cdb12a35da50e0ddb5ee";
hash = "sha256-low2S2z5vaFSS6ZdDVaxJdwBZk+mjXaG2zhConCWQPQ=";
}}/default.nix"
{ inherit pkgs; };
libjade = callPackage "${fetchFromGitHub {
owner = "formosa-crypto";
repo = "libjade";
rev = "b0940068243f01dc3c185d166f1450936eec3eed";
hash = "sha256-w71QmJn5TG1cJ+SGXJyjh86uge177uRGSvwgnJXpKYg=";
}}/default.nix"
{ inherit pkgs jasminc; };
in
rustPlatform.buildRustPackage
{
name = "wireguard-agent";
src = nix-gitignore.gitignoreSource [ ] ./.;
nativeBuildInputs = [ agent ];
nativeBuildInputs = [
(rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)
agent
libjade
];
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"agent_lib-0.1.0" = "sha256-G9PCNDLaJ18pXjoKmdSFOYFT81VJ9GxapOi7EFZMTks=";
"shared_memory_heap-0.1.0" = "sha256-pWwmMGofgXrF7U4Tj/S6pRWXrcy6OPj9zyMii+vrWNo=";
};
};
doCheck = false;
RUSTC_BOOTSTRAP = true;
AGENT_PATH = "${agent}/bin/agent_harness";
KEY_FILE = "keyfile";
RUST_LOG = "debug";

BIN
keyfile

Binary file not shown.

View File

@@ -4,7 +4,6 @@ use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, SystemTime};
use agent_lib::X25519PrivKey;
use x25519_dalek::{PublicKey, StaticSecret};
use super::udp::Owner;
@@ -79,7 +78,7 @@ pub trait Configuration {
/// # Returns
///
/// The private if set, otherwise None.
fn get_private_key(&self) -> Option<X25519PrivKey>;
fn get_private_key(&self) -> Option<StaticSecret>;
/// Returns the protocol version of the device
///
@@ -241,14 +240,13 @@ impl<T: tun::Tun, B: udp::PlatformUDP> Configuration for WireGuardConfig<T, B> {
self.lock().fwmark
}
fn set_private_key(&self, _sk: Option<StaticSecret>) {
// log::info!("configuration, set private key");
// self.lock().wireguard.set_key(sk)
fn set_private_key(&self, sk: Option<StaticSecret>) {
log::info!("configuration, set private key");
self.lock().wireguard.set_key(sk)
}
fn get_private_key(&self) -> Option<X25519PrivKey> {
// self.lock().wireguard.get_sk()
Some(X25519PrivKey::from(&[0; 8]))
fn get_private_key(&self) -> Option<StaticSecret> {
self.lock().wireguard.get_sk()
}
fn get_protocol_version(&self) -> usize {

View File

@@ -16,7 +16,7 @@ pub fn serialize<C: Configuration, W: io::Write>(writer: &mut W, config: &C) ->
// serialize interface
config
.get_private_key()
.map(|_sk| write("private_key", hex::encode([0; 32])));
.map(|sk| write("private_key", hex::encode(sk.to_bytes())));
config
.get_listen_port()

View File

@@ -3,9 +3,6 @@ use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Mutex;
use agent_lib::x22519_pubkey;
use agent_lib::X25519PrivKey;
use agent_lib::X25519PubKey;
use byteorder::{ByteOrder, LittleEndian};
use dashmap::mapref::entry::Entry;
use dashmap::DashMap;
@@ -17,6 +14,7 @@ use rand_core::{CryptoRng, RngCore};
use clear_on_drop::clear::Clear;
use x25519_dalek::PublicKey;
use x25519_dalek::StaticSecret;
use super::macs;
use super::messages::{CookieReply, Initiation, Response};
@@ -29,9 +27,9 @@ use super::types::*;
const MAX_PEER_PER_DEVICE: usize = 1 << 20;
pub struct KeyState {
pub(super) sk: X25519PrivKey, // static secret key
pub(super) pk: X25519PubKey, // static public key
macs: macs::Validator, // validator for the mac fields
pub(super) sk: StaticSecret, // static secret key
pub(super) pk: PublicKey, // static public key
macs: macs::Validator, // validator for the mac fields
}
/// The device is generic over an "opaque" type
@@ -97,31 +95,25 @@ impl<O> Device<O> {
impl<O> Device<O> {
/// Initialize a new handshake state machine
pub fn new() -> Device<O> {
let sk = X25519PrivKey::from(&[0; 8]);
let pk = x22519_pubkey(&sk);
let macs = {
let pk = PublicKey::from(*pk);
macs::Validator::new(pk)
};
Device {
keyst: Some(KeyState { sk, pk, macs }),
keyst: None,
id_map: DashMap::new(),
pk_map: HashMap::new(),
limiter: Mutex::new(RateLimiter::new()),
}
}
fn update_ss(&mut self) -> (Vec<u32>, Option<X25519PubKey>) {
fn update_ss(&mut self) -> (Vec<u32>, Option<PublicKey>) {
let mut same = None;
let mut ids = Vec::with_capacity(self.pk_map.len());
for (pk, peer) in self.pk_map.iter_mut() {
if let Some(key) = self.keyst.as_ref() {
if key.pk.as_bytes() == pk {
same = Some(X25519PubKey::from(pk));
same = Some(PublicKey::from(*pk));
peer.ss.clear()
} else {
let pk = X25519PubKey::from(pk);
peer.ss = *noise::shared_secret_agent(&key.sk, &pk);
let pk = PublicKey::from(*pk);
peer.ss = *key.sk.diffie_hellman(&pk).as_bytes();
}
} else {
peer.ss.clear();
@@ -134,6 +126,44 @@ impl<O> Device<O> {
(ids, same)
}
/// Update the secret key of the device
///
/// # Arguments
///
/// * `sk` - x25519 scalar representing the local private key
pub fn set_sk(&mut self, sk: Option<StaticSecret>) -> Option<PublicKey> {
// update secret and public key
self.keyst = sk.map(|sk| {
let pk = PublicKey::from(&sk);
let macs = macs::Validator::new(pk);
KeyState { pk, sk, macs }
});
// recalculate / erase the shared secrets for every peer
let (ids, same) = self.update_ss();
// release ids from aborted handshakes
for id in ids {
self.release(id)
}
// if we found a peer matching the device public key
// remove it and return its value to the caller
same.map(|pk| {
self.pk_map.remove(pk.as_bytes());
pk
})
}
/// Return the secret key of the device
///
/// # Returns
///
/// A secret key (x25519 scalar)
pub fn get_sk(&self) -> Option<&StaticSecret> {
self.keyst.as_ref().map(|key| &key.sk)
}
/// Add a new public key to the state machine
/// To remove public keys, you must create a new machine instance
///
@@ -142,7 +172,6 @@ impl<O> Device<O> {
/// * `pk` - The public key to add
/// * `identifier` - Associated identifier which can be used to distinguish the peers
pub fn add(&mut self, pk: PublicKey, opaque: O) -> Result<(), ConfigError> {
let pk = X25519PubKey::from(pk.as_bytes());
// ensure less than 2^20 peers
if self.pk_map.len() > MAX_PEER_PER_DEVICE {
return Err(ConfigError::new("Too many peers for device"));
@@ -156,12 +185,17 @@ impl<O> Device<O> {
}
// pre-compute shared secret and add to pk_map
let ss = self
.keyst
.as_ref()
.map(|key| *noise::shared_secret_agent(&key.sk, &pk))
.unwrap_or([0u8; 32]);
self.pk_map.insert(*pk, Peer::new(pk, ss, opaque));
self.pk_map.insert(
*pk.as_bytes(),
Peer::new(
pk,
self.keyst
.as_ref()
.map(|key| *key.sk.diffie_hellman(&pk).as_bytes())
.unwrap_or([0u8; 32]),
opaque,
),
);
Ok(())
}

View File

@@ -1,4 +1,3 @@
use agent_lib::X25519PubKey;
use generic_array::GenericArray;
use rand_core::{CryptoRng, RngCore};
use spin::RwLock;
@@ -120,10 +119,10 @@ impl Generator {
/// # Returns
///
/// A freshly initated generator
pub fn new(pk: X25519PubKey) -> Generator {
pub fn new(pk: PublicKey) -> Generator {
Generator {
mac1_key: HASH!(LABEL_MAC1, *pk).into(),
cookie_key: HASH!(LABEL_COOKIE, *pk).into(),
mac1_key: HASH!(LABEL_MAC1, pk.as_bytes()).into(),
cookie_key: HASH!(LABEL_COOKIE, pk.as_bytes()).into(),
last_mac1: None,
cookie: None,
}

View File

@@ -1,6 +1,5 @@
use std::time::Instant;
use agent_lib::{x25519, X25519PrivKey, X25519PubKey, X25519SharedKey};
// DH
use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
@@ -37,7 +36,7 @@ type HMACBlake2s = Hmac<Blake2s>;
// convenient alias to pass state temporarily into device.rs and back
type TemporaryState = (u32, X25519PubKey, GenericArray<u8, U32>, GenericArray<u8, U32>);
type TemporaryState = (u32, PublicKey, GenericArray<u8, U32>, GenericArray<u8, U32>);
const SIZE_CK: usize = 32;
const SIZE_HS: usize = 32;
@@ -213,24 +212,31 @@ mod tests {
}
}
#[link(name = "jade")]
extern "C" {
fn jade_scalarmult_curve25519_amd64_mulx(
out: *mut [u8; 32],
sk: *const [u8; 32],
pk: *const [u8; 32],
);
}
// Computes an X25519 shared secret.
//
// This function wraps dalek to add a zero-check.
// This is not recommended by the Noise specification,
// but implemented in the kernel with which we strive for absolute equivalent behavior.
#[inline(always)]
fn shared_secret(sk: &StaticSecret, pk: &PublicKey) -> Result<SharedSecret, HandshakeError> {
let ss = sk.diffie_hellman(pk);
if ss.as_bytes().ct_eq(&[0u8; 32]).into() {
Err(HandshakeError::InvalidSharedSecret)
} else {
Ok(ss)
fn shared_secret(sk: &StaticSecret, pk: &PublicKey) -> Result<[u8; 32], HandshakeError> {
let mut ss = [0u8; 32];
unsafe {
jade_scalarmult_curve25519_amd64_mulx(
ss.as_mut_ptr() as *mut [u8; 32],
sk.to_bytes().as_ptr() as *const [u8; 32],
pk.to_bytes().as_ptr() as *const [u8; 32],
);
}
}
#[inline(always)]
pub fn shared_secret_agent(sk: &X25519PrivKey, pk: &X25519PubKey) -> X25519SharedKey {
x25519(sk, pk)
Ok(ss)
}
pub(super) fn create_initiation<R: RngCore + CryptoRng, O>(
@@ -277,14 +283,14 @@ pub(super) fn create_initiation<R: RngCore + CryptoRng, O>(
// (C, k) := Kdf2(C, DH(E_priv, S_pub))
let (ck, key) = KDF2!(&ck, shared_secret(&eph_sk, &pk)?.as_bytes());
let (ck, key) = KDF2!(&ck, &shared_secret(&eph_sk, &pk)?);
// msg.static := Aead(k, 0, S_pub, H)
SEAL!(
&key,
&hs, // ad
keyst.pk.as_slice(), // pt
keyst.pk.as_bytes(), // pt
&mut msg.f_static // ct || tag
);
@@ -334,7 +340,7 @@ pub(super) fn consume_initiation<'a, O>(
let ck = INITIAL_CK;
let hs = INITIAL_HS;
let hs = HASH!(&hs, *keyst.pk);
let hs = HASH!(&hs, keyst.pk.as_bytes());
// C := Kdf(C, E_pub)
@@ -346,8 +352,8 @@ pub(super) fn consume_initiation<'a, O>(
// (C, k) := Kdf2(C, DH(E_priv, S_pub))
let eph_r_pk = X25519PubKey::from(&msg.f_ephemeral);
let (ck, key) = KDF2!(&ck, shared_secret_agent(&keyst.sk, &eph_r_pk).as_slice());
let eph_r_pk = PublicKey::from(msg.f_ephemeral);
let (ck, key) = KDF2!(&ck, &shared_secret(&keyst.sk, &eph_r_pk)?);
// msg.static := Aead(k, 0, S_pub, H)
@@ -393,7 +399,7 @@ pub(super) fn consume_initiation<'a, O>(
// check and update timestamp
peer.check_replay_flood(device, &ts)?;
// peer.check_replay_flood(device, &ts)?;
// H := Hash(H || msg.timestamp)
@@ -446,12 +452,11 @@ pub(super) fn create_response<R: RngCore + CryptoRng, O>(
// C := Kdf1(C, DH(E_priv, E_pub))
let eph_r_pk = PublicKey::from(*eph_r_pk);
let ck = KDF1!(&ck, shared_secret(&eph_sk, &eph_r_pk)?.as_bytes());
let ck = KDF1!(&ck, &shared_secret(&eph_sk, &eph_r_pk)?);
// C := Kdf1(C, DH(E_priv, S_pub))
let ck = KDF1!(&ck, shared_secret(&eph_sk, &pk)?.as_bytes());
let ck = KDF1!(&ck, &shared_secret(&eph_sk, &pk)?);
// (C, tau, k) := Kdf3(C, Q)
@@ -529,12 +534,11 @@ pub(super) fn consume_response<'a, O>(
// C := Kdf1(C, DH(E_priv, E_pub))
let eph_r_pk = PublicKey::from(msg.f_ephemeral);
let ck = KDF1!(&ck, shared_secret(&eph_sk, &eph_r_pk)?.as_bytes());
let ck = KDF1!(&ck, &shared_secret(&eph_sk, &eph_r_pk)?);
// C := Kdf1(C, DH(E_priv, S_pub))
let eph_r_pk = X25519PubKey::from(&msg.f_ephemeral);
let ck = KDF1!(&ck, shared_secret_agent(&keyst.sk, &eph_r_pk).as_slice());
let ck = KDF1!(&ck, &shared_secret(&keyst.sk, &eph_r_pk)?);
// (C, tau, k) := Kdf3(C, Q)

View File

@@ -1,4 +1,3 @@
use agent_lib::X25519PubKey;
use spin::Mutex;
use std::mem;
@@ -60,7 +59,7 @@ impl Drop for State {
}
impl<O> Peer<O> {
pub fn new(pk: X25519PubKey, ss: [u8; 32], opaque: O) -> Self {
pub fn new(pk: PublicKey, ss: [u8; 32], opaque: O) -> Self {
Self {
opaque,
macs: Mutex::new(macs::Generator::new(pk)),

View File

@@ -183,17 +183,16 @@ impl<T: Tun, B: UDP> WireGuard<T, B> {
}
pub fn set_key(&self, sk: Option<StaticSecret>) {
// let mut peers = self.peers.write();
// peers.set_sk(sk);
// self.router.clear_sending_keys();
let mut peers = self.peers.write();
peers.set_sk(sk);
self.router.clear_sending_keys();
}
pub fn get_sk(&self) -> Option<StaticSecret> {
// self.peers
// .read()
// .get_sk()
// .map(|sk| StaticSecret::from(sk.to_bytes()))
None
self.peers
.read()
.get_sk()
.map(|sk| StaticSecret::from(sk.to_bytes()))
}
pub fn set_psk(&self, pk: PublicKey, psk: [u8; 32]) -> bool {

View File

@@ -1,9 +0,0 @@
# Agent implementation
[Interface]
ListenPort = 31337
[Peer]
PublicKey = hu/U5Dg+tt9z0vfJZgSjPlVXBoi0Ux4ELFhfLnzBWRc=
AllowedIPs = 100.64.100.2/32
Endpoint = 167.235.26.58:31338

View File

@@ -1,11 +0,0 @@
# Normal implementation
[Interface]
ListenPort = 31338
PrivateKey = cAOHZMXEq20N8/keW4lYrkhej3oCFeMV3uJ/3f22eGQ=
Address = 100.64.100.2/24
[Peer]
PublicKey = 9z0xORKt5DZJeqc55/6YFljRz3Kv90yPFsIPgHjIkTs=
AllowedIPs = 100.64.100.1/32
Endpoint = 127.0.0.1:31337