Skip to content

feat(rust/rbac-registration): Record each role data fields #244

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

Merged
merged 18 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 0 deletions rust/rbac-registration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ der-parser = "9.0.0"
tracing = "0.1.40"
ed25519-dalek = "2.1.1"
uuid = "1.11.0"
oid-registry = "0.7.1"
thiserror = "2.0.11"

c509-certificate = { version = "0.0.3", path = "../c509-certificate" }
pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
Expand Down
6 changes: 3 additions & 3 deletions rust/rbac-registration/src/cardano/cip509/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ pub use cip509::Cip509;
#[allow(clippy::module_name_repetitions)]
pub use rbac::{C509Cert, Cip509RbacMetadata, SimplePublicKeyType, X509DerCert};
pub use types::{
CertKeyHash, KeyLocalRef, LocalRefInt, Payment, PaymentHistory, PointData, PointTxnIdx,
RoleData, RoleNumber, TxInputHash, ValidationSignature,
CertKeyHash, CertOrPk, KeyLocalRef, LocalRefInt, Payment, PaymentHistory, PointData,
PointTxnIdx, RoleData, RoleDataRecord, RoleNumber, TxInputHash, ValidationSignature,
};
pub use utils::Cip0134UriSet;
pub use utils::{extract_key, Cip0134UriSet};

#[allow(clippy::module_inception)]
mod cip509;
Expand Down
32 changes: 32 additions & 0 deletions rust/rbac-registration/src/cardano/cip509/types/cert_or_pk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! C509 certificate, X509 certificate, or public key.

use std::sync::Arc;

use c509_certificate::c509::C509;
use ed25519_dalek::VerifyingKey;
use x509_cert::Certificate as X509;

use crate::cardano::cip509::extract_key;

/// Actual data of key local ref. Containing X509, C509 and public key.
#[derive(Debug, Clone)]
pub enum CertOrPk {
/// X509 certificate, None if deleted.
X509(Option<Arc<X509>>),
/// C509 certificate, None if deleted.
C509(Option<Arc<C509>>),
/// Public key, None if deleted.
PublicKey(Option<VerifyingKey>),
}

impl CertOrPk {
/// Extract public key from the given certificate or public key.
pub(crate) fn extract_pk(&self) -> Option<VerifyingKey> {
match self {
CertOrPk::X509(Some(x509)) => extract_key::x509_key(x509).ok(),
CertOrPk::C509(Some(c509)) => extract_key::c509_key(c509).ok(),
CertOrPk::PublicKey(pk) => *pk,
_ => None,
}
}
}
4 changes: 4 additions & 0 deletions rust/rbac-registration/src/cardano/cip509/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
//! Types use in CIP-509

pub use cert_key_hash::CertKeyHash;
pub use cert_or_pk::CertOrPk;
pub use key_local_ref::{KeyLocalRef, LocalRefInt};
pub use payment_history::{Payment, PaymentHistory};
pub use point_data::PointData;
pub use point_tx_idx::PointTxnIdx;
pub use role_data::RoleData;
pub use role_data_record::RoleDataRecord;
pub use role_number::RoleNumber;
pub use tx_input_hash::TxInputHash;
pub use validation_signature::ValidationSignature;

mod cert_key_hash;
mod cert_or_pk;
mod key_local_ref;
mod payment_history;
mod point_data;
mod point_tx_idx;
mod role_data;
mod role_data_record;
mod role_number;
mod tx_input_hash;
mod validation_signature;
100 changes: 100 additions & 0 deletions rust/rbac-registration/src/cardano/cip509/types/role_data_record.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Role data record where each role data fields are stored.

use std::collections::HashMap;

use pallas::ledger::addresses::ShelleyAddress;

use super::{CertOrPk, PointData, PointTxnIdx};

/// Role data record where each field has it own record of its value and its associated
/// point and transaction index. If a field has key rotation, then the vector index is
/// used. Eg. Accessing key rotation 0 can be done by `signing_keys.first()`
#[derive(Debug, Clone)]
pub struct RoleDataRecord {
/// List of signing key data and its associated point + tx index .
/// The vector index is used to indicate the key rotation.
signing_keys: Vec<PointData<CertOrPk>>,
/// List of encryption key data and its associated point + tx index
/// The vector index is used to indicate the key rotation.
encryption_keys: Vec<PointData<CertOrPk>>,
/// List of payment key and its associated point + tx index.
payment_keys: Vec<PointData<ShelleyAddress>>,
/// List of extended data and its associated point + tx index.
extended_data: Vec<PointData<HashMap<u8, Vec<u8>>>>,
}

impl RoleDataRecord {
/// Create a new empty role data record.
pub(crate) fn new() -> Self {
Self {
signing_keys: Vec::new(),
encryption_keys: Vec::new(),
payment_keys: Vec::new(),
extended_data: Vec::new(),
}
}

/// Add a signing key data to the signing key list. The vector index is used to
/// indicate the key rotation.
pub(crate) fn add_signing_key(&mut self, data: CertOrPk, point_tx_idx: &PointTxnIdx) {
self.signing_keys
.push(PointData::new(point_tx_idx.clone(), data));
}

/// Add an encryption key data to the encryption key list. The vector index is used to
/// indicate the key rotation.
pub(crate) fn add_encryption_key(&mut self, data: CertOrPk, point_tx_idx: &PointTxnIdx) {
self.encryption_keys
.push(PointData::new(point_tx_idx.clone(), data));
}

/// Add a payment key to the payment key list.
pub(crate) fn add_payment_key(&mut self, data: PointData<ShelleyAddress>) {
self.payment_keys.push(data);
}

/// Add extended data to the extended data list.
pub(crate) fn add_extended_data(&mut self, data: PointData<HashMap<u8, Vec<u8>>>) {
self.extended_data.push(data);
}

/// Get the list of signing keys associated with this role.
#[must_use]
pub fn signing_keys(&self) -> &Vec<PointData<CertOrPk>> {
&self.signing_keys
}

/// Get the list of encryption keys associated with this role.
#[must_use]
pub fn encryption_keys(&self) -> &Vec<PointData<CertOrPk>> {
&self.encryption_keys
}

/// Get the list of payment keys associated with this role.
#[must_use]
pub fn payment_keys(&self) -> &Vec<PointData<ShelleyAddress>> {
&self.payment_keys
}

/// Get the list of extended data associated with this role.
#[must_use]
pub fn extended_data(&self) -> &Vec<PointData<HashMap<u8, Vec<u8>>>> {
&self.extended_data
}

/// Get the signing key data associated with this role and the given key rotation.
/// The first signing key is at rotation 0, the second at rotation 1, and so on.
/// Returns `None` if the given key rotation does not exist.
#[must_use]
pub fn signing_key_from_rotation(&self, rotation: usize) -> Option<&CertOrPk> {
self.signing_keys.get(rotation).map(PointData::data)
}

/// Get the encryption key data associated with this role and the given key rotation.
/// The first encryption key is at rotation 0, the second at rotation 1, and so on.
/// Returns `None` if the given key rotation does not exist.
#[must_use]
pub fn encryption_key_from_rotation(&self, rotation: usize) -> Option<&CertOrPk> {
self.encryption_keys.get(rotation).map(PointData::data)
}
}
94 changes: 94 additions & 0 deletions rust/rbac-registration/src/cardano/cip509/utils/extract_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Functions for extracting public key from X509 and C509 certificates with additional
//! verification.

use anyhow::{anyhow, Context};
use c509_certificate::c509::C509;
use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
use oid_registry::{Oid, OID_SIG_ED25519};
use thiserror::Error;
use x509_cert::Certificate as X509Certificate;

/// Error type for unsupported signature algorithms.
#[derive(Error, Debug)]
#[error("Unsupported signature algorithm: {oid}")]
pub struct SignatureAlgoError {
/// An OID of unsupported signature algorithm.
oid: String,
}

/// Returns `VerifyingKey` from the given X509 certificate.
///
/// # Errors
///
/// Returns an error if the signature algorithm is not supported and
/// the public key cannot be extracted.
pub fn x509_key(cert: &X509Certificate) -> anyhow::Result<VerifyingKey> {
let oid: Oid = cert
.tbs_certificate
.subject_public_key_info
.algorithm
.oid
.to_string()
.parse()
// `Context` cannot be used here because `OidParseError` doesn't implement `std::Error`.
.map_err(|e| anyhow!("Invalid signature algorithm OID: {e:?}"))?;
check_signature_algorithm(&oid)?;
let extended_public_key = cert
.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
.context("Invalid subject_public_key value (has unused bits)")?;
verifying_key(extended_public_key).context("Unable to get verifying key from X509 certificate")
}

/// Returns `VerifyingKey` from the given C509 certificate.
///
/// # Errors
///
/// Returns an error if the signature algorithm is not supported and
/// the public key cannot be extracted.
pub fn c509_key(cert: &C509) -> anyhow::Result<VerifyingKey> {
let oid = cert
.tbs_cert()
.subject_public_key_algorithm()
.algo_identifier()
.oid();
check_signature_algorithm(oid)?;
verifying_key(cert.tbs_cert().subject_public_key())
.context("Unable to get verifying key from C509 certificate")
}

/// Checks that the signature algorithm is supported.
fn check_signature_algorithm(oid: &Oid) -> Result<(), SignatureAlgoError> {
// Currently the only supported signature algorithm is ED25519.
if *oid != OID_SIG_ED25519 {
return Err(SignatureAlgoError {
oid: oid.to_id_string(),
});
}
Ok(())
}

// TODO: The very similar logic exists in the `rbac-registration` crate. It should be
// moved somewhere and reused. See https://github.com/input-output-hk/catalyst-voices/issues/1952
/// Creates `VerifyingKey` from the given extended public key.
fn verifying_key(extended_public_key: &[u8]) -> anyhow::Result<VerifyingKey> {
/// An extender public key length in bytes.
const EXTENDED_PUBLIC_KEY_LENGTH: usize = 64;

if extended_public_key.len() != EXTENDED_PUBLIC_KEY_LENGTH {
return Err(anyhow!(
"Unexpected extended public key length in certificate: {}, expected {EXTENDED_PUBLIC_KEY_LENGTH}",
extended_public_key.len()
));
}
// This should never fail because of the check above.
let public_key = extended_public_key
.get(0..PUBLIC_KEY_LENGTH)
.context("Unable to get public key part")?;
let bytes: &[u8; PUBLIC_KEY_LENGTH] = public_key
.try_into()
.context("Invalid public key length in X509 certificate")?;
VerifyingKey::from_bytes(bytes).context("Invalid public key in X509 certificate")
}
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,7 +1,7 @@
//! Utility functions for CIP-509

pub mod cip19;

pub mod extract_key;
pub use cip134_uri_set::Cip0134UriSet;

mod cip134_uri_set;
Loading
Loading