security(memory): zeroization of bootstrap token
This commit is contained in:
9
server/Cargo.lock
generated
9
server/Cargo.lock
generated
@@ -787,6 +787,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"x25519-dalek 2.0.1",
|
"x25519-dalek 2.0.1",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6261,18 +6262,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.8.2"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zeroize_derive",
|
"zeroize_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize_derive"
|
name = "zeroize_derive"
|
||||||
version = "1.4.3"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ chrono.workspace = true
|
|||||||
kameo.workspace = true
|
kameo.workspace = true
|
||||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||||
argon2 = { version = "0.5.3", features = ["zeroize"] }
|
argon2 = { version = "0.5.3", features = ["zeroize"] }
|
||||||
|
zeroize = "1.9"
|
||||||
restructed = "0.2.2"
|
restructed = "0.2.2"
|
||||||
strum = { version = "0.28.0", features = ["derive"] }
|
strum = { version = "0.28.0", features = ["derive"] }
|
||||||
pem = "3.0.6"
|
pem = "3.0.6"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use kameo::{Actor, messages};
|
|||||||
use rand::{RngExt, distr::Alphanumeric, make_rng, rngs::StdRng};
|
use rand::{RngExt, distr::Alphanumeric, make_rng, rngs::StdRng};
|
||||||
use subtle::ConstantTimeEq as _;
|
use subtle::ConstantTimeEq as _;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
const TOKEN_LENGTH: usize = 64;
|
const TOKEN_LENGTH: usize = 64;
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ pub enum Error {
|
|||||||
|
|
||||||
#[derive(Actor)]
|
#[derive(Actor)]
|
||||||
pub struct Bootstrapper {
|
pub struct Bootstrapper {
|
||||||
token: Option<String>,
|
token: Option<Zeroizing<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bootstrapper {
|
impl Bootstrapper {
|
||||||
@@ -56,7 +57,7 @@ impl Bootstrapper {
|
|||||||
|
|
||||||
let token = if row_count == 0 {
|
let token = if row_count == 0 {
|
||||||
let token = generate_token().await?;
|
let token = generate_token().await?;
|
||||||
Some(token)
|
Some(Zeroizing::new(token))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -65,10 +66,8 @@ impl Bootstrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
|
||||||
impl Bootstrapper {
|
impl Bootstrapper {
|
||||||
#[message]
|
fn is_correct_token(&self, token: &str) -> bool {
|
||||||
pub fn is_correct_token(&self, token: String) -> bool {
|
|
||||||
self.token.as_ref().is_some_and(|expected| {
|
self.token.as_ref().is_some_and(|expected| {
|
||||||
let expected_bytes = expected.as_bytes();
|
let expected_bytes = expected.as_bytes();
|
||||||
let token_bytes = token.as_bytes();
|
let token_bytes = token.as_bytes();
|
||||||
@@ -77,10 +76,13 @@ impl Bootstrapper {
|
|||||||
bool::from(choice)
|
bool::from(choice)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[messages]
|
||||||
|
impl Bootstrapper {
|
||||||
#[message]
|
#[message]
|
||||||
pub fn consume_token(&mut self, token: String) -> bool {
|
pub fn consume_token(&mut self, token: Zeroizing<String>) -> bool {
|
||||||
if self.is_correct_token(token) {
|
if self.is_correct_token(&token) {
|
||||||
self.token = None;
|
self.token = None;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -93,6 +95,6 @@ impl Bootstrapper {
|
|||||||
impl Bootstrapper {
|
impl Bootstrapper {
|
||||||
#[message]
|
#[message]
|
||||||
pub fn get_token(&self) -> Option<String> {
|
pub fn get_token(&self) -> Option<String> {
|
||||||
self.token.clone()
|
self.token.as_ref().map(|token| token.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use arbiter_proto::{
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
pub(super) struct AuthTransportAdapter<'a> {
|
pub(super) struct AuthTransportAdapter<'a> {
|
||||||
pub(super) bi: &'a mut GrpcBi<OperatorRequest, OperatorResponse>,
|
pub(super) bi: &'a mut GrpcBi<OperatorRequest, OperatorResponse>,
|
||||||
@@ -171,7 +172,7 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
|
|
||||||
Some(auth::Inbound::AuthChallengeRequest {
|
Some(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey,
|
pubkey,
|
||||||
bootstrap_token,
|
bootstrap_token: bootstrap_token.map(Zeroizing::new),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution { signature }) => {
|
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution { signature }) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::{Credentials, OperatorConnection};
|
use super::{Credentials, OperatorConnection};
|
||||||
use arbiter_crypto::authn::{self, AuthChallenge};
|
use arbiter_crypto::authn::{self, AuthChallenge};
|
||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::Bi;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
use state::{
|
use state::{
|
||||||
AuthContext, AuthError, AuthEvents, AuthStateMachine, AuthStates, ChallengeRequest,
|
AuthContext, AuthError, AuthEvents, AuthStateMachine, AuthStates, ChallengeRequest,
|
||||||
@@ -14,7 +15,7 @@ mod state;
|
|||||||
pub enum Inbound {
|
pub enum Inbound {
|
||||||
AuthChallengeRequest {
|
AuthChallengeRequest {
|
||||||
pubkey: authn::PublicKey,
|
pubkey: authn::PublicKey,
|
||||||
bootstrap_token: Option<String>,
|
bootstrap_token: Option<Zeroizing<String>>,
|
||||||
},
|
},
|
||||||
AuthChallengeSolution {
|
AuthChallengeSolution {
|
||||||
signature: Vec<u8>,
|
signature: Vec<u8>,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use arbiter_crypto::authn::{self, AuthChallenge, OPERATOR_CONTEXT};
|
use arbiter_crypto::authn::{self, AuthChallenge, OPERATOR_CONTEXT};
|
||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::Bi;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl};
|
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
@@ -16,13 +17,13 @@ use tracing::error;
|
|||||||
|
|
||||||
pub(super) struct ChallengeRequest {
|
pub(super) struct ChallengeRequest {
|
||||||
pub(super) pubkey: authn::PublicKey,
|
pub(super) pubkey: authn::PublicKey,
|
||||||
pub(super) bootstrap_token: Option<String>,
|
pub(super) bootstrap_token: Option<Zeroizing<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChallengeContext {
|
pub struct ChallengeContext {
|
||||||
pub(super) challenge: AuthChallenge,
|
pub(super) challenge: AuthChallenge,
|
||||||
pub(super) pubkey: authn::PublicKey,
|
pub(super) pubkey: authn::PublicKey,
|
||||||
pub(super) bootstrap_token: Option<String>,
|
pub(super) bootstrap_token: Option<Zeroizing<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct ChallengeSolution {
|
pub(super) struct ChallengeSolution {
|
||||||
@@ -152,15 +153,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve client id: bootstrap (consume token + register) or lookup
|
// Resolve client id: bootstrap (consume token + register) or lookup
|
||||||
let id = match bootstrap_token {
|
let id = match bootstrap_token.clone() {
|
||||||
Some(token) => {
|
Some(token) => {
|
||||||
let token_ok: bool = self
|
let token_ok: bool = self
|
||||||
.conn
|
.conn
|
||||||
.actors
|
.actors
|
||||||
.bootstrapper
|
.bootstrapper
|
||||||
.ask(ConsumeToken {
|
.ask(ConsumeToken { token })
|
||||||
token: token.clone(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(?e, "Failed to consume bootstrap token");
|
error!(?e, "Failed to consume bootstrap token");
|
||||||
|
|||||||
Reference in New Issue
Block a user