crypto: Allow secrets to be requested and imported

master
Damir Jelić 2021-08-02 08:03:08 +02:00
parent e57d70b089
commit 68df9b6ed2
8 changed files with 249 additions and 41 deletions

View File

@ -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,

View File

@ -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))

View File

@ -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();

View File

@ -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
}

View File

@ -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(),
}
}

View File

@ -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(())
}
}

View File

@ -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()
}
}

View File

@ -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]>,