crypto: Allow secrets to be requested and imported
parent
e57d70b089
commit
68df9b6ed2
|
@ -48,7 +48,7 @@ use crate::{
|
|||
olm::{InboundGroupSession, Session, ShareState},
|
||||
requests::{OutgoingRequest, ToDeviceRequest},
|
||||
session_manager::GroupSessionCache,
|
||||
store::{Changes, CryptoStoreError, Store},
|
||||
store::{Changes, CryptoStoreError, SecretImportError, Store},
|
||||
Device,
|
||||
};
|
||||
|
||||
|
@ -230,6 +230,16 @@ impl From<SecretName> for SecretInfo {
|
|||
}
|
||||
|
||||
impl OutgoingKeyRequest {
|
||||
/// Create an ougoing secret request for the given secret.
|
||||
pub(crate) fn from_secret_name(own_user_id: UserId, secret_name: SecretName) -> Self {
|
||||
Self {
|
||||
request_recipient: own_user_id,
|
||||
request_id: Uuid::new_v4(),
|
||||
info: secret_name.into(),
|
||||
sent_out: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn request_type(&self) -> &str {
|
||||
match &self.info {
|
||||
SecretInfo::KeyRequest(_) => "m.room_key_request",
|
||||
|
@ -815,34 +825,22 @@ impl KeyRequestMachine {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn request_missing_secrets(&self) -> Result<Vec<OutgoingRequest>, CryptoStoreError> {
|
||||
let secret_names = self.store.get_missing_secrets().await;
|
||||
|
||||
Ok(if secret_names.is_empty() {
|
||||
/// Create outgoing secret requests for the given
|
||||
pub fn request_missing_secrets(
|
||||
own_user_id: &UserId,
|
||||
secret_names: Vec<SecretName>,
|
||||
) -> Vec<OutgoingKeyRequest> {
|
||||
if !secret_names.is_empty() {
|
||||
info!(secret_names =? secret_names, "Creating new outgoing secret requests");
|
||||
|
||||
let requests: Vec<OutgoingKeyRequest> = secret_names
|
||||
secret_names
|
||||
.into_iter()
|
||||
.map(|n| OutgoingKeyRequest {
|
||||
request_recipient: self.user_id().to_owned(),
|
||||
request_id: Uuid::new_v4(),
|
||||
info: n.into(),
|
||||
sent_out: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let outgoing_requests =
|
||||
requests.iter().map(|r| r.to_request(self.device_id())).collect();
|
||||
|
||||
let changes = Changes { key_requests: requests, ..Default::default() };
|
||||
self.store.save_changes(changes).await?;
|
||||
|
||||
outgoing_requests
|
||||
.map(|n| OutgoingKeyRequest::from_secret_name(own_user_id.to_owned(), n))
|
||||
.collect()
|
||||
} else {
|
||||
trace!("No secrets are missing from our store, not requesting them");
|
||||
vec![]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_key_helper(
|
||||
|
@ -972,6 +970,94 @@ impl KeyRequestMachine {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn receive_secret(
|
||||
&self,
|
||||
sender_key: &str,
|
||||
event: &mut ToDeviceEvent<SecretSendEventContent>,
|
||||
) -> Result<Option<AnyToDeviceEvent>, CryptoStoreError> {
|
||||
debug!(
|
||||
sender = event.sender.as_str(),
|
||||
request_id = event.content.request_id.as_str(),
|
||||
"Received a m.secret.send event"
|
||||
);
|
||||
|
||||
let request_id = if let Ok(r) = Uuid::parse_str(&event.content.request_id) {
|
||||
r
|
||||
} else {
|
||||
warn!("Received a m.secret.send event but the request ID is invalid");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if let Some(request) = self.store.get_outgoing_secret_requests(request_id).await? {
|
||||
match &request.info {
|
||||
SecretInfo::KeyRequest(_) => {
|
||||
warn!(
|
||||
sender = event.sender.as_str(),
|
||||
request_id = event.content.request_id.as_str(),
|
||||
"Received a m.secret.send event but the request was for a room key"
|
||||
);
|
||||
}
|
||||
SecretInfo::SecretRequest(secret_name) => {
|
||||
debug!(
|
||||
sender = event.sender.as_str(),
|
||||
request_id = event.content.request_id.as_str(),
|
||||
secret_name = secret_name.as_ref(),
|
||||
"Received a m.secret.send event with a matching request"
|
||||
);
|
||||
|
||||
if let Some(device) =
|
||||
self.store.get_device_from_curve_key(&event.sender, sender_key).await?
|
||||
{
|
||||
if device.verified() {
|
||||
match self
|
||||
.store
|
||||
.import_secret(
|
||||
&secret_name,
|
||||
std::mem::take(&mut event.content.secret),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => self.mark_as_done(request).await?,
|
||||
Err(e) => {
|
||||
// If this is a store error propagate it up
|
||||
// the call stack.
|
||||
if let SecretImportError::Store(e) = e {
|
||||
return Err(e);
|
||||
} else {
|
||||
// Otherwise warn that there was
|
||||
// something wrong with the secret.
|
||||
warn!(
|
||||
secret_name = secret_name.as_ref(),
|
||||
error =? e,
|
||||
"Error while importing a secret"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
sender = event.sender.as_str(),
|
||||
request_id = event.content.request_id.as_str(),
|
||||
secret_name = secret_name.as_ref(),
|
||||
"Received a m.secret.send event from an unverified device"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
sender = event.sender.as_str(),
|
||||
request_id = event.content.request_id.as_str(),
|
||||
secret_name = secret_name.as_ref(),
|
||||
"Received a m.secret.send event from an unknown device"
|
||||
);
|
||||
self.store.update_tracked_user(&event.sender, true).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(AnyToDeviceEvent::SecretSend(event.clone())))
|
||||
}
|
||||
|
||||
/// Receive a forwarded room key event.
|
||||
pub async fn receive_forwarded_room_key(
|
||||
&self,
|
||||
|
|
|
@ -706,6 +706,10 @@ impl OlmMachine {
|
|||
.key_request_machine
|
||||
.receive_forwarded_room_key(&decrypted.sender_key, &mut e)
|
||||
.await?),
|
||||
AnyToDeviceEvent::SecretSend(mut e) => Ok((
|
||||
self.key_request_machine.receive_secret(&decrypted.sender_key, &mut e).await?,
|
||||
None,
|
||||
)),
|
||||
_ => {
|
||||
warn!(event_type =? event.event_type(), "Received an unexpected encrypted to-device event");
|
||||
Ok((Some(event), None))
|
||||
|
|
|
@ -35,7 +35,8 @@ use serde_json::Error as JsonError;
|
|||
|
||||
use crate::{
|
||||
error::SignatureError, identities::MasterPubkey, requests::UploadSigningKeysRequest,
|
||||
ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity,
|
||||
store::SecretImportError, utilities::decode, OwnUserIdentity, ReadOnlyAccount, ReadOnlyDevice,
|
||||
ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity,
|
||||
};
|
||||
|
||||
/// Private cross signing identity.
|
||||
|
@ -135,6 +136,49 @@ impl PrivateCrossSigningIdentity {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn import_secret(
|
||||
&self,
|
||||
public_identity: OwnUserIdentity,
|
||||
secret_name: &SecretName,
|
||||
seed: String,
|
||||
) -> Result<(), SecretImportError> {
|
||||
let seed = decode(seed)?;
|
||||
|
||||
match secret_name {
|
||||
SecretName::CrossSigningMasterKey => {
|
||||
let master = MasterSigning::from_seed(self.user_id().clone(), seed);
|
||||
|
||||
if public_identity.master_key() == &master.public_key {
|
||||
*self.master_key.lock().await = Some(master);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SecretImportError::MissmatchedPublicKeys)
|
||||
}
|
||||
}
|
||||
SecretName::CrossSigningUserSigningKey => {
|
||||
let subkey = UserSigning::from_seed(self.user_id().clone(), seed);
|
||||
|
||||
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);
|
||||
|
||||
if public_identity.self_signing_key() == &subkey.public_key {
|
||||
*self.self_signing_key.lock().await = Some(subkey);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SecretImportError::MissmatchedPublicKeys)
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the names of the secrets we are missing.
|
||||
pub(crate) async fn get_missing_secrets(&self) -> Vec<SecretName> {
|
||||
let mut missing = Vec::new();
|
||||
|
|
|
@ -146,6 +146,13 @@ impl MasterSigning {
|
|||
encode(self.inner.seed.as_slice())
|
||||
}
|
||||
|
||||
pub fn from_seed(user_id: UserId, seed: Vec<u8>) -> Self {
|
||||
let inner = Signing::from_seed(seed);
|
||||
let public_key = inner.cross_signing_key(user_id, KeyUsage::Master).into();
|
||||
|
||||
Self { inner, public_key }
|
||||
}
|
||||
|
||||
pub fn from_pickle(
|
||||
pickle: PickledMasterSigning,
|
||||
pickle_key: &[u8],
|
||||
|
@ -192,6 +199,13 @@ impl UserSigning {
|
|||
encode(self.inner.seed.as_slice())
|
||||
}
|
||||
|
||||
pub fn from_seed(user_id: UserId, seed: Vec<u8>) -> Self {
|
||||
let inner = Signing::from_seed(seed);
|
||||
let public_key = inner.cross_signing_key(user_id, KeyUsage::UserSigning).into();
|
||||
|
||||
Self { inner, public_key }
|
||||
}
|
||||
|
||||
pub async fn sign_user(
|
||||
&self,
|
||||
user: &ReadOnlyUserIdentity,
|
||||
|
@ -237,6 +251,13 @@ impl SelfSigning {
|
|||
encode(self.inner.seed.as_slice())
|
||||
}
|
||||
|
||||
pub fn from_seed(user_id: UserId, seed: Vec<u8>) -> Self {
|
||||
let inner = Signing::from_seed(seed);
|
||||
let public_key = inner.cross_signing_key(user_id, KeyUsage::SelfSigning).into();
|
||||
|
||||
Self { inner, public_key }
|
||||
}
|
||||
|
||||
pub async fn sign_device_helper(&self, value: Value) -> Result<Signature, SignatureError> {
|
||||
self.inner.sign_json(value).await
|
||||
}
|
||||
|
|
|
@ -36,11 +36,7 @@ fn encode_key_info(info: &SecretInfo) -> String {
|
|||
SecretInfo::KeyRequest(info) => {
|
||||
format!("{}{}{}{}", info.room_id, info.sender_key, info.algorithm, info.session_id)
|
||||
}
|
||||
SecretInfo::SecretRequest(i) => {
|
||||
// TODO don't use serde here, use `as_ref()` when it becomes
|
||||
// available
|
||||
serde_json::to_string(i).expect("Can't serialize secret name")
|
||||
}
|
||||
SecretInfo::SecretRequest(i) => i.as_ref().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use base64::DecodeError;
|
||||
use matrix_sdk_common::{async_trait, locks::Mutex, uuid::Uuid, AsyncTraitDeps};
|
||||
pub use memorystore::MemoryStore;
|
||||
use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError};
|
||||
|
@ -61,7 +62,7 @@ use ruma::{
|
|||
};
|
||||
use serde_json::Error as SerdeError;
|
||||
use thiserror::Error;
|
||||
use tracing::warn;
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[cfg(feature = "sled_cryptostore")]
|
||||
pub use self::sled::SledStore;
|
||||
|
@ -134,6 +135,23 @@ impl DeviceChanges {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum SecretImportError {
|
||||
/// The seed for the private key wasn't valid base64.
|
||||
#[error(transparent)]
|
||||
Base64(#[from] DecodeError),
|
||||
/// The public key of the imported private key doesn't match to the public
|
||||
/// key that was uploaded to the server.
|
||||
#[error(
|
||||
"The public key of the imported private key doesn't match to the \
|
||||
public key that was uploaded to the server"
|
||||
)]
|
||||
MissmatchedPublicKeys,
|
||||
/// The new version of the identity couldn't be stored.
|
||||
#[error(transparent)]
|
||||
Store(#[from] CryptoStoreError),
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn new(
|
||||
user_id: Arc<UserId>,
|
||||
|
@ -273,9 +291,39 @@ impl Store {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn get_missing_secrets(&self) -> Vec<SecretName> {
|
||||
// TODO add the backup key to our missing secrets
|
||||
self.identity.lock().await.get_missing_secrets().await
|
||||
pub async fn import_secret(
|
||||
&self,
|
||||
secret_name: &SecretName,
|
||||
secret: String,
|
||||
) -> Result<(), SecretImportError> {
|
||||
match secret_name {
|
||||
SecretName::CrossSigningMasterKey
|
||||
| SecretName::CrossSigningUserSigningKey
|
||||
| SecretName::CrossSigningSelfSigningKey => {
|
||||
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_secret(public_identity, secret_name, secret).await?;
|
||||
info!(
|
||||
secret_name = secret_name.as_ref(),
|
||||
"Successfully imported a private cross signing key"
|
||||
);
|
||||
|
||||
let mut changes = Changes::default();
|
||||
changes.private_identity = Some(identity.clone());
|
||||
|
||||
self.save_changes(changes).await?;
|
||||
}
|
||||
}
|
||||
SecretName::RecoveryKey => (),
|
||||
name => {
|
||||
warn!(secret =? name, "Tried to import an unknown secret");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,13 +60,7 @@ impl EncodeKey for Uuid {
|
|||
|
||||
impl EncodeKey for SecretName {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
[
|
||||
// TODO don't use serde here, use `as_ref()` when it becomes
|
||||
// available
|
||||
serde_json::to_string(self).expect("Can't serialize secret name").as_bytes(),
|
||||
&[Self::SEPARATOR],
|
||||
]
|
||||
.concat()
|
||||
[self.as_ref().as_bytes(), &[Self::SEPARATOR]].concat()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ use tracing::{error, info, trace, warn};
|
|||
|
||||
use crate::{
|
||||
error::SignatureError,
|
||||
key_request::{KeyRequestMachine, OutgoingKeyRequest},
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
store::{Changes, CryptoStore},
|
||||
CryptoStoreError, LocalTrust, ReadOnlyDevice, ReadOnlyUserIdentities,
|
||||
|
@ -351,6 +352,10 @@ impl IdentitiesBeingVerified {
|
|||
return Ok(VerificationResult::Cancel(CancelCode::KeyMismatch));
|
||||
}
|
||||
|
||||
let is_self_verification =
|
||||
device.as_ref().map(|d| d.user_id() == self.user_id()).unwrap_or_default()
|
||||
|| identity.as_ref().map(|i| i.own().is_some()).unwrap_or_default();
|
||||
|
||||
let mut changes = Changes::default();
|
||||
|
||||
let signature_request = if let Some(device) = device {
|
||||
|
@ -361,7 +366,7 @@ impl IdentitiesBeingVerified {
|
|||
Err(SignatureError::MissingSigningKey) => {
|
||||
warn!(
|
||||
"Can't sign the device keys for {} {}, \
|
||||
no private user signing key found",
|
||||
no private device signing key found",
|
||||
device.user_id(),
|
||||
device.device_id(),
|
||||
);
|
||||
|
@ -437,6 +442,11 @@ impl IdentitiesBeingVerified {
|
|||
identity_signature_request
|
||||
};
|
||||
|
||||
if is_self_verification {
|
||||
let secret_requests = self.request_missing_secrets().await;
|
||||
changes.key_requests = secret_requests;
|
||||
}
|
||||
|
||||
// TODO store the signature upload request as well.
|
||||
self.store.save_changes(changes).await?;
|
||||
|
||||
|
@ -445,6 +455,11 @@ impl IdentitiesBeingVerified {
|
|||
.unwrap_or(VerificationResult::Ok))
|
||||
}
|
||||
|
||||
async fn request_missing_secrets(&self) -> Vec<OutgoingKeyRequest> {
|
||||
let secrets = self.private_identity.get_missing_secrets().await;
|
||||
KeyRequestMachine::request_missing_secrets(self.user_id(), secrets)
|
||||
}
|
||||
|
||||
async fn mark_identity_as_verified(
|
||||
&self,
|
||||
verified_identities: Option<&[ReadOnlyUserIdentities]>,
|
||||
|
|
Loading…
Reference in New Issue