identifiers: Differentiate signing keys from device keys

Use OwnedCrossSigningKeyId, OwnedDeviceSigningKeyId and
OwnedCrossSigningOrDeviceSigningKeyId instead of OwnedDeviceKeyId
to identify signing keys.
This commit is contained in:
Kévin Commaille 2024-10-10 11:25:49 +02:00 committed by strawberry
parent 09ff0b2819
commit 7f8f89eff7
12 changed files with 265 additions and 22 deletions

View File

@ -21,6 +21,9 @@ Breaking changes:
- Use `OwnedOneTimeKeyId` and `OneTimeKeyAlgorithm` instead of
`OwnedDeviceKeyId` and `DeviceKeyAlgorithm` respectively to identify one-time
and fallback keys and their algorithm.
- Use `OwnedCrossSigningOrDeviceSigningKeyId` instead of `OwnedDeviceKeyId` to
identify signing keys in `BackupAlgorithm::MegolmBackupV1Curve25519AesSha2`'s
`signatures`.
Improvements:

View File

@ -20,7 +20,7 @@ use std::collections::BTreeMap;
use js_int::UInt;
use ruma_common::{
serde::{Base64, Raw},
OwnedDeviceKeyId, OwnedUserId,
OwnedCrossSigningOrDeviceSigningKeyId, OwnedUserId,
};
use serde::{Deserialize, Serialize};
@ -51,7 +51,7 @@ pub enum BackupAlgorithm {
public_key: Base64,
/// Signatures of the auth_data as Signed JSON.
signatures: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>>,
signatures: BTreeMap<OwnedUserId, BTreeMap<OwnedCrossSigningOrDeviceSigningKeyId, String>>,
},
}

View File

@ -29,6 +29,13 @@ Breaking changes:
`Signatures::insert` is now dereferenced to `BTreeMap::insert`.
- Move the `DeviceKeyAlgorithm::SignedCurve25519` into the new
`OneTimeKeyAlgorithm` type.
- Add `(Owned)CrossSigningKeyId` and use it instead of `OwnedDeviceKeyId` to
identify `CrossSigningKey`'s `keys`.
- Add `(Owned)CrossSigningOrDeviceSigningKeyId` and use it instead of
`OwnedDeviceKeyId` to identify signing keys in `DeviceKeys`'s and
`CrossSigningKey`'s `signatures`.
- Use `OwnedDeviceSigningKeyId` instead of `OwnedDeviceKeyId` to identify
signing keys in `SignedKey`'s `signatures`.
Improvements:
@ -49,6 +56,10 @@ Improvements:
`(entity, key_identifier, value)` tuples.
- Add `(Owned)OneTimeKeyId` and `(Owned)OneTimeKeyName` to identify one-time and
fallback keys instead of using `(Owned)DeviceKeyId`.
- Add `(Owned)Base64PublicKey` and `(Owned)Base64PublicKeyOrDeviceId` to
identify cross-signing keys.
- Add `(owned_)base_64_public_key` to construct a compile-time validated
`(Owned)Base64PublicKey`.
# 0.13.0

View File

@ -8,7 +8,8 @@ use serde::{Deserialize, Serialize};
use crate::{
serde::{Base64, StringEnum},
EventEncryptionAlgorithm, OwnedDeviceId, OwnedDeviceKeyId, OwnedUserId, PrivOwnedStr,
EventEncryptionAlgorithm, OwnedCrossSigningKeyId, OwnedCrossSigningOrDeviceSigningKeyId,
OwnedDeviceId, OwnedDeviceKeyId, OwnedDeviceSigningKeyId, OwnedUserId, PrivOwnedStr,
};
/// Identity keys for a device.
@ -32,7 +33,7 @@ pub struct DeviceKeys {
pub keys: BTreeMap<OwnedDeviceKeyId, String>,
/// Signatures for the device key object.
pub signatures: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>>,
pub signatures: BTreeMap<OwnedUserId, BTreeMap<OwnedCrossSigningOrDeviceSigningKeyId, String>>,
/// Additional data added to the device key information by intermediate servers, and
/// not covered by the signatures.
@ -48,7 +49,7 @@ impl DeviceKeys {
device_id: OwnedDeviceId,
algorithms: Vec<EventEncryptionAlgorithm>,
keys: BTreeMap<OwnedDeviceKeyId, String>,
signatures: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>>,
signatures: BTreeMap<OwnedUserId, BTreeMap<OwnedCrossSigningOrDeviceSigningKeyId, String>>,
) -> Self {
Self { user_id, device_id, algorithms, keys, signatures, unsigned: Default::default() }
}
@ -76,7 +77,7 @@ impl UnsignedDeviceInfo {
}
/// Signatures for a `SignedKey` object.
pub type SignedKeySignatures = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>>;
pub type SignedKeySignatures = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceSigningKeyId, String>>;
/// A key for the SignedCurve25519 algorithm
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -118,9 +119,12 @@ pub enum OneTimeKey {
}
/// Signatures for a `CrossSigningKey` object.
pub type CrossSigningKeySignatures = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>>;
pub type CrossSigningKeySignatures =
BTreeMap<OwnedUserId, BTreeMap<OwnedCrossSigningOrDeviceSigningKeyId, String>>;
/// A cross signing key.
/// A [cross-signing] key.
///
/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct CrossSigningKey {
@ -133,11 +137,15 @@ pub struct CrossSigningKey {
/// The public key.
///
/// The object must have exactly one property.
pub keys: BTreeMap<OwnedDeviceKeyId, String>,
pub keys: BTreeMap<OwnedCrossSigningKeyId, String>,
/// Signatures of the key.
///
/// Only optional for master key.
/// The master key should be signed by the device key and can be signed by other users'
/// user-signing key. The user-signing and self-signing keys must be signed by the master
/// key.
///
/// Only optional for the master key.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub signatures: CrossSigningKeySignatures,
}
@ -147,7 +155,7 @@ impl CrossSigningKey {
pub fn new(
user_id: OwnedUserId,
usage: Vec<KeyUsage>,
keys: BTreeMap<OwnedDeviceKeyId, String>,
keys: BTreeMap<OwnedCrossSigningKeyId, String>,
signatures: CrossSigningKeySignatures,
) -> Self {
Self { user_id, usage, keys, signatures }

View File

@ -16,6 +16,8 @@ use serde::de::{self, Deserializer, Unexpected};
#[doc(inline)]
pub use self::{
base64_public_key::{Base64PublicKey, OwnedBase64PublicKey},
base64_public_key_or_device_id::{Base64PublicKeyOrDeviceId, OwnedBase64PublicKeyOrDeviceId},
client_secret::{ClientSecret, OwnedClientSecret},
crypto_algorithms::{
DeviceKeyAlgorithm, EventEncryptionAlgorithm, KeyDerivationAlgorithm, OneTimeKeyAlgorithm,
@ -25,9 +27,10 @@ pub use self::{
device_key_id::{DeviceKeyId, OwnedDeviceKeyId},
event_id::{EventId, OwnedEventId},
key_id::{
DeviceSigningKeyId, KeyAlgorithm, KeyId, OneTimeKeyId, OwnedDeviceSigningKeyId, OwnedKeyId,
OwnedOneTimeKeyId, OwnedServerSigningKeyId, OwnedSigningKeyId, ServerSigningKeyId,
SigningKeyId,
CrossSigningKeyId, CrossSigningOrDeviceSigningKeyId, DeviceSigningKeyId, KeyAlgorithm,
KeyId, OneTimeKeyId, OwnedCrossSigningKeyId, OwnedCrossSigningOrDeviceSigningKeyId,
OwnedDeviceSigningKeyId, OwnedKeyId, OwnedOneTimeKeyId, OwnedServerSigningKeyId,
OwnedSigningKeyId, ServerSigningKeyId, SigningKeyId,
},
matrix_uri::{MatrixToUri, MatrixUri},
mxc_uri::{Mxc, MxcUri, OwnedMxcUri},
@ -49,6 +52,8 @@ pub use self::{
pub mod matrix_uri;
pub mod user_id;
mod base64_public_key;
mod base64_public_key_or_device_id;
mod client_secret;
mod crypto_algorithms;
mod device_id;
@ -113,8 +118,8 @@ macro_rules! owned_device_id {
#[doc(hidden)]
pub mod __private_macros {
pub use ruma_macros::{
device_key_id, event_id, mxc_uri, room_alias_id, room_id, room_version_id, server_name,
server_signing_key_version, user_id,
base64_public_key, device_key_id, event_id, mxc_uri, room_alias_id, room_id,
room_version_id, server_name, server_signing_key_version, user_id,
};
}
@ -274,3 +279,19 @@ macro_rules! owned_user_id {
$crate::user_id!($s).to_owned()
};
}
/// Compile-time checked [`Base64PublicKey`] construction.
#[macro_export]
macro_rules! base64_public_key {
($s:literal) => {
$crate::__private_macros::base64_public_key!($crate, $s)
};
}
/// Compile-time checked [`OwnedBase64PublicKey`] construction.
#[macro_export]
macro_rules! owned_base64_public_key {
($s:literal) => {
$crate::base64_public_key!($s).to_owned()
};
}

View File

@ -0,0 +1,84 @@
use ruma_macros::IdZst;
use super::{IdParseError, KeyName};
use crate::serde::{base64::Standard, Base64, Base64DecodeError};
/// A public key encoded using unpadded base64, used as an identifier for [cross-signing] keys.
///
/// This string is validated using the set `[a-zA-Z0-9+/=]`, but it is not validated to be decodable
/// as base64. This type is provided simply for its semantic value.
///
/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
#[repr(transparent)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)]
#[ruma_id(validate = ruma_identifiers_validation::base64_public_key::validate)]
pub struct Base64PublicKey(str);
impl OwnedBase64PublicKey {
/// Construct a new `OwnedBase64PublicKey` by encoding the given bytes using unpadded base64.
pub fn with_bytes<B: AsRef<[u8]>>(bytes: B) -> OwnedBase64PublicKey {
Base64::<Standard, B>::new(bytes).into()
}
}
impl KeyName for Base64PublicKey {
fn validate(s: &str) -> Result<(), IdParseError> {
ruma_identifiers_validation::base64_public_key::validate(s)
}
}
impl KeyName for OwnedBase64PublicKey {
fn validate(s: &str) -> Result<(), IdParseError> {
ruma_identifiers_validation::base64_public_key::validate(s)
}
}
impl<B: AsRef<[u8]>> From<Base64<Standard, B>> for OwnedBase64PublicKey {
fn from(value: Base64<Standard, B>) -> Self {
value.to_string().try_into().unwrap_or_else(|_| unreachable!())
}
}
impl TryFrom<&Base64PublicKey> for Base64<Standard, Vec<u8>> {
type Error = Base64DecodeError;
fn try_from(value: &Base64PublicKey) -> Result<Self, Self::Error> {
Base64::parse(value)
}
}
impl TryFrom<&OwnedBase64PublicKey> for Base64<Standard, Vec<u8>> {
type Error = Base64DecodeError;
fn try_from(value: &OwnedBase64PublicKey) -> Result<Self, Self::Error> {
Base64::parse(value)
}
}
impl TryFrom<OwnedBase64PublicKey> for Base64<Standard, Vec<u8>> {
type Error = Base64DecodeError;
fn try_from(value: OwnedBase64PublicKey) -> Result<Self, Self::Error> {
Base64::parse(value)
}
}
#[cfg(test)]
mod tests {
use super::{Base64PublicKey, OwnedBase64PublicKey};
#[test]
fn valid_string() {
<&Base64PublicKey>::try_from("base64+master+public+key").unwrap();
}
#[test]
fn invalid_string() {
<&Base64PublicKey>::try_from("not@base@64").unwrap_err();
}
#[test]
fn constructor() {
_ = OwnedBase64PublicKey::with_bytes(b"self-signing master public key");
}
}

View File

@ -0,0 +1,65 @@
use ruma_macros::IdZst;
use super::{
Base64PublicKey, DeviceId, IdParseError, KeyName, OwnedBase64PublicKey, OwnedDeviceId,
};
/// A Matrix ID that can be either a [`DeviceId`] or a [`Base64PublicKey`].
///
/// Device identifiers in Matrix are completely opaque character sequences and cross-signing keys
/// are identified by their base64-encoded public key. This type is provided simply for its semantic
/// value.
///
/// It is not recommended to construct this type directly, it should instead be converted from a
/// [`DeviceId`] or a [`Base64PublicKey`].
///
/// # Example
///
/// ```
/// use ruma_common::{Base64PublicKeyOrDeviceId, OwnedBase64PublicKeyOrDeviceId};
///
/// let ref_id: &Base64PublicKeyOrDeviceId = "abcdefghi".into();
/// assert_eq!(ref_id.as_str(), "abcdefghi");
///
/// let owned_id: OwnedBase64PublicKeyOrDeviceId = "ijklmnop".into();
/// assert_eq!(owned_id.as_str(), "ijklmnop");
/// ```
#[repr(transparent)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)]
pub struct Base64PublicKeyOrDeviceId(str);
impl KeyName for Base64PublicKeyOrDeviceId {
fn validate(_s: &str) -> Result<(), IdParseError> {
Ok(())
}
}
impl KeyName for OwnedBase64PublicKeyOrDeviceId {
fn validate(_s: &str) -> Result<(), IdParseError> {
Ok(())
}
}
impl<'a> From<&'a DeviceId> for &'a Base64PublicKeyOrDeviceId {
fn from(value: &'a DeviceId) -> Self {
Self::from(value.as_str())
}
}
impl From<OwnedDeviceId> for OwnedBase64PublicKeyOrDeviceId {
fn from(value: OwnedDeviceId) -> Self {
Self::from(value.as_str())
}
}
impl<'a> From<&'a Base64PublicKey> for &'a Base64PublicKeyOrDeviceId {
fn from(value: &'a Base64PublicKey) -> Self {
Self::from(value.as_str())
}
}
impl From<OwnedBase64PublicKey> for OwnedBase64PublicKeyOrDeviceId {
fn from(value: OwnedBase64PublicKey) -> Self {
Self::from(value.as_str())
}
}

View File

@ -7,8 +7,8 @@ use std::{
use ruma_macros::IdZst;
use super::{
crypto_algorithms::SigningKeyAlgorithm, DeviceId, KeyName, OneTimeKeyAlgorithm, OneTimeKeyName,
ServerSigningKeyVersion,
crypto_algorithms::SigningKeyAlgorithm, Base64PublicKey, Base64PublicKeyOrDeviceId, DeviceId,
KeyName, OneTimeKeyAlgorithm, OneTimeKeyName, ServerSigningKeyVersion,
};
/// A key algorithm and key name delimited by a colon.
@ -63,12 +63,38 @@ pub type ServerSigningKeyId = SigningKeyId<ServerSigningKeyVersion>;
/// Algorithm + key name for homeserver signing keys.
pub type OwnedServerSigningKeyId = OwnedSigningKeyId<ServerSigningKeyVersion>;
/// Algorithm + key name for device keys.
/// Algorithm + key name for [device signing keys].
///
/// [device signing keys]: https://spec.matrix.org/latest/client-server-api/#device-keys
pub type DeviceSigningKeyId = SigningKeyId<DeviceId>;
/// Algorithm + key name for device keys.
/// Algorithm + key name for [device signing] keys.
///
/// [device signing keys]: https://spec.matrix.org/latest/client-server-api/#device-keys
pub type OwnedDeviceSigningKeyId = OwnedSigningKeyId<DeviceId>;
/// Algorithm + key name for [cross-signing] keys.
///
/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
pub type CrossSigningKeyId = SigningKeyId<Base64PublicKey>;
/// Algorithm + key name for [cross-signing] keys.
///
/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
pub type OwnedCrossSigningKeyId = OwnedSigningKeyId<Base64PublicKey>;
/// Algorithm + key name for [cross-signing] or [device signing] keys.
///
/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
/// [device signing]: https://spec.matrix.org/latest/client-server-api/#device-keys
pub type CrossSigningOrDeviceSigningKeyId = SigningKeyId<Base64PublicKeyOrDeviceId>;
/// Algorithm + key name for [cross-signing] or [device signing] keys.
///
/// [cross-signing]: https://spec.matrix.org/latest/client-server-api/#cross-signing
/// [device signing]: https://spec.matrix.org/latest/client-server-api/#device-keys
pub type OwnedCrossSigningOrDeviceSigningKeyId = OwnedSigningKeyId<Base64PublicKeyOrDeviceId>;
/// Algorithm + key name for [one-time and fallback keys].
///
/// [one-time and fallback keys]: https://spec.matrix.org/latest/client-server-api/#one-time-and-fallback-keys

View File

@ -11,6 +11,7 @@ Breaking changes:
Improvements:
- Add `server_signing_key_version::validate`.
- Add `base64_public_key::validate`.
# 0.9.5

View File

@ -0,0 +1,10 @@
use crate::Error;
pub fn validate(s: &str) -> Result<(), Error> {
if s.is_empty() {
return Err(Error::Empty);
} else if !s.chars().all(|c| c.is_alphanumeric() || matches!(c, '+' | '/' | '=')) {
return Err(Error::InvalidCharacters);
}
Ok(())
}

View File

@ -1,6 +1,7 @@
#![doc(html_favicon_url = "https://ruma.dev/favicon.ico")]
#![doc(html_logo_url = "https://ruma.dev/images/logo.png")]
pub mod base64_public_key;
pub mod client_secret;
pub mod device_key_id;
pub mod error;

View File

@ -15,8 +15,8 @@ use proc_macro::TokenStream;
use proc_macro2 as pm2;
use quote::quote;
use ruma_identifiers_validation::{
device_key_id, event_id, mxc_uri, room_alias_id, room_id, room_version_id, server_name,
server_signing_key_version, user_id,
base64_public_key, device_key_id, event_id, mxc_uri, room_alias_id, room_id, room_version_id,
server_name, server_signing_key_version, user_id,
};
use syn::{parse_macro_input, DeriveInput, ItemEnum, ItemStruct};
@ -266,6 +266,19 @@ pub fn user_id(input: TokenStream) -> TokenStream {
output.into()
}
/// Compile-time checked `Base64PublicKey` construction.
#[proc_macro]
pub fn base64_public_key(input: TokenStream) -> TokenStream {
let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
assert!(base64_public_key::validate(&id.value()).is_ok(), "Invalid base64 public key");
let output = quote! {
<&#dollar_crate::DeviceKeyId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
};
output.into()
}
/// Derive the `AsRef<str>` trait for an enum.
#[proc_macro_derive(AsRefStr, attributes(ruma_enum))]
pub fn derive_enum_as_ref_str(input: TokenStream) -> TokenStream {