crypto: Add support for device keys signing.
parent
478f0d7784
commit
45890a27f3
|
@ -12,12 +12,12 @@ version = "0.1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
encryption = ["olm-rs", "serde/derive", "serde_json"]
|
encryption = ["olm-rs", "serde/derive", "serde_json", "cjson"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
js_int = "0.1.2"
|
js_int = "0.1.2"
|
||||||
futures = "0.3.4"
|
futures = "0.3.4"
|
||||||
reqwest = "0.10.1"
|
reqwest = "0.10.2"
|
||||||
http = "0.2.0"
|
http = "0.2.0"
|
||||||
async-std = "1.5.0"
|
async-std = "1.5.0"
|
||||||
ruma-api = "0.13.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}
|
olm-rs = { git = "https://gitlab.gnome.org/jhaye/olm-rs/", optional = true}
|
||||||
serde = { version = "1.0.104", optional = true, features = ["derive"] }
|
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]
|
[dev-dependencies]
|
||||||
tokio = { version = "0.2.11", features = ["full"] }
|
tokio = { version = "0.2.11", features = ["full"] }
|
||||||
|
|
|
@ -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 std::convert::TryInto;
|
||||||
|
|
||||||
|
use super::error::SignatureError;
|
||||||
use super::olm::Account;
|
use super::olm::Account;
|
||||||
use crate::api;
|
use crate::api;
|
||||||
|
|
||||||
use api::r0::keys;
|
use api::r0::keys;
|
||||||
|
|
||||||
|
use cjson;
|
||||||
|
use olm_rs::utility::OlmUtility;
|
||||||
|
use serde_json::json;
|
||||||
|
use serde_json::value::Value;
|
||||||
|
|
||||||
struct OlmMachine {
|
struct OlmMachine {
|
||||||
/// The unique user id that owns this account.
|
/// The unique user id that owns this account.
|
||||||
user_id: String,
|
user_id: String,
|
||||||
|
@ -34,6 +40,14 @@ struct OlmMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
/// Create a new account.
|
||||||
pub fn new(user_id: &str, device_id: &str) -> Self {
|
pub fn new(user_id: &str, device_id: &str) -> Self {
|
||||||
OlmMachine {
|
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
|
/// `response` - The keys upload response of the request that the client
|
||||||
/// performed.
|
/// 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)]
|
#[cfg(test)]
|
||||||
|
@ -194,4 +305,21 @@ mod test {
|
||||||
machine.receive_keys_upload_response(&response).await;
|
machine.receive_keys_upload_response(&response).await;
|
||||||
assert!(machine.generate_one_time_keys().is_err());
|
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.
|
// limitations under the License.
|
||||||
|
|
||||||
// TODO remove this.
|
// TODO remove this.
|
||||||
|
mod error;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod machine;
|
mod machine;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
|
@ -151,6 +151,10 @@ impl Account {
|
||||||
pub fn mark_keys_as_published(&self) {
|
pub fn mark_keys_as_published(&self) {
|
||||||
self.inner.mark_keys_as_published();
|
self.inner.mark_keys_as_published();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sign(&self, string: &str) -> String {
|
||||||
|
self.inner.sign(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in New Issue