fix(server::user_agent::auth): not sending AuthOk on succesful auth
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
use arbiter_proto::proto::user_agent::{
|
use arbiter_proto::proto::user_agent::{
|
||||||
AuthChallenge, UserAgentResponse, user_agent_response::Payload as UserAgentResponsePayload,
|
AuthChallenge, AuthOk, UserAgentResponse,
|
||||||
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
|
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
@@ -71,7 +72,7 @@ smlang::statemachine!(
|
|||||||
transitions: {
|
transitions: {
|
||||||
*Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext),
|
*Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext),
|
||||||
Init + BootstrapAuthRequest(BootstrapAuthRequest) [async verify_bootstrap_token] / provide_key_bootstrap = AuthOk(AuthPublicKey),
|
Init + BootstrapAuthRequest(BootstrapAuthRequest) [async verify_bootstrap_token] / provide_key_bootstrap = AuthOk(AuthPublicKey),
|
||||||
SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) [async verify_solution] / provide_key = AuthOk(AuthPublicKey),
|
SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthPublicKey),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -147,43 +148,6 @@ impl<'a> AuthContext<'a> {
|
|||||||
impl AuthStateMachineContext for AuthContext<'_> {
|
impl AuthStateMachineContext for AuthContext<'_> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
async fn verify_solution(
|
|
||||||
&self,
|
|
||||||
ChallengeContext { challenge, key }: &ChallengeContext,
|
|
||||||
ChallengeSolution { solution }: &ChallengeSolution,
|
|
||||||
) -> Result<bool, Self::Error> {
|
|
||||||
let formatted = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey);
|
|
||||||
|
|
||||||
let valid = match key {
|
|
||||||
AuthPublicKey::Ed25519(vk) => {
|
|
||||||
let sig = solution.as_slice().try_into().map_err(|_| {
|
|
||||||
error!(?solution, "Invalid Ed25519 signature length");
|
|
||||||
Error::InvalidChallengeSolution
|
|
||||||
})?;
|
|
||||||
vk.verify_strict(&formatted, &sig).is_ok()
|
|
||||||
}
|
|
||||||
AuthPublicKey::EcdsaSecp256k1(vk) => {
|
|
||||||
use k256::ecdsa::signature::Verifier as _;
|
|
||||||
let sig = k256::ecdsa::Signature::try_from(solution.as_slice()).map_err(|_| {
|
|
||||||
error!(?solution, "Invalid ECDSA signature bytes");
|
|
||||||
Error::InvalidChallengeSolution
|
|
||||||
})?;
|
|
||||||
vk.verify(&formatted, &sig).is_ok()
|
|
||||||
}
|
|
||||||
AuthPublicKey::Rsa(pk) => {
|
|
||||||
use rsa::signature::Verifier as _;
|
|
||||||
let verifying_key = rsa::pss::VerifyingKey::<sha2::Sha256>::new(pk.clone());
|
|
||||||
let sig = rsa::pss::Signature::try_from(solution.as_slice()).map_err(|_| {
|
|
||||||
error!(?solution, "Invalid RSA signature bytes");
|
|
||||||
Error::InvalidChallengeSolution
|
|
||||||
})?;
|
|
||||||
verifying_key.verify(&formatted, &sig).is_ok()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(valid)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn prepare_challenge(
|
async fn prepare_challenge(
|
||||||
&mut self,
|
&mut self,
|
||||||
ChallengeRequest { pubkey }: ChallengeRequest,
|
ChallengeRequest { pubkey }: ChallengeRequest,
|
||||||
@@ -249,49 +213,52 @@ impl AuthStateMachineContext for AuthContext<'_> {
|
|||||||
Ok(event_data.pubkey)
|
Ok(event_data.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_key(
|
#[allow(missing_docs)]
|
||||||
|
#[allow(clippy::unused_unit)]
|
||||||
|
async fn verify_solution(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_data: &ChallengeContext,
|
ChallengeContext { challenge, key }: &ChallengeContext,
|
||||||
_: ChallengeSolution,
|
ChallengeSolution { solution }: ChallengeSolution,
|
||||||
) -> Result<AuthPublicKey, Self::Error> {
|
) -> Result<AuthPublicKey, Self::Error> {
|
||||||
// ChallengeContext.key cannot be taken by value because smlang passes it by ref;
|
let formatted = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey);
|
||||||
// we reconstruct stored bytes and return them wrapped in Ed25519 placeholder.
|
|
||||||
// Session uses only the raw bytes, so we carry them via a Vec<u8>.
|
let valid = match key {
|
||||||
// IMPORTANT: do NOT simplify this by storing the key type separately — the
|
AuthPublicKey::Ed25519(vk) => {
|
||||||
// `AuthPublicKey` enum IS the source of truth for key bytes and type.
|
let sig = solution.as_slice().try_into().map_err(|_| {
|
||||||
//
|
error!(?solution, "Invalid Ed25519 signature length");
|
||||||
// smlang state-machine trait requires returning an owned value from `provide_key`,
|
Error::InvalidChallengeSolution
|
||||||
// but `state_data` is only available by shared reference here. We extract the
|
})?;
|
||||||
// stored bytes and re-wrap as the correct variant so the caller can call
|
vk.verify_strict(&formatted, &sig).is_ok()
|
||||||
// `to_stored_bytes()` / `key_type()` without losing information.
|
|
||||||
let bytes = state_data.challenge.pubkey.clone();
|
|
||||||
let key_type = state_data.key.key_type();
|
|
||||||
let rebuilt = match key_type {
|
|
||||||
crate::db::models::KeyType::Ed25519 => {
|
|
||||||
let arr: &[u8; 32] = bytes
|
|
||||||
.as_slice()
|
|
||||||
.try_into()
|
|
||||||
.expect("ed25519 pubkey must be 32 bytes in challenge");
|
|
||||||
AuthPublicKey::Ed25519(
|
|
||||||
ed25519_dalek::VerifyingKey::from_bytes(arr)
|
|
||||||
.expect("key was already validated in parse_auth_event"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
crate::db::models::KeyType::EcdsaSecp256k1 => {
|
AuthPublicKey::EcdsaSecp256k1(vk) => {
|
||||||
// bytes are SEC1 compressed (33 bytes produced by to_encoded_point(true))
|
use k256::ecdsa::signature::Verifier as _;
|
||||||
AuthPublicKey::EcdsaSecp256k1(
|
let sig = k256::ecdsa::Signature::try_from(solution.as_slice()).map_err(|_| {
|
||||||
k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes)
|
error!(?solution, "Invalid ECDSA signature bytes");
|
||||||
.expect("ecdsa key was already validated in parse_auth_event"),
|
Error::InvalidChallengeSolution
|
||||||
)
|
})?;
|
||||||
|
vk.verify(&formatted, &sig).is_ok()
|
||||||
}
|
}
|
||||||
crate::db::models::KeyType::Rsa => {
|
AuthPublicKey::Rsa(pk) => {
|
||||||
use rsa::pkcs8::DecodePublicKey as _;
|
use rsa::signature::Verifier as _;
|
||||||
AuthPublicKey::Rsa(
|
let verifying_key = rsa::pss::VerifyingKey::<sha2::Sha256>::new(pk.clone());
|
||||||
rsa::RsaPublicKey::from_public_key_der(&bytes)
|
let sig = rsa::pss::Signature::try_from(solution.as_slice()).map_err(|_| {
|
||||||
.expect("rsa key was already validated in parse_auth_event"),
|
error!(?solution, "Invalid RSA signature bytes");
|
||||||
)
|
Error::InvalidChallengeSolution
|
||||||
|
})?;
|
||||||
|
verifying_key.verify(&formatted, &sig).is_ok()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(rebuilt)
|
|
||||||
|
if valid {
|
||||||
|
self.conn
|
||||||
|
.transport
|
||||||
|
.send(Ok(UserAgentResponse {
|
||||||
|
payload: Some(UserAgentResponsePayload::AuthOk(AuthOk {})),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::Transport)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(key.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ impl UserAgentConnection {
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(props))]
|
||||||
pub async fn connect_user_agent(props: UserAgentConnection) {
|
pub async fn connect_user_agent(props: UserAgentConnection) {
|
||||||
match auth::authenticate_and_create(props).await {
|
match auth::authenticate_and_create(props).await {
|
||||||
Ok(session) => {
|
Ok(session) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user