crypto: Add support for device keys signing.
This commit is contained in:
parent
478f0d7784
commit
45890a27f3
5 changed files with 187 additions and 6 deletions
|
@ -12,12 +12,12 @@ version = "0.1.0"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
encryption = ["olm-rs", "serde/derive", "serde_json"]
|
||||
encryption = ["olm-rs", "serde/derive", "serde_json", "cjson"]
|
||||
|
||||
[dependencies]
|
||||
js_int = "0.1.2"
|
||||
futures = "0.3.4"
|
||||
reqwest = "0.10.1"
|
||||
reqwest = "0.10.2"
|
||||
http = "0.2.0"
|
||||
async-std = "1.5.0"
|
||||
ruma-api = "0.13.0"
|
||||
|
@ -30,7 +30,8 @@ url = "2.1.1"
|
|||
|
||||
olm-rs = { git = "https://gitlab.gnome.org/jhaye/olm-rs/", optional = true}
|
||||
serde = { version = "1.0.104", optional = true, features = ["derive"] }
|
||||
serde_json = { version = "*", optional = true }
|
||||
serde_json = { version = "1.0.48", optional = true }
|
||||
cjson = { version = "0.1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.11", features = ["full"] }
|
||||
|
|
47
src/crypto/error.rs
Normal file
47
src/crypto/error.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
// 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 cjson::Error as CjsonError;
|
||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SignatureError {
|
||||
NotAnObject,
|
||||
NoSignatureFound,
|
||||
CanonicalJsonError(CjsonError),
|
||||
VerificationError,
|
||||
}
|
||||
|
||||
impl Display for SignatureError {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
let message = match self {
|
||||
SignatureError::NotAnObject => "The provided JSON value isn't an object.",
|
||||
SignatureError::NoSignatureFound => {
|
||||
"The provided JSON object doesn't contain a signatures field."
|
||||
}
|
||||
SignatureError::CanonicalJsonError(_) => {
|
||||
"The provided JSON object can't be converted to a canonical representation."
|
||||
}
|
||||
SignatureError::VerificationError => "The signature didn't match the provided key.",
|
||||
};
|
||||
|
||||
write!(f, "{}", message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CjsonError> for SignatureError {
|
||||
fn from(error: CjsonError) -> Self {
|
||||
Self::CanonicalJsonError(error)
|
||||
}
|
||||
}
|
|
@ -14,11 +14,17 @@
|
|||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use super::error::SignatureError;
|
||||
use super::olm::Account;
|
||||
use crate::api;
|
||||
|
||||
use api::r0::keys;
|
||||
|
||||
use cjson;
|
||||
use olm_rs::utility::OlmUtility;
|
||||
use serde_json::json;
|
||||
use serde_json::value::Value;
|
||||
|
||||
struct OlmMachine {
|
||||
/// The unique user id that owns this account.
|
||||
user_id: String,
|
||||
|
@ -34,6 +40,14 @@ struct OlmMachine {
|
|||
}
|
||||
|
||||
impl OlmMachine {
|
||||
const OLM_V1_ALGORITHM: &'static str = "m.olm.v1.curve25519-aes-sha2";
|
||||
const MEGOLM_V1_ALGORITHM: &'static str = "m.megolm.v1.aes-sha2";
|
||||
|
||||
const ALGORITHMS: &'static [&'static str] = &[
|
||||
OlmMachine::OLM_V1_ALGORITHM,
|
||||
OlmMachine::MEGOLM_V1_ALGORITHM,
|
||||
];
|
||||
|
||||
/// Create a new account.
|
||||
pub fn new(user_id: &str, device_id: &str) -> Self {
|
||||
OlmMachine {
|
||||
|
@ -62,9 +76,9 @@ impl OlmMachine {
|
|||
}
|
||||
}
|
||||
|
||||
/// Receive a successfull keys upload response.
|
||||
/// Receive a successful keys upload response.
|
||||
///
|
||||
/// # Arugments
|
||||
/// # Arguments
|
||||
///
|
||||
/// `response` - The keys upload response of the request that the client
|
||||
/// performed.
|
||||
|
@ -108,7 +122,104 @@ impl OlmMachine {
|
|||
}
|
||||
}
|
||||
|
||||
fn device_keys() -> () {}
|
||||
/// Sign the device keys and return a JSON Value to upload them.
|
||||
fn device_keys(&self) -> Value {
|
||||
let identity_keys = self.account.identity_keys();
|
||||
|
||||
let mut device_keys = json!({
|
||||
"user_id": self.user_id,
|
||||
"device_id": self.device_id,
|
||||
"algorithms": OlmMachine::ALGORITHMS,
|
||||
"keys": {
|
||||
format!("curve25519:{}", self.device_id): identity_keys.curve25519(),
|
||||
format!("ed25519:{}", self.device_id): identity_keys.ed25519(),
|
||||
},
|
||||
});
|
||||
|
||||
let signature = json!({
|
||||
self.user_id.clone(): {
|
||||
format!("ed25519:{}", self.device_id): self.sign_json(&device_keys),
|
||||
}
|
||||
});
|
||||
|
||||
let device_keys_object = device_keys
|
||||
.as_object_mut()
|
||||
.expect("Device keys json value isn't an object");
|
||||
|
||||
device_keys_object.insert("signatures".to_string(), signature);
|
||||
|
||||
device_keys
|
||||
}
|
||||
|
||||
/// Convert a JSON value to the canonical representation and sign the JSON string.
|
||||
fn sign_json(&self, json: &Value) -> String {
|
||||
let canonical_json =
|
||||
cjson::to_string(json).expect(&format!("Can't serialize {} to canonical JSON", json));
|
||||
println!("HELLO SIGNING {}", canonical_json);
|
||||
self.account.sign(&canonical_json)
|
||||
}
|
||||
|
||||
/// Verify a signed JSON object.
|
||||
///
|
||||
/// The object must have a signatures key associated with an object of the
|
||||
/// form `user_id: {key_id: signature}`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The user who signed the JSON object.
|
||||
/// * `device_id` - The device that signed the JSON object.
|
||||
/// * `user_key` - The public ed25519 key which was used to sign the JSON
|
||||
/// object.
|
||||
/// * `json` - The JSON object that should be verified.
|
||||
///
|
||||
/// Returns Ok if the signature was successfully verified, otherwise an
|
||||
/// SignatureError.
|
||||
fn verify_json(
|
||||
&self,
|
||||
user_id: &str,
|
||||
device_id: &str,
|
||||
user_key: &str,
|
||||
json: &mut Value,
|
||||
) -> Result<(), SignatureError> {
|
||||
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
||||
let unsigned = json_object.remove("unsigned");
|
||||
let signatures = json_object.remove("signatures");
|
||||
|
||||
let canonical_json = cjson::to_string(json_object)?;
|
||||
|
||||
if let Some(u) = unsigned {
|
||||
json_object.insert("unsigned".to_string(), u);
|
||||
}
|
||||
|
||||
let key_id = format!("ed25519:{}", device_id);
|
||||
|
||||
let signatures = signatures.ok_or(SignatureError::NoSignatureFound)?;
|
||||
let signature_object = signatures
|
||||
.as_object()
|
||||
.ok_or(SignatureError::NoSignatureFound)?;
|
||||
let signature = signature_object
|
||||
.get(user_id)
|
||||
.ok_or(SignatureError::NoSignatureFound)?;
|
||||
let signature = signature
|
||||
.get(key_id)
|
||||
.ok_or(SignatureError::NoSignatureFound)?;
|
||||
let signature = signature.as_str().ok_or(SignatureError::NoSignatureFound)?;
|
||||
|
||||
let utility = OlmUtility::new();
|
||||
|
||||
let ret = if utility
|
||||
.ed25519_verify(&user_key, &canonical_json, signature)
|
||||
.is_ok()
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SignatureError::VerificationError)
|
||||
};
|
||||
|
||||
json_object.insert("signatures".to_string(), signatures);
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -194,4 +305,21 @@ mod test {
|
|||
machine.receive_keys_upload_response(&response).await;
|
||||
assert!(machine.generate_one_time_keys().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_key_signing() {
|
||||
let machine = OlmMachine::new(USER_ID, DEVICE_ID);
|
||||
|
||||
let mut device_keys = machine.device_keys();
|
||||
let identity_keys = machine.account.identity_keys();
|
||||
let ed25519_key = identity_keys.ed25519();
|
||||
|
||||
let ret = machine.verify_json(
|
||||
&machine.user_id,
|
||||
&machine.device_id,
|
||||
ed25519_key,
|
||||
&mut device_keys,
|
||||
);
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
// TODO remove this.
|
||||
mod error;
|
||||
#[allow(dead_code)]
|
||||
mod machine;
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -151,6 +151,10 @@ impl Account {
|
|||
pub fn mark_keys_as_published(&self) {
|
||||
self.inner.mark_keys_as_published();
|
||||
}
|
||||
|
||||
pub fn sign(&self, string: &str) -> String {
|
||||
self.inner.sign(string)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue