crypto: WIP cross signing bootstrap.
parent
8ed1e37cef
commit
4cc803fe27
|
@ -0,0 +1,119 @@
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
env, io,
|
||||||
|
process::exit,
|
||||||
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde_json::json;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use matrix_sdk::{
|
||||||
|
self,
|
||||||
|
api::r0::uiaa::AuthData,
|
||||||
|
identifiers::{user_id, UserId},
|
||||||
|
Client, ClientConfig, LoopCtrl, SyncSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> AuthData<'a> {
|
||||||
|
let mut auth_parameters = BTreeMap::new();
|
||||||
|
let identifier = json!({
|
||||||
|
"type": "m.id.user",
|
||||||
|
"user": user,
|
||||||
|
});
|
||||||
|
|
||||||
|
auth_parameters.insert("identifier".to_owned(), identifier);
|
||||||
|
auth_parameters.insert("password".to_owned(), password.to_owned().into());
|
||||||
|
|
||||||
|
// This is needed because of https://github.com/matrix-org/synapse/issues/5665
|
||||||
|
auth_parameters.insert("user".to_owned(), user.as_str().into());
|
||||||
|
|
||||||
|
AuthData::DirectRequest {
|
||||||
|
kind: "m.login.password",
|
||||||
|
auth_parameters,
|
||||||
|
session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn bootstrap(client: Client) {
|
||||||
|
println!("Bootstrapping a new cross signing identity, press enter to continue.");
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
|
||||||
|
io::stdin()
|
||||||
|
.read_line(&mut input)
|
||||||
|
.expect("error: unable to read user input");
|
||||||
|
|
||||||
|
if let Err(e) = client.bootstrap_cross_signing(None).await {
|
||||||
|
if let Some(response) = e.uiaa_response() {
|
||||||
|
let auth_data = auth_data(
|
||||||
|
&user_id!("@example:localhost"),
|
||||||
|
"wordpass",
|
||||||
|
response.session.as_deref(),
|
||||||
|
);
|
||||||
|
client
|
||||||
|
.bootstrap_cross_signing(Some(auth_data))
|
||||||
|
.await
|
||||||
|
.expect("Couldn't bootstrap cross signing")
|
||||||
|
} else {
|
||||||
|
panic!("Error durign cross signing bootstrap {:#?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn login(
|
||||||
|
homeserver_url: String,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<(), matrix_sdk::Error> {
|
||||||
|
let client_config = ClientConfig::new()
|
||||||
|
.disable_ssl_verification()
|
||||||
|
.proxy("http://localhost:8080")
|
||||||
|
.unwrap();
|
||||||
|
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
||||||
|
let client = Client::new_with_config(homeserver_url, client_config).unwrap();
|
||||||
|
|
||||||
|
client
|
||||||
|
.login(username, password, None, Some("rust-sdk"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let client_ref = &client;
|
||||||
|
let asked = AtomicBool::new(false);
|
||||||
|
let asked_ref = &asked;
|
||||||
|
|
||||||
|
client
|
||||||
|
.sync_with_callback(SyncSettings::new(), |_| async move {
|
||||||
|
let asked = asked_ref;
|
||||||
|
let client = &client_ref;
|
||||||
|
|
||||||
|
// Wait for sync to be done then ask the user to bootstrap.
|
||||||
|
if !asked.load(Ordering::SeqCst) {
|
||||||
|
tokio::spawn(bootstrap((*client).clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
asked.store(true, Ordering::SeqCst);
|
||||||
|
LoopCtrl::Continue
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), matrix_sdk::Error> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let (homeserver_url, username, password) =
|
||||||
|
match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) {
|
||||||
|
(Some(a), Some(b), Some(c)) => (a, b, c),
|
||||||
|
_ => {
|
||||||
|
eprintln!(
|
||||||
|
"Usage: {} <homeserver_url> <username> <password>",
|
||||||
|
env::args().next().unwrap()
|
||||||
|
);
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
login(homeserver_url, &username, &password).await
|
||||||
|
}
|
|
@ -107,7 +107,7 @@ use matrix_sdk_common::{
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0::{
|
api::r0::{
|
||||||
keys::{get_keys, upload_keys},
|
keys::{get_keys, upload_keys, upload_signing_keys::Request as UploadSigningKeysRequest},
|
||||||
to_device::send_event_to_device::{
|
to_device::send_event_to_device::{
|
||||||
Request as RumaToDeviceRequest, Response as ToDeviceResponse,
|
Request as RumaToDeviceRequest, Response as ToDeviceResponse,
|
||||||
},
|
},
|
||||||
|
@ -1823,6 +1823,33 @@ impl Client {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||||
|
pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData<'_>>) -> Result<()> {
|
||||||
|
let olm = self
|
||||||
|
.base_client
|
||||||
|
.olm_machine()
|
||||||
|
.await
|
||||||
|
.ok_or(Error::AuthenticationRequired)?;
|
||||||
|
|
||||||
|
let (request, signature_request) = olm.bootstrap_cross_signing(false).await?;
|
||||||
|
|
||||||
|
println!("HELLOOO MAKING REQUEST {:#?}", request);
|
||||||
|
|
||||||
|
let request = UploadSigningKeysRequest {
|
||||||
|
auth: auth_data,
|
||||||
|
master_key: request.master_key,
|
||||||
|
self_signing_key: request.self_signing_key,
|
||||||
|
user_signing_key: request.user_signing_key,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send(request).await?;
|
||||||
|
self.send(signature_request).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a map holding all the devices of an user.
|
/// Get a map holding all the devices of an user.
|
||||||
///
|
///
|
||||||
/// This will always return an empty map if the client hasn't been logged
|
/// This will always return an empty map if the client hasn't been logged
|
||||||
|
|
|
@ -20,7 +20,7 @@ js_int = "0.1.9"
|
||||||
|
|
||||||
[dependencies.ruma]
|
[dependencies.ruma]
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
git = "https://github.com/ruma/ruma"
|
path = "/home/poljar/werk/priv/ruma/ruma"
|
||||||
rev = "db2f58032953ccb6d8ae712d64d713ebdf412598"
|
rev = "db2f58032953ccb6d8ae712d64d713ebdf412598"
|
||||||
features = ["client-api", "unstable-pre-spec", "unstable-exhaustive-types"]
|
features = ["client-api", "unstable-pre-spec", "unstable-exhaustive-types"]
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,9 @@ pub enum SignatureError {
|
||||||
#[error("the provided JSON object can't be converted to a canonical representation")]
|
#[error("the provided JSON object can't be converted to a canonical representation")]
|
||||||
CanonicalJsonError(CjsonError),
|
CanonicalJsonError(CjsonError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
JsonError(#[from] SerdeError),
|
||||||
|
|
||||||
#[error("the signature didn't match the provided key")]
|
#[error("the signature didn't match the provided key")]
|
||||||
VerificationError,
|
VerificationError,
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ use matrix_sdk_common::{
|
||||||
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
|
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
|
||||||
get_keys::Response as KeysQueryResponse,
|
get_keys::Response as KeysQueryResponse,
|
||||||
upload_keys,
|
upload_keys,
|
||||||
|
upload_signatures::Request as UploadSignaturesRequest,
|
||||||
},
|
},
|
||||||
sync::sync_events::Response as SyncResponse,
|
sync::sync_events::Response as SyncResponse,
|
||||||
},
|
},
|
||||||
|
@ -95,6 +96,7 @@ pub struct OlmMachine {
|
||||||
/// State machine handling public user identities and devices, keeping track
|
/// State machine handling public user identities and devices, keeping track
|
||||||
/// of when a key query needs to be done and handling one.
|
/// of when a key query needs to be done and handling one.
|
||||||
identity_manager: IdentityManager,
|
identity_manager: IdentityManager,
|
||||||
|
cross_signing_request: Arc<Mutex<Option<UploadSignaturesRequest>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
@ -181,13 +183,14 @@ impl OlmMachine {
|
||||||
user_id,
|
user_id,
|
||||||
device_id,
|
device_id,
|
||||||
account,
|
account,
|
||||||
|
user_identity,
|
||||||
store,
|
store,
|
||||||
session_manager,
|
session_manager,
|
||||||
group_session_manager,
|
group_session_manager,
|
||||||
verification_machine,
|
verification_machine,
|
||||||
key_request_machine,
|
key_request_machine,
|
||||||
identity_manager,
|
identity_manager,
|
||||||
user_identity,
|
cross_signing_request: Arc::new(Mutex::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,12 +372,32 @@ impl OlmMachine {
|
||||||
/// devices.
|
/// devices.
|
||||||
///
|
///
|
||||||
/// Uploading these keys will require user interactive auth.
|
/// Uploading these keys will require user interactive auth.
|
||||||
pub async fn bootstrap_cross_signing(&self) -> StoreResult<UploadSigningKeysRequest> {
|
pub async fn bootstrap_cross_signing(
|
||||||
// TODO should we save the request until we get a response?
|
&self,
|
||||||
|
reset: bool,
|
||||||
|
) -> StoreResult<(UploadSigningKeysRequest, UploadSignaturesRequest)> {
|
||||||
let mut identity = self.user_identity.lock().await;
|
let mut identity = self.user_identity.lock().await;
|
||||||
*identity = PrivateCrossSigningIdentity::new(self.user_id().to_owned()).await;
|
|
||||||
|
if identity.is_empty().await || reset {
|
||||||
|
info!("Creating new cross signing identity");
|
||||||
|
let (id, signature_request) = self.account.bootstrap_cross_signing().await;
|
||||||
|
let request = id.as_upload_request().await;
|
||||||
|
|
||||||
|
*identity = id;
|
||||||
|
|
||||||
self.store.save_identity(identity.clone()).await?;
|
self.store.save_identity(identity.clone()).await?;
|
||||||
Ok(identity.as_upload_request().await)
|
Ok((request, signature_request))
|
||||||
|
} else {
|
||||||
|
info!("Trying to upload the existing cross signing identity");
|
||||||
|
let request = identity.as_upload_request().await;
|
||||||
|
let device_keys = self.account.unsigned_device_keys();
|
||||||
|
// TODO remove this expect.
|
||||||
|
let signature_request = identity
|
||||||
|
.sign_device(device_keys)
|
||||||
|
.await
|
||||||
|
.expect("Can't sign device keys");
|
||||||
|
Ok((request, signature_request))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should device or one-time keys be uploaded to the server.
|
/// Should device or one-time keys be uploaded to the server.
|
||||||
|
|
|
@ -30,7 +30,9 @@ use tracing::{debug, trace, warn};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use matrix_sdk_common::events::EventType;
|
use matrix_sdk_common::events::EventType;
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0::keys::{upload_keys, OneTimeKey, SignedKey},
|
api::r0::keys::{
|
||||||
|
upload_keys, upload_signatures::Request as SignatureUploadRequest, OneTimeKey, SignedKey,
|
||||||
|
},
|
||||||
encryption::DeviceKeys,
|
encryption::DeviceKeys,
|
||||||
events::{room::encrypted::EncryptedEventContent, AnyToDeviceEvent},
|
events::{room::encrypted::EncryptedEventContent, AnyToDeviceEvent},
|
||||||
identifiers::{
|
identifiers::{
|
||||||
|
@ -50,13 +52,16 @@ use olm_rs::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{EventError, OlmResult, SessionCreationError},
|
error::{EventError, OlmResult, SessionCreationError, SignatureError},
|
||||||
identities::ReadOnlyDevice,
|
identities::ReadOnlyDevice,
|
||||||
store::Store,
|
store::Store,
|
||||||
OlmError,
|
OlmError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{EncryptionSettings, InboundGroupSession, OutboundGroupSession, Session};
|
use super::{
|
||||||
|
EncryptionSettings, InboundGroupSession, OutboundGroupSession, PrivateCrossSigningIdentity,
|
||||||
|
Session,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
|
@ -618,9 +623,7 @@ impl ReadOnlyAccount {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign the device keys of the account and return them so they can be
|
pub(crate) fn unsigned_device_keys(&self) -> DeviceKeys {
|
||||||
/// uploaded.
|
|
||||||
pub(crate) async fn device_keys(&self) -> DeviceKeys {
|
|
||||||
let identity_keys = self.identity_keys();
|
let identity_keys = self.identity_keys();
|
||||||
|
|
||||||
let mut keys = BTreeMap::new();
|
let mut keys = BTreeMap::new();
|
||||||
|
@ -634,34 +637,41 @@ impl ReadOnlyAccount {
|
||||||
identity_keys.ed25519().to_owned(),
|
identity_keys.ed25519().to_owned(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let device_keys = json!({
|
|
||||||
"user_id": (*self.user_id).clone(),
|
|
||||||
"device_id": (*self.device_id).clone(),
|
|
||||||
"algorithms": Self::ALGORITHMS,
|
|
||||||
"keys": keys,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut signatures = BTreeMap::new();
|
|
||||||
|
|
||||||
let mut signature = BTreeMap::new();
|
|
||||||
signature.insert(
|
|
||||||
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id),
|
|
||||||
self.sign_json(&device_keys).await,
|
|
||||||
);
|
|
||||||
signatures.insert((*self.user_id).clone(), signature);
|
|
||||||
|
|
||||||
DeviceKeys::new(
|
DeviceKeys::new(
|
||||||
(*self.user_id).clone(),
|
(*self.user_id).clone(),
|
||||||
(*self.device_id).clone(),
|
(*self.device_id).clone(),
|
||||||
vec![
|
Self::ALGORITHMS.iter().map(|a| (&**a).clone()).collect(),
|
||||||
EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
|
|
||||||
EventEncryptionAlgorithm::MegolmV1AesSha2,
|
|
||||||
],
|
|
||||||
keys,
|
keys,
|
||||||
signatures,
|
BTreeMap::new(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sign the device keys of the account and return them so they can be
|
||||||
|
/// uploaded.
|
||||||
|
pub(crate) async fn device_keys(&self) -> DeviceKeys {
|
||||||
|
let mut device_keys = self.unsigned_device_keys();
|
||||||
|
let jsond_device_keys = serde_json::to_value(&device_keys).unwrap();
|
||||||
|
|
||||||
|
device_keys
|
||||||
|
.signatures
|
||||||
|
.entry(self.user_id().clone())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id),
|
||||||
|
self.sign_json(jsond_device_keys)
|
||||||
|
.await
|
||||||
|
.expect("Can't sign own device keys"),
|
||||||
|
);
|
||||||
|
|
||||||
|
device_keys
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn bootstrap_cross_signing(
|
||||||
|
&self,
|
||||||
|
) -> (PrivateCrossSigningIdentity, SignatureUploadRequest) {
|
||||||
|
PrivateCrossSigningIdentity::new_with_account(self).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a JSON value to the canonical representation and sign the JSON
|
/// Convert a JSON value to the canonical representation and sign the JSON
|
||||||
/// string.
|
/// string.
|
||||||
///
|
///
|
||||||
|
@ -673,10 +683,13 @@ impl ReadOnlyAccount {
|
||||||
/// # Panic
|
/// # Panic
|
||||||
///
|
///
|
||||||
/// Panics if the json value can't be serialized.
|
/// Panics if the json value can't be serialized.
|
||||||
pub async fn sign_json(&self, json: &Value) -> String {
|
pub async fn sign_json(&self, mut json: Value) -> Result<String, SignatureError> {
|
||||||
let canonical_json = cjson::to_string(json)
|
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
||||||
.unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", json)));
|
let _ = json_object.remove("unsigned");
|
||||||
self.sign(&canonical_json).await
|
let _ = json_object.remove("signatures");
|
||||||
|
|
||||||
|
let canonical_json = cjson::to_string(&json)?;
|
||||||
|
Ok(self.sign(&canonical_json).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn signed_one_time_keys_helper(
|
pub(crate) async fn signed_one_time_keys_helper(
|
||||||
|
@ -690,7 +703,10 @@ impl ReadOnlyAccount {
|
||||||
"key": key,
|
"key": key,
|
||||||
});
|
});
|
||||||
|
|
||||||
let signature = self.sign_json(&key_json).await;
|
let signature = self
|
||||||
|
.sign_json(key_json)
|
||||||
|
.await
|
||||||
|
.expect("Can't sign own one-time keys");
|
||||||
|
|
||||||
let mut signature_map = BTreeMap::new();
|
let mut signature_map = BTreeMap::new();
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,12 @@ use aes_gcm::{
|
||||||
};
|
};
|
||||||
use base64::{decode_config, encode_config, DecodeError, URL_SAFE_NO_PAD};
|
use base64::{decode_config, encode_config, DecodeError, URL_SAFE_NO_PAD};
|
||||||
use getrandom::getrandom;
|
use getrandom::getrandom;
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
encryption::DeviceKeys,
|
||||||
|
identifiers::{DeviceKeyAlgorithm, DeviceKeyId},
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Error as JsonError;
|
use serde_json::{Error as JsonError, Value};
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -35,16 +39,22 @@ use zeroize::Zeroizing;
|
||||||
use olm_rs::{errors::OlmUtilityError, pk::OlmPkSigning, utility::OlmUtility};
|
use olm_rs::{errors::OlmUtilityError, pk::OlmPkSigning, utility::OlmUtility};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0::keys::{CrossSigningKey, KeyUsage},
|
api::r0::keys::{
|
||||||
|
upload_signatures::Request as SignatureUploadRequest, CrossSigningKey, KeyUsage,
|
||||||
|
},
|
||||||
identifiers::UserId,
|
identifiers::UserId,
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::SignatureError,
|
||||||
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
|
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
|
||||||
requests::UploadSigningKeysRequest,
|
requests::UploadSigningKeysRequest,
|
||||||
|
UserIdentity,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ReadOnlyAccount;
|
||||||
|
|
||||||
const NONCE_SIZE: usize = 12;
|
const NONCE_SIZE: usize = 12;
|
||||||
|
|
||||||
fn encode<T: AsRef<[u8]>>(input: T) -> String {
|
fn encode<T: AsRef<[u8]>>(input: T) -> String {
|
||||||
|
@ -163,6 +173,10 @@ impl UserSigning {
|
||||||
PickledUserSigning { pickle, public_key }
|
PickledUserSigning { pickle, public_key }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn sign_user(&self, _: &UserIdentity) -> BTreeMap<UserId, BTreeMap<String, Value>> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
fn from_pickle(pickle: PickledUserSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
fn from_pickle(pickle: PickledUserSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
||||||
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
||||||
|
|
||||||
|
@ -180,6 +194,29 @@ impl SelfSigning {
|
||||||
PickledSelfSigning { pickle, public_key }
|
PickledSelfSigning { pickle, public_key }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn sign_device_raw(&self, value: Value) -> Result<Signature, SignatureError> {
|
||||||
|
self.inner.sign_json(value).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> {
|
||||||
|
let json_device = serde_json::to_value(&device_keys)?;
|
||||||
|
let signature = self.sign_device_raw(json_device).await?;
|
||||||
|
|
||||||
|
device_keys
|
||||||
|
.signatures
|
||||||
|
.entry(self.public_key.user_id().to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
DeviceKeyId::from_parts(
|
||||||
|
DeviceKeyAlgorithm::Ed25519,
|
||||||
|
self.inner.public_key.as_str().into(),
|
||||||
|
),
|
||||||
|
signature.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn from_pickle(pickle: PickledSelfSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
fn from_pickle(pickle: PickledSelfSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
||||||
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
||||||
|
|
||||||
|
@ -346,6 +383,13 @@ impl Signing {
|
||||||
utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str())
|
utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn sign_json(&self, mut json: Value) -> Result<Signature, SignatureError> {
|
||||||
|
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
||||||
|
let _ = json_object.remove("signatures");
|
||||||
|
let canonical_json = cjson::to_string(json_object)?;
|
||||||
|
Ok(self.sign(&canonical_json).await)
|
||||||
|
}
|
||||||
|
|
||||||
async fn sign(&self, message: &str) -> Signature {
|
async fn sign(&self, message: &str) -> Signature {
|
||||||
Signature(self.inner.lock().await.sign(message))
|
Signature(self.inner.lock().await.sign(message))
|
||||||
}
|
}
|
||||||
|
@ -356,6 +400,14 @@ impl PrivateCrossSigningIdentity {
|
||||||
&self.user_id
|
&self.user_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn is_empty(&self) -> bool {
|
||||||
|
let has_master = self.master_key.lock().await.is_some();
|
||||||
|
let has_user = self.user_signing_key.lock().await.is_some();
|
||||||
|
let has_self = self.self_signing_key.lock().await.is_some();
|
||||||
|
|
||||||
|
!(has_master && has_user && has_self)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn empty(user_id: UserId) -> Self {
|
pub(crate) fn empty(user_id: UserId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
user_id: Arc::new(user_id),
|
user_id: Arc::new(user_id),
|
||||||
|
@ -366,6 +418,94 @@ impl PrivateCrossSigningIdentity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn sign_device(
|
||||||
|
&self,
|
||||||
|
mut device_keys: DeviceKeys,
|
||||||
|
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||||
|
self.self_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SignatureError::MissingSigningKey)?
|
||||||
|
.sign_device(&mut device_keys)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut signed_keys = BTreeMap::new();
|
||||||
|
signed_keys
|
||||||
|
.entry((&*self.user_id).to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
device_keys.device_id.to_string(),
|
||||||
|
serde_json::to_value(device_keys)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(SignatureUploadRequest { signed_keys })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn new_with_account(
|
||||||
|
account: &ReadOnlyAccount,
|
||||||
|
) -> (Self, SignatureUploadRequest) {
|
||||||
|
let master = Signing::new();
|
||||||
|
|
||||||
|
let mut public_key =
|
||||||
|
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
|
||||||
|
let signature = account
|
||||||
|
.sign_json(
|
||||||
|
serde_json::to_value(&public_key)
|
||||||
|
.expect("Can't convert own public master key to json"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Can't sign own public master key");
|
||||||
|
public_key
|
||||||
|
.signatures
|
||||||
|
.entry(account.user_id().to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(format!("ed25519:{}", account.device_id()), signature);
|
||||||
|
|
||||||
|
let master = MasterSigning {
|
||||||
|
inner: master,
|
||||||
|
public_key: public_key.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let identity = Self::new_helper(account.user_id(), master).await;
|
||||||
|
let device_keys = account.unsigned_device_keys();
|
||||||
|
let request = identity
|
||||||
|
.sign_device(device_keys)
|
||||||
|
.await
|
||||||
|
.expect("Can't sign own device with new cross signign keys");
|
||||||
|
|
||||||
|
(identity, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_helper(user_id: &UserId, master: MasterSigning) -> Self {
|
||||||
|
let user = Signing::new();
|
||||||
|
let mut public_key = user.cross_signing_key(user_id.to_owned(), KeyUsage::UserSigning);
|
||||||
|
master.sign_subkey(&mut public_key).await;
|
||||||
|
|
||||||
|
let user = UserSigning {
|
||||||
|
inner: user,
|
||||||
|
public_key: public_key.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let self_signing = Signing::new();
|
||||||
|
let mut public_key =
|
||||||
|
self_signing.cross_signing_key(user_id.to_owned(), KeyUsage::SelfSigning);
|
||||||
|
master.sign_subkey(&mut public_key).await;
|
||||||
|
|
||||||
|
let self_signing = SelfSigning {
|
||||||
|
inner: self_signing,
|
||||||
|
public_key: public_key.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
user_id: Arc::new(user_id.to_owned()),
|
||||||
|
shared: Arc::new(AtomicBool::new(false)),
|
||||||
|
master_key: Arc::new(Mutex::new(Some(master))),
|
||||||
|
self_signing_key: Arc::new(Mutex::new(Some(self_signing))),
|
||||||
|
user_signing_key: Arc::new(Mutex::new(Some(user))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn new(user_id: UserId) -> Self {
|
pub(crate) async fn new(user_id: UserId) -> Self {
|
||||||
let master = Signing::new();
|
let master = Signing::new();
|
||||||
|
|
||||||
|
@ -520,6 +660,8 @@ impl PrivateCrossSigningIdentity {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::olm::ReadOnlyAccount;
|
||||||
|
|
||||||
use super::{PrivateCrossSigningIdentity, Signing};
|
use super::{PrivateCrossSigningIdentity, Signing};
|
||||||
|
|
||||||
use matrix_sdk_common::identifiers::{user_id, UserId};
|
use matrix_sdk_common::identifiers::{user_id, UserId};
|
||||||
|
@ -617,4 +759,14 @@ mod test {
|
||||||
&*unpickled.self_signing_key.lock().await
|
&*unpickled.self_signing_key.lock().await
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn private_identity_signed_by_accound() {
|
||||||
|
let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into());
|
||||||
|
let (identity, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||||
|
let master = identity.master_key.lock().await;
|
||||||
|
let master = master.as_ref().unwrap();
|
||||||
|
|
||||||
|
assert!(!master.public_key.signatures().is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue