crypto: Add methods to export/import cross signing keys

master
Damir Jelić 2021-08-09 17:26:16 +02:00
parent b540b8df62
commit ee838087ca
4 changed files with 162 additions and 34 deletions

View File

@ -56,7 +56,7 @@ pub use requests::{
IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests, IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests,
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
}; };
pub use store::CryptoStoreError; pub use store::{CrossSigningKeyExport, CryptoStoreError, SecretImportError};
pub use verification::{ pub use verification::{
AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest, AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest,
}; };

View File

@ -38,6 +38,7 @@ use ruma::{
EncryptedEventContent, EncryptedEventScheme, EncryptedToDeviceEventContent, EncryptedEventContent, EncryptedEventScheme, EncryptedToDeviceEventContent,
}, },
room_key::RoomKeyToDeviceEventContent, room_key::RoomKeyToDeviceEventContent,
secret::request::SecretName,
AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent, AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent,
}, },
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId, DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId,
@ -59,10 +60,10 @@ use crate::{
session_manager::{GroupSessionManager, SessionManager}, session_manager::{GroupSessionManager, SessionManager},
store::{ store::{
Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult, Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult,
Store, SecretImportError, Store,
}, },
verification::{Verification, VerificationMachine, VerificationRequest}, verification::{Verification, VerificationMachine, VerificationRequest},
ToDeviceRequest, CrossSigningKeyExport, ToDeviceRequest,
}; };
/// State machine implementation of the Olm/Megolm encryption protocol used for /// State machine implementation of the Olm/Megolm encryption protocol used for
@ -1262,6 +1263,38 @@ impl OlmMachine {
pub async fn cross_signing_status(&self) -> CrossSigningStatus { pub async fn cross_signing_status(&self) -> CrossSigningStatus {
self.user_identity.lock().await.status().await self.user_identity.lock().await.status().await
} }
/// Export all the private cross signing keys we have.
///
/// The export will contain the seed for the ed25519 keys as a unpadded
/// base64 encoded string.
///
/// This method returns `None` if we don't have any private cross signing
/// keys.
pub async fn export_cross_signing_keys(&self) -> Option<CrossSigningKeyExport> {
let master_key = self.store.export_secret(&SecretName::CrossSigningMasterKey).await;
let self_signing_key =
self.store.export_secret(&SecretName::CrossSigningSelfSigningKey).await;
let user_signing_key =
self.store.export_secret(&SecretName::CrossSigningUserSigningKey).await;
if master_key.is_none() && self_signing_key.is_none() && user_signing_key.is_none() {
None
} else {
Some(CrossSigningKeyExport { master_key, self_signing_key, user_signing_key })
}
}
/// Import our private cross signing keys.
///
/// The export needs to contain the seed for the ed25519 keys as an unpadded
/// base64 encoded string.
pub async fn import_cross_signing_keys(
&self,
export: CrossSigningKeyExport,
) -> Result<CrossSigningStatus, SecretImportError> {
self.store.import_cross_signing_keys(export).await
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -164,43 +164,81 @@ impl PrivateCrossSigningIdentity {
&self, &self,
public_identity: OwnUserIdentity, public_identity: OwnUserIdentity,
secret_name: &SecretName, secret_name: &SecretName,
seed: String, seed: &str,
) -> Result<(), SecretImportError> { ) -> Result<(), SecretImportError> {
let seed = decode(seed)?; let (master, self_signing, user_signing) = match secret_name {
SecretName::CrossSigningMasterKey => (Some(seed), None, None),
SecretName::CrossSigningSelfSigningKey => (None, Some(seed), None),
SecretName::CrossSigningUserSigningKey => (None, None, Some(seed)),
_ => return Ok(()),
};
match secret_name { self.import_secrets(public_identity, master, self_signing, user_signing).await
SecretName::CrossSigningMasterKey => { }
let master = MasterSigning::from_seed(self.user_id().clone(), seed);
if public_identity.master_key() == &master.public_key { pub(crate) async fn import_secrets(
*self.master_key.lock().await = Some(master); &self,
Ok(()) public_identity: OwnUserIdentity,
} else { master_key: Option<&str>,
Err(SecretImportError::MissmatchedPublicKeys) self_signing_key: Option<&str>,
} user_signing_key: Option<&str>,
) -> Result<(), SecretImportError> {
let master = if let Some(master_key) = master_key {
let seed = decode(master_key)?;
let master = MasterSigning::from_seed(self.user_id().clone(), seed);
if public_identity.master_key() == &master.public_key {
Ok(Some(master))
// *self.master_key.lock().await = Some(master);
} else {
Err(SecretImportError::MissmatchedPublicKeys)
} }
SecretName::CrossSigningUserSigningKey => { } else {
let subkey = UserSigning::from_seed(self.user_id().clone(), seed); Ok(None)
}?;
if public_identity.user_signing_key() == &subkey.public_key { let user_signing = if let Some(user_signing_key) = user_signing_key {
*self.user_signing_key.lock().await = Some(subkey); let seed = decode(user_signing_key)?;
Ok(()) let subkey = UserSigning::from_seed(self.user_id().clone(), seed);
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
}
SecretName::CrossSigningSelfSigningKey => {
let subkey = SelfSigning::from_seed(self.user_id().clone(), seed);
if public_identity.self_signing_key() == &subkey.public_key { if public_identity.user_signing_key() == &subkey.public_key {
*self.self_signing_key.lock().await = Some(subkey); // *self.user_signing_key.lock().await = Some(subkey);
Ok(()) Ok(Some(subkey))
} else { } else {
Err(SecretImportError::MissmatchedPublicKeys) Err(SecretImportError::MissmatchedPublicKeys)
}
} }
_ => Ok(()), } else {
Ok(None)
}?;
let self_signing = if let Some(self_signing_key) = self_signing_key {
let seed = decode(self_signing_key)?;
let subkey = SelfSigning::from_seed(self.user_id().clone(), seed);
if public_identity.self_signing_key() == &subkey.public_key {
// *self.self_signing_key.lock().await = Some(subkey);
Ok(Some(subkey))
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
} else {
Ok(None)
}?;
if let Some(master) = master {
*self.master_key.lock().await = Some(master);
} }
if let Some(self_signing) = self_signing {
*self.self_signing_key.lock().await = Some(self_signing);
}
if let Some(user_signing) = user_signing {
*self.user_signing_key.lock().await = Some(user_signing);
}
Ok(())
} }
/// Get the names of the secrets we are missing. /// Get the names of the secrets we are missing.

View File

@ -63,6 +63,7 @@ use ruma::{
use serde_json::Error as SerdeError; use serde_json::Error as SerdeError;
use thiserror::Error; use thiserror::Error;
use tracing::{info, warn}; use tracing::{info, warn};
use zeroize::Zeroize;
#[cfg(feature = "sled_cryptostore")] #[cfg(feature = "sled_cryptostore")]
pub use self::sled::SledStore; pub use self::sled::SledStore;
@ -136,8 +137,33 @@ impl DeviceChanges {
} }
} }
/// A struct containing private cross signing keys that can be backed up or
/// uploaded to the secret store.
#[derive(Zeroize)]
#[zeroize(drop)]
pub struct CrossSigningKeyExport {
/// The seed of the master key encoded as unpadded base64.
pub master_key: Option<String>,
/// The seed of the self signing key encoded as unpadded base64.
pub self_signing_key: Option<String>,
/// The seed of the user signing key encoded as unpadded base64.
pub user_signing_key: Option<String>,
}
impl Debug for CrossSigningKeyExport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CrossSigningKeyExport")
.field("master_key", &self.master_key.is_some())
.field("self_signing_key", &self.self_signing_key.is_some())
.field("user_signing_key", &self.user_signing_key.is_some())
.finish_non_exhaustive()
}
}
/// Error describing what went wrong when importing private cross signing keys
/// or the key backup key.
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum SecretImportError { pub enum SecretImportError {
/// The seed for the private key wasn't valid base64. /// The seed for the private key wasn't valid base64.
#[error(transparent)] #[error(transparent)]
Base64(#[from] DecodeError), Base64(#[from] DecodeError),
@ -367,11 +393,42 @@ impl Store {
} }
} }
pub async fn import_cross_signing_keys(
&self,
export: CrossSigningKeyExport,
) -> Result<CrossSigningStatus, SecretImportError> {
if let Some(public_identity) = self.get_identity(&self.user_id).await?.and_then(|i| i.own())
{
let identity = self.identity.lock().await;
identity
.import_secrets(
public_identity,
export.master_key.as_deref(),
export.self_signing_key.as_deref(),
export.user_signing_key.as_deref(),
)
.await?;
let status = identity.status().await;
info!(status =? status, "Successfully imported the private cross signing keys");
let changes =
Changes { private_identity: Some(identity.clone()), ..Default::default() };
self.save_changes(changes).await?;
}
Ok(self.identity.lock().await.status().await)
}
pub async fn import_secret( pub async fn import_secret(
&self, &self,
secret_name: &SecretName, secret_name: &SecretName,
secret: String, secret: String,
) -> Result<(), SecretImportError> { ) -> Result<(), SecretImportError> {
let secret = zeroize::Zeroizing::new(secret);
match secret_name { match secret_name {
SecretName::CrossSigningMasterKey SecretName::CrossSigningMasterKey
| SecretName::CrossSigningUserSigningKey | SecretName::CrossSigningUserSigningKey
@ -381,7 +438,7 @@ impl Store {
{ {
let identity = self.identity.lock().await; let identity = self.identity.lock().await;
identity.import_secret(public_identity, secret_name, secret).await?; identity.import_secret(public_identity, secret_name, &secret).await?;
info!( info!(
secret_name = secret_name.as_ref(), secret_name = secret_name.as_ref(),
"Successfully imported a private cross signing key" "Successfully imported a private cross signing key"