mirror of
https://github.com/bitwarden/server.git
synced 2026-04-22 08:17:08 -05:00
User key value material in web requests rather than akd label/values
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)) => (
|
||||
|
||||
Reference in New Issue
Block a user