Skip to content

Commit d7f51fc

Browse files
Introduce Cip0134UriList type
1 parent 0aeadb9 commit d7f51fc

File tree

6 files changed

+215
-164
lines changed

6 files changed

+215
-164
lines changed

rust/rbac-registration/src/cardano/cip509/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use x509_chunks::X509Chunks;
2727
use super::transaction::witness::TxWitness;
2828
use crate::utils::{
2929
decode_helper::{decode_bytes, decode_helper, decode_map_len},
30-
general::{decode_utf8, decremented_index},
30+
general::decremented_index,
3131
hashing::{blake2b_128, blake2b_256},
3232
};
3333

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Utilities for [CIP-134] (Cardano URIs - Address Representation).
2+
//!
3+
//! [CIP-134]: https://github.com/cardano-foundation/CIPs/tree/master/CIP-0134
4+
5+
pub use self::{uri::Cip0134Uri, uri_list::Cip0134UriList};
6+
7+
mod uri;
8+
mod uri_list;

rust/rbac-registration/src/cardano/cip509/utils/cip134.rs renamed to rust/rbac-registration/src/cardano/cip509/utils/cip134/uri.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Utility functions for CIP-0134 address.
1+
//! An URI in the CIP-0134 format.
22
33
// Ignore URIs that are used in tests and doc-examples.
44
// cSpell:ignoreRegExp web\+cardano:.+
@@ -14,6 +14,7 @@ use pallas::ledger::addresses::Address;
1414
///
1515
/// [proposal]: https://github.com/cardano-foundation/CIPs/pull/888
1616
#[derive(Debug)]
17+
#[allow(clippy::module_name_repetitions)]
1718
pub struct Cip0134Uri {
1819
/// A URI string.
1920
uri: String,
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//! A list of [`Cip0134Uri`].
2+
3+
use std::sync::Arc;
4+
5+
use anyhow::{anyhow, Result};
6+
use c509_certificate::{
7+
extensions::{alt_name::GeneralNamesOrText, extension::ExtensionValue},
8+
general_names::general_name::{GeneralNameTypeRegistry, GeneralNameValue},
9+
C509ExtensionType,
10+
};
11+
use der_parser::der::parse_der_sequence;
12+
use pallas::ledger::traverse::MultiEraTx;
13+
use tracing::debug;
14+
use x509_cert::der::{oid::db::rfc5912::ID_CE_SUBJECT_ALT_NAME, Decode};
15+
16+
use crate::{
17+
cardano::cip509::{
18+
rbac::{
19+
certs::{C509Cert, X509DerCert},
20+
Cip509RbacMetadata,
21+
},
22+
utils::Cip0134Uri,
23+
validation::URI,
24+
Cip509,
25+
},
26+
utils::general::decode_utf8,
27+
};
28+
29+
/// A list of [`Cip0134Uri`].
30+
///
31+
/// This structure uses [`Arc`] internally, so it is cheap to clone.
32+
#[derive(Debug, Clone)]
33+
#[allow(clippy::module_name_repetitions)]
34+
pub struct Cip0134UriList {
35+
/// An internal list of URIs.
36+
uris: Arc<[Cip0134Uri]>,
37+
}
38+
39+
impl Cip0134UriList {
40+
/// Creates a new `Cip0134UriList` instance from the given `Cip509`.
41+
///
42+
/// # Errors
43+
/// - Unsupported transaction era.
44+
pub fn new(cip509: &Cip509, tx: &MultiEraTx) -> Result<Self> {
45+
if !matches!(tx, MultiEraTx::Conway(_)) {
46+
return Err(anyhow!("Unsupported transaction era ({})", tx.era()));
47+
}
48+
49+
let metadata = &cip509.x509_chunks.0;
50+
let mut uris = process_x509_certificates(metadata);
51+
uris.extend(process_c509_certificates(metadata));
52+
53+
Ok(Self { uris: uris.into() })
54+
}
55+
56+
/// Returns an iterator over the contained Cip0134 URIs.
57+
pub fn iter(&self) -> impl Iterator<Item = &Cip0134Uri> {
58+
self.uris.iter()
59+
}
60+
61+
/// Returns a slice with all URIs in the list.
62+
#[must_use]
63+
pub fn as_slice(&self) -> &[Cip0134Uri] {
64+
&self.uris
65+
}
66+
}
67+
68+
/// Iterates over X509 certificates and extracts CIP-0134 URIs.
69+
fn process_x509_certificates(metadata: &Cip509RbacMetadata) -> Vec<Cip0134Uri> {
70+
let mut result = Vec::new();
71+
72+
for cert in metadata.x509_certs.iter().flatten() {
73+
let X509DerCert::X509Cert(cert) = cert else {
74+
continue;
75+
};
76+
let cert = match x509_cert::Certificate::from_der(cert) {
77+
Ok(cert) => cert,
78+
Err(e) => {
79+
debug!("Failed to decode x509 certificate DER: {e:?}");
80+
continue;
81+
},
82+
};
83+
// Find the "subject alternative name" extension.
84+
let Some(extension) = cert
85+
.tbs_certificate
86+
.extensions
87+
.iter()
88+
.flatten()
89+
.find(|e| e.extn_id == ID_CE_SUBJECT_ALT_NAME)
90+
else {
91+
continue;
92+
};
93+
let der = match parse_der_sequence(extension.extn_value.as_bytes()) {
94+
Ok((_, der)) => der,
95+
Err(e) => {
96+
debug!("Failed to parse DER sequence for Subject Alternative Name ({extension:?}): {e:?}");
97+
continue;
98+
},
99+
};
100+
for data in der.ref_iter() {
101+
if data.header.raw_tag() != Some(&[URI]) {
102+
continue;
103+
}
104+
let content = match data.content.as_slice() {
105+
Ok(c) => c,
106+
Err(e) => {
107+
debug!("Unable to process content for {data:?}: {e:?}");
108+
continue;
109+
},
110+
};
111+
let address = match decode_utf8(content) {
112+
Ok(a) => a,
113+
Err(e) => {
114+
debug!("Failed to decode content of {data:?}: {e:?}");
115+
continue;
116+
},
117+
};
118+
match Cip0134Uri::parse(&address) {
119+
Ok(a) => result.push(a),
120+
Err(e) => {
121+
debug!("Failed to parse CIP-0134 address ({address}): {e:?}");
122+
},
123+
}
124+
}
125+
}
126+
127+
result
128+
}
129+
130+
/// Iterates over C509 certificates and extracts CIP-0134 URIs.
131+
fn process_c509_certificates(metadata: &Cip509RbacMetadata) -> Vec<Cip0134Uri> {
132+
let mut result = Vec::new();
133+
134+
for cert in metadata.c509_certs.iter().flatten() {
135+
let cert = match cert {
136+
C509Cert::C509Certificate(c) => c,
137+
C509Cert::C509CertInMetadatumReference(_) => {
138+
debug!("Ignoring unsupported metadadum reference");
139+
continue;
140+
},
141+
_ => continue,
142+
};
143+
144+
for extension in cert.tbs_cert().extensions().extensions() {
145+
if extension.registered_oid().c509_oid().oid()
146+
!= &C509ExtensionType::SubjectAlternativeName.oid()
147+
{
148+
continue;
149+
}
150+
let ExtensionValue::AlternativeName(alt_name) = extension.value() else {
151+
debug!("Unexpected extension value type for {extension:?}");
152+
continue;
153+
};
154+
let GeneralNamesOrText::GeneralNames(gen_names) = alt_name.general_name() else {
155+
debug!("Unexpected general name type: {extension:?}");
156+
continue;
157+
};
158+
for name in gen_names.general_names() {
159+
if *name.gn_type() != GeneralNameTypeRegistry::UniformResourceIdentifier {
160+
continue;
161+
}
162+
let GeneralNameValue::Text(address) = name.gn_value() else {
163+
debug!("Unexpected general name value format: {name:?}");
164+
continue;
165+
};
166+
match Cip0134Uri::parse(address) {
167+
Ok(a) => result.push(a),
168+
Err(e) => {
169+
debug!("Failed to parse CIP-0134 address ({address}): {e:?}");
170+
},
171+
}
172+
}
173+
}
174+
}
175+
176+
result
177+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Utility functions for CIP-509
22
33
pub mod cip19;
4-
pub use cip134::Cip0134Uri;
4+
pub use cip134::{Cip0134Uri, Cip0134UriList};
55

66
mod cip134;

0 commit comments

Comments
 (0)