security: batch of fixes #95

Merged
Skipper merged 14 commits from zeroized-bootstrap-token into main 2026-06-29 18:00:14 +00:00
6 changed files with 25 additions and 20 deletions
Showing only changes of commit dc03923c24 - Show all commits

9
server/Cargo.lock generated
View File

@@ -787,6 +787,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"x25519-dalek 2.0.1",
"zeroize",
]
[[package]]
@@ -6261,18 +6262,18 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.8.2"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.3"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -36,6 +36,7 @@ chrono.workspace = true
kameo.workspace = true
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
argon2 = { version = "0.5.3", features = ["zeroize"] }
zeroize = "1.9"
restructed = "0.2.2"
strum = { version = "0.28.0", features = ["derive"] }
pem = "3.0.6"

View File

@@ -7,6 +7,7 @@ use kameo::{Actor, messages};
use rand::{RngExt, distr::Alphanumeric, make_rng, rngs::StdRng};
use subtle::ConstantTimeEq as _;
use thiserror::Error;
use zeroize::Zeroizing;
const TOKEN_LENGTH: usize = 64;
@@ -40,7 +41,7 @@ pub enum Error {
#[derive(Actor)]
pub struct Bootstrapper {
token: Option<String>,
token: Option<Zeroizing<String>>,
}
impl Bootstrapper {
@@ -56,7 +57,7 @@ impl Bootstrapper {
let token = if row_count == 0 {
let token = generate_token().await?;
Some(token)
Some(Zeroizing::new(token))
} else {
None
};
@@ -65,10 +66,8 @@ impl Bootstrapper {
}
}
#[messages]
impl Bootstrapper {
#[message]
pub fn is_correct_token(&self, token: String) -> bool {
fn is_correct_token(&self, token: &str) -> bool {
self.token.as_ref().is_some_and(|expected| {
let expected_bytes = expected.as_bytes();
let token_bytes = token.as_bytes();
@@ -77,10 +76,13 @@ impl Bootstrapper {
bool::from(choice)
})
}
}
#[messages]
impl Bootstrapper {
#[message]
pub fn consume_token(&mut self, token: String) -> bool {
if self.is_correct_token(token) {
pub fn consume_token(&mut self, token: Zeroizing<String>) -> bool {
if self.is_correct_token(&token) {
self.token = None;
true
} else {
@@ -93,6 +95,6 @@ impl Bootstrapper {
impl Bootstrapper {
#[message]
pub fn get_token(&self) -> Option<String> {
self.token.clone()
self.token.as_ref().map(|token| token.to_string())
}
}

View File

@@ -18,6 +18,7 @@ use arbiter_proto::{
use async_trait::async_trait;
use tonic::Status;
use tracing::warn;
use zeroize::Zeroizing;
pub(super) struct AuthTransportAdapter<'a> {
pub(super) bi: &'a mut GrpcBi<OperatorRequest, OperatorResponse>,
@@ -171,7 +172,7 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
Some(auth::Inbound::AuthChallengeRequest {
pubkey,
bootstrap_token,
bootstrap_token: bootstrap_token.map(Zeroizing::new),
})
}
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution { signature }) => {

View File

@@ -1,6 +1,7 @@
use super::{Credentials, OperatorConnection};
use arbiter_crypto::authn::{self, AuthChallenge};
use arbiter_proto::transport::Bi;
use zeroize::Zeroizing;
use state::{
AuthContext, AuthError, AuthEvents, AuthStateMachine, AuthStates, ChallengeRequest,
@@ -14,7 +15,7 @@ mod state;
pub enum Inbound {
AuthChallengeRequest {
pubkey: authn::PublicKey,
bootstrap_token: Option<String>,
bootstrap_token: Option<Zeroizing<String>>,
},
AuthChallengeSolution {
signature: Vec<u8>,

View File

@@ -9,6 +9,7 @@ use crate::{
};
use arbiter_crypto::authn::{self, AuthChallenge, OPERATOR_CONTEXT};
use arbiter_proto::transport::Bi;
use zeroize::Zeroizing;
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl};
use diesel_async::RunQueryDsl;
@@ -16,13 +17,13 @@ use tracing::error;
pub(super) struct ChallengeRequest {
pub(super) pubkey: authn::PublicKey,
pub(super) bootstrap_token: Option<String>,
pub(super) bootstrap_token: Option<Zeroizing<String>>,
}
pub struct ChallengeContext {
pub(super) challenge: AuthChallenge,
pub(super) pubkey: authn::PublicKey,
pub(super) bootstrap_token: Option<String>,
pub(super) bootstrap_token: Option<Zeroizing<String>>,
}
pub(super) struct ChallengeSolution {
@@ -152,15 +153,13 @@ where
}
// Resolve client id: bootstrap (consume token + register) or lookup
let id = match bootstrap_token {
let id = match bootstrap_token.clone() {
Some(token) => {
let token_ok: bool = self
.conn
.actors
.bootstrapper
.ask(ConsumeToken {
token: token.clone(),
})
.ask(ConsumeToken { token })
.await
.map_err(|e| {
error!(?e, "Failed to consume bootstrap token");