User key value material in web requests rather than akd label/values

This commit is contained in:
Matt Gibson
2026-03-25 16:26:46 -07:00
parent 0020a4cfcd
commit bdd15e1d91
7 changed files with 71 additions and 75 deletions

View File

@@ -10,7 +10,7 @@ keywords.workspace = true
akd.workspace = true
blake3.workspace = true
config = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde = { workspace = true }
tracing = { workspace = true, optional = true }
uuid = { workspace = true }
thiserror = { workspace = true }
@@ -19,6 +19,6 @@ thiserror = { workspace = true }
workspace = true
[features]
config = ["dep:config", "dep:serde"]
config = ["dep:config"]
tracing = ["dep:tracing"]
default = ["tracing"]

View File

@@ -18,30 +18,34 @@ pub mod config;
mod bitwarden_v1_configuration;
pub use bitwarden_v1_configuration::BitwardenV1Configuration;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub enum BitwardenAkdPair {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum BitwardenAkdPairMaterial {
UserRealWorldId {
real_world_id: String,
user_id: Uuid,
},
}
impl BitwardenAkdPair {
impl BitwardenAkdPairMaterial {
fn akd_label(&self) -> AkdLabel {
let bytes = match self {
BitwardenAkdPair::UserRealWorldId {
match self {
BitwardenAkdPairMaterial::UserRealWorldId {
real_world_id,
user_id: _,
} => format!("User:RwId:{real_world_id}").as_bytes().to_vec(),
};
AkdLabel(bytes)
} => (&BitwardenAkdLabelMaterial::UserRealWorldId {
real_world_id: real_world_id.clone(),
})
.into(),
}
}
fn akd_value(&self) -> AkdValue {
let bytes = match self {
BitwardenAkdPair::UserRealWorldId {
BitwardenAkdPairMaterial::UserRealWorldId {
real_world_id: _,
user_id,
} => user_id.as_bytes().to_vec(),
@@ -50,20 +54,31 @@ impl BitwardenAkdPair {
}
}
impl From<BitwardenAkdPair> for AkdPair {
fn from(pair: BitwardenAkdPair) -> Self {
AkdPair {
label: pair.akd_label(),
value: pair.akd_value(),
}
impl From<&BitwardenAkdPairMaterial> for AkdLabel {
fn from(pair: &BitwardenAkdPairMaterial) -> Self {
pair.akd_label()
}
}
pub struct AkdPair {
label: AkdLabel,
value: AkdValue,
impl From<&BitwardenAkdPairMaterial> for AkdValue {
fn from(pair: &BitwardenAkdPairMaterial) -> Self {
pair.akd_value()
}
}
pub fn akd_label_value_for(pair: BitwardenAkdPair) -> AkdPair {
pair.into()
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum BitwardenAkdLabelMaterial {
UserRealWorldId { real_world_id: String },
}
impl From<&BitwardenAkdLabelMaterial> for AkdLabel {
fn from(key: &BitwardenAkdLabelMaterial) -> Self {
let bytes = match key {
BitwardenAkdLabelMaterial::UserRealWorldId { real_world_id } => {
format!("User:RwId:{real_world_id}").as_bytes().to_vec()
}
};
AkdLabel(bytes)
}
}

View File

@@ -1,26 +1,7 @@
use akd::{directory::ReadOnlyDirectory, Directory};
use akd_storage::{AkdDatabase, VrfKeyDatabase};
use bitwarden_akd_configuration::BitwardenV1Configuration;
use serde::{Deserialize, Serialize};
pub type BitAkdDirectory = Directory<BitwardenV1Configuration, AkdDatabase, VrfKeyDatabase>;
pub type ReadOnlyBitAkdDirectory =
ReadOnlyDirectory<BitwardenV1Configuration, AkdDatabase, VrfKeyDatabase>;
#[derive(Debug, Serialize, Deserialize)]
pub struct AkdLabelB64(pub(crate) bitwarden_encoding::B64);
impl From<AkdLabelB64> for akd::AkdLabel {
fn from(label_b64: AkdLabelB64) -> Self {
akd::AkdLabel(label_b64.0.into_bytes())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AkdValueB64(pub(crate) bitwarden_encoding::B64);
impl From<AkdValueB64> for akd::AkdValue {
fn from(value_b64: AkdValueB64) -> Self {
akd::AkdValue(value_b64.0.into_bytes())
}
}

View File

@@ -1,16 +1,10 @@
use super::AppState;
use akd_storage::PublishQueue;
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
use common::{AkdLabelB64, AkdValueB64};
use serde::{Deserialize, Serialize};
use bitwarden_akd_configuration::BitwardenAkdPairMaterial;
use serde::Serialize;
use tracing::{error, info, instrument};
#[derive(Debug, Serialize, Deserialize)]
pub struct PublishRequest {
pub label_b64: AkdLabelB64,
pub value_b64: AkdValueB64,
}
#[derive(Debug, Serialize)]
pub struct PublishResponse {
pub success: bool,
@@ -19,17 +13,13 @@ pub struct PublishResponse {
#[instrument(skip_all)]
pub async fn publish_handler(
State(AppState { publish_queue, .. }): State<AppState>,
Json(PublishRequest {
label_b64,
value_b64,
}): Json<PublishRequest>,
Json(bitwarden_akd_pair): Json<BitwardenAkdPairMaterial>,
) -> impl IntoResponse {
info!("Handling publish request");
if let Err(e) = publish_queue
.enqueue(label_b64.into(), value_b64.into())
.await
{
let label = (&bitwarden_akd_pair).into();
let value = (&bitwarden_akd_pair).into();
if let Err(e) = publish_queue.enqueue(label, value).await {
error!("Failed to enqueue publish request: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,

View File

@@ -1,5 +1,5 @@
use axum::{extract::State, http::StatusCode, Json};
use common::AkdLabelB64;
use bitwarden_akd_configuration::BitwardenAkdLabelMaterial;
use serde::{Deserialize, Serialize};
use tracing::{error, info, instrument};
@@ -12,7 +12,7 @@ use crate::{
#[derive(Debug, Serialize, Deserialize)]
pub struct BatchLookupRequest {
/// An array of labels to look up. Each label is encoded as base64.
pub labels_b64: Vec<AkdLabelB64>,
pub bitwarden_akd_labels: Vec<BitwardenAkdLabelMaterial>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -28,12 +28,14 @@ pub async fn batch_lookup_handler(
max_batch_lookup_size,
..
}): State<AppState>,
Json(BatchLookupRequest { labels_b64 }): Json<BatchLookupRequest>,
Json(BatchLookupRequest {
bitwarden_akd_labels,
}): Json<BatchLookupRequest>,
) -> (StatusCode, Json<Response<BatchLookupData>>) {
info!("Handling batch lookup request");
// Validate batch not empty
if labels_b64.is_empty() {
if bitwarden_akd_labels.is_empty() {
error!("Empty batch request received");
return (
StatusCode::BAD_REQUEST,
@@ -42,9 +44,9 @@ pub async fn batch_lookup_handler(
}
// Validate batch size
if labels_b64.len() > max_batch_lookup_size {
if bitwarden_akd_labels.len() > max_batch_lookup_size {
error!(
batch_size = labels_b64.len(),
batch_size = bitwarden_akd_labels.len(),
max_size = max_batch_lookup_size,
"Batch size exceeds limit"
);
@@ -56,9 +58,9 @@ pub async fn batch_lookup_handler(
);
}
let labels = labels_b64
let labels = bitwarden_akd_labels
.into_iter()
.map(|label_b64| label_b64.into())
.map(|label_b64| (&label_b64).into())
.collect::<Vec<akd::AkdLabel>>();
let lookup_proofs = directory.batch_lookup(&labels).await;
@@ -90,7 +92,7 @@ mod tests {
fn test_empty_batch_rejected() {
// Threat model: DoS via empty batch requests
// Tests handler logic at lines 36-42
let labels_b64: Vec<AkdLabelB64> = vec![];
let labels_b64: Vec<BitwardenAkdLabelMaterial> = vec![];
assert!(labels_b64.is_empty(), "Empty batch should be detected");
}
@@ -109,7 +111,8 @@ mod tests {
for (batch_size, max_size, should_be_rejected) in test_cases {
let exceeds_limit = batch_size > max_size;
assert_eq!(
exceeds_limit, should_be_rejected,
exceeds_limit,
should_be_rejected,
"Batch size {} with max {} should {}be rejected",
batch_size,
max_size,

View File

@@ -1,5 +1,5 @@
use axum::{extract::State, http::StatusCode, Json};
use common::AkdLabelB64;
use bitwarden_akd_configuration::BitwardenAkdLabelMaterial;
use serde::{Deserialize, Serialize};
use tracing::{error, info, instrument};
@@ -11,7 +11,7 @@ use crate::{
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyHistoryRequest {
pub label_b64: AkdLabelB64,
pub bitwarden_akd_label_material: BitwardenAkdLabelMaterial,
/// the label to look up encoded as an uppercase hex string
pub history_params: HistoryParams,
}
@@ -45,13 +45,16 @@ pub struct HistoryData {
pub async fn key_history_handler(
State(AppState { directory, .. }): State<AppState>,
Json(KeyHistoryRequest {
label_b64,
bitwarden_akd_label_material,
history_params,
}): Json<KeyHistoryRequest>,
) -> (StatusCode, Json<Response<HistoryData>>) {
info!("Handling get key history request");
let history_proof = directory
.key_history(&label_b64.into(), history_params.into())
.key_history(
&(&bitwarden_akd_label_material).into(),
history_params.into(),
)
.await;
match history_proof {

View File

@@ -1,5 +1,5 @@
use axum::{extract::State, http::StatusCode, Json};
use common::AkdLabelB64;
use bitwarden_akd_configuration::BitwardenAkdLabelMaterial;
use serde::{Deserialize, Serialize};
use tracing::{error, info, instrument};
@@ -12,7 +12,7 @@ use crate::{
#[derive(Debug, Serialize, Deserialize)]
pub struct LookupRequest {
/// the label to look up encoded as base64
pub label_b64: AkdLabelB64,
pub bitwarden_akd_label_material: BitwardenAkdLabelMaterial,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -24,10 +24,14 @@ pub struct LookupData {
#[instrument(skip_all)]
pub async fn lookup_handler(
State(AppState { directory, .. }): State<AppState>,
Json(LookupRequest { label_b64 }): Json<LookupRequest>,
Json(LookupRequest {
bitwarden_akd_label_material,
}): Json<LookupRequest>,
) -> (StatusCode, Json<Response<LookupData>>) {
info!("Handling lookup request");
let lookup_proof = directory.lookup(label_b64.into()).await;
let lookup_proof = directory
.lookup((&bitwarden_akd_label_material).into())
.await;
match lookup_proof {
Ok((lookup_proof, epoch_hash)) => (