Skip to content

Commit

Permalink
Changes to support toradex uptane/tuf schema
Browse files Browse the repository at this point in the history
  • Loading branch information
simao committed Jun 22, 2023
1 parent f6b7053 commit dc16cf3
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 27 deletions.
58 changes: 54 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions olpc-cjson/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,17 @@ impl Formatter for CanonicalFormatter {
}

// Only quotes and backslashes are escaped in canonical JSON.
// TRX: Our cjson implementation is in fact valid json and therefore allows literal \n (0x5c6c)
// on a json string, following the json spec.
fn write_char_escape<W: Write + ?Sized>(
&mut self,
writer: &mut W,
char_escape: CharEscape,
) -> Result<()> {
match char_escape {
CharEscape::Quote | CharEscape::ReverseSolidus => {
CharEscape::Quote | CharEscape::ReverseSolidus | CharEscape::LineFeed => {
self.writer(writer).write_all(b"\\")?;
}
},
_ => {}
}
self.writer(writer).write_all(&[match char_escape {
Expand All @@ -204,7 +206,7 @@ impl Formatter for CanonicalFormatter {
CharEscape::Solidus => b'/',
CharEscape::Backspace => b'\x08',
CharEscape::FormFeed => b'\x0c',
CharEscape::LineFeed => b'\n',
CharEscape::LineFeed => b'n',// TRX
CharEscape::CarriageReturn => b'\r',
CharEscape::Tab => b'\t',
CharEscape::AsciiControl(byte) => byte,
Expand Down
2 changes: 2 additions & 0 deletions tough/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ keywords = ["tuf", "update", "repository"]
edition = "2018"

[dependencies]
base64 = "0.21.2"
chrono = { version = "0.4", default-features = false, features = ["std", "alloc", "serde", "clock"] }
dyn-clone = "1"
globset = { version = "0.4" }
Expand All @@ -24,6 +25,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_plain = "1"
snafu = "0.7"
spki = { version = "0.7.2", features = ["alloc", "pem"] }
tempfile = "3"
untrusted = "0.7"
url = "2"
Expand Down
2 changes: 1 addition & 1 deletion tough/src/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl RepositoryEditor {
for (roletype, rolekeys) in &root.signed.roles {
if rolekeys.threshold.get() > rolekeys.keyids.len() as u64 {
return Err(error::Error::UnstableRoot {
role: *roletype,
role: roletype,
threshold: rolekeys.threshold.get(),
actual: rolekeys.keyids.len(),
});
Expand Down
128 changes: 128 additions & 0 deletions tough/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub use crate::transport::{
use chrono::{DateTime, Utc};
use log::warn;
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use schema::RemoteSessions;
use snafu::{ensure, OptionExt, ResultExt};
use std::collections::HashMap;
use std::fs::create_dir_all;
Expand Down Expand Up @@ -195,6 +196,11 @@ impl<R: Read> RepositoryLoader<R> {
Repository::load(self)
}

/// Load and verify Uptane repository metadata.
pub fn load_uptane(self) -> Result<UptaneRepository> {
UptaneRepository::load(self)
}

/// Set the transport. If no transport has been set, [`DefaultTransport`] will be used.
#[must_use]
pub fn transport<T: Transport + Send + Sync + 'static>(mut self, transport: T) -> Self {
Expand Down Expand Up @@ -310,7 +316,71 @@ pub struct Repository {
expiration_enforcement: ExpirationEnforcement,
}

/// An Uptane repository
///
/// You can create a `Repository` using a [`RepositoryLoader`].
#[derive(Debug, Clone)]
pub struct UptaneRepository {
root: Signed<Root>,
remote_sessions: std::result::Result<Signed<crate::schema::RemoteSessions>, String>,
}

impl UptaneRepository {
fn load<R: Read>(loader: RepositoryLoader<R>) -> Result<UptaneRepository> {
let datastore = Datastore::new(loader.datastore)?;
let transport = loader
.transport
.unwrap_or_else(|| Box::new(DefaultTransport::new()));
let limits = loader.limits.unwrap_or_default();
let expiration_enforcement = loader.expiration_enforcement.unwrap_or_default();
let metadata_base_url = parse_url(loader.metadata_base_url)?;

// 0. Load the trusted root metadata file + 1. Update the root metadata file
let root = load_root(
transport.as_ref(),
loader.root,
&datastore,
limits.max_root_size,
limits.max_root_updates,
&metadata_base_url,
expiration_enforcement,
)?;

let remote_sessions = match root.signed.roles.get(&RoleType::RemoteSessions) {
Some(_) => load_remote_sessions(
transport.as_ref(),
&root,
&datastore,
limits.max_timestamp_size,
&metadata_base_url,
expiration_enforcement
).map_err(|err|
format!("{}", err)
),
None =>
Err("remote-sessions not set in root.json".to_string())
};

Ok(Self {
root,
remote_sessions,
})
}

/// Returns a reference to the signed root
pub fn root(&self) -> &Signed<Root> {
&self.root
}

/// Returns a reference to the signed remote sessions
pub fn remote_sessions(&self) -> std::result::Result<&Signed<RemoteSessions>, String> {
self.remote_sessions.as_ref().map_err(|err|err.to_owned())
}

}

impl Repository {

/// Load and verify TUF repository metadata using a [`RepositoryLoader`] for the settings.
fn load<R: Read>(loader: RepositoryLoader<R>) -> Result<Self> {
let datastore = Datastore::new(loader.datastore)?;
Expand Down Expand Up @@ -1211,6 +1281,64 @@ fn load_delegations(
Ok(())
}

fn load_remote_sessions(
transport: &dyn Transport,
root: &Signed<Root>,
datastore: &Datastore,
max_remote_sessions_size: u64,
metadata_base_url: &Url,
expiration_enforcement: ExpirationEnforcement,
) -> Result<Signed<RemoteSessions>> {
let path = "remote-sessions.json";
let reader = fetch_max_size(
transport,
metadata_base_url.join(path).context(error::JoinUrlSnafu {
path,
url: metadata_base_url.clone(),
})?,
max_remote_sessions_size,
"max_timestamp_size argument",
)?;


let remote_sessions: Signed<RemoteSessions> =
serde_json::from_reader(reader).context(error::ParseMetadataSnafu {
role: RoleType::RemoteSessions,
})?;

root.signed
.verify_role(&remote_sessions)
.context(error::VerifyMetadataSnafu {
role: RoleType::RemoteSessions,
})?;

// 2.2. Check for a rollback attack.
if let Some(Ok(old_remote_sessions)) = datastore
.reader("remote-sessions.json")?
.map(serde_json::from_reader::<_, Signed<RemoteSessions>>)
{
if root.signed.verify_role(&old_remote_sessions).is_ok() {
ensure!(
old_remote_sessions.signed.version <= old_remote_sessions.signed.version,
error::OlderMetadataSnafu {
role: RoleType::Timestamp,
current_version: old_remote_sessions.signed.version,
new_version: old_remote_sessions.signed.version
}
);
}
}

// TUF v1.0.16, 5.3.3. Check for a freeze attack
if expiration_enforcement == ExpirationEnforcement::Safe {
check_expired(datastore, &remote_sessions.signed)?;
}

datastore.create("remote-sessions.json", &remote_sessions)?;

Ok(remote_sessions)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
19 changes: 19 additions & 0 deletions tough/src/schema/decoded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ pub trait Encode {
fn encode(b: &[u8]) -> String;
}


/// [`Decode`]/[`Encode`] implementation for base64-encoded strings.
#[derive(Debug, Clone, Copy)]
pub struct Base64;

use base64::prelude::{Engine as _, BASE64_STANDARD};

impl Decode for Base64 {
fn decode(s: &str) -> Result<Vec<u8>, Error> {
BASE64_STANDARD.decode(s).context(error::Base64DecodeSnafu)
}
}

impl Encode for Base64 {
fn encode(b: &[u8]) -> String {
BASE64_STANDARD.encode(b)
}
}

/// [`Decode`]/[`Encode`] implementation for hex-encoded strings.
#[derive(Debug, Clone, Copy)]
pub struct Hex;
Expand Down
7 changes: 7 additions & 0 deletions tough/src/schema/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ pub enum Error {
backtrace: Backtrace,
},

/// Failed to decode a base64-encoded string.
#[snafu(display("Invalid base64 string: {}", source))]
Base64Decode {
source: base64::DecodeError,
backtrace: Backtrace,
},

/// The library failed to serialize an object to JSON.
#[snafu(display("Failed to serialize {} to JSON: {}", what, source))]
JsonSerialization {
Expand Down
Loading

0 comments on commit dc16cf3

Please sign in to comment.