crypto: Add methods to export/import cross signing keys
This commit is contained in:
parent
b540b8df62
commit
ee838087ca
4 changed files with 162 additions and 34 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue