mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-04 22:15:44 -04:00
feat(crypto): Add a SecretsBundle type
This commit is contained in:
@@ -81,10 +81,10 @@ indoc = "2.0.1"
|
||||
matrix-sdk-test = { workspace = true }
|
||||
olm-rs = { version = "2.2.0", features = ["serde"] }
|
||||
proptest = { version = "1.0.0", default-features = false, features = ["std"] }
|
||||
similar-asserts = "1.5.0"
|
||||
# required for async_test macro
|
||||
stream_assert = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
similar-asserts = "1.5.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -35,8 +35,9 @@ use as_variant::as_variant;
|
||||
use ruma::{
|
||||
serde::StringEnum, DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceKeyId, OwnedUserId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey, Ed25519Signature, KeyError};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
mod backup;
|
||||
mod cross_signing;
|
||||
@@ -46,6 +47,110 @@ mod one_time_keys;
|
||||
pub mod qr_login;
|
||||
|
||||
pub use self::{backup::*, cross_signing::*, device_keys::*, one_time_keys::*};
|
||||
use crate::store::BackupDecryptionKey;
|
||||
|
||||
macro_rules! from_base64 {
|
||||
($foo:ident, $name:ident) => {
|
||||
pub(crate) fn $name<'de, D>(deserializer: D) -> Result<$foo, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let mut string = String::deserialize(deserializer)?;
|
||||
|
||||
let result = $foo::from_base64(&string);
|
||||
string.zeroize();
|
||||
|
||||
result.map_err(serde::de::Error::custom)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! to_base64 {
|
||||
($foo:ident, $name:ident) => {
|
||||
pub(crate) fn $name<S>(v: &$foo, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut string = v.to_base64();
|
||||
let ret = string.serialize(serializer);
|
||||
|
||||
string.zeroize();
|
||||
|
||||
ret
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Struct containing the bundle of secrets to fully activate a new devices for
|
||||
/// end-to-end encryption.
|
||||
#[derive(Debug, Deserialize, Clone, Serialize, ZeroizeOnDrop)]
|
||||
pub struct SecretsBundle {
|
||||
/// The cross-signing keys.
|
||||
pub cross_signing: CrossSigningSecrets,
|
||||
/// The backup key, if available.
|
||||
pub backup: Option<BackupSecrets>,
|
||||
}
|
||||
|
||||
/// Data for the secrets bundle containing the cross-signing keys.
|
||||
#[derive(Deserialize, Clone, Serialize, ZeroizeOnDrop)]
|
||||
pub struct CrossSigningSecrets {
|
||||
/// The seed for the private part of the cross-signing master key, encoded
|
||||
/// as base64.
|
||||
pub master_key: String,
|
||||
/// The seed for the private part of the cross-signing user-signing key,
|
||||
/// encoded as base64.
|
||||
pub user_signing_key: String,
|
||||
/// The seed for the private part of the cross-signing self-signing key,
|
||||
/// encoded as base64.
|
||||
pub self_signing_key: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CrossSigningSecrets {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CrossSigningSecrets")
|
||||
.field("master_key", &"...")
|
||||
.field("user_signing_key", &"...")
|
||||
.field("self_signing_key", &"...")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for the secrets bundle containing the secret and version for a
|
||||
/// `m.megolm_backup.v1.curve25519-aes-sha2` backup.
|
||||
#[derive(Debug, Deserialize, Clone, Serialize, ZeroizeOnDrop)]
|
||||
pub struct MegolmBackupV1Curve25519AesSha2Secrets {
|
||||
/// The private half of the backup key, can be used to access and decrypt
|
||||
/// room keys in the backup. Also called the recovery key in the
|
||||
/// [spec](https://spec.matrix.org/v1.10/client-server-api/#recovery-key).
|
||||
#[serde(serialize_with = "backup_key_to_base64", deserialize_with = "backup_key_from_base64")]
|
||||
pub key: BackupDecryptionKey,
|
||||
/// The backup version that is tied to the above backup key.
|
||||
pub backup_version: String,
|
||||
}
|
||||
|
||||
from_base64!(BackupDecryptionKey, backup_key_from_base64);
|
||||
to_base64!(BackupDecryptionKey, backup_key_to_base64);
|
||||
|
||||
/// Enum for the algorithm-specific secrets for the room key backup.
|
||||
#[derive(Debug, Clone, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
#[serde(tag = "algorithm")]
|
||||
pub enum BackupSecrets {
|
||||
/// Backup secrets for the `m.megolm_backup.v1.curve25519-aes-sha2` backup
|
||||
/// algorithm.
|
||||
#[serde(rename = "m.megolm_backup.v1.curve25519-aes-sha2")]
|
||||
MegolmBackupV1Curve25519AesSha2(MegolmBackupV1Curve25519AesSha2Secrets),
|
||||
}
|
||||
|
||||
impl BackupSecrets {
|
||||
/// Get the algorithm of the secrets contained in the [`BackupSecrets`].
|
||||
pub fn algorithm(&self) -> &str {
|
||||
match &self {
|
||||
BackupSecrets::MegolmBackupV1Curve25519AesSha2(_) => {
|
||||
"m.megolm_backup.v1.curve25519-aes-sha2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a potentially decoded signature (but *not* a validated one).
|
||||
///
|
||||
@@ -170,7 +275,7 @@ impl IntoIterator for Signatures {
|
||||
impl<'de> Deserialize<'de> for Signatures {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let map: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>> =
|
||||
Deserialize::deserialize(deserializer)?;
|
||||
@@ -372,7 +477,7 @@ impl<T: Ord + Serialize> Serialize for SigningKeys<T> {
|
||||
impl<'de, T: Algorithm + Ord + Deserialize<'de>> Deserialize<'de> for SigningKeys<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let map: BTreeMap<T, String> = Deserialize::deserialize(deserializer)?;
|
||||
|
||||
@@ -394,41 +499,15 @@ impl<'de, T: Algorithm + Ord + Deserialize<'de>> Deserialize<'de> for SigningKey
|
||||
// likes to base64 encode all byte slices.
|
||||
//
|
||||
// This ensures that we serialize/deserialize in a Matrix-compatible way.
|
||||
pub(crate) fn deserialize_curve_key<'de, D>(de: D) -> Result<Curve25519PublicKey, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let key: String = Deserialize::deserialize(de)?;
|
||||
Curve25519PublicKey::from_base64(&key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
from_base64!(Curve25519PublicKey, deserialize_curve_key);
|
||||
to_base64!(Curve25519PublicKey, serialize_curve_key);
|
||||
|
||||
pub(crate) fn serialize_curve_key<S>(key: &Curve25519PublicKey, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let key = key.to_base64();
|
||||
s.serialize_str(&key)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_ed25519_key<'de, D>(de: D) -> Result<Ed25519PublicKey, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let key: String = Deserialize::deserialize(de)?;
|
||||
Ed25519PublicKey::from_base64(&key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_ed25519_key<S>(key: &Ed25519PublicKey, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let key = key.to_base64();
|
||||
s.serialize_str(&key)
|
||||
}
|
||||
from_base64!(Ed25519PublicKey, deserialize_ed25519_key);
|
||||
to_base64!(Ed25519PublicKey, serialize_ed25519_key);
|
||||
|
||||
pub(crate) fn deserialize_curve_key_vec<'de, D>(de: D) -> Result<Vec<Curve25519PublicKey>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let keys: Vec<String> = Deserialize::deserialize(de)?;
|
||||
let keys: Result<Vec<Curve25519PublicKey>, KeyError> =
|
||||
@@ -447,3 +526,35 @@ where
|
||||
let keys: Vec<String> = keys.iter().map(|k| k.to_base64()).collect();
|
||||
keys.serialize(s)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use serde_json::json;
|
||||
use similar_asserts::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_secrets_bundle() {
|
||||
let json = json!({
|
||||
"cross_signing": {
|
||||
"master_key": "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw",
|
||||
"self_signing_key": "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk",
|
||||
"user_signing_key": "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM",
|
||||
},
|
||||
"backup": {
|
||||
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||
"backup_version": "2",
|
||||
"key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
},
|
||||
});
|
||||
|
||||
let deserialized: SecretsBundle = serde_json::from_value(json.clone())
|
||||
.expect("We should be able to deserialize the secrets bundle");
|
||||
|
||||
let serialized = serde_json::to_value(&deserialized)
|
||||
.expect("We should be able to serialize a secrets bundle");
|
||||
|
||||
assert_eq!(json, serialized, "A serialization cycle should yield the same result");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user