Skip to content

Commit

Permalink
parent 4e7fc9f
Browse files Browse the repository at this point in the history
author Arjen van Veen <[email protected]> 1701187280 +0100
committer Arjen van Veen <[email protected]> 1717744754 +0200

refactor for readability WIP

clean up comments and duplicates

clean up, add some comments

cargo fmt

clippy fix

fmt

assert on tests

address pr comments

refactor handle_response to return a validated_response, submit parsing and decryption errors under errors

support creating a trust_anchor_registry from pem strings

Fix x5chain encoding.

X5Chain decoding fixes and version checking

Improve reader validation code.

- Also add a CLI tool for validating issuer certificates.

Fix public key parsing

Feat/reader auth cn (#79)

* rebase onto feat/mdoc-auth

* rebase and use mdoc-auth functions

* wip experiment with cert building

* small clean up

* Fix inconsistency. (#78)

* validated request improvements

---------

Co-authored-by: Jacob <[email protected]>

remove duplicate code

clippy fix
  • Loading branch information
justAnIdentity committed Jun 7, 2024
1 parent 4e7fc9f commit b1ec197
Show file tree
Hide file tree
Showing 25 changed files with 1,784 additions and 1,256 deletions.
14 changes: 9 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ homepage = "https://github.com/spruceid/isomdl"
repository = "https://github.com/spruceid/isomdl"
license = "Apache-2.0"
exclude = ["test/"]


[[bin]]
name = "isomdl-utils"
path = "src/bin/utils.rs"

[dependencies]
anyhow = "1.0"
ecdsa = { version = "0.16.0", features = ["serde"] }
ecdsa = { version = "0.16.0", features = ["serde","verifying"] }
p256 = { version = "0.13.0", features = ["serde", "ecdh"] }
p384 = { version = "0.13.0", features = ["serde", "ecdh"] }
rand = { version = "0.8.5", features = ["getrandom"] }
Expand All @@ -35,15 +39,15 @@ async-signature = "0.3.0"
#tracing = "0.1"
base64 = "0.13"
pem-rfc7468 = "0.7.0"
x509-cert = {version = "0.2.3", features = ["std"]}
const-oid = "0.9.2"
x509-cert = { version = "0.2.4", features = ["pem", "builder"] }
ssi-jwk = { version = "0.1" }
isomdl-macros = { version = "0.1.0", path = "macros" }
clap = { version = "4", features = ["derive"] }
clap-stdin = "0.2.1"
const-oid = "0.9.2"
der = { version = "0.7", features = ["std", "derive", "alloc"] }
hex = "0.4.3"
asn1-rs = { version = "0.5.2", features = ["bits"]}
asn1-rs = { version = "0.5.2", features = ["bits"] }

[dependencies.cose-rs]
git = "https://github.com/spruceid/cose-rs"
Expand Down
84 changes: 84 additions & 0 deletions src/bin/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::{collections::BTreeMap, fs::File, io::Read, path::PathBuf};

use anyhow::{Context, Error, Ok};
use clap::Parser;
use clap_stdin::MaybeStdin;
use isomdl::presentation::{device::Document, Stringify};

mod x509;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[command(subcommand)]
action: Action,
}

#[derive(Debug, clap::Subcommand)]
enum Action {
/// Print the namespaces and element identifiers used in an mDL.
GetNamespaces {
/// Base64 encoded mDL in the format used in the issuance module of this crate.
mdl: MaybeStdin<String>,
},
/// Validate a document signer cert against a possible root certificate.
ValidateCerts {
/// Validation rule set.
rules: RuleSet,
/// Path to PEM-encoded document signer cert.
ds: PathBuf,
/// Path to PEM-encoded IACA root cert.
root: PathBuf,
},
}

#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum RuleSet {
Iaca,
Aamva,
NamesOnly,
}

fn main() -> Result<(), Error> {
match Args::parse().action {
Action::GetNamespaces { mdl } => print_namespaces(mdl.to_string()),
Action::ValidateCerts { rules, ds, root } => validate_certs(rules, ds, root),
}
}

fn print_namespaces(mdl: String) -> Result<(), Error> {
let claims = Document::parse(mdl)
.context("could not parse mdl")?
.namespaces
.into_inner()
.into_iter()
.map(|(ns, inner)| (ns, inner.into_inner().into_keys().collect()))
.collect::<BTreeMap<String, Vec<String>>>();
println!("{}", serde_json::to_string_pretty(&claims)?);
Ok(())
}

fn validate_certs(rules: RuleSet, ds: PathBuf, root: PathBuf) -> Result<(), Error> {
let mut ds_bytes = vec![];
File::open(ds)?.read_to_end(&mut ds_bytes)?;
let mut root_bytes = vec![];
File::open(root)?.read_to_end(&mut root_bytes)?;
let validation_errors = x509::validate(rules, &ds_bytes, &root_bytes)?;
if validation_errors.is_empty() {
println!("Validated!");
} else {
println!(
"Validation errors:\n{}",
serde_json::to_string_pretty(&validation_errors)?
)
}
Ok(())
}

#[cfg(test)]
mod test {
#[test]
fn print_namespaces() {
super::print_namespaces(include_str!("../../test/stringified-mdl.txt").to_string()).unwrap()
}
}
41 changes: 41 additions & 0 deletions src/bin/x509/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use anyhow::anyhow;
use isomdl::definitions::x509::{
error::Error as X509Error,
trust_anchor::{RuleSetType, TrustAnchor, TrustAnchorRegistry, ValidationRuleSet},
x5chain::X509,
X5Chain,
};

use crate::RuleSet;

pub fn validate(
rules: RuleSet,
signer: &[u8],
root: &[u8],
) -> Result<Vec<X509Error>, anyhow::Error> {
let root_bytes = pem_rfc7468::decode_vec(root)
.map_err(|e| anyhow!("unable to parse pem: {}", e))?
.1;

let ruleset = ValidationRuleSet {
distinguished_names: vec!["2.5.4.6".to_string(), "2.5.4.8".to_string()],
typ: match rules {
RuleSet::Iaca => RuleSetType::IACA,
RuleSet::Aamva => RuleSetType::AAMVA,
RuleSet::NamesOnly => RuleSetType::NamesOnly,
},
};

let trust_anchor = TrustAnchor::Custom(X509 { bytes: root_bytes }, ruleset);
let trust_anchor_registry = TrustAnchorRegistry {
certificates: vec![trust_anchor],
};
let bytes = pem_rfc7468::decode_vec(signer)
.map_err(|e| anyhow!("unable to parse pem: {}", e))?
.1;
let x5chain_cbor: serde_cbor::Value = serde_cbor::Value::Bytes(bytes);

let x5chain = X5Chain::from_cbor(x5chain_cbor)?;

Ok(x5chain.validate(Some(trust_anchor_registry)))
}
2 changes: 1 addition & 1 deletion src/definitions/device_engagement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl TryFrom<CborValue> for DeviceEngagement {
if let CborValue::Map(mut map) = v {
let device_engagement_version = map.remove(&CborValue::Integer(0));
if let Some(CborValue::Text(v)) = device_engagement_version {
if v != "1.0" {
if !v.starts_with("1.") {
return Err(Error::UnsupportedVersion);
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/definitions/device_engagement/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde_cbor::Error as SerdeCborError;
/// Errors that can occur when deserialising a DeviceEngagement.
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("Expected isomdl version 1.0")]
#[error("Expected isomdl major version 1")]
UnsupportedVersion,
#[error("Unsupported device retrieval method")]
UnsupportedDRM,
Expand Down
4 changes: 4 additions & 0 deletions src/definitions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ pub mod mso;
pub mod namespaces;
pub mod session;
pub mod traits;
pub mod validated_request;
pub mod validated_response;
pub mod validity_info;
pub mod x509;

pub use device_engagement::{
BleOptions, DeviceEngagement, DeviceRetrievalMethod, NfcOptions, Security, WifiOptions,
Expand All @@ -22,4 +25,5 @@ pub use device_signed::{DeviceAuth, DeviceSigned};
pub use issuer_signed::{IssuerSigned, IssuerSignedItem};
pub use mso::{DigestAlgorithm, DigestId, DigestIds, Mso};
pub use session::{SessionData, SessionEstablishment, SessionTranscript180135};
pub use validated_response::{Status, ValidatedResponse, ValidationErrors};
pub use validity_info::ValidityInfo;
18 changes: 18 additions & 0 deletions src/definitions/validated_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::{definitions::ValidationErrors, presentation::device::RequestedItems};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Default)]
pub struct ValidatedRequest {
pub items_requests: RequestedItems,
pub common_name: Option<String>,
pub reader_authentication: Status,
pub errors: ValidationErrors,
}

#[derive(Serialize, Deserialize, Default)]
pub enum Status {
#[default]
Unchecked,
Invalid,
Valid,
}
23 changes: 23 additions & 0 deletions src/definitions/validated_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;

#[derive(Serialize, Deserialize, Default)]
pub struct ValidatedResponse {
pub response: BTreeMap<String, Value>,
pub decryption: Status,
pub parsing: Status,
pub issuer_authentication: Status,
pub device_authentication: Status,
pub errors: ValidationErrors,
}

pub type ValidationErrors = BTreeMap<String, serde_json::Value>;

#[derive(Serialize, Deserialize, Default)]
pub enum Status {
#[default]
Unchecked,
Invalid,
Valid,
}
63 changes: 63 additions & 0 deletions src/definitions/x509/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::definitions::device_key::cose_key::Error as CoseError;
use crate::definitions::helpers::non_empty_vec;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, thiserror::Error)]
pub enum Error {
#[error("Error occurred while validating x509 certificate: {0}")]
ValidationError(String),
#[error("Error occurred while decoding a x509 certificate: {0}")]
DecodingError(String),
#[error("Error decoding cbor")]
CborDecodingError,
#[error("Error decoding json")]
JsonError,
}

impl From<serde_cbor::Error> for Error {
fn from(_: serde_cbor::Error) -> Self {
Error::CborDecodingError
}
}

impl From<serde_json::Error> for Error {
fn from(_: serde_json::Error) -> Self {
Error::JsonError
}
}

impl From<x509_cert::der::Error> for Error {
fn from(value: x509_cert::der::Error) -> Self {
Error::ValidationError(value.to_string())
}
}

impl From<p256::ecdsa::Error> for Error {
fn from(value: p256::ecdsa::Error) -> Self {
Error::ValidationError(value.to_string())
}
}

impl From<x509_cert::spki::Error> for Error {
fn from(value: x509_cert::spki::Error) -> Self {
Error::ValidationError(value.to_string())
}
}

impl From<CoseError> for Error {
fn from(value: CoseError) -> Self {
Error::ValidationError(value.to_string())
}
}

impl From<non_empty_vec::Error> for Error {
fn from(value: non_empty_vec::Error) -> Self {
Error::ValidationError(value.to_string())
}
}

impl From<asn1_rs::Error> for Error {
fn from(value: asn1_rs::Error) -> Self {
Error::ValidationError(value.to_string())
}
}
Loading

0 comments on commit b1ec197

Please sign in to comment.