crypto: Add methods to export/import cross signing keys
parent
b540b8df62
commit
ee838087ca
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.import_secrets(public_identity, master, self_signing, user_signing).await
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
|
||||||
match secret_name {
|
|
||||||
SecretName::CrossSigningMasterKey => {
|
|
||||||
let master = MasterSigning::from_seed(self.user_id().clone(), seed);
|
let master = MasterSigning::from_seed(self.user_id().clone(), seed);
|
||||||
|
|
||||||
if public_identity.master_key() == &master.public_key {
|
if public_identity.master_key() == &master.public_key {
|
||||||
*self.master_key.lock().await = Some(master);
|
Ok(Some(master))
|
||||||
Ok(())
|
// *self.master_key.lock().await = Some(master);
|
||||||
} else {
|
} else {
|
||||||
Err(SecretImportError::MissmatchedPublicKeys)
|
Err(SecretImportError::MissmatchedPublicKeys)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
SecretName::CrossSigningUserSigningKey => {
|
Ok(None)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
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);
|
let subkey = UserSigning::from_seed(self.user_id().clone(), seed);
|
||||||
|
|
||||||
if public_identity.user_signing_key() == &subkey.public_key {
|
if public_identity.user_signing_key() == &subkey.public_key {
|
||||||
*self.user_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)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
SecretName::CrossSigningSelfSigningKey => {
|
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);
|
let subkey = SelfSigning::from_seed(self.user_id().clone(), seed);
|
||||||
|
|
||||||
if public_identity.self_signing_key() == &subkey.public_key {
|
if public_identity.self_signing_key() == &subkey.public_key {
|
||||||
*self.self_signing_key.lock().await = Some(subkey);
|
// *self.self_signing_key.lock().await = Some(subkey);
|
||||||
Ok(())
|
Ok(Some(subkey))
|
||||||
} else {
|
} else {
|
||||||
Err(SecretImportError::MissmatchedPublicKeys)
|
Err(SecretImportError::MissmatchedPublicKeys)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
if let Some(master) = master {
|
||||||
|
*self.master_key.lock().await = Some(master);
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
|
||||||
|
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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue