Skip to content

Commit ed688fc

Browse files
bkioshnstevenj
andauthored
feat(rust/rbac-registration): Record each role data fields (#244)
* feat(rbac-registration): record each role data fields Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): comment Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): syntax Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): role data record Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): remove key ref usize conversion Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): move update rbac to its own file Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): add extract pk from cert or pk + rename Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): move all key extraction from cat-voice Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): add more function to get the actual pk from cert Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): typo + format Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): move cert_or_pk to its own file Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): return ref Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): add structured error Signed-off-by: bkioshn <[email protected]> * fix(rbac-registration): change box to arc Signed-off-by: bkioshn <[email protected]> --------- Signed-off-by: bkioshn <[email protected]> Co-authored-by: Steven Johnson <[email protected]>
1 parent 8827bb0 commit ed688fc

File tree

9 files changed

+697
-206
lines changed

9 files changed

+697
-206
lines changed

rust/rbac-registration/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ der-parser = "9.0.0"
2929
tracing = "0.1.40"
3030
ed25519-dalek = "2.1.1"
3131
uuid = "1.11.0"
32+
oid-registry = "0.7.1"
33+
thiserror = "2.0.11"
3234

3335
c509-certificate = { version = "0.0.3", path = "../c509-certificate" }
3436
pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ pub use cip509::Cip509;
66
#[allow(clippy::module_name_repetitions)]
77
pub use rbac::{C509Cert, Cip509RbacMetadata, SimplePublicKeyType, X509DerCert};
88
pub use types::{
9-
CertKeyHash, KeyLocalRef, LocalRefInt, Payment, PaymentHistory, PointData, PointTxnIdx,
10-
RoleData, RoleNumber, TxInputHash, ValidationSignature,
9+
CertKeyHash, CertOrPk, KeyLocalRef, LocalRefInt, Payment, PaymentHistory, PointData,
10+
PointTxnIdx, RoleData, RoleDataRecord, RoleNumber, TxInputHash, ValidationSignature,
1111
};
12-
pub use utils::Cip0134UriSet;
12+
pub use utils::{extract_key, Cip0134UriSet};
1313

1414
#[allow(clippy::module_inception)]
1515
mod cip509;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//! C509 certificate, X509 certificate, or public key.
2+
3+
use std::sync::Arc;
4+
5+
use c509_certificate::c509::C509;
6+
use ed25519_dalek::VerifyingKey;
7+
use x509_cert::Certificate as X509;
8+
9+
use crate::cardano::cip509::extract_key;
10+
11+
/// Actual data of key local ref. Containing X509, C509 and public key.
12+
#[derive(Debug, Clone)]
13+
pub enum CertOrPk {
14+
/// X509 certificate, None if deleted.
15+
X509(Option<Arc<X509>>),
16+
/// C509 certificate, None if deleted.
17+
C509(Option<Arc<C509>>),
18+
/// Public key, None if deleted.
19+
PublicKey(Option<VerifyingKey>),
20+
}
21+
22+
impl CertOrPk {
23+
/// Extract public key from the given certificate or public key.
24+
pub(crate) fn extract_pk(&self) -> Option<VerifyingKey> {
25+
match self {
26+
CertOrPk::X509(Some(x509)) => extract_key::x509_key(x509).ok(),
27+
CertOrPk::C509(Some(c509)) => extract_key::c509_key(c509).ok(),
28+
CertOrPk::PublicKey(pk) => *pk,
29+
_ => None,
30+
}
31+
}
32+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
//! Types use in CIP-509
22
33
pub use cert_key_hash::CertKeyHash;
4+
pub use cert_or_pk::CertOrPk;
45
pub use key_local_ref::{KeyLocalRef, LocalRefInt};
56
pub use payment_history::{Payment, PaymentHistory};
67
pub use point_data::PointData;
78
pub use point_tx_idx::PointTxnIdx;
89
pub use role_data::RoleData;
10+
pub use role_data_record::RoleDataRecord;
911
pub use role_number::RoleNumber;
1012
pub use tx_input_hash::TxInputHash;
1113
pub use validation_signature::ValidationSignature;
1214

1315
mod cert_key_hash;
16+
mod cert_or_pk;
1417
mod key_local_ref;
1518
mod payment_history;
1619
mod point_data;
1720
mod point_tx_idx;
1821
mod role_data;
22+
mod role_data_record;
1923
mod role_number;
2024
mod tx_input_hash;
2125
mod validation_signature;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//! Role data record where each role data fields are stored.
2+
3+
use std::collections::HashMap;
4+
5+
use pallas::ledger::addresses::ShelleyAddress;
6+
7+
use super::{CertOrPk, PointData, PointTxnIdx};
8+
9+
/// Role data record where each field has it own record of its value and its associated
10+
/// point and transaction index. If a field has key rotation, then the vector index is
11+
/// used. Eg. Accessing key rotation 0 can be done by `signing_keys.first()`
12+
#[derive(Debug, Clone)]
13+
pub struct RoleDataRecord {
14+
/// List of signing key data and its associated point + tx index .
15+
/// The vector index is used to indicate the key rotation.
16+
signing_keys: Vec<PointData<CertOrPk>>,
17+
/// List of encryption key data and its associated point + tx index
18+
/// The vector index is used to indicate the key rotation.
19+
encryption_keys: Vec<PointData<CertOrPk>>,
20+
/// List of payment key and its associated point + tx index.
21+
payment_keys: Vec<PointData<ShelleyAddress>>,
22+
/// List of extended data and its associated point + tx index.
23+
extended_data: Vec<PointData<HashMap<u8, Vec<u8>>>>,
24+
}
25+
26+
impl RoleDataRecord {
27+
/// Create a new empty role data record.
28+
pub(crate) fn new() -> Self {
29+
Self {
30+
signing_keys: Vec::new(),
31+
encryption_keys: Vec::new(),
32+
payment_keys: Vec::new(),
33+
extended_data: Vec::new(),
34+
}
35+
}
36+
37+
/// Add a signing key data to the signing key list. The vector index is used to
38+
/// indicate the key rotation.
39+
pub(crate) fn add_signing_key(&mut self, data: CertOrPk, point_tx_idx: &PointTxnIdx) {
40+
self.signing_keys
41+
.push(PointData::new(point_tx_idx.clone(), data));
42+
}
43+
44+
/// Add an encryption key data to the encryption key list. The vector index is used to
45+
/// indicate the key rotation.
46+
pub(crate) fn add_encryption_key(&mut self, data: CertOrPk, point_tx_idx: &PointTxnIdx) {
47+
self.encryption_keys
48+
.push(PointData::new(point_tx_idx.clone(), data));
49+
}
50+
51+
/// Add a payment key to the payment key list.
52+
pub(crate) fn add_payment_key(&mut self, data: PointData<ShelleyAddress>) {
53+
self.payment_keys.push(data);
54+
}
55+
56+
/// Add extended data to the extended data list.
57+
pub(crate) fn add_extended_data(&mut self, data: PointData<HashMap<u8, Vec<u8>>>) {
58+
self.extended_data.push(data);
59+
}
60+
61+
/// Get the list of signing keys associated with this role.
62+
#[must_use]
63+
pub fn signing_keys(&self) -> &Vec<PointData<CertOrPk>> {
64+
&self.signing_keys
65+
}
66+
67+
/// Get the list of encryption keys associated with this role.
68+
#[must_use]
69+
pub fn encryption_keys(&self) -> &Vec<PointData<CertOrPk>> {
70+
&self.encryption_keys
71+
}
72+
73+
/// Get the list of payment keys associated with this role.
74+
#[must_use]
75+
pub fn payment_keys(&self) -> &Vec<PointData<ShelleyAddress>> {
76+
&self.payment_keys
77+
}
78+
79+
/// Get the list of extended data associated with this role.
80+
#[must_use]
81+
pub fn extended_data(&self) -> &Vec<PointData<HashMap<u8, Vec<u8>>>> {
82+
&self.extended_data
83+
}
84+
85+
/// Get the signing key data associated with this role and the given key rotation.
86+
/// The first signing key is at rotation 0, the second at rotation 1, and so on.
87+
/// Returns `None` if the given key rotation does not exist.
88+
#[must_use]
89+
pub fn signing_key_from_rotation(&self, rotation: usize) -> Option<&CertOrPk> {
90+
self.signing_keys.get(rotation).map(PointData::data)
91+
}
92+
93+
/// Get the encryption key data associated with this role and the given key rotation.
94+
/// The first encryption key is at rotation 0, the second at rotation 1, and so on.
95+
/// Returns `None` if the given key rotation does not exist.
96+
#[must_use]
97+
pub fn encryption_key_from_rotation(&self, rotation: usize) -> Option<&CertOrPk> {
98+
self.encryption_keys.get(rotation).map(PointData::data)
99+
}
100+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! Functions for extracting public key from X509 and C509 certificates with additional
2+
//! verification.
3+
4+
use anyhow::{anyhow, Context};
5+
use c509_certificate::c509::C509;
6+
use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
7+
use oid_registry::{Oid, OID_SIG_ED25519};
8+
use thiserror::Error;
9+
use x509_cert::Certificate as X509Certificate;
10+
11+
/// Error type for unsupported signature algorithms.
12+
#[derive(Error, Debug)]
13+
#[error("Unsupported signature algorithm: {oid}")]
14+
pub struct SignatureAlgoError {
15+
/// An OID of unsupported signature algorithm.
16+
oid: String,
17+
}
18+
19+
/// Returns `VerifyingKey` from the given X509 certificate.
20+
///
21+
/// # Errors
22+
///
23+
/// Returns an error if the signature algorithm is not supported and
24+
/// the public key cannot be extracted.
25+
pub fn x509_key(cert: &X509Certificate) -> anyhow::Result<VerifyingKey> {
26+
let oid: Oid = cert
27+
.tbs_certificate
28+
.subject_public_key_info
29+
.algorithm
30+
.oid
31+
.to_string()
32+
.parse()
33+
// `Context` cannot be used here because `OidParseError` doesn't implement `std::Error`.
34+
.map_err(|e| anyhow!("Invalid signature algorithm OID: {e:?}"))?;
35+
check_signature_algorithm(&oid)?;
36+
let extended_public_key = cert
37+
.tbs_certificate
38+
.subject_public_key_info
39+
.subject_public_key
40+
.as_bytes()
41+
.context("Invalid subject_public_key value (has unused bits)")?;
42+
verifying_key(extended_public_key).context("Unable to get verifying key from X509 certificate")
43+
}
44+
45+
/// Returns `VerifyingKey` from the given C509 certificate.
46+
///
47+
/// # Errors
48+
///
49+
/// Returns an error if the signature algorithm is not supported and
50+
/// the public key cannot be extracted.
51+
pub fn c509_key(cert: &C509) -> anyhow::Result<VerifyingKey> {
52+
let oid = cert
53+
.tbs_cert()
54+
.subject_public_key_algorithm()
55+
.algo_identifier()
56+
.oid();
57+
check_signature_algorithm(oid)?;
58+
verifying_key(cert.tbs_cert().subject_public_key())
59+
.context("Unable to get verifying key from C509 certificate")
60+
}
61+
62+
/// Checks that the signature algorithm is supported.
63+
fn check_signature_algorithm(oid: &Oid) -> Result<(), SignatureAlgoError> {
64+
// Currently the only supported signature algorithm is ED25519.
65+
if *oid != OID_SIG_ED25519 {
66+
return Err(SignatureAlgoError {
67+
oid: oid.to_id_string(),
68+
});
69+
}
70+
Ok(())
71+
}
72+
73+
// TODO: The very similar logic exists in the `rbac-registration` crate. It should be
74+
// moved somewhere and reused. See https://github.com/input-output-hk/catalyst-voices/issues/1952
75+
/// Creates `VerifyingKey` from the given extended public key.
76+
fn verifying_key(extended_public_key: &[u8]) -> anyhow::Result<VerifyingKey> {
77+
/// An extender public key length in bytes.
78+
const EXTENDED_PUBLIC_KEY_LENGTH: usize = 64;
79+
80+
if extended_public_key.len() != EXTENDED_PUBLIC_KEY_LENGTH {
81+
return Err(anyhow!(
82+
"Unexpected extended public key length in certificate: {}, expected {EXTENDED_PUBLIC_KEY_LENGTH}",
83+
extended_public_key.len()
84+
));
85+
}
86+
// This should never fail because of the check above.
87+
let public_key = extended_public_key
88+
.get(0..PUBLIC_KEY_LENGTH)
89+
.context("Unable to get public key part")?;
90+
let bytes: &[u8; PUBLIC_KEY_LENGTH] = public_key
91+
.try_into()
92+
.context("Invalid public key length in X509 certificate")?;
93+
VerifyingKey::from_bytes(bytes).context("Invalid public key in X509 certificate")
94+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Utility functions for CIP-509
22
33
pub mod cip19;
4-
4+
pub mod extract_key;
55
pub use cip134_uri_set::Cip0134UriSet;
66

77
mod cip134_uri_set;

0 commit comments

Comments
 (0)