Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CRL handling #77

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
10867e6
mdoc auth and iaca certificate validation
justAnIdentity Nov 24, 2023
c52d24d
refactor for readability WIP
justAnIdentity Nov 28, 2023
37c7557
clean up comments and duplicates
justAnIdentity Nov 29, 2023
9afdde4
clean up, add some comments
justAnIdentity Nov 29, 2023
6dbe575
cargo fmt
justAnIdentity Nov 29, 2023
9a274a1
clippy fix
justAnIdentity Nov 29, 2023
81647ed
fmt
justAnIdentity Nov 30, 2023
e8c7b1e
clippy fix
justAnIdentity Nov 30, 2023
362caf1
assert on tests
justAnIdentity Dec 1, 2023
2d45b12
Add CRL handling
tristanmiller-spruceid Dec 1, 2023
b3cd197
address pr comments
justAnIdentity Dec 3, 2023
8f69a94
refactor handle_response to return a validated_response, submit parsi…
justAnIdentity Dec 5, 2023
2cfa41d
support creating a trust_anchor_registry from pem strings
justAnIdentity Dec 5, 2023
811415d
Errors using thiserror
tristanmiller-spruceid Dec 6, 2023
01b762d
Allow seperation of CRL from which cert is being checked
tristanmiller-spruceid Dec 6, 2023
d87fbd0
Document all public interfaces
tristanmiller-spruceid Dec 6, 2023
92f58aa
Takes an array of cert_lists since that's what is fetched
tristanmiller-spruceid Dec 6, 2023
3b40f1b
Update comment
tristanmiller-spruceid Dec 6, 2023
2f89cdf
Fix x5chain encoding.
cobward Dec 6, 2023
adf41c3
X5Chain decoding fixes and version checking
cobward Dec 11, 2023
cda4e35
Improve reader validation code.
cobward Dec 12, 2023
479c8c0
Fix public key parsing
cobward Dec 12, 2023
9a3e3aa
Convert Error::FetchCrl for thiserror #from
tristanmiller-spruceid Dec 20, 2023
abf844b
Merge branch 'feat/mdoc-auth' into feat/mdoc-auth-crl
tristanmiller-spruceid Jan 3, 2024
7cbf2fc
Change root_cert to crl_signing_cert
tristanmiller-spruceid Jan 10, 2024
cb6d766
Remove commented out code
tristanmiller-spruceid Jan 10, 2024
3b0b901
find_reason_code returns Option instead of Result
tristanmiller-spruceid Jan 11, 2024
4e7fc9f
mdoc auth and iaca certificate validation
justAnIdentity Nov 24, 2023
b1ec197
parent 4e7fc9f0f11991114ac197b2d374c82ac7db5253
justAnIdentity Nov 28, 2023
300ab3e
wip: resolve merge conflicts with main
Ryanmtate Nov 16, 2024
8085446
Update src/definitions/x509/extensions.rs
Ryanmtate Nov 16, 2024
be54eb2
resolve merge conflicts with mdoc-auth branch
Ryanmtate Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ 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"] }
Expand All @@ -35,12 +39,16 @@ async-signature = "0.3.0"
#tracing = "0.1"
base64 = "0.13"
pem-rfc7468 = "0.7.0"
x509-cert = { version = "0.1.1", features = ["pem"] }

x509-cert = { version = "0.2.3", features = ["std"] }
const-oid = "0.9.2"
ssi-jwk = { version = "0.1" }
isomdl-macros = { version = "0.1.0", path = "macros" }
clap = { version = "4", features = ["derive"] }
clap-stdin = "0.2.1"
der = { version = "0.7", features = ["std", "derive", "alloc"] }
hex = "0.4.3"
asn1-rs = { version = "0.5.2", features = ["bits"]}
reqwest = "0.11.22"

[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
2 changes: 1 addition & 1 deletion src/definitions/helpers/non_empty_vec.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use std::ops::Deref;

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
#[serde(try_from = "Vec<T>", into = "Vec<T>")]
pub struct NonEmptyVec<T: Clone>(Vec<T>);

Expand Down
3 changes: 3 additions & 0 deletions src/definitions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ pub mod mso;
pub mod namespaces;
pub mod session;
pub mod traits;
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 +24,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;
12 changes: 6 additions & 6 deletions src/definitions/namespaces/latin1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ mod test {
#[test]
fn upper_latin() {
let upper_latin_chars = vec![
' ', '¡', '¢', '£', '¤', '¥', '¦', '§', '¨', '©', 'ª', '«', '¬', '­', '®', '¯', '°',
'±', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', 'À', 'Á',
'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò',
'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã',
'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô',
'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ',
' ', '¡', '¢', '£', '¤', '¥', '¦', '§', '¨', '©', 'ª', '«', '¬', '\u{AD}', '®', '¯',
'°', '±', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', 'À',
'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ',
'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â',
'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó',
'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ',
];
assert!(upper_latin_chars.iter().all(is_upper_latin));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ impl FromJsonMap for IssuingJurisdiction {
.and_then(Alpha2::from_json)?;

if !jurisdiction.starts_with(country.as_str()) {
return Err(Error::CountryMismatch)
.map_err(Into::into)
.map_err(FromJsonError::Parsing);
return Err(FromJsonError::Parsing(Into::into(Error::CountryMismatch)));
}

Ok(Self(jurisdiction))
Expand Down
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,
}
Loading