mirror of
https://github.com/fluencelabs/trust-graph
synced 2024-12-04 15:20:19 +00:00
INIT
This commit is contained in:
commit
f2afc13ea2
1533
Cargo.lock
generated
Normal file
1533
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "trust-graph"
|
||||
version = "0.2.0"
|
||||
authors = ["Fluence Labs"]
|
||||
edition = "2018"
|
||||
description = "trust graph"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
libp2p-core = { package = "fluence-fork-libp2p-core", version = "0.26.0" }
|
||||
serde = { version = "=1.0.118", features = ["derive"] }
|
||||
serde_json = "1.0.58"
|
||||
bs58 = "0.3.1"
|
||||
failure = "0.1.6"
|
||||
log = "0.4.11"
|
||||
ref-cast = "1.0.2"
|
||||
derivative = "2.1.1"
|
||||
ed25519-dalek = "1.0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
532
src/certificate.rs
Normal file
532
src/certificate.rs
Normal file
@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::ed25519::PublicKey;
|
||||
use crate::key_pair::KeyPair;
|
||||
use crate::trust::{Trust, TRUST_LEN};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Serialization format of a certificate.
|
||||
/// TODO
|
||||
const FORMAT: &[u8; 2] = &[0, 0];
|
||||
/// Serialization format version of a certificate.
|
||||
/// TODO
|
||||
const VERSION: &[u8; 4] = &[0, 0, 0, 0];
|
||||
|
||||
/// Chain of trusts started from self-signed root trust.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Certificate {
|
||||
pub chain: Vec<Trust>,
|
||||
}
|
||||
|
||||
impl Certificate {
|
||||
pub fn new_unverified(chain: Vec<Trust>) -> Self {
|
||||
Self { chain }
|
||||
}
|
||||
|
||||
/// Creates new certificate with root trust (self-signed public key) from a key pair.
|
||||
#[allow(dead_code)]
|
||||
pub fn issue_root(
|
||||
root_kp: &KeyPair,
|
||||
for_pk: PublicKey,
|
||||
expires_at: Duration,
|
||||
issued_at: Duration,
|
||||
) -> Self {
|
||||
let root_expiration = Duration::from_secs(u64::max_value());
|
||||
|
||||
let root_trust = Trust::create(root_kp, root_kp.public_key(), root_expiration, issued_at);
|
||||
|
||||
let trust = Trust::create(root_kp, for_pk, expires_at, issued_at);
|
||||
|
||||
let chain = vec![root_trust, trust];
|
||||
Self { chain }
|
||||
}
|
||||
|
||||
/// Adds a new trust into chain of trust in certificate.
|
||||
#[allow(dead_code)]
|
||||
pub fn issue(
|
||||
issued_by: &KeyPair,
|
||||
for_pk: PublicKey,
|
||||
extend_cert: &Certificate,
|
||||
expires_at: Duration,
|
||||
issued_at: Duration,
|
||||
cur_time: Duration,
|
||||
) -> Result<Self, String> {
|
||||
if expires_at.lt(&issued_at) {
|
||||
return Err("Expiration time should be greater than issued time.".to_string());
|
||||
}
|
||||
|
||||
// first, verify given certificate
|
||||
Certificate::verify(
|
||||
extend_cert,
|
||||
&[extend_cert.chain[0].issued_for.clone()],
|
||||
cur_time,
|
||||
)?;
|
||||
|
||||
let issued_by_pk = issued_by.public_key();
|
||||
|
||||
// check if `issued_by_pk` is allowed to issue a certificate (i.e., there’s a trust for it in a chain)
|
||||
let mut previous_trust_num: i32 = -1;
|
||||
for pk_id in 0..extend_cert.chain.len() {
|
||||
if extend_cert.chain[pk_id].issued_for == issued_by_pk {
|
||||
previous_trust_num = pk_id as i32;
|
||||
}
|
||||
}
|
||||
|
||||
if previous_trust_num == -1 {
|
||||
return Err("Your public key should be in certificate.".to_string());
|
||||
};
|
||||
|
||||
// splitting old chain to add new trust after given public key
|
||||
let mut new_chain = extend_cert
|
||||
.chain
|
||||
.split_at((previous_trust_num + 1) as usize)
|
||||
.0
|
||||
.to_vec();
|
||||
|
||||
let trust = Trust::create(issued_by, for_pk, expires_at, issued_at);
|
||||
|
||||
new_chain.push(trust);
|
||||
|
||||
Ok(Self { chain: new_chain })
|
||||
}
|
||||
|
||||
/// Verifies that a certificate is valid and you trust to this certificate.
|
||||
pub fn verify(
|
||||
cert: &Certificate,
|
||||
trusted_roots: &[PublicKey],
|
||||
cur_time: Duration,
|
||||
) -> Result<(), String> {
|
||||
let chain = &cert.chain;
|
||||
|
||||
if chain.is_empty() {
|
||||
return Err("The certificate must have at least 1 trust".to_string());
|
||||
}
|
||||
|
||||
// check root trust and its existence in trusted roots list
|
||||
let root = &chain[0];
|
||||
Trust::verify(root, &root.issued_for, cur_time)
|
||||
.map_err(|e| format!("Root trust did not pass verification: {}", e))?;
|
||||
if !trusted_roots.contains(&root.issued_for) {
|
||||
return Err("Certificate does not contain a trusted root.".to_string());
|
||||
}
|
||||
|
||||
// check if every element in a chain is not expired and has the correct signature
|
||||
for trust_id in (1..chain.len()).rev() {
|
||||
let trust = &chain[trust_id];
|
||||
|
||||
let trust_giver = &chain[trust_id - 1];
|
||||
|
||||
Trust::verify(trust, &trust_giver.issued_for, cur_time).map_err(|e| {
|
||||
format!(
|
||||
"Trust {} in chain did not pass verification: {}",
|
||||
trust_id, e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert certificate to byte format
|
||||
/// 2 format + 4 version + (64 signature + 32 public key + 8 expiration) * number of trusts
|
||||
#[allow(dead_code)]
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
let mut encoded =
|
||||
Vec::with_capacity(FORMAT.len() + VERSION.len() + TRUST_LEN * self.chain.len());
|
||||
encoded.extend_from_slice(FORMAT);
|
||||
encoded.extend_from_slice(VERSION);
|
||||
|
||||
for t in &self.chain {
|
||||
encoded.extend(t.encode());
|
||||
}
|
||||
|
||||
encoded
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn decode(arr: &[u8]) -> Result<Self, String> {
|
||||
let trusts_offset = arr.len() - 2 - 4;
|
||||
if trusts_offset % TRUST_LEN != 0 {
|
||||
return Err("Incorrect length of an array. Should be 2 bytes of a format, 4 bytes of a version and 104 bytes for each trust. ".to_string());
|
||||
}
|
||||
|
||||
let number_of_trusts = trusts_offset / TRUST_LEN;
|
||||
|
||||
if number_of_trusts < 2 {
|
||||
return Err("The certificate must have at least 2 trusts.".to_string());
|
||||
}
|
||||
|
||||
// TODO do match different formats and versions
|
||||
let _format = &arr[0..1];
|
||||
let _version = &arr[2..5];
|
||||
|
||||
let mut chain = Vec::with_capacity(number_of_trusts);
|
||||
|
||||
for i in 0..number_of_trusts {
|
||||
let from = i * TRUST_LEN + 6;
|
||||
let to = (i + 1) * TRUST_LEN + 6;
|
||||
let slice = &arr[from..to];
|
||||
let t = Trust::decode(slice)?;
|
||||
chain.push(t);
|
||||
}
|
||||
|
||||
Ok(Self { chain })
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Certificate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", bs58::encode(FORMAT).into_string())?;
|
||||
writeln!(f, "{}", bs58::encode(VERSION).into_string())?;
|
||||
for trust in self.chain.iter() {
|
||||
writeln!(f, "{}", trust.to_string())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Certificate {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let str_lines: Vec<&str> = s.lines().collect();
|
||||
|
||||
// TODO for future purposes
|
||||
let _format = str_lines[0];
|
||||
let _version = str_lines[1];
|
||||
|
||||
if (str_lines.len() - 2) % 4 != 0 {
|
||||
return Err(format!("Incorrect format of the certificate: {}", s));
|
||||
}
|
||||
|
||||
let num_of_trusts = (str_lines.len() - 2) / 4;
|
||||
let mut trusts = Vec::with_capacity(num_of_trusts);
|
||||
|
||||
for i in (2..str_lines.len()).step_by(4) {
|
||||
let trust = Trust::convert_from_strings(
|
||||
str_lines[i],
|
||||
str_lines[i + 1],
|
||||
str_lines[i + 2],
|
||||
str_lines[i + 3],
|
||||
)?;
|
||||
|
||||
trusts.push(trust);
|
||||
}
|
||||
|
||||
Ok(Self::new_unverified(trusts))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::misc::current_time;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub fn one_second() -> Duration {
|
||||
Duration::from_secs(1)
|
||||
}
|
||||
|
||||
pub fn one_minute() -> Duration {
|
||||
Duration::from_secs(60)
|
||||
}
|
||||
|
||||
pub fn one_year() -> Duration {
|
||||
Duration::from_secs(31_557_600)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_string_encoding_decoding() {
|
||||
let (_root_kp, second_kp, cert) = generate_root_cert();
|
||||
|
||||
let cur_time = current_time();
|
||||
|
||||
let third_kp = KeyPair::generate();
|
||||
|
||||
let new_cert = Certificate::issue(
|
||||
&second_kp,
|
||||
third_kp.key_pair.public(),
|
||||
&cert,
|
||||
cur_time.checked_add(one_second()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let serialized = new_cert.to_string();
|
||||
let deserialized = Certificate::from_str(&serialized);
|
||||
|
||||
assert!(deserialized.is_ok());
|
||||
let after_cert = deserialized.unwrap();
|
||||
assert_eq!(&new_cert.chain[0], &after_cert.chain[0]);
|
||||
assert_eq!(&new_cert, &after_cert);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_serialization_deserialization() {
|
||||
let (_root_kp, second_kp, cert) = generate_root_cert();
|
||||
|
||||
let cur_time = current_time();
|
||||
|
||||
let third_kp = KeyPair::generate();
|
||||
|
||||
let new_cert = Certificate::issue(
|
||||
&second_kp,
|
||||
third_kp.key_pair.public(),
|
||||
&cert,
|
||||
cur_time.checked_add(one_second()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let serialized = new_cert.encode();
|
||||
let deserialized = Certificate::decode(serialized.as_slice());
|
||||
|
||||
assert!(deserialized.is_ok());
|
||||
let after_cert = deserialized.unwrap();
|
||||
assert_eq!(&new_cert.chain[0], &after_cert.chain[0]);
|
||||
assert_eq!(&new_cert, &after_cert);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_small_chain() {
|
||||
let bad_cert = Certificate { chain: Vec::new() };
|
||||
|
||||
let check = Certificate::verify(&bad_cert, &[], current_time());
|
||||
assert!(check.is_err());
|
||||
}
|
||||
|
||||
fn generate_root_cert() -> (KeyPair, KeyPair, Certificate) {
|
||||
let root_kp = KeyPair::generate();
|
||||
let second_kp = KeyPair::generate();
|
||||
|
||||
let cur_time = current_time();
|
||||
|
||||
(
|
||||
root_kp.clone(),
|
||||
second_kp.clone(),
|
||||
Certificate::issue_root(
|
||||
&root_kp,
|
||||
second_kp.public_key(),
|
||||
cur_time.checked_add(one_year()).unwrap(),
|
||||
cur_time,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_issue_cert() {
|
||||
let (root_kp, second_kp, cert) = generate_root_cert();
|
||||
let trusted_roots = [root_kp.public_key()];
|
||||
|
||||
// we don't need nanos for serialization, etc
|
||||
let cur_time = Duration::from_secs(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as u64,
|
||||
);
|
||||
|
||||
let third_kp = KeyPair::generate();
|
||||
|
||||
let new_cert = Certificate::issue(
|
||||
&second_kp,
|
||||
third_kp.key_pair.public(),
|
||||
&cert,
|
||||
cur_time.checked_add(one_year()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
);
|
||||
assert_eq!(new_cert.is_ok(), true);
|
||||
let new_cert = new_cert.unwrap();
|
||||
|
||||
println!(
|
||||
"root_kp:\n\tprivate: {}\n\tpublic: {}",
|
||||
bs58::encode(root_kp.key_pair.secret()).into_string(),
|
||||
bs58::encode(&root_kp.key_pair.public().encode().to_vec()).into_string()
|
||||
);
|
||||
println!(
|
||||
"second_kp:\n\tprivate: {}\n\tpublic: {}",
|
||||
bs58::encode(second_kp.key_pair.secret()).into_string(),
|
||||
bs58::encode(&second_kp.key_pair.public().encode().to_vec()).into_string()
|
||||
);
|
||||
println!(
|
||||
"third_kp:\n\tprivate: {}\n\tpublic: {}",
|
||||
bs58::encode(third_kp.key_pair.secret()).into_string(),
|
||||
bs58::encode(&third_kp.key_pair.public().encode().to_vec()).into_string()
|
||||
);
|
||||
println!("cert is\n{}", new_cert.to_string());
|
||||
|
||||
assert_eq!(new_cert.chain.len(), 3);
|
||||
assert_eq!(new_cert.chain[0].issued_for, root_kp.public_key());
|
||||
assert_eq!(new_cert.chain[1].issued_for, second_kp.public_key());
|
||||
assert_eq!(new_cert.chain[2].issued_for, third_kp.public_key());
|
||||
assert!(Certificate::verify(&new_cert, &trusted_roots, cur_time).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cert_expiration() {
|
||||
let (root_kp, second_kp, cert) = generate_root_cert();
|
||||
let trusted_roots = [root_kp.public_key()];
|
||||
let cur_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let third_kp = KeyPair::generate();
|
||||
|
||||
let new_cert = Certificate::issue(
|
||||
&second_kp,
|
||||
third_kp.key_pair.public(),
|
||||
&cert,
|
||||
cur_time.checked_sub(one_second()).unwrap(),
|
||||
cur_time.checked_sub(one_minute()).unwrap(),
|
||||
cur_time,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(Certificate::verify(&new_cert, &trusted_roots, cur_time).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_issue_in_chain_tail() {
|
||||
let (root_kp, second_kp, cert) = generate_root_cert();
|
||||
let trusted_roots = [root_kp.public_key()];
|
||||
let cur_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let third_kp = KeyPair::generate();
|
||||
let fourth_kp = KeyPair::generate();
|
||||
|
||||
let new_cert = Certificate::issue(
|
||||
&second_kp,
|
||||
third_kp.key_pair.public(),
|
||||
&cert,
|
||||
cur_time.checked_add(one_second()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
)
|
||||
.unwrap();
|
||||
let new_cert = Certificate::issue(
|
||||
&third_kp,
|
||||
fourth_kp.key_pair.public(),
|
||||
&new_cert,
|
||||
cur_time.checked_add(one_second()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
);
|
||||
|
||||
assert_eq!(new_cert.is_ok(), true);
|
||||
let new_cert = new_cert.unwrap();
|
||||
|
||||
assert_eq!(new_cert.chain.len(), 4);
|
||||
assert_eq!(new_cert.chain[0].issued_for, root_kp.public_key());
|
||||
assert_eq!(new_cert.chain[1].issued_for, second_kp.public_key());
|
||||
assert_eq!(new_cert.chain[2].issued_for, third_kp.public_key());
|
||||
assert_eq!(new_cert.chain[3].issued_for, fourth_kp.public_key());
|
||||
assert!(Certificate::verify(&new_cert, &trusted_roots, cur_time).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_issue_in_chain_body() {
|
||||
let (root_kp, second_kp, cert) = generate_root_cert();
|
||||
let trusted_roots = [root_kp.public_key()];
|
||||
let cur_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let third_kp = KeyPair::generate();
|
||||
let fourth_kp = KeyPair::generate();
|
||||
|
||||
let new_cert = Certificate::issue(
|
||||
&second_kp,
|
||||
third_kp.key_pair.public(),
|
||||
&cert,
|
||||
cur_time.checked_add(one_second()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
)
|
||||
.unwrap();
|
||||
let new_cert = Certificate::issue(
|
||||
&second_kp,
|
||||
fourth_kp.key_pair.public(),
|
||||
&new_cert,
|
||||
cur_time.checked_add(one_second()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
);
|
||||
|
||||
assert_eq!(new_cert.is_ok(), true);
|
||||
let new_cert = new_cert.unwrap();
|
||||
|
||||
assert_eq!(new_cert.chain.len(), 3);
|
||||
assert_eq!(new_cert.chain[0].issued_for, root_kp.public_key());
|
||||
assert_eq!(new_cert.chain[1].issued_for, second_kp.public_key());
|
||||
assert_eq!(new_cert.chain[2].issued_for, fourth_kp.public_key());
|
||||
assert!(Certificate::verify(&new_cert, &trusted_roots, cur_time).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_cert_in_chain() {
|
||||
let (_root_kp, _second_kp, cert) = generate_root_cert();
|
||||
let cur_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let bad_kp = KeyPair::generate();
|
||||
let new_cert_bad = Certificate::issue(
|
||||
&bad_kp,
|
||||
bad_kp.key_pair.public(),
|
||||
&cert,
|
||||
cur_time.checked_add(one_second()).unwrap(),
|
||||
cur_time,
|
||||
cur_time,
|
||||
);
|
||||
assert_eq!(new_cert_bad.is_err(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_trusted_root_in_chain() {
|
||||
let (_root_kp, second_kp, cert) = generate_root_cert();
|
||||
let cur_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let trusted_roots = [second_kp.public_key()];
|
||||
assert!(Certificate::verify(&cert, &trusted_roots, cur_time).is_err());
|
||||
assert!(Certificate::verify(&cert, &[], cur_time).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forged_cert() {
|
||||
let (root_kp, _second_kp, cert) = generate_root_cert();
|
||||
let cur_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
let trusted_roots = [root_kp.public_key()];
|
||||
|
||||
// forged cert
|
||||
let mut bad_chain = cert.chain;
|
||||
bad_chain.remove(0);
|
||||
let bad_cert = Certificate { chain: bad_chain };
|
||||
|
||||
assert!(Certificate::verify(&bad_cert, &trusted_roots, cur_time).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_root_cert() {
|
||||
let (root_kp, second_kp, cert) = generate_root_cert();
|
||||
let cur_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
let trusted_roots = [root_kp.public_key()];
|
||||
|
||||
assert_eq!(cert.chain.len(), 2);
|
||||
assert_eq!(cert.chain[0].issued_for, root_kp.public_key());
|
||||
assert_eq!(cert.chain[1].issued_for, second_kp.public_key());
|
||||
assert!(Certificate::verify(&cert, &trusted_roots, cur_time).is_ok());
|
||||
}
|
||||
}
|
71
src/certificate_serde.rs
Normal file
71
src/certificate_serde.rs
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::Certificate;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
mod single {
|
||||
#![allow(dead_code)]
|
||||
use super::*;
|
||||
pub fn serialize<S>(value: &Certificate, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
value.to_string().serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Certificate, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = String::deserialize(deserializer)?;
|
||||
Certificate::from_str(&str)
|
||||
.map_err(|e| Error::custom(format!("certificate deserialization failed for {:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
pub mod vec {
|
||||
use super::*;
|
||||
use serde::ser::SerializeSeq;
|
||||
|
||||
pub fn serialize<S>(value: &[Certificate], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(value.len()))?;
|
||||
for e in value {
|
||||
let e = e.to_string();
|
||||
seq.serialize_element(&e)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Certificate>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v: Vec<String> = Vec::deserialize(deserializer)?;
|
||||
v.into_iter()
|
||||
.map(|e| {
|
||||
Certificate::from_str(&e).map_err(|e| {
|
||||
Error::custom(format!("certificate deserialization failed for {:?}", e))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
126
src/key_pair.rs
Normal file
126
src/key_pair.rs
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::ed25519::{Keypair as Libp2pKeyPair, PublicKey, SecretKey};
|
||||
use ed25519_dalek::SignatureError;
|
||||
use libp2p_core::identity::error::DecodingError;
|
||||
use std::fmt;
|
||||
|
||||
pub type Signature = Vec<u8>;
|
||||
|
||||
/// An Ed25519 keypair.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyPair {
|
||||
pub key_pair: Libp2pKeyPair,
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
/// Generate a new Ed25519 keypair.
|
||||
#[allow(dead_code)]
|
||||
pub fn generate() -> Self {
|
||||
let kp = Libp2pKeyPair::generate();
|
||||
kp.into()
|
||||
}
|
||||
|
||||
pub fn from_bytes(sk_bytes: impl AsMut<[u8]>) -> Result<Self, DecodingError> {
|
||||
let sk = SecretKey::from_bytes(sk_bytes)?;
|
||||
Ok(Libp2pKeyPair::from(sk).into())
|
||||
}
|
||||
|
||||
/// Encode the keypair into a byte array by concatenating the bytes
|
||||
/// of the secret scalar and the compressed public point/
|
||||
#[allow(dead_code)]
|
||||
pub fn encode(&self) -> [u8; 64] {
|
||||
self.key_pair.encode()
|
||||
}
|
||||
|
||||
/// Decode a keypair from the format produced by `encode`.
|
||||
#[allow(dead_code)]
|
||||
pub fn decode(kp: &[u8]) -> Result<KeyPair, SignatureError> {
|
||||
let kp = ed25519_dalek::Keypair::from_bytes(kp)?;
|
||||
Ok(Self {
|
||||
key_pair: kp.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the public key of this keypair.
|
||||
#[allow(dead_code)]
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.key_pair.public()
|
||||
}
|
||||
|
||||
/// Sign a message using the private key of this keypair.
|
||||
pub fn sign(&self, msg: &[u8]) -> Vec<u8> {
|
||||
self.key_pair.sign(msg)
|
||||
}
|
||||
|
||||
/// Verify the Ed25519 signature on a message using the public key.
|
||||
pub fn verify(pk: &PublicKey, msg: &[u8], signature: &[u8]) -> Result<(), String> {
|
||||
if pk.verify(msg, signature) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err("Signature is not valid.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Libp2pKeyPair> for KeyPair {
|
||||
fn from(kp: Libp2pKeyPair) -> Self {
|
||||
Self { key_pair: kp }
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement serde::Deserialize for KeyPair
|
||||
impl<'de> serde::Deserialize<'de> for KeyPair {
|
||||
fn deserialize<D>(deserializer: D) -> Result<KeyPair, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Error, Unexpected, Visitor};
|
||||
|
||||
struct KeyPairVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for KeyPairVisitor {
|
||||
type Value = KeyPair;
|
||||
|
||||
/// Error message stating what was expected
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("byte array or base58 string")
|
||||
}
|
||||
|
||||
/// Implement deserialization from base58 string
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
bs58::decode(s)
|
||||
.into_vec()
|
||||
.map_err(|_| Error::invalid_value(Unexpected::Str(s), &self))
|
||||
.and_then(|v| self.visit_bytes(v.as_slice()))
|
||||
}
|
||||
|
||||
/// Implement deserialization from bytes
|
||||
fn visit_bytes<E>(self, b: &[u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
KeyPair::decode(b).map_err(|_| Error::invalid_value(Unexpected::Bytes(b), &self))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(KeyPairVisitor)
|
||||
}
|
||||
}
|
46
src/lib.rs
Normal file
46
src/lib.rs
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
mod certificate;
|
||||
pub mod certificate_serde;
|
||||
mod key_pair;
|
||||
mod misc;
|
||||
mod public_key_hashable;
|
||||
mod revoke;
|
||||
mod trust;
|
||||
mod trust_graph;
|
||||
mod trust_node;
|
||||
|
||||
pub(crate) use libp2p_core::identity::ed25519;
|
||||
|
||||
pub use crate::certificate::Certificate;
|
||||
pub use crate::key_pair::KeyPair;
|
||||
pub use crate::misc::current_time;
|
||||
pub use crate::public_key_hashable::PublicKeyHashable;
|
||||
pub use crate::trust::Trust;
|
||||
pub use crate::trust_graph::TrustGraph;
|
10
src/misc/mod.rs
Normal file
10
src/misc/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub fn current_time() -> Duration {
|
||||
Duration::from_secs(
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as u64,
|
||||
)
|
||||
}
|
80
src/public_key_hashable.rs
Normal file
80
src/public_key_hashable.rs
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::ed25519::PublicKey;
|
||||
|
||||
use ref_cast::RefCast;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
/// Wrapper to use PublicKey in HashMap
|
||||
#[derive(PartialEq, Eq, Debug, Clone, RefCast, Deserialize)]
|
||||
#[repr(transparent)]
|
||||
pub struct PublicKeyHashable(PublicKey);
|
||||
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
impl Hash for PublicKeyHashable {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write(&self.0.encode());
|
||||
state.finish();
|
||||
}
|
||||
|
||||
fn hash_slice<H: Hasher>(data: &[Self], state: &mut H)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// TODO check for overflow
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(data.len() * 32);
|
||||
for d in data {
|
||||
bytes.extend_from_slice(&d.0.encode())
|
||||
}
|
||||
state.write(bytes.as_slice());
|
||||
state.finish();
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicKey> for PublicKeyHashable {
|
||||
fn from(pk: PublicKey) -> Self {
|
||||
Self(pk)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<PublicKey> for PublicKeyHashable {
|
||||
fn into(self) -> PublicKey {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<PublicKey> for PublicKeyHashable {
|
||||
fn as_ref(&self) -> &PublicKey {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<PublicKeyHashable> for PublicKey {
|
||||
fn as_ref(&self) -> &PublicKeyHashable {
|
||||
PublicKeyHashable::ref_cast(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PublicKeyHashable {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0.encode()).into_string())
|
||||
}
|
||||
}
|
121
src/revoke.rs
Normal file
121
src/revoke.rs
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::ed25519::PublicKey;
|
||||
use crate::key_pair::KeyPair;
|
||||
use crate::key_pair::Signature;
|
||||
use crate::trust::{EXPIRATION_LEN, PK_LEN};
|
||||
use std::time::Duration;
|
||||
|
||||
/// "A document" that cancels trust created before.
|
||||
/// TODO delete pk from Revoke (it is already in a trust node)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Revoke {
|
||||
/// who is revoked
|
||||
pub pk: PublicKey,
|
||||
/// date when revocation was created
|
||||
pub revoked_at: Duration,
|
||||
/// the issuer of this revocation
|
||||
pub revoked_by: PublicKey,
|
||||
/// proof of this revocation
|
||||
signature: Signature,
|
||||
}
|
||||
|
||||
impl Revoke {
|
||||
#[allow(dead_code)]
|
||||
fn new(
|
||||
pk: PublicKey,
|
||||
revoked_by: PublicKey,
|
||||
revoked_at: Duration,
|
||||
signature: Signature,
|
||||
) -> Self {
|
||||
Self {
|
||||
pk,
|
||||
revoked_at,
|
||||
revoked_by,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new revocation signed by a revoker.
|
||||
#[allow(dead_code)]
|
||||
pub fn create(revoker: &KeyPair, to_revoke: PublicKey, revoked_at: Duration) -> Self {
|
||||
let msg = Revoke::signature_bytes(&to_revoke, revoked_at);
|
||||
let signature = revoker.sign(&msg);
|
||||
|
||||
Revoke::new(to_revoke, revoker.public_key(), revoked_at, signature)
|
||||
}
|
||||
|
||||
fn signature_bytes(pk: &PublicKey, revoked_at: Duration) -> Vec<u8> {
|
||||
let mut msg = Vec::with_capacity(PK_LEN + EXPIRATION_LEN);
|
||||
msg.extend_from_slice(&pk.encode());
|
||||
msg.extend_from_slice(&(revoked_at.as_secs() as u64).to_le_bytes());
|
||||
|
||||
msg
|
||||
}
|
||||
|
||||
/// Verifies that revocation is cryptographically correct.
|
||||
pub fn verify(revoke: &Revoke) -> Result<(), String> {
|
||||
let msg = Revoke::signature_bytes(&revoke.pk, revoke.revoked_at);
|
||||
|
||||
if !revoke
|
||||
.revoked_by
|
||||
.verify(msg.as_slice(), revoke.signature.as_slice())
|
||||
{
|
||||
return Err("Revoke has incorrect signature.".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gen_revoke_and_validate() {
|
||||
let revoker = KeyPair::generate();
|
||||
let to_revoke = KeyPair::generate();
|
||||
|
||||
let duration = Duration::new(100, 0);
|
||||
|
||||
let revoke = Revoke::create(&revoker, to_revoke.public_key(), duration);
|
||||
|
||||
assert_eq!(Revoke::verify(&revoke).is_ok(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_corrupted_revoke() {
|
||||
let revoker = KeyPair::generate();
|
||||
let to_revoke = KeyPair::generate();
|
||||
|
||||
let duration = Duration::new(100, 0);
|
||||
|
||||
let revoke = Revoke::create(&revoker, to_revoke.public_key(), duration);
|
||||
|
||||
let duration2 = Duration::new(95, 0);
|
||||
let corrupted_revoke = Revoke::new(
|
||||
to_revoke.public_key(),
|
||||
revoker.public_key(),
|
||||
duration2,
|
||||
revoke.signature,
|
||||
);
|
||||
|
||||
assert_eq!(Revoke::verify(&corrupted_revoke).is_ok(), false);
|
||||
}
|
||||
}
|
277
src/trust.rs
Normal file
277
src/trust.rs
Normal file
@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::ed25519::PublicKey;
|
||||
use crate::key_pair::{KeyPair, Signature};
|
||||
use derivative::Derivative;
|
||||
use std::convert::TryInto;
|
||||
use std::time::Duration;
|
||||
|
||||
pub const SIG_LEN: usize = 64;
|
||||
pub const PK_LEN: usize = 32;
|
||||
pub const EXPIRATION_LEN: usize = 8;
|
||||
pub const ISSUED_LEN: usize = 8;
|
||||
pub const METADATA_LEN: usize = PK_LEN + EXPIRATION_LEN + ISSUED_LEN;
|
||||
pub const TRUST_LEN: usize = SIG_LEN + PK_LEN + EXPIRATION_LEN + ISSUED_LEN;
|
||||
|
||||
/// One element in chain of trust in a certificate.
|
||||
/// TODO delete pk from Trust (it is already in a trust node)
|
||||
#[derive(Clone, PartialEq, Derivative, Eq)]
|
||||
#[derivative(Debug)]
|
||||
pub struct Trust {
|
||||
/// For whom this certificate is issued
|
||||
#[derivative(Debug(format_with = "show_pubkey"))]
|
||||
pub issued_for: PublicKey,
|
||||
/// Expiration date of a trust.
|
||||
pub expires_at: Duration,
|
||||
/// Signature of a previous trust in a chain.
|
||||
/// Signature is self-signed if it is a root trust.
|
||||
#[derivative(Debug(format_with = "show_sig"))]
|
||||
pub signature: Signature,
|
||||
/// Creation time of a trust
|
||||
pub issued_at: Duration,
|
||||
}
|
||||
|
||||
fn show_pubkey(key: &PublicKey, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{}", bs58::encode(key.encode()).into_string())
|
||||
}
|
||||
|
||||
fn show_sig(sig: &[u8], f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{}", bs58::encode(sig).into_string())
|
||||
}
|
||||
|
||||
impl Trust {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(
|
||||
issued_for: PublicKey,
|
||||
expires_at: Duration,
|
||||
issued_at: Duration,
|
||||
signature: Signature,
|
||||
) -> Self {
|
||||
Self {
|
||||
issued_for,
|
||||
expires_at,
|
||||
issued_at,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
issued_by: &KeyPair,
|
||||
issued_for: PublicKey,
|
||||
expires_at: Duration,
|
||||
issued_at: Duration,
|
||||
) -> Self {
|
||||
let msg = Self::metadata_bytes(&issued_for, expires_at, issued_at);
|
||||
|
||||
let signature = issued_by.sign(&msg);
|
||||
|
||||
Self {
|
||||
issued_for,
|
||||
expires_at,
|
||||
signature,
|
||||
issued_at,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies that authorization is cryptographically correct.
|
||||
pub fn verify(trust: &Trust, issued_by: &PublicKey, cur_time: Duration) -> Result<(), String> {
|
||||
if trust.expires_at < cur_time {
|
||||
return Err("Trust in chain is expired.".to_string());
|
||||
}
|
||||
|
||||
let msg: &[u8] =
|
||||
&Self::metadata_bytes(&trust.issued_for, trust.expires_at, trust.issued_at);
|
||||
|
||||
KeyPair::verify(issued_by, msg, trust.signature.as_slice())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn metadata_bytes(pk: &PublicKey, expires_at: Duration, issued_at: Duration) -> [u8; 48] {
|
||||
let pk_encoded = pk.encode();
|
||||
let expires_at_encoded: [u8; EXPIRATION_LEN] = (expires_at.as_secs() as u64).to_le_bytes();
|
||||
let issued_at_encoded: [u8; ISSUED_LEN] = (issued_at.as_secs() as u64).to_le_bytes();
|
||||
let mut metadata = [0; METADATA_LEN];
|
||||
|
||||
metadata[..PK_LEN].clone_from_slice(&pk_encoded[..PK_LEN]);
|
||||
metadata[PK_LEN..PK_LEN + EXPIRATION_LEN]
|
||||
.clone_from_slice(&expires_at_encoded[0..EXPIRATION_LEN]);
|
||||
metadata[PK_LEN + EXPIRATION_LEN..METADATA_LEN]
|
||||
.clone_from_slice(&issued_at_encoded[0..ISSUED_LEN]);
|
||||
|
||||
metadata
|
||||
}
|
||||
|
||||
/// Encode the trust into a byte array
|
||||
#[allow(dead_code)]
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(TRUST_LEN);
|
||||
vec.extend_from_slice(&self.issued_for.encode());
|
||||
vec.extend_from_slice(&self.signature.as_slice());
|
||||
vec.extend_from_slice(&(self.expires_at.as_secs() as u64).to_le_bytes());
|
||||
vec.extend_from_slice(&(self.issued_at.as_secs() as u64).to_le_bytes());
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
/// Decode a trust from a byte array as produced by `encode`.
|
||||
#[allow(dead_code)]
|
||||
pub fn decode(arr: &[u8]) -> Result<Self, String> {
|
||||
if arr.len() != TRUST_LEN {
|
||||
return Err(
|
||||
format!("Trust length should be 104: public key(32) + signature(64) + expiration date(8), was: {}", arr.len()),
|
||||
);
|
||||
}
|
||||
|
||||
let pk = PublicKey::decode(&arr[0..PK_LEN]).map_err(|err| err.to_string())?;
|
||||
let signature = &arr[PK_LEN..PK_LEN + SIG_LEN];
|
||||
|
||||
let expiration_bytes = &arr[PK_LEN + SIG_LEN..PK_LEN + SIG_LEN + EXPIRATION_LEN];
|
||||
let expiration_date = u64::from_le_bytes(expiration_bytes.try_into().unwrap());
|
||||
let expiration_date = Duration::from_secs(expiration_date);
|
||||
|
||||
let issued_bytes = &arr[PK_LEN + SIG_LEN + EXPIRATION_LEN..TRUST_LEN];
|
||||
let issued_date = u64::from_le_bytes(issued_bytes.try_into().unwrap());
|
||||
let issued_date = Duration::from_secs(issued_date);
|
||||
|
||||
Ok(Self {
|
||||
issued_for: pk,
|
||||
signature: signature.to_vec(),
|
||||
expires_at: expiration_date,
|
||||
issued_at: issued_date,
|
||||
})
|
||||
}
|
||||
|
||||
fn bs58_str_to_vec(str: &str, field: &str) -> Result<Vec<u8>, String> {
|
||||
bs58::decode(str).into_vec().map_err(|e| {
|
||||
format!(
|
||||
"Cannot decode `{}` from base58 format in the trust '{}': {}",
|
||||
field, str, e
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn str_to_duration(str: &str, field: &str) -> Result<Duration, String> {
|
||||
let secs = str.parse().map_err(|e| {
|
||||
format!(
|
||||
"Cannot parse `{}` field in the trust '{}': {}",
|
||||
field, str, e
|
||||
)
|
||||
})?;
|
||||
Ok(Duration::from_secs(secs))
|
||||
}
|
||||
|
||||
pub fn convert_from_strings(
|
||||
issued_for: &str,
|
||||
signature: &str,
|
||||
expires_at: &str,
|
||||
issued_at: &str,
|
||||
) -> Result<Self, String> {
|
||||
// PublicKey
|
||||
let issued_for_bytes = Self::bs58_str_to_vec(issued_for, "issued_for")?;
|
||||
let issued_for = PublicKey::decode(issued_for_bytes.as_slice()).map_err(|e| {
|
||||
format!(
|
||||
"Cannot decode the public key: {} in the trust '{}'",
|
||||
issued_for, e
|
||||
)
|
||||
})?;
|
||||
|
||||
// 64 bytes signature
|
||||
let signature = Self::bs58_str_to_vec(signature, "signature")?;
|
||||
|
||||
// Duration
|
||||
let expires_at = Self::str_to_duration(expires_at, "expires_at")?;
|
||||
|
||||
// Duration
|
||||
let issued_at = Self::str_to_duration(issued_at, "issued_at")?;
|
||||
|
||||
Ok(Trust::new(issued_for, expires_at, issued_at, signature))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Trust {
|
||||
fn to_string(&self) -> String {
|
||||
let issued_for = bs58::encode(self.issued_for.encode()).into_string();
|
||||
let signature = bs58::encode(self.signature.as_slice()).into_string();
|
||||
let expires_at = (self.expires_at.as_secs() as u64).to_string();
|
||||
let issued_at = (self.issued_at.as_secs() as u64).to_string();
|
||||
|
||||
format!(
|
||||
"{}\n{}\n{}\n{}",
|
||||
issued_for, signature, expires_at, issued_at
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gen_revoke_and_validate() {
|
||||
let truster = KeyPair::generate();
|
||||
let trusted = KeyPair::generate();
|
||||
|
||||
let current = Duration::new(100, 0);
|
||||
let duration = Duration::new(1000, 0);
|
||||
let issued_at = Duration::new(10, 0);
|
||||
|
||||
let trust = Trust::create(&truster, trusted.public_key(), duration, issued_at);
|
||||
|
||||
assert_eq!(
|
||||
Trust::verify(&trust, &truster.public_key(), current).is_ok(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_corrupted_revoke() {
|
||||
let truster = KeyPair::generate();
|
||||
let trusted = KeyPair::generate();
|
||||
|
||||
let current = Duration::new(1000, 0);
|
||||
let issued_at = Duration::new(10, 0);
|
||||
|
||||
let trust = Trust::create(&truster, trusted.public_key(), current, issued_at);
|
||||
|
||||
let corrupted_duration = Duration::new(1234, 0);
|
||||
let corrupted_trust = Trust::new(
|
||||
trust.issued_for,
|
||||
trust.expires_at,
|
||||
corrupted_duration,
|
||||
trust.signature,
|
||||
);
|
||||
|
||||
assert!(Trust::verify(&corrupted_trust, &truster.public_key(), current).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_decode() {
|
||||
let truster = KeyPair::generate();
|
||||
let trusted = KeyPair::generate();
|
||||
|
||||
let current = Duration::new(1000, 0);
|
||||
let issued_at = Duration::new(10, 0);
|
||||
|
||||
let trust = Trust::create(&truster, trusted.public_key(), current, issued_at);
|
||||
|
||||
let encoded = trust.encode();
|
||||
let decoded = Trust::decode(encoded.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(trust, decoded);
|
||||
}
|
||||
}
|
618
src/trust_graph.rs
Normal file
618
src/trust_graph.rs
Normal file
@ -0,0 +1,618 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::certificate::Certificate;
|
||||
use crate::ed25519::PublicKey;
|
||||
use crate::public_key_hashable::PublicKeyHashable;
|
||||
use crate::revoke::Revoke;
|
||||
use crate::trust::Trust;
|
||||
use crate::trust_node::{Auth, TrustNode};
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::hash_map::Entry::Occupied;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::fmt::Debug;
|
||||
use std::time::Duration;
|
||||
|
||||
/// for simplicity, we store `n` where Weight = 1/n^2
|
||||
type Weight = u32;
|
||||
|
||||
/// Graph to efficiently calculate weights of certificates and get chains of certificates.
|
||||
/// TODO serialization/deserialization
|
||||
/// TODO export a certificate from graph
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TrustGraph {
|
||||
// TODO abstract this into a trait with key access methods
|
||||
// TODO: add docs on fields
|
||||
nodes: HashMap<PublicKeyHashable, TrustNode>,
|
||||
root_weights: HashMap<PublicKeyHashable, Weight>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TrustGraph {
|
||||
pub fn new(root_weights: Vec<(PublicKey, Weight)>) -> Self {
|
||||
Self {
|
||||
nodes: HashMap::new(),
|
||||
root_weights: root_weights
|
||||
.into_iter()
|
||||
.map(|(k, w)| (k.into(), w))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert new root weights
|
||||
pub fn add_root_weights<P: Into<PublicKeyHashable>>(&mut self, weights: Vec<(P, Weight)>) {
|
||||
self.root_weights
|
||||
.extend(weights.into_iter().map(|(k, w)| (k.into(), w)))
|
||||
}
|
||||
|
||||
/// Get trust by public key
|
||||
pub fn get(&self, pk: PublicKey) -> Option<&TrustNode> {
|
||||
self.nodes.get(&pk.into())
|
||||
}
|
||||
|
||||
// TODO: remove cur_time from api, leave it for tests only
|
||||
/// Certificate is a chain of trusts, add this chain to graph
|
||||
pub fn add<C>(&mut self, cert: C, cur_time: Duration) -> Result<(), String>
|
||||
where
|
||||
C: Borrow<Certificate>,
|
||||
{
|
||||
let roots: Vec<PublicKey> = self.root_weights.keys().cloned().map(Into::into).collect();
|
||||
// Check that certificate is valid and converges to one of the known roots
|
||||
Certificate::verify(cert.borrow(), roots.as_slice(), cur_time)?;
|
||||
|
||||
let mut chain = cert.borrow().chain.iter();
|
||||
let root_trust = chain.next().ok_or("empty chain")?;
|
||||
let root_pk: PublicKeyHashable = root_trust.issued_for.clone().into();
|
||||
|
||||
// Insert new TrustNode for this root_pk if there wasn't one
|
||||
if self.nodes.get_mut(&root_pk).is_none() {
|
||||
let mut trust_node = TrustNode::new(root_trust.issued_for.clone(), cur_time);
|
||||
let root_auth = Auth {
|
||||
trust: root_trust.clone(),
|
||||
issued_by: root_trust.issued_for.clone(),
|
||||
};
|
||||
trust_node.update_auth(root_auth);
|
||||
self.nodes.insert(root_pk, trust_node);
|
||||
}
|
||||
|
||||
// Insert remaining trusts to the graph
|
||||
let mut previous_trust = root_trust;
|
||||
for trust in chain {
|
||||
let pk = trust.issued_for.clone().into();
|
||||
|
||||
let auth = Auth {
|
||||
trust: trust.clone(),
|
||||
issued_by: previous_trust.issued_for.clone(),
|
||||
};
|
||||
|
||||
match self.nodes.get_mut(&pk) {
|
||||
Some(trust_node) => {
|
||||
trust_node.update_auth(auth);
|
||||
}
|
||||
None => {
|
||||
let mut trust_node = TrustNode::new(root_trust.issued_for.clone(), cur_time);
|
||||
trust_node.update_auth(auth);
|
||||
self.nodes.insert(pk, trust_node);
|
||||
}
|
||||
}
|
||||
|
||||
previous_trust = trust;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the maximum weight of trust for one public key.
|
||||
pub fn weight<P>(&self, pk: P) -> Option<Weight>
|
||||
where
|
||||
P: Borrow<PublicKey>,
|
||||
{
|
||||
if let Some(weight) = self.root_weights.get(pk.borrow().as_ref()) {
|
||||
return Some(*weight);
|
||||
}
|
||||
|
||||
let roots: Vec<PublicKey> = self
|
||||
.root_weights
|
||||
.keys()
|
||||
.map(|pk| pk.clone().into())
|
||||
.collect();
|
||||
|
||||
// get all possible certificates from the given public key to all roots in the graph
|
||||
let certs = self.get_all_certs(pk, roots.as_slice());
|
||||
self.certificates_weight(certs)
|
||||
}
|
||||
|
||||
/// Calculate weight from given certificates
|
||||
/// Returns None if there is no such public key
|
||||
/// or some trust between this key and a root key is revoked.
|
||||
/// TODO handle non-direct revocations
|
||||
pub fn certificates_weight<C, I>(&self, certs: I) -> Option<Weight>
|
||||
where
|
||||
C: Borrow<Certificate>,
|
||||
I: IntoIterator<Item = C>,
|
||||
{
|
||||
let mut certs = certs.into_iter().peekable();
|
||||
// if there are no certificates for the given public key, there is no info about this public key
|
||||
// or some elements of possible certificate chains was revoked
|
||||
certs.peek()?;
|
||||
|
||||
let mut weight = std::u32::MAX;
|
||||
|
||||
for cert in certs {
|
||||
let cert = cert.borrow();
|
||||
|
||||
let root_weight = *self
|
||||
.root_weights
|
||||
.get(cert.chain.first()?.issued_for.as_ref())
|
||||
// This panic shouldn't happen // TODO: why?
|
||||
.expect("first trust in chain must be in root_weights");
|
||||
|
||||
// certificate weight = root weight + 1 * every other element in the chain
|
||||
// (except root, so the formula is `root weight + chain length - 1`)
|
||||
weight = std::cmp::min(weight, root_weight + cert.chain.len() as u32 - 1)
|
||||
}
|
||||
|
||||
Some(weight)
|
||||
}
|
||||
|
||||
/// BF search for all converging paths (chains) in the graph
|
||||
/// TODO could be optimized with closure, that will calculate the weight on the fly
|
||||
/// TODO or store auths to build certificates
|
||||
fn bf_search_paths(
|
||||
&self,
|
||||
node: &TrustNode,
|
||||
roots: HashSet<&PublicKeyHashable>,
|
||||
) -> Vec<Vec<Auth>> {
|
||||
// queue to collect all chains in the trust graph (each chain is a path in the trust graph)
|
||||
let mut chains_queue: VecDeque<Vec<Auth>> = VecDeque::new();
|
||||
|
||||
let node_auths: Vec<Auth> = node.authorizations().cloned().collect();
|
||||
// put all auth in the queue as the first possible paths through the graph
|
||||
for auth in node_auths {
|
||||
chains_queue.push_back(vec![auth]);
|
||||
}
|
||||
|
||||
// List of all chains that converge (terminate) to known roots
|
||||
let mut terminated_chains: Vec<Vec<Auth>> = Vec::new();
|
||||
|
||||
while !chains_queue.is_empty() {
|
||||
let cur_chain = chains_queue
|
||||
.pop_front()
|
||||
.expect("`chains_queue` always has at least one element");
|
||||
|
||||
let last = cur_chain
|
||||
.last()
|
||||
.expect("`cur_chain` always has at least one element");
|
||||
|
||||
let auths: Vec<Auth> = self
|
||||
.nodes
|
||||
.get(&last.issued_by.clone().into())
|
||||
.expect(
|
||||
"there cannot be paths without any nodes after adding verified certificates",
|
||||
)
|
||||
.authorizations()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
for auth in auths {
|
||||
// if there is auth, that we not visited in the current chain, copy chain and append this auth
|
||||
if !cur_chain
|
||||
.iter()
|
||||
.any(|a| a.trust.issued_for == auth.issued_by)
|
||||
{
|
||||
let mut new_chain = cur_chain.clone();
|
||||
new_chain.push(auth);
|
||||
chains_queue.push_back(new_chain);
|
||||
}
|
||||
}
|
||||
|
||||
// to be considered a valid chain, the chain must:
|
||||
// - end with a self-signed trust
|
||||
// - that trust must converge to one of the root weights
|
||||
// - there should be more than 1 trust in the chain
|
||||
let self_signed = last.issued_by == last.trust.issued_for;
|
||||
let converges_to_root = roots.contains(last.issued_by.as_ref());
|
||||
|
||||
if self_signed && converges_to_root && cur_chain.len() > 1 {
|
||||
terminated_chains.push(cur_chain);
|
||||
}
|
||||
}
|
||||
|
||||
terminated_chains
|
||||
}
|
||||
|
||||
// TODO: remove `roots` argument from api, leave it for tests and internal usage only
|
||||
/// Get all possible certificates where `issued_for` will be the last element of the chain
|
||||
/// and one of the destinations is the root of this chain.
|
||||
pub fn get_all_certs<P>(&self, issued_for: P, roots: &[PublicKey]) -> Vec<Certificate>
|
||||
where
|
||||
P: Borrow<PublicKey>,
|
||||
{
|
||||
// get all auths (edges) for issued public key
|
||||
let issued_for_node = self.nodes.get(issued_for.borrow().as_ref());
|
||||
|
||||
let roots = roots.iter().map(|pk| pk.as_ref());
|
||||
let roots = self.root_weights.keys().chain(roots).collect();
|
||||
|
||||
match issued_for_node {
|
||||
Some(node) => self
|
||||
.bf_search_paths(node, roots)
|
||||
.iter()
|
||||
.map(|auths| {
|
||||
// TODO: can avoid cloning here by returning &Certificate
|
||||
let trusts: Vec<Trust> =
|
||||
auths.iter().map(|auth| auth.trust.clone()).rev().collect();
|
||||
Certificate::new_unverified(trusts)
|
||||
})
|
||||
.filter(|c| {
|
||||
// Certificate with one trust means nothing, gotta be a bug. Checking for it here.
|
||||
debug_assert!(
|
||||
c.chain.len() > 1,
|
||||
"certificate with chain of len 1 arose: {:#?}",
|
||||
c
|
||||
);
|
||||
c.chain.len() > 1
|
||||
})
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark public key as revoked.
|
||||
pub fn revoke(&mut self, revoke: Revoke) -> Result<(), String> {
|
||||
Revoke::verify(&revoke)?;
|
||||
|
||||
let pk: PublicKeyHashable = revoke.pk.clone().into();
|
||||
|
||||
match self.nodes.entry(pk) {
|
||||
Occupied(mut entry) => {
|
||||
entry.get_mut().update_revoke(revoke);
|
||||
Ok(())
|
||||
}
|
||||
Entry::Vacant(_) => Err("There is no trust with such PublicKey".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check information about new certificates and about revoked certificates.
|
||||
/// Do it once per some time
|
||||
// TODO
|
||||
fn maintain() {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::key_pair::KeyPair;
|
||||
use crate::misc::current_time;
|
||||
use failure::_core::time::Duration;
|
||||
|
||||
pub fn one_minute() -> Duration {
|
||||
Duration::new(60, 0)
|
||||
}
|
||||
|
||||
fn generate_root_cert() -> (KeyPair, KeyPair, Certificate) {
|
||||
let root_kp = KeyPair::generate();
|
||||
let second_kp = KeyPair::generate();
|
||||
|
||||
let cur_time = current_time();
|
||||
|
||||
(
|
||||
root_kp.clone(),
|
||||
second_kp.clone(),
|
||||
Certificate::issue_root(
|
||||
&root_kp,
|
||||
second_kp.public_key(),
|
||||
cur_time.checked_add(one_minute()).unwrap(),
|
||||
cur_time,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_cert_with(
|
||||
len: usize,
|
||||
// Map of index to keypair. These key pairs will be used in trust chains at the given indexes
|
||||
keys: HashMap<usize, KeyPair>,
|
||||
expires_at: Duration,
|
||||
issued_at: Duration,
|
||||
) -> (Vec<KeyPair>, Certificate) {
|
||||
assert!(len > 2);
|
||||
|
||||
let root_kp = KeyPair::generate();
|
||||
let second_kp = KeyPair::generate();
|
||||
|
||||
let mut cert =
|
||||
Certificate::issue_root(&root_kp, second_kp.public_key(), expires_at, issued_at);
|
||||
|
||||
let mut key_pairs = vec![root_kp, second_kp];
|
||||
|
||||
for idx in 2..len {
|
||||
let kp = keys.get(&idx).unwrap_or(&KeyPair::generate()).clone();
|
||||
let previous_kp = &key_pairs[idx - 1];
|
||||
cert = Certificate::issue(
|
||||
&previous_kp,
|
||||
kp.public_key(),
|
||||
&cert,
|
||||
expires_at,
|
||||
// TODO: why `issued_at = issued_at - 60 seconds`?
|
||||
issued_at.checked_sub(Duration::from_secs(60)).unwrap(),
|
||||
current_time(),
|
||||
)
|
||||
.unwrap();
|
||||
key_pairs.push(kp);
|
||||
}
|
||||
|
||||
(key_pairs, cert)
|
||||
}
|
||||
|
||||
fn generate_cert_with_len(
|
||||
len: usize,
|
||||
keys: HashMap<usize, KeyPair>,
|
||||
) -> (Vec<KeyPair>, Certificate) {
|
||||
let cur_time = current_time();
|
||||
let far_future = cur_time.checked_add(one_minute()).unwrap();
|
||||
|
||||
generate_cert_with(len, keys, far_future, cur_time)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_cert_without_trusted_root() {
|
||||
let (_, _, cert) = generate_root_cert();
|
||||
|
||||
let cur_time = current_time();
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
let addition = graph.add(cert, cur_time);
|
||||
assert_eq!(addition.is_ok(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_cert() {
|
||||
let (root, _, cert) = generate_root_cert();
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
graph.root_weights.insert(root.key_pair.public().into(), 0);
|
||||
|
||||
let addition = graph.add(cert, current_time());
|
||||
assert_eq!(addition.is_ok(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_certs_with_same_trusts_and_different_expirations() {
|
||||
let cur_time = current_time();
|
||||
let far_future = cur_time + Duration::from_secs(10);
|
||||
let far_far_future = cur_time + Duration::from_secs(900);
|
||||
let key_pair1 = KeyPair::generate();
|
||||
let key_pair2 = KeyPair::generate();
|
||||
|
||||
// Use key_pair1 and key_pair2 for 5th and 6th trust in the cert chain
|
||||
let mut chain_keys = HashMap::new();
|
||||
chain_keys.insert(5, key_pair1.clone());
|
||||
chain_keys.insert(6, key_pair2.clone());
|
||||
|
||||
let (key_pairs1, cert1) = generate_cert_with(10, chain_keys, far_future * 2, far_future);
|
||||
|
||||
// Use key_pair1 and key_pair2 for 7th and 8th trust in the cert chain
|
||||
let mut chain_keys = HashMap::new();
|
||||
chain_keys.insert(7, key_pair1.clone());
|
||||
chain_keys.insert(8, key_pair2.clone());
|
||||
|
||||
let (key_pairs2, cert2) =
|
||||
generate_cert_with(10, chain_keys, far_far_future * 2, far_far_future);
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
let root1_pk = key_pairs1[0].public_key();
|
||||
let root2_pk = key_pairs2[0].public_key();
|
||||
graph.root_weights.insert(root1_pk.into(), 1);
|
||||
graph.root_weights.insert(root2_pk.into(), 0);
|
||||
graph.add(cert1, cur_time).unwrap();
|
||||
|
||||
let node2 = graph.get(key_pair2.public_key()).unwrap();
|
||||
let auth_by_kp1 = node2
|
||||
.authorizations()
|
||||
.find(|a| a.issued_by == key_pair1.public_key())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(auth_by_kp1.trust.expires_at, far_future * 2);
|
||||
|
||||
graph.add(cert2, cur_time).unwrap();
|
||||
|
||||
let node2 = graph.get(key_pair2.public_key()).unwrap();
|
||||
let auth_by_kp1 = node2
|
||||
.authorizations()
|
||||
.find(|a| a.issued_by == key_pair1.public_key())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(auth_by_kp1.trust.expires_at, far_far_future * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_cert_in_graph() {
|
||||
let (key_pairs, cert1) = generate_cert_with_len(10, HashMap::new());
|
||||
let last_trust = cert1.chain[9].clone();
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
|
||||
let root_pk = key_pairs[0].public_key();
|
||||
graph.root_weights.insert(root_pk.into(), 1);
|
||||
|
||||
graph.add(cert1, current_time()).unwrap();
|
||||
|
||||
let w1 = graph.weight(key_pairs[0].public_key()).unwrap();
|
||||
assert_eq!(w1, 1);
|
||||
|
||||
let w2 = graph.weight(key_pairs[1].public_key()).unwrap();
|
||||
assert_eq!(w2, 2);
|
||||
|
||||
let w3 = graph.weight(key_pairs[9].public_key()).unwrap();
|
||||
assert_eq!(w3, 10);
|
||||
|
||||
let node = graph.get(key_pairs[9].public_key()).unwrap();
|
||||
let auths: Vec<&Auth> = node.authorizations().collect();
|
||||
|
||||
assert_eq!(auths.len(), 1);
|
||||
assert_eq!(auths[0].trust, last_trust);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cycles_in_graph() {
|
||||
let key_pair1 = KeyPair::generate();
|
||||
let key_pair2 = KeyPair::generate();
|
||||
let key_pair3 = KeyPair::generate();
|
||||
|
||||
let mut chain_keys = HashMap::new();
|
||||
chain_keys.insert(3, key_pair1.clone());
|
||||
chain_keys.insert(5, key_pair2.clone());
|
||||
chain_keys.insert(7, key_pair3.clone());
|
||||
|
||||
let (key_pairs1, cert1) = generate_cert_with_len(10, chain_keys);
|
||||
|
||||
let mut chain_keys = HashMap::new();
|
||||
chain_keys.insert(7, key_pair1.clone());
|
||||
chain_keys.insert(6, key_pair2.clone());
|
||||
chain_keys.insert(5, key_pair3.clone());
|
||||
|
||||
let (key_pairs2, cert2) = generate_cert_with_len(10, chain_keys);
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
let root1_pk = key_pairs1[0].public_key();
|
||||
let root2_pk = key_pairs2[0].public_key();
|
||||
graph.root_weights.insert(root1_pk.into(), 1);
|
||||
graph.root_weights.insert(root2_pk.into(), 0);
|
||||
|
||||
let last_pk1 = cert1.chain[9].issued_for.clone();
|
||||
let last_pk2 = cert2.chain[9].issued_for.clone();
|
||||
|
||||
graph.add(cert1, current_time()).unwrap();
|
||||
graph.add(cert2, current_time()).unwrap();
|
||||
|
||||
let revoke1 = Revoke::create(&key_pairs1[3], key_pairs1[4].public_key(), current_time());
|
||||
graph.revoke(revoke1).unwrap();
|
||||
let revoke2 = Revoke::create(&key_pairs2[5], key_pairs2[6].public_key(), current_time());
|
||||
graph.revoke(revoke2).unwrap();
|
||||
|
||||
let w1 = graph.weight(key_pair1.public_key()).unwrap();
|
||||
// all upper trusts are revoked for this public key
|
||||
let w2 = graph.weight(key_pair2.public_key());
|
||||
let w3 = graph.weight(key_pair3.public_key()).unwrap();
|
||||
let w_last1 = graph.weight(last_pk1).unwrap();
|
||||
let w_last2 = graph.weight(last_pk2).unwrap();
|
||||
|
||||
assert_eq!(w1, 4);
|
||||
assert_eq!(w2.is_none(), true);
|
||||
assert_eq!(w3, 5);
|
||||
assert_eq!(w_last1, 7);
|
||||
assert_eq!(w_last2, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_one_cert() {
|
||||
let (key_pairs, cert) = generate_cert_with_len(5, HashMap::new());
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
let root1_pk = key_pairs[0].public_key();
|
||||
graph.root_weights.insert(root1_pk.clone().into(), 1);
|
||||
|
||||
graph.add(cert.clone(), current_time()).unwrap();
|
||||
|
||||
let certs = graph.get_all_certs(key_pairs.last().unwrap().public_key(), &[root1_pk]);
|
||||
|
||||
assert_eq!(certs.len(), 1);
|
||||
assert_eq!(certs[0], cert);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chain_from_root_to_another_root() {
|
||||
let (_, cert) = generate_cert_with_len(6, HashMap::new());
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
// add first and last trusts as roots
|
||||
graph
|
||||
.root_weights
|
||||
.insert(cert.chain[0].clone().issued_for.into(), 1);
|
||||
graph
|
||||
.root_weights
|
||||
.insert(cert.chain[3].clone().issued_for.into(), 1);
|
||||
graph
|
||||
.root_weights
|
||||
.insert(cert.chain[5].clone().issued_for.into(), 1);
|
||||
|
||||
graph.add(cert.clone(), current_time()).unwrap();
|
||||
|
||||
let t = cert.chain[5].clone();
|
||||
let certs = graph.get_all_certs(t.issued_for, &[]);
|
||||
|
||||
assert_eq!(certs.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_certs() {
|
||||
let key_pair1 = KeyPair::generate();
|
||||
let key_pair2 = KeyPair::generate();
|
||||
let key_pair3 = KeyPair::generate();
|
||||
|
||||
let mut chain_keys = HashMap::new();
|
||||
chain_keys.insert(2, key_pair1.clone());
|
||||
chain_keys.insert(3, key_pair2.clone());
|
||||
chain_keys.insert(4, key_pair3.clone());
|
||||
|
||||
let (key_pairs1, cert1) = generate_cert_with_len(5, chain_keys);
|
||||
|
||||
let mut chain_keys = HashMap::new();
|
||||
chain_keys.insert(4, key_pair1.clone());
|
||||
chain_keys.insert(3, key_pair2.clone());
|
||||
chain_keys.insert(2, key_pair3.clone());
|
||||
|
||||
let (key_pairs2, cert2) = generate_cert_with_len(5, chain_keys);
|
||||
|
||||
let mut chain_keys = HashMap::new();
|
||||
chain_keys.insert(3, key_pair1.clone());
|
||||
chain_keys.insert(4, key_pair2.clone());
|
||||
chain_keys.insert(2, key_pair3.clone());
|
||||
|
||||
let (key_pairs3, cert3) = generate_cert_with_len(5, chain_keys);
|
||||
|
||||
let mut graph = TrustGraph::default();
|
||||
let root1_pk = key_pairs1[0].public_key();
|
||||
let root2_pk = key_pairs2[0].public_key();
|
||||
let root3_pk = key_pairs3[0].public_key();
|
||||
graph.root_weights.insert(root1_pk.clone().into(), 1);
|
||||
graph.root_weights.insert(root2_pk.clone().into(), 0);
|
||||
graph.root_weights.insert(root3_pk.clone().into(), 0);
|
||||
|
||||
graph.add(cert1, current_time()).unwrap();
|
||||
graph.add(cert2, current_time()).unwrap();
|
||||
graph.add(cert3, current_time()).unwrap();
|
||||
|
||||
let roots_values = [root1_pk, root2_pk, root3_pk];
|
||||
|
||||
let certs1 = graph.get_all_certs(key_pair1.public_key(), &roots_values);
|
||||
let lenghts1: Vec<usize> = certs1.iter().map(|c| c.chain.len()).collect();
|
||||
let check_lenghts1: Vec<usize> = vec![3, 4, 4, 5, 5];
|
||||
assert_eq!(lenghts1, check_lenghts1);
|
||||
|
||||
let certs2 = graph.get_all_certs(key_pair2.public_key(), &roots_values);
|
||||
let lenghts2: Vec<usize> = certs2.iter().map(|c| c.chain.len()).collect();
|
||||
let check_lenghts2: Vec<usize> = vec![4, 4, 4, 5, 5];
|
||||
assert_eq!(lenghts2, check_lenghts2);
|
||||
|
||||
let certs3 = graph.get_all_certs(key_pair3.public_key(), &roots_values);
|
||||
let lenghts3: Vec<usize> = certs3.iter().map(|c| c.chain.len()).collect();
|
||||
let check_lenghts3: Vec<usize> = vec![3, 3, 5];
|
||||
assert_eq!(lenghts3, check_lenghts3);
|
||||
}
|
||||
}
|
202
src/trust_node.rs
Normal file
202
src/trust_node.rs
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* 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 crate::ed25519::PublicKey;
|
||||
use crate::public_key_hashable::PublicKeyHashable;
|
||||
use crate::revoke::Revoke;
|
||||
use crate::trust::Trust;
|
||||
use failure::_core::time::Duration;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum TrustRelation {
|
||||
Auth(Auth),
|
||||
Revoke(Revoke),
|
||||
}
|
||||
|
||||
impl TrustRelation {
|
||||
/// Returns timestamp of when this relation was created
|
||||
pub fn issued_at(&self) -> Duration {
|
||||
match self {
|
||||
TrustRelation::Auth(auth) => auth.trust.issued_at,
|
||||
TrustRelation::Revoke(revoke) => revoke.revoked_at,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns public key of the creator of this relation
|
||||
pub fn issued_by(&self) -> &PublicKey {
|
||||
match self {
|
||||
TrustRelation::Auth(auth) => &auth.issued_by,
|
||||
TrustRelation::Revoke(revoke) => &revoke.revoked_by,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents who give a certificate
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Auth {
|
||||
/// proof of this authorization
|
||||
pub trust: Trust,
|
||||
/// the issuer of this authorization
|
||||
pub issued_by: PublicKey,
|
||||
}
|
||||
|
||||
/// An element of trust graph that store relations (trust or revoke)
|
||||
/// that given by some owners of public keys.
|
||||
#[derive(Debug)]
|
||||
pub struct TrustNode {
|
||||
/// identity key of this element
|
||||
pub pk: PublicKey,
|
||||
|
||||
/// one public key could be authorized or revoked by multiple certificates
|
||||
trust_relations: HashMap<PublicKeyHashable, TrustRelation>,
|
||||
|
||||
/// for maintain
|
||||
pub verified_at: Duration,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TrustNode {
|
||||
pub fn new(pk: PublicKey, verified_at: Duration) -> Self {
|
||||
Self {
|
||||
pk,
|
||||
trust_relations: HashMap::new(),
|
||||
verified_at,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_auth(&self, pk: PublicKey) -> Option<Auth> {
|
||||
match self.trust_relations.get(&pk.into()) {
|
||||
Some(TrustRelation::Auth(auth)) => Some(auth.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_revoke(&self, pk: PublicKey) -> Option<Revoke> {
|
||||
match self.trust_relations.get(&pk.into()) {
|
||||
Some(TrustRelation::Revoke(rev)) => Some(rev.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn authorizations(&self) -> impl Iterator<Item = &Auth> + '_ {
|
||||
self.trust_relations.values().filter_map(|tr| {
|
||||
if let TrustRelation::Auth(auth) = tr {
|
||||
Some(auth)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn revocations(&self) -> impl Iterator<Item = &Revoke> + '_ {
|
||||
self.trust_relations.values().filter_map(|tr| {
|
||||
if let TrustRelation::Revoke(revoke) = tr {
|
||||
Some(revoke)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds authorization. If the trust node already has this authorization,
|
||||
/// add auth with later expiration date.
|
||||
pub fn update_auth(&mut self, auth: Auth) {
|
||||
self.update_relation(TrustRelation::Auth(auth));
|
||||
}
|
||||
|
||||
// insert new trust relation, ignore if there is another one with same public key
|
||||
fn insert(&mut self, pk: PublicKeyHashable, tr: TrustRelation) {
|
||||
self.trust_relations.insert(pk, tr);
|
||||
}
|
||||
|
||||
fn update_relation(&mut self, relation: TrustRelation) {
|
||||
let issued_by = relation.issued_by().as_ref();
|
||||
|
||||
match self.trust_relations.get(issued_by) {
|
||||
Some(TrustRelation::Auth(auth)) => {
|
||||
if auth.trust.issued_at < relation.issued_at() {
|
||||
self.insert(issued_by.clone(), relation)
|
||||
}
|
||||
}
|
||||
Some(TrustRelation::Revoke(existed_revoke)) => {
|
||||
if existed_revoke.revoked_at < relation.issued_at() {
|
||||
self.insert(issued_by.clone(), relation)
|
||||
}
|
||||
}
|
||||
None => self.insert(issued_by.clone(), relation),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn update_revoke(&mut self, revoke: Revoke) {
|
||||
self.update_relation(TrustRelation::Revoke(revoke));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::key_pair::KeyPair;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_auth_and_revoke_trust_node() {
|
||||
let kp = KeyPair::generate();
|
||||
|
||||
let now = Duration::new(50, 0);
|
||||
let past = Duration::new(5, 0);
|
||||
let future = Duration::new(500, 0);
|
||||
|
||||
let mut trust_node = TrustNode {
|
||||
pk: kp.public_key(),
|
||||
trust_relations: HashMap::new(),
|
||||
verified_at: now,
|
||||
};
|
||||
|
||||
let truster = KeyPair::generate();
|
||||
|
||||
let revoke = Revoke::create(&truster, kp.public_key(), now);
|
||||
|
||||
trust_node.update_revoke(revoke);
|
||||
|
||||
assert!(trust_node.get_revoke(truster.public_key()).is_some());
|
||||
|
||||
let old_trust = Trust::create(&truster, kp.public_key(), Duration::new(60, 0), past);
|
||||
|
||||
let old_auth = Auth {
|
||||
trust: old_trust,
|
||||
issued_by: truster.public_key(),
|
||||
};
|
||||
|
||||
trust_node.update_auth(old_auth);
|
||||
|
||||
assert!(trust_node.get_revoke(truster.public_key()).is_some());
|
||||
assert!(trust_node.get_auth(truster.public_key()).is_none());
|
||||
|
||||
let trust = Trust::create(&truster, kp.public_key(), Duration::new(60, 0), future);
|
||||
let auth = Auth {
|
||||
trust,
|
||||
issued_by: truster.public_key(),
|
||||
};
|
||||
|
||||
trust_node.update_auth(auth);
|
||||
|
||||
assert!(trust_node.get_auth(truster.public_key()).is_some());
|
||||
assert!(trust_node.get_revoke(truster.public_key()).is_none());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user