Skip to content

Commit f62bcd9

Browse files
authored
feat(rust/rbac-registration): Certificate self-signed validation (#280)
* fix(rbac-registration): validate certificate, should all be self-signed Signed-off-by: bkioshn <[email protected]> * chore(rbac-registration): improve logging and comment Signed-off-by: bkioshn <[email protected]> * chore(rbac-registration): fix format Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): refactor validate self sign cert and change return type of to_cbor in c509 Signed-off-by: bkioshn <[email protected]> * chore(rbac-registration): fix format Signed-off-by: bkioshn <[email protected]> --------- Signed-off-by: bkioshn <[email protected]>
1 parent c56e144 commit f62bcd9

File tree

3 files changed

+150
-7
lines changed

3 files changed

+150
-7
lines changed

rust/c509-certificate/src/cert_tbs.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,18 @@ impl TbsCert {
133133
pub fn issuer_signature_algorithm(&self) -> &IssuerSignatureAlgorithm {
134134
&self.issuer_signature_algorithm
135135
}
136+
137+
/// Convert the TBS Certificate to CBOR.
138+
///
139+
/// # Errors
140+
/// Returns an error if encoding fails.
141+
pub fn to_cbor<W: Write>(&self) -> Result<Vec<u8>, minicbor::encode::Error<W::Error>> {
142+
let mut buf = Vec::new();
143+
let mut e = Encoder::new(&mut buf);
144+
self.encode(&mut e, &mut ())
145+
.map_err(minicbor::encode::Error::message)?;
146+
Ok(buf)
147+
}
136148
}
137149

138150
impl Encode<()> for TbsCert {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ use crate::cardano::cip509::{
3636
types::{PaymentHistory, RoleNumber, TxInputHash, ValidationSignature},
3737
utils::Cip0134UriSet,
3838
validation::{
39-
validate_aux, validate_role_data, validate_stake_public_key, validate_txn_inputs_hash,
39+
validate_aux, validate_role_data, validate_self_sign_cert, validate_stake_public_key,
40+
validate_txn_inputs_hash,
4041
},
4142
x509_chunks::X509Chunks,
4243
Payment, PointTxnIdx, RoleData,
@@ -162,6 +163,7 @@ impl Cip509 {
162163
}
163164
if let Some(metadata) = &cip509.metadata {
164165
cip509.catalyst_id = validate_role_data(metadata, block.network(), &cip509.report);
166+
validate_self_sign_cert(metadata, &report);
165167
}
166168

167169
Ok(Some(cip509))

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

Lines changed: 135 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ use catalyst_types::{
1313
id_uri::IdUri,
1414
problem_report::ProblemReport,
1515
};
16-
use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
16+
use ed25519_dalek::{Signature, VerifyingKey, PUBLIC_KEY_LENGTH};
1717
use pallas::{
1818
codec::{
1919
minicbor::{Encode, Encoder},
2020
utils::Bytes,
2121
},
2222
ledger::{addresses::Address, primitives::conway, traverse::MultiEraTx},
2323
};
24-
use x509_cert::Certificate;
24+
use x509_cert::{der::Encode as X509Encode, Certificate as X509};
2525

26-
use super::utils::cip19::compare_key_hash;
26+
use super::{
27+
extract_key::{c509_key, x509_key},
28+
utils::cip19::compare_key_hash,
29+
};
2730
use crate::cardano::cip509::{
2831
rbac::Cip509RbacMetadata, types::TxInputHash, C509Cert, Cip0134UriSet, LocalRefInt, RoleData,
2932
RoleNumber, SimplePublicKeyType, X509DerCert,
@@ -162,6 +165,134 @@ fn extract_stake_addresses(uris: Option<&Cip0134UriSet>) -> Vec<VKeyHash> {
162165
.collect()
163166
}
164167

168+
/// Validate self-signed certificates.
169+
/// All certificates should be self-signed and support only ED25519 signature.
170+
pub fn validate_self_sign_cert(metadata: &Cip509RbacMetadata, report: &ProblemReport) {
171+
let context = "Cip509 self-signed certificate validation";
172+
173+
for (index, cert) in metadata.c509_certs.iter().enumerate() {
174+
if let C509Cert::C509Certificate(c) = cert {
175+
validate_c509_self_signed_cert(c, index, report, context);
176+
}
177+
}
178+
179+
for (index, cert) in metadata.x509_certs.iter().enumerate() {
180+
if let X509DerCert::X509Cert(c) = cert {
181+
validate_x509_self_signed_cert(c, index, report, context);
182+
}
183+
}
184+
}
185+
186+
/// Validate C509 certificate that it is a self-signed.
187+
fn validate_c509_self_signed_cert(c: &C509, index: usize, report: &ProblemReport, context: &str) {
188+
// Self-sign certificate must be type 2
189+
if c.tbs_cert().c509_certificate_type() != 2 {
190+
report.invalid_value(
191+
&format!("C509 certificate type at index {index}"),
192+
&c.tbs_cert().c509_certificate_type().to_string(),
193+
"Certificate must have cert type 2",
194+
context,
195+
);
196+
return;
197+
}
198+
199+
let pk = match c509_key(c) {
200+
Ok(pk) => pk,
201+
Err(e) => {
202+
report.other(
203+
&format!(
204+
"Failed to extract subject public key from C509 certificate at index {index}: {e:?}",
205+
),
206+
context,
207+
);
208+
return;
209+
},
210+
};
211+
212+
let Some(sig) = c
213+
.issuer_signature_value()
214+
.clone()
215+
.and_then(|b| b.try_into().ok())
216+
.map(|arr: [u8; 64]| Signature::from_bytes(&arr))
217+
else {
218+
report.conversion_error(
219+
&format!("C509 issuer signature at index {index}"),
220+
&format!("{:?}", c.issuer_signature_value()),
221+
"Expected 64-byte Ed25519 signature",
222+
context,
223+
);
224+
return;
225+
};
226+
227+
// TODO(bkioshn): signature verification should be improved in c509 crate
228+
let Ok(tbs_cbor) = c.tbs_cert().to_cbor::<Vec<u8>>() else {
229+
report.invalid_encoding(
230+
&format!("C509 TBS certificate at index {index}"),
231+
"CBOR encoding",
232+
"Expected CBOR encoded TBS certificate",
233+
context,
234+
);
235+
return;
236+
};
237+
if pk.verify_strict(&tbs_cbor, &sig).is_err() {
238+
report.other(
239+
&format!("Cannot verify C509 certificate signature at index {index}",),
240+
context,
241+
);
242+
}
243+
}
244+
245+
/// Validate X509 certificate that it is a self-signed.
246+
fn validate_x509_self_signed_cert(c: &X509, index: usize, report: &ProblemReport, context: &str) {
247+
let pk = match x509_key(c) {
248+
Ok(pk) => pk,
249+
Err(e) => {
250+
report.other(
251+
&format!(
252+
"Failed to extract subject public key from X509 certificate at index {index}: {e:?}",
253+
),
254+
context,
255+
);
256+
return;
257+
},
258+
};
259+
260+
let Some(sig) = c
261+
.signature
262+
.as_bytes()
263+
.and_then(|b| b.try_into().ok())
264+
.map(|arr: [u8; 64]| Signature::from_bytes(&arr))
265+
else {
266+
report.conversion_error(
267+
&format!("X509 signature at index {index}"),
268+
&format!("{:?}", c.signature.as_bytes()),
269+
"Expected 64-byte Ed25519 signature",
270+
context,
271+
);
272+
return;
273+
};
274+
275+
let tbs_der = match c.tbs_certificate.to_der() {
276+
Ok(tbs_der) => tbs_der,
277+
Err(e) => {
278+
report.invalid_encoding(
279+
&format!("X509 tbs certificate at index {index}"),
280+
"DER encoding",
281+
&format!("Expected DER encoded X509 certificate: {e:?}"),
282+
context,
283+
);
284+
return;
285+
},
286+
};
287+
288+
if pk.verify_strict(&tbs_der, &sig).is_err() {
289+
report.other(
290+
&format!("Cannot verify X509 certificate signature at index {index} "),
291+
context,
292+
);
293+
}
294+
}
295+
165296
/// Checks the role data.
166297
#[allow(clippy::similar_names)]
167298
pub fn validate_role_data(
@@ -343,9 +474,7 @@ fn validate_role_0(
343474
}
344475

345476
/// Extracts `VerifyingKey` from the given `X509` certificate.
346-
fn x509_cert_key(
347-
cert: &Certificate, context: &str, report: &ProblemReport,
348-
) -> Option<VerifyingKey> {
477+
fn x509_cert_key(cert: &X509, context: &str, report: &ProblemReport) -> Option<VerifyingKey> {
349478
let Some(extended_public_key) = cert
350479
.tbs_certificate
351480
.subject_public_key_info

0 commit comments

Comments
 (0)