// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::{collections::BTreeMap, convert::TryInto, sync::Arc}; use aes_gcm::{ aead::{generic_array::GenericArray, Aead, NewAead}, Aes256Gcm, }; use getrandom::getrandom; use matrix_sdk_common::locks::Mutex; use olm_rs::pk::OlmPkSigning; #[cfg(test)] use olm_rs::{errors::OlmUtilityError, utility::OlmUtility}; use ruma::{ encryption::{CrossSigningKey, CrossSigningKeySignatures, DeviceKeys, KeyUsage}, serde::CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::{json, Error as JsonError, Value}; use thiserror::Error; use zeroize::Zeroizing; use crate::{ error::SignatureError, identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, utilities::{decode_url_safe, encode, encode_url_safe, DecodeError}, ReadOnlyUserIdentity, }; const NONCE_SIZE: usize = 12; /// Error type reporting failures in the Signign operations. #[derive(Debug, Error)] pub enum SigningError { /// Error decoding the base64 encoded pickle data. #[error(transparent)] Decode(#[from] DecodeError), /// Error decrypting the pickled signing seed #[error("Error decrypting the pickled signign seed")] Decryption(String), /// Error deserializing the pickle data. #[error(transparent)] Json(#[from] JsonError), } #[derive(Clone)] pub struct Signing { inner: Arc>, seed: Arc>>, public_key: PublicSigningKey, } impl std::fmt::Debug for Signing { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Signing").field("public_key", &self.public_key.as_str()).finish() } } impl PartialEq for Signing { fn eq(&self, other: &Signing) -> bool { self.seed == other.seed } } #[derive(Debug, Clone, Serialize, Deserialize)] struct InnerPickle { version: u8, nonce: String, ciphertext: String, } #[derive(Clone, PartialEq, Debug)] pub struct MasterSigning { pub inner: Signing, pub public_key: MasterPubkey, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PickledMasterSigning { pickle: PickledSigning, public_key: CrossSigningKey, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PickledUserSigning { pickle: PickledSigning, public_key: CrossSigningKey, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PickledSelfSigning { pickle: PickledSigning, public_key: CrossSigningKey, } impl PickledSigning { pub fn as_str(&self) -> &str { &self.0 } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct PublicSigningKey(Arc); impl PublicSigningKey { pub fn as_str(&self) -> &str { &self.0 } #[allow(clippy::inherent_to_string)] fn to_string(&self) -> String { self.0.to_string() } } impl MasterSigning { pub async fn pickle(&self, pickle_key: &[u8]) -> PickledMasterSigning { let pickle = self.inner.pickle(pickle_key).await; let public_key = self.public_key.clone().into(); PickledMasterSigning { pickle, public_key } } pub fn export_seed(&self) -> String { encode(self.inner.seed.as_slice()) } pub fn from_seed(user_id: UserId, seed: Vec) -> 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], ) -> Result { let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; Ok(Self { inner, public_key: pickle.public_key.into() }) } pub async fn sign_subkey<'a>(&self, subkey: &mut CrossSigningKey) { let subkey_wihtout_signatures = json!({ "user_id": subkey.user_id.clone(), "keys": subkey.keys.clone(), "usage": subkey.usage.clone(), }); let message = serde_json::to_string(&subkey_wihtout_signatures) .expect("Can't serialize cross signing subkey"); let signature = self.inner.sign(&message).await; subkey .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(), ) .to_string(), signature.0, ); } } impl UserSigning { pub async fn pickle(&self, pickle_key: &[u8]) -> PickledUserSigning { let pickle = self.inner.pickle(pickle_key).await; let public_key = self.public_key.clone().into(); PickledUserSigning { pickle, public_key } } pub fn export_seed(&self) -> String { encode(self.inner.seed.as_slice()) } pub fn from_seed(user_id: UserId, seed: Vec) -> 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, ) -> Result { let signature = self.sign_user_helper(user).await?; let mut master_key: CrossSigningKey = user.master_key().to_owned().into(); master_key.signatures.extend(signature); Ok(master_key) } pub async fn sign_user_helper( &self, user: &ReadOnlyUserIdentity, ) -> Result { let user_master: &CrossSigningKey = user.master_key().as_ref(); let signature = self.inner.sign_json(serde_json::to_value(user_master)?).await?; let mut signatures = BTreeMap::new(); 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(), ) .to_string(), signature.0, ); Ok(signatures) } pub fn from_pickle( pickle: PickledUserSigning, pickle_key: &[u8], ) -> Result { let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; Ok(Self { inner, public_key: pickle.public_key.into() }) } } impl SelfSigning { pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSelfSigning { let pickle = self.inner.pickle(pickle_key).await; let public_key = self.public_key.clone().into(); PickledSelfSigning { pickle, public_key } } pub fn export_seed(&self) -> String { encode(self.inner.seed.as_slice()) } pub fn from_seed(user_id: UserId, seed: Vec) -> 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 { self.inner.sign_json(value).await } pub async fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> { // Create a copy of the device keys containing only fields that will // get signed. let json_device = json!({ "user_id": device_keys.user_id, "device_id": device_keys.device_id, "algorithms": device_keys.algorithms, "keys": device_keys.keys, }); let signature = self.sign_device_helper(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(()) } pub fn from_pickle( pickle: PickledSelfSigning, pickle_key: &[u8], ) -> Result { let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; Ok(Self { inner, public_key: pickle.public_key.into() }) } } #[derive(Clone, PartialEq, Debug)] pub struct SelfSigning { pub inner: Signing, pub public_key: SelfSigningPubkey, } #[derive(Clone, PartialEq, Debug)] pub struct UserSigning { pub inner: Signing, pub public_key: UserSigningPubkey, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PickledSignings { pub master_key: Option, pub user_signing_key: Option, pub self_signing_key: Option, } #[derive(Debug, Clone)] pub struct Signature(String); impl std::fmt::Display for Signature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PickledSigning(String); impl Signing { pub fn new() -> Self { let seed = OlmPkSigning::generate_seed(); Self::from_seed(seed) } pub fn from_seed(seed: Vec) -> Self { let inner = OlmPkSigning::new(&seed).expect("Unable to create pk signing object"); let public_key = PublicSigningKey(inner.public_key().into()); Signing { inner: Arc::new(Mutex::new(inner)), seed: Arc::new(Zeroizing::from(seed)), public_key, } } pub fn from_pickle(pickle: PickledSigning, pickle_key: &[u8]) -> Result { let pickled: InnerPickle = serde_json::from_str(pickle.as_str())?; let key = GenericArray::from_slice(pickle_key); let cipher = Aes256Gcm::new(key); let nonce = decode_url_safe(pickled.nonce)?; let nonce = GenericArray::from_slice(&nonce); let ciphertext = &decode_url_safe(pickled.ciphertext)?; let seed = cipher .decrypt(nonce, ciphertext.as_slice()) .map_err(|e| SigningError::Decryption(e.to_string()))?; Ok(Self::from_seed(seed)) } pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSigning { let key = GenericArray::from_slice(pickle_key); let cipher = Aes256Gcm::new(key); let mut nonce = vec![0u8; NONCE_SIZE]; getrandom(&mut nonce).expect("Can't generate nonce to pickle the signing object"); let nonce = GenericArray::from_slice(nonce.as_slice()); let ciphertext = cipher.encrypt(nonce, self.seed.as_slice()).expect("Can't encrypt signing pickle"); let ciphertext = encode_url_safe(ciphertext); let pickle = InnerPickle { version: 1, nonce: encode_url_safe(nonce.as_slice()), ciphertext }; PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing")) } pub fn public_key(&self) -> &PublicSigningKey { &self.public_key } pub fn cross_signing_key(&self, user_id: UserId, usage: KeyUsage) -> CrossSigningKey { let mut keys = BTreeMap::new(); keys.insert( DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.public_key().as_str().into()) .to_string(), self.public_key().to_string(), ); CrossSigningKey::new(user_id, vec![usage], keys, BTreeMap::new()) } #[cfg(test)] pub async fn verify( &self, message: &str, signature: &Signature, ) -> Result { let utility = OlmUtility::new(); utility.ed25519_verify(self.public_key.as_str(), message, signature.to_string()) } pub async fn sign_json(&self, mut json: Value) -> Result { let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?; let _ = json_object.remove("signatures"); let canonical_json: CanonicalJsonValue = json.try_into().expect("Can't canonicalize the json value"); Ok(self.sign(&canonical_json.to_string()).await) } pub async fn sign(&self, message: &str) -> Signature { Signature(self.inner.lock().await.sign(message)) } }