mirror of
https://github.com/fluencelabs/trust-graph-test
synced 2025-04-24 23:32:28 +00:00
533 lines
17 KiB
Rust
533 lines
17 KiB
Rust
/*
|
||
* 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 ed25519_dalek::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());
|
||
}
|
||
}
|