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,
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
};
pub use store::CryptoStoreError;
pub use store::{CrossSigningKeyExport, CryptoStoreError, SecretImportError};
pub use verification::{
AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest,
};

View File

@ -38,6 +38,7 @@ use ruma::{
EncryptedEventContent, EncryptedEventScheme, EncryptedToDeviceEventContent,
},
room_key::RoomKeyToDeviceEventContent,
secret::request::SecretName,
AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent,
},
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId,
@ -59,10 +60,10 @@ use crate::{
session_manager::{GroupSessionManager, SessionManager},
store::{
Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult,
Store,
SecretImportError, Store,
},
verification::{Verification, VerificationMachine, VerificationRequest},
ToDeviceRequest,
CrossSigningKeyExport, ToDeviceRequest,
};
/// 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 {
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)]

View File

@ -164,43 +164,81 @@ impl PrivateCrossSigningIdentity {
&self,
public_identity: OwnUserIdentity,
secret_name: &SecretName,
seed: String,
seed: &str,
) -> 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 {
SecretName::CrossSigningMasterKey => {
let master = MasterSigning::from_seed(self.user_id().clone(), seed);
self.import_secrets(public_identity, master, self_signing, user_signing).await
}
if public_identity.master_key() == &master.public_key {
*self.master_key.lock().await = Some(master);
Ok(())
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
pub(crate) async fn import_secrets(
&self,
public_identity: OwnUserIdentity,
master_key: Option<&str>,
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 => {
let subkey = UserSigning::from_seed(self.user_id().clone(), seed);
} else {
Ok(None)
}?;
if public_identity.user_signing_key() == &subkey.public_key {
*self.user_signing_key.lock().await = Some(subkey);
Ok(())
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
}
SecretName::CrossSigningSelfSigningKey => {
let subkey = SelfSigning::from_seed(self.user_id().clone(), seed);
let user_signing = if let Some(user_signing_key) = user_signing_key {
let seed = decode(user_signing_key)?;
let subkey = UserSigning::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(())
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
if public_identity.user_signing_key() == &subkey.public_key {
// *self.user_signing_key.lock().await = Some(subkey);
Ok(Some(subkey))
} else {
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.

View File

@ -63,6 +63,7 @@ use ruma::{
use serde_json::Error as SerdeError;
use thiserror::Error;
use tracing::{info, warn};
use zeroize::Zeroize;
#[cfg(feature = "sled_cryptostore")]
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)]
pub(crate) enum SecretImportError {
pub enum SecretImportError {
/// The seed for the private key wasn't valid base64.
#[error(transparent)]
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(
&self,
secret_name: &SecretName,
secret: String,
) -> Result<(), SecretImportError> {
let secret = zeroize::Zeroizing::new(secret);
match secret_name {
SecretName::CrossSigningMasterKey
| SecretName::CrossSigningUserSigningKey
@ -381,7 +438,7 @@ impl Store {
{
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!(
secret_name = secret_name.as_ref(),
"Successfully imported a private cross signing key"