Skip to content

Commit 87368cf

Browse files
Introduce Cip0134UriList type
1 parent 0aeadb9 commit 87368cf

File tree

5 files changed

+189
-151
lines changed

5 files changed

+189
-151
lines changed
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: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
pub fn new(cip509: &Cip509, tx: &MultiEraTx) -> Result<Self> {
42+
if !matches!(tx, MultiEraTx::Conway(_)) {
43+
return Err(anyhow!("Unsupported transaction era ({})", tx.era()));
44+
}
45+
46+
let metadata = &cip509.x509_chunks.0;
47+
let mut uris = process_x509_certificates(metadata);
48+
uris.extend(process_c509_certificates(metadata));
49+
50+
Ok(Self { uris: uris.into() })
51+
}
52+
53+
/// Returns an iterator over the contained Cip0134 URIs.
54+
pub fn iter(&self) -> impl Iterator<Item = &Cip0134Uri> {
55+
self.uris.iter()
56+
}
57+
58+
/// Returns a slice with all URIs in the list.
59+
pub fn as_slice(&self) -> &[Cip0134Uri] {
60+
&self.uris
61+
}
62+
}
63+
64+
/// Iterates over X509 certificates and extracts CIP-0134 URIs.
65+
fn process_x509_certificates(metadata: &Cip509RbacMetadata) -> Vec<Cip0134Uri> {
66+
let mut result = Vec::new();
67+
68+
for cert in metadata.x509_certs.iter().flatten() {
69+
let X509DerCert::X509Cert(cert) = cert else {
70+
continue;
71+
};
72+
let cert = match x509_cert::Certificate::from_der(cert) {
73+
Ok(cert) => cert,
74+
Err(e) => {
75+
debug!("Failed to decode x509 certificate DER: {e:?}");
76+
continue;
77+
},
78+
};
79+
// Find the "subject alternative name" extension.
80+
let Some(extension) = cert
81+
.tbs_certificate
82+
.extensions
83+
.iter()
84+
.flatten()
85+
.find(|e| e.extn_id == ID_CE_SUBJECT_ALT_NAME)
86+
else {
87+
continue;
88+
};
89+
let der = match parse_der_sequence(extension.extn_value.as_bytes()) {
90+
Ok((_, der)) => der,
91+
Err(e) => {
92+
debug!("Failed to parse DER sequence for Subject Alternative Name ({extension:?}): {e:?}");
93+
continue;
94+
},
95+
};
96+
for data in der.ref_iter() {
97+
if data.header.raw_tag() != Some(&[URI]) {
98+
continue;
99+
}
100+
let content = match data.content.as_slice() {
101+
Ok(c) => c,
102+
Err(e) => {
103+
debug!("Unable to process content for {data:?}: {e:?}");
104+
continue;
105+
},
106+
};
107+
let address = match decode_utf8(content) {
108+
Ok(a) => a,
109+
Err(e) => {
110+
debug!("Failed to decode content of {data:?}: {e:?}");
111+
continue;
112+
},
113+
};
114+
match Cip0134Uri::parse(&address) {
115+
Ok(a) => result.push(a),
116+
Err(e) => {
117+
debug!("Failed to parse CIP-0134 address ({address}): {e:?}");
118+
},
119+
}
120+
}
121+
}
122+
123+
result
124+
}
125+
126+
/// Iterates over C509 certificates and extracts CIP-0134 URIs.
127+
fn process_c509_certificates(metadata: &Cip509RbacMetadata) -> Vec<Cip0134Uri> {
128+
let mut result = Vec::new();
129+
130+
for cert in metadata.c509_certs.iter().flatten() {
131+
let cert = match cert {
132+
C509Cert::C509Certificate(c) => c,
133+
C509Cert::C509CertInMetadatumReference(_) => {
134+
debug!("Ignoring unsupported metadadum reference");
135+
continue;
136+
},
137+
_ => continue,
138+
};
139+
140+
for extension in cert.tbs_cert().extensions().extensions() {
141+
if extension.registered_oid().c509_oid().oid()
142+
!= &C509ExtensionType::SubjectAlternativeName.oid()
143+
{
144+
continue;
145+
}
146+
let ExtensionValue::AlternativeName(alt_name) = extension.value() else {
147+
debug!("Unexpected extension value type for {extension:?}");
148+
continue;
149+
};
150+
let GeneralNamesOrText::GeneralNames(gen_names) = alt_name.general_name() else {
151+
debug!("Unexpected general name type: {extension:?}");
152+
continue;
153+
};
154+
for name in gen_names.general_names() {
155+
if *name.gn_type() != GeneralNameTypeRegistry::UniformResourceIdentifier {
156+
continue;
157+
}
158+
let GeneralNameValue::Text(address) = name.gn_value() else {
159+
debug!("Unexpected general name value format: {name:?}");
160+
continue;
161+
};
162+
match Cip0134Uri::parse(&address) {
163+
Ok(a) => result.push(a),
164+
Err(e) => {
165+
debug!("Failed to parse CIP-0134 address ({address}): {e:?}");
166+
},
167+
}
168+
}
169+
}
170+
}
171+
172+
result
173+
}
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;

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

Lines changed: 5 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use super::{
4444
},
4545
Cip509, TxInputHash, TxWitness,
4646
};
47-
use crate::utils::general::zero_out_last_n_bytes;
47+
use crate::{cardano::cip509::utils::Cip0134UriList, utils::general::zero_out_last_n_bytes};
4848

4949
/// Context-specific primitive type with tag number 6 (`raw_tag` 134) for
5050
/// uniform resource identifier (URI) in the subject alternative name extension.
@@ -113,155 +113,11 @@ pub(crate) fn validate_stake_public_key(
113113
cip509: &Cip509, txn: &MultiEraTx, validation_report: &mut Vec<String>,
114114
) -> Option<bool> {
115115
let function_name = "Validate Stake Public Key";
116-
let mut pk_addrs = Vec::new();
117116

118-
// CIP-0509 should only be in conway era
119-
if let MultiEraTx::Conway(_) = txn {
120-
// X509 certificate
121-
if let Some(x509_certs) = &cip509.x509_chunks.0.x509_certs {
122-
for x509_cert in x509_certs {
123-
match x509_cert {
124-
X509DerCert::X509Cert(cert) => {
125-
// Attempt to decode the DER certificate
126-
let der_cert = match x509_cert::Certificate::from_der(cert) {
127-
Ok(cert) => cert,
128-
Err(e) => {
129-
validation_report.push(format!(
130-
"{function_name}, Failed to decode x509 certificate DER: {e}"
131-
));
132-
return None;
133-
},
134-
};
135-
136-
// Find the Subject Alternative Name extension
137-
let san_ext =
138-
der_cert
139-
.tbs_certificate
140-
.extensions
141-
.as_ref()
142-
.and_then(|exts| {
143-
exts.iter()
144-
.find(|ext| ext.extn_id == ID_CE_SUBJECT_ALT_NAME)
145-
});
146-
147-
// Subject Alternative Name extension if it exists
148-
if let Some(san_ext) = san_ext {
149-
match parse_der_sequence(san_ext.extn_value.as_bytes()) {
150-
Ok((_, parsed_seq)) => {
151-
for data in parsed_seq.ref_iter() {
152-
// Check for context-specific primitive type with tag
153-
// number
154-
// 6 (raw_tag 134)
155-
if data.header.raw_tag() == Some(&[URI]) {
156-
match data.content.as_slice() {
157-
Ok(content) => {
158-
// Decode the UTF-8 string
159-
let addr: String = match decode_utf8(content) {
160-
Ok(addr) => addr,
161-
Err(e) => {
162-
validation_report.push(format!(
163-
"{function_name}, Failed to decode UTF-8 string for context-specific primitive type with raw tag 134: {e}",
164-
),
165-
);
166-
return None;
167-
},
168-
};
169-
170-
// Extract the CIP19 hash and push into
171-
// array
172-
if let Ok(uri) = Cip0134Uri::parse(&addr) {
173-
if let Address::Stake(a) = uri.address() {
174-
pk_addrs.push(
175-
a.payload().as_hash().to_vec(),
176-
);
177-
}
178-
}
179-
},
180-
Err(e) => {
181-
validation_report.push(
182-
format!("{function_name}, Failed to process content for context-specific primitive type with raw tag 134: {e}"));
183-
return None;
184-
},
185-
}
186-
}
187-
}
188-
},
189-
Err(e) => {
190-
validation_report.push(
191-
format!(
192-
"{function_name}, Failed to parse DER sequence for Subject Alternative Name extension: {e}"
193-
)
194-
);
195-
return None;
196-
},
197-
}
198-
}
199-
},
200-
_ => continue,
201-
}
202-
}
203-
}
204-
// C509 Certificate
205-
if let Some(c509_certs) = &cip509.x509_chunks.0.c509_certs {
206-
for c509_cert in c509_certs {
207-
match c509_cert {
208-
C509Cert::C509CertInMetadatumReference(_) => {
209-
validation_report.push(format!(
210-
"{function_name}, C509 metadatum reference is currently not supported"
211-
));
212-
},
213-
C509Cert::C509Certificate(c509) => {
214-
for exts in c509.tbs_cert().extensions().extensions() {
215-
if *exts.registered_oid().c509_oid().oid()
216-
== C509ExtensionType::SubjectAlternativeName.oid()
217-
{
218-
match exts.value() {
219-
c509_certificate::extensions::extension::ExtensionValue::AlternativeName(alt_name) => {
220-
match alt_name.general_name() {
221-
c509_certificate::extensions::alt_name::GeneralNamesOrText::GeneralNames(gn) => {
222-
for name in gn.general_names() {
223-
if name.gn_type() == &c509_certificate::general_names::general_name::GeneralNameTypeRegistry::UniformResourceIdentifier {
224-
match name.gn_value() {
225-
GeneralNameValue::Text(s) => {
226-
if let Ok(uri) = Cip0134Uri::parse(s) {
227-
if let Address::Stake(a) = uri.address() {
228-
pk_addrs.push(a.payload().as_hash().to_vec());
229-
}
230-
}
231-
},
232-
_ => {
233-
validation_report.push(
234-
format!("{function_name}, Failed to get the value of subject alternative name"),
235-
);
236-
}
237-
}
238-
}
239-
}
240-
},
241-
c509_certificate::extensions::alt_name::GeneralNamesOrText::Text(_) => {
242-
validation_report.push(
243-
format!("{function_name}, Failed to find C509 general names in subject alternative name"),
244-
);
245-
}
246-
}
247-
},
248-
_ => {
249-
validation_report.push(
250-
format!("{function_name}, Failed to get C509 subject alternative name")
251-
);
252-
}
253-
}
254-
}
255-
}
256-
},
257-
_ => continue,
258-
}
259-
}
260-
}
261-
} else {
262-
validation_report.push(format!("{function_name}, Unsupported transaction era"));
263-
return None;
264-
}
117+
// TODO: FIXME: Filter addressed (Address::Stake)
118+
let Ok(addresses) = Cip0134UriList::new(cip509, txn) else {
119+
todo!();
120+
};
265121

266122
// Create TxWitness
267123
// Note that TxWitness designs to work with multiple transactions

0 commit comments

Comments
 (0)