diff --git a/services/identity/src/database/device_list.rs b/services/identity/src/database/device_list.rs --- a/services/identity/src/database/device_list.rs +++ b/services/identity/src/database/device_list.rs @@ -83,6 +83,8 @@ /// Last primary device signature, in case the primary device has changed /// since last device list update. pub last_primary_signature: Option, + /// Raw update payload to verify signatures + pub raw_payload: String, } impl DeviceRow { @@ -934,6 +936,11 @@ let new_list = update.devices.clone(); self .transact_update_devicelist(user_id, |current_list, _| { + crate::device_list::verify_device_list_signatures( + current_list.first(), + &update, + )?; + let previous_device_ids: Vec<&str> = current_list.iter().map(AsRef::as_ref).collect(); let new_device_ids: Vec<&str> = diff --git a/services/identity/src/device_list.rs b/services/identity/src/device_list.rs --- a/services/identity/src/device_list.rs +++ b/services/identity/src/device_list.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Duration, Utc}; use std::collections::HashSet; -use tracing::{error, warn}; +use tracing::{debug, error, warn}; use crate::{ constants::{error_types, DEVICE_LIST_TIMESTAMP_VALID_FOR}, @@ -115,6 +115,7 @@ timestamp, current_primary_signature: signed_list.cur_primary_signature, last_primary_signature: signed_list.last_primary_signature, + raw_payload: signed_list.raw_device_list, }) } } @@ -164,6 +165,46 @@ Ok(()) } +pub fn verify_device_list_signatures( + previous_primary_device_id: Option<&String>, + new_device_list: &DeviceListUpdate, +) -> Result<(), DeviceListError> { + let Some(primary_device_id) = new_device_list.devices.first() else { + return Ok(()); + }; + + // verify current signature + if let Some(signature) = &new_device_list.current_primary_signature { + crate::grpc_utils::ed25519_verify( + primary_device_id, + &new_device_list.raw_payload, + signature, + ) + .map_err(|err| { + debug!("curPrimarySignature verification failed: {err}"); + DeviceListError::InvalidSignature + })?; + } + + // verify last signature if primary device changed + if let (Some(previous_primary_id), Some(last_signature)) = ( + previous_primary_device_id.filter(|prev| *prev != primary_device_id), + &new_device_list.last_primary_signature, + ) { + crate::grpc_utils::ed25519_verify( + previous_primary_id, + &new_device_list.raw_payload, + last_signature, + ) + .map_err(|err| { + debug!("lastPrimarySignature verification failed: {err}"); + DeviceListError::InvalidSignature + })?; + } + + Ok(()) +} + pub mod validation { use super::*; /// Returns `true` if `new_device_list` contains exactly one more new device diff --git a/services/identity/src/error.rs b/services/identity/src/error.rs --- a/services/identity/src/error.rs +++ b/services/identity/src/error.rs @@ -40,6 +40,7 @@ DeviceNotFound, ConcurrentUpdateError, InvalidDeviceListUpdate, + InvalidSignature, } pub fn consume_error(result: Result) {