refactor(server): migrated auth to ml-dsa

This commit is contained in:
hdbg
2026-04-07 11:43:21 +02:00
parent 1497884ce6
commit 0d424f3afc
25 changed files with 457 additions and 414 deletions

View File

@@ -18,7 +18,7 @@ tonic.workspace = true
tonic.features = ["tls-aws-lc"]
tokio.workspace = true
tokio-stream.workspace = true
ed25519-dalek.workspace = true
ml-dsa.workspace = true
thiserror.workspace = true
http = "1.4.0"
rustls-webpki = { version = "0.103.10", features = ["aws-lc-rs"] }

View File

@@ -1,5 +1,5 @@
use arbiter_proto::{
ClientMetadata, format_challenge,
CLIENT_CONTEXT, ClientMetadata, format_challenge,
proto::{
client::{
ClientRequest,
@@ -14,7 +14,7 @@ use arbiter_proto::{
shared::ClientInfo as ProtoClientInfo,
},
};
use ed25519_dalek::Signer as _;
use ml_dsa::{MlDsa87, SigningKey, signature::Keypair as _};
use crate::{
storage::StorageError,
@@ -54,14 +54,14 @@ fn map_auth_result(code: i32) -> AuthError {
async fn send_auth_challenge_request(
transport: &mut ClientTransport,
metadata: ClientMetadata,
key: &ed25519_dalek::SigningKey,
key: &SigningKey<MlDsa87>,
) -> std::result::Result<(), AuthError> {
transport
.send(ClientRequest {
request_id: next_request_id(),
payload: Some(ClientRequestPayload::Auth(proto_auth::Request {
payload: Some(AuthRequestPayload::ChallengeRequest(AuthChallengeRequest {
pubkey: key.verifying_key().to_bytes().to_vec(),
pubkey: key.verifying_key().encode().to_vec(),
client_info: Some(ProtoClientInfo {
name: metadata.name,
description: metadata.description,
@@ -95,11 +95,16 @@ async fn receive_auth_challenge(
async fn send_auth_challenge_solution(
transport: &mut ClientTransport,
key: &ed25519_dalek::SigningKey,
key: &SigningKey<MlDsa87>,
challenge: AuthChallenge,
) -> std::result::Result<(), AuthError> {
let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey);
let signature = key.sign(&challenge_payload).to_bytes().to_vec();
let signature = key
.signing_key()
.sign_deterministic(&challenge_payload, CLIENT_CONTEXT)
.map_err(|_| AuthError::UnexpectedAuthResponse)?
.encode()
.to_vec();
transport
.send(ClientRequest {
@@ -140,7 +145,7 @@ async fn receive_auth_confirmation(
pub(crate) async fn authenticate(
transport: &mut ClientTransport,
metadata: ClientMetadata,
key: &ed25519_dalek::SigningKey,
key: &SigningKey<MlDsa87>,
) -> std::result::Result<(), AuthError> {
send_auth_challenge_request(transport, metadata, key).await?;
let challenge = receive_auth_challenge(transport).await?;

View File

@@ -1,6 +1,7 @@
use arbiter_proto::{
ClientMetadata, proto::arbiter_service_client::ArbiterServiceClient, url::ArbiterUrl,
};
use ml_dsa::{MlDsa87, SigningKey};
use std::sync::Arc;
use tokio::sync::{Mutex, mpsc};
use tokio_stream::wrappers::ReceiverStream;
@@ -60,7 +61,7 @@ impl ArbiterClient {
pub async fn connect_with_key(
url: ArbiterUrl,
metadata: ClientMetadata,
key: ed25519_dalek::SigningKey,
key: SigningKey<MlDsa87>,
) -> Result<Self, Error> {
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
let tls = ClientTlsConfig::new().trust_anchor(anchor);

View File

@@ -1,4 +1,5 @@
use arbiter_proto::home_path;
use ml_dsa::{KeyGen, MlDsa87, Seed, SigningKey};
use std::path::{Path, PathBuf};
#[derive(Debug, thiserror::Error)]
@@ -11,7 +12,7 @@ pub enum StorageError {
}
pub trait SigningKeyStorage {
fn load_or_create(&self) -> std::result::Result<ed25519_dalek::SigningKey, StorageError>;
fn load_or_create(&self) -> std::result::Result<SigningKey<MlDsa87>, StorageError>;
}
#[derive(Debug, Clone)]
@@ -20,7 +21,7 @@ pub struct FileSigningKeyStorage {
}
impl FileSigningKeyStorage {
pub const DEFAULT_FILE_NAME: &str = "sdk_client_ed25519.key";
pub const DEFAULT_FILE_NAME: &str = "sdk_client_ml_dsa.key";
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
@@ -30,21 +31,20 @@ impl FileSigningKeyStorage {
Ok(Self::new(home_path()?.join(Self::DEFAULT_FILE_NAME)))
}
fn read_key(path: &Path) -> std::result::Result<ed25519_dalek::SigningKey, StorageError> {
fn read_key(path: &Path) -> std::result::Result<SigningKey<MlDsa87>, StorageError> {
let bytes = std::fs::read(path)?;
let raw: [u8; 32] =
bytes
.try_into()
.map_err(|v: Vec<u8>| StorageError::InvalidKeyLength {
expected: 32,
actual: v.len(),
})?;
Ok(ed25519_dalek::SigningKey::from_bytes(&raw))
let raw: [u8; 32] = bytes
.try_into()
.map_err(|v: Vec<u8>| StorageError::InvalidKeyLength {
expected: 32,
actual: v.len(),
})?;
Ok(MlDsa87::from_seed(&Seed::from(raw)))
}
}
impl SigningKeyStorage for FileSigningKeyStorage {
fn load_or_create(&self) -> std::result::Result<ed25519_dalek::SigningKey, StorageError> {
fn load_or_create(&self) -> std::result::Result<SigningKey<MlDsa87>, StorageError> {
if let Some(parent) = self.path.parent() {
std::fs::create_dir_all(parent)?;
}
@@ -53,8 +53,8 @@ impl SigningKeyStorage for FileSigningKeyStorage {
return Self::read_key(&self.path);
}
let key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
let raw_key = key.to_bytes();
let key = MlDsa87::key_gen(&mut rand::rng());
let raw_key = key.to_seed();
// Use create_new to prevent accidental overwrite if another process creates the key first.
match std::fs::OpenOptions::new()
@@ -103,7 +103,7 @@ mod tests {
.load_or_create()
.expect("second load_or_create should read same key");
assert_eq!(key_a.to_bytes(), key_b.to_bytes());
assert_eq!(key_a.to_seed(), key_b.to_seed());
assert!(path.exists());
std::fs::remove_file(path).expect("temp key file should be removable");