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

feat(rust/rbac-registration): Introduce Cip0134UriList type #106

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 1 addition & 1 deletion rust/rbac-registration/src/cardano/cip509/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use x509_chunks::X509Chunks;
use super::transaction::witness::TxWitness;
use crate::utils::{
decode_helper::{decode_bytes, decode_helper, decode_map_len},
general::{decode_utf8, decremented_index},
general::decremented_index,
hashing::{blake2b_128, blake2b_256},
};

Expand Down
8 changes: 8 additions & 0 deletions rust/rbac-registration/src/cardano/cip509/utils/cip134/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Utilities for [CIP-134] (Cardano URIs - Address Representation).
//!
//! [CIP-134]: https://github.com/cardano-foundation/CIPs/tree/master/CIP-0134

pub use self::{uri::Cip0134Uri, uri_list::Cip0134UriList};

mod uri;
mod uri_list;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Utility functions for CIP-0134 address.
//! An URI in the CIP-0134 format.

// Ignore URIs that are used in tests and doc-examples.
// cSpell:ignoreRegExp web\+cardano:.+
Expand All @@ -14,6 +14,7 @@ use pallas::ledger::addresses::Address;
///
/// [proposal]: https://github.com/cardano-foundation/CIPs/pull/888
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct Cip0134Uri {
/// A URI string.
uri: String,
Expand Down
225 changes: 225 additions & 0 deletions rust/rbac-registration/src/cardano/cip509/utils/cip134/uri_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
//! A list of [`Cip0134Uri`].

use std::sync::Arc;

use c509_certificate::{
extensions::{alt_name::GeneralNamesOrText, extension::ExtensionValue},
general_names::general_name::{GeneralNameTypeRegistry, GeneralNameValue},
C509ExtensionType,
};
use der_parser::der::parse_der_sequence;
use tracing::debug;
use x509_cert::der::{oid::db::rfc5912::ID_CE_SUBJECT_ALT_NAME, Decode};

use crate::{
cardano::cip509::{
rbac::{
certs::{C509Cert, X509DerCert},
Cip509RbacMetadata,
},
utils::Cip0134Uri,
validation::URI,
Cip509,
},
utils::general::decode_utf8,
};

/// A list of [`Cip0134Uri`].
///
/// This structure uses [`Arc`] internally, so it is cheap to clone.
#[derive(Debug, Clone)]
#[allow(clippy::module_name_repetitions)]
pub struct Cip0134UriList {
/// An internal list of URIs.
uris: Arc<[Cip0134Uri]>,
}

impl Cip0134UriList {
/// Creates a new `Cip0134UriList` instance from the given `Cip509`.
#[must_use]
pub fn new(cip509: &Cip509) -> Self {
let metadata = &cip509.x509_chunks.0;
let mut uris = process_x509_certificates(metadata);
uris.extend(process_c509_certificates(metadata));

Self { uris: uris.into() }
}

/// Returns an iterator over the contained Cip0134 URIs.
pub fn iter(&self) -> impl Iterator<Item = &Cip0134Uri> {
self.uris.iter()
}

/// Returns a slice with all URIs in the list.
#[must_use]
pub fn as_slice(&self) -> &[Cip0134Uri] {
&self.uris
}
}

/// Iterates over X509 certificates and extracts CIP-0134 URIs.
fn process_x509_certificates(metadata: &Cip509RbacMetadata) -> Vec<Cip0134Uri> {
let mut result = Vec::new();

for cert in metadata.x509_certs.iter().flatten() {
let X509DerCert::X509Cert(cert) = cert else {
continue;
};
let cert = match x509_cert::Certificate::from_der(cert) {
Ok(cert) => cert,
Err(e) => {
debug!("Failed to decode x509 certificate DER: {e:?}");
continue;
},
};
// Find the "subject alternative name" extension.
let Some(extension) = cert
.tbs_certificate
.extensions
.iter()
.flatten()
.find(|e| e.extn_id == ID_CE_SUBJECT_ALT_NAME)
else {
continue;
};
let der = match parse_der_sequence(extension.extn_value.as_bytes()) {
Ok((_, der)) => der,
Err(e) => {
debug!("Failed to parse DER sequence for Subject Alternative Name ({extension:?}): {e:?}");
continue;
},
};
for data in der.ref_iter() {
if data.header.raw_tag() != Some(&[URI]) {
continue;
}
let content = match data.content.as_slice() {
Ok(c) => c,
Err(e) => {
debug!("Unable to process content for {data:?}: {e:?}");
continue;
},
};
let address = match decode_utf8(content) {
Ok(a) => a,
Err(e) => {
debug!("Failed to decode content of {data:?}: {e:?}");
continue;
},
};
match Cip0134Uri::parse(&address) {
Ok(a) => result.push(a),
Err(e) => {
debug!("Failed to parse CIP-0134 address ({address}): {e:?}");
},
}
}
}

result
}

/// Iterates over C509 certificates and extracts CIP-0134 URIs.
fn process_c509_certificates(metadata: &Cip509RbacMetadata) -> Vec<Cip0134Uri> {
let mut result = Vec::new();

for cert in metadata.c509_certs.iter().flatten() {
let cert = match cert {
C509Cert::C509Certificate(c) => c,
C509Cert::C509CertInMetadatumReference(_) => {
debug!("Ignoring unsupported metadatum reference");
continue;
},
_ => continue,
};

for extension in cert.tbs_cert().extensions().extensions() {
if extension.registered_oid().c509_oid().oid()
!= &C509ExtensionType::SubjectAlternativeName.oid()
{
continue;
}
let ExtensionValue::AlternativeName(alt_name) = extension.value() else {
debug!("Unexpected extension value type for {extension:?}");
continue;
};
let GeneralNamesOrText::GeneralNames(gen_names) = alt_name.general_name() else {
debug!("Unexpected general name type: {extension:?}");
continue;
};
for name in gen_names.general_names() {
if *name.gn_type() != GeneralNameTypeRegistry::UniformResourceIdentifier {
continue;
}
let GeneralNameValue::Text(address) = name.gn_value() else {
debug!("Unexpected general name value format: {name:?}");
continue;
};
match Cip0134Uri::parse(address) {
Ok(a) => result.push(a),
Err(e) => {
debug!("Failed to parse CIP-0134 address ({address}): {e:?}");
},
}
}
}
}

result
}

#[cfg(test)]
mod tests {
use minicbor::{Decode, Decoder};
use pallas::{
codec::utils::Nullable,
ledger::{
addresses::{Address, Network},
traverse::{MultiEraBlock, MultiEraTx},
},
};

use super::*;
use crate::cardano::transaction::raw_aux_data::RawAuxData;

// This lint is disabled locally because unfortunately there is no
// `allow-indexing-slicing-in-tests` option. Also it is impossible to use
// `get(n).unwrap()` instead because Clippy will still complain (clippy::get-unwrap).
#[allow(clippy::indexing_slicing)]
#[test]
fn list_new() {
let block =
hex::decode(include_str!("../../../../test_data/cardano/conway_1.block")).unwrap();
let block = MultiEraBlock::decode(&block).unwrap();
let tx = &block.txs()[3];
let cip509 = cip509(tx);

let list = Cip0134UriList::new(&cip509);
assert_eq!(list.as_slice().len(), 1);
// cSpell:disable
assert_eq!(
list.as_slice()[0].uri(),
"web+cardano://addr/stake_test1urs8t0ssa3w9wh90ld5tprp3gurxd487rth2qlqk6ernjqcef4ugr"
);
// cSpell:enable
let Address::Stake(address) = list.as_slice()[0].address() else {
panic!("Unexpected address type");
};
assert_eq!(Network::Testnet, address.network());
assert_eq!(
"e075be10ec5c575caffb68b08c31470666d4fe1aeea07c16d6473903",
address.payload().as_hash().to_string()
);
}

fn cip509(tx: &MultiEraTx) -> Cip509 {
let Nullable::Some(data) = tx.as_conway().unwrap().clone().auxiliary_data else {
panic!("Auxiliary data is missing");
};
let data = RawAuxData::new(data.raw_cbor());
let metadata = data.get_metadata(509).unwrap();

let mut decoder = Decoder::new(metadata.as_slice());
Cip509::decode(&mut decoder, &mut ()).unwrap()
}
}
2 changes: 1 addition & 1 deletion rust/rbac-registration/src/cardano/cip509/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Utility functions for CIP-509

pub mod cip19;
pub use cip134::Cip0134Uri;
pub use cip134::{Cip0134Uri, Cip0134UriList};

mod cip134;
Loading
Loading