Skip to content
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

feat(rust/rbac-registration): Introduce Cip0134UriSet type #119

Merged
merged 1 commit into from
Dec 30, 2024
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
119 changes: 76 additions & 43 deletions rust/rbac-registration/src/cardano/cip509/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,43 @@ use validation::{
use x509_chunks::X509Chunks;

use super::transaction::witness::TxWitness;
use crate::utils::{
decode_helper::{decode_bytes, decode_helper, decode_map_len},
general::{decode_utf8, decremented_index},
hashing::{blake2b_128, blake2b_256},
use crate::{
cardano::cip509::{rbac::Cip509RbacMetadata, types::ValidationSignature},
utils::{
decode_helper::{decode_bytes, decode_helper, decode_map_len},
general::decremented_index,
hashing::{blake2b_128, blake2b_256},
},
};

/// CIP509 label.
pub const LABEL: u64 = 509;

/// CIP509.
#[derive(Debug, PartialEq, Clone, Default)]
/// A x509 metadata envelope.
///
/// The envelope is required to prevent replayability attacks. See [this document] for
/// more details.
///
/// [this document]: https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md
#[derive(Debug, PartialEq, Clone)]
pub struct Cip509 {
/// `UUIDv4` Purpose .
pub purpose: Uuid, // (bytes .size 16)
/// A registration purpose (`UUIDv4`).
///
/// The purpose is defined by the consuming dApp.
pub purpose: Uuid,
/// Transaction inputs hash.
pub txn_inputs_hash: TxInputHash, // bytes .size 16
/// Optional previous transaction ID.
pub prv_tx_id: Option<Hash<32>>, // bytes .size 32
/// x509 chunks.
pub x509_chunks: X509Chunks, // chunk_type => [ + x509_chunk ]
pub txn_inputs_hash: TxInputHash,
/// An optional hash of the previous transaction.
///
/// The hash must always be present except for the first registration transaction.
// TODO: Use the `Blake2b256Hash` type from the `cardano-blockchain-types` crate.
pub prv_tx_id: Option<Hash<32>>,
stanislav-tkach marked this conversation as resolved.
Show resolved Hide resolved
/// Metadata.
///
/// This field encoded in chunks. See [`X509Chunks`] for more details.
pub metadata: Cip509RbacMetadata,
/// Validation signature.
stanislav-tkach marked this conversation as resolved.
Show resolved Hide resolved
pub validation_signature: Vec<u8>, // bytes size (1..64)
pub validation_signature: ValidationSignature,
}

/// Validation value for CIP509 metadatum.
Expand Down Expand Up @@ -92,7 +107,13 @@ pub(crate) enum Cip509IntIdentifier {
impl Decode<'_, ()> for Cip509 {
fn decode(d: &mut Decoder, ctx: &mut ()) -> Result<Self, decode::Error> {
let map_len = decode_map_len(d, "CIP509")?;
let mut cip509_metadatum = Cip509::default();

let mut purpose = Uuid::default();
let mut txn_inputs_hash = TxInputHash::default();
let mut prv_tx_id = None;
let mut metadata = None;
let mut validation_signature = Vec::new();

for _ in 0..map_len {
// Use probe to peak
let key = d.probe().u8()?;
Expand All @@ -101,43 +122,57 @@ impl Decode<'_, ()> for Cip509 {
let _: u8 = decode_helper(d, "CIP509", ctx)?;
match key {
Cip509IntIdentifier::Purpose => {
cip509_metadatum.purpose =
Uuid::try_from(decode_bytes(d, "CIP509 purpose")?).map_err(|_| {
decode::Error::message("Invalid data size of Purpose")
})?;
purpose = Uuid::try_from(decode_bytes(d, "CIP509 purpose")?)
.map_err(|_| decode::Error::message("Invalid data size of Purpose"))?;
},
Cip509IntIdentifier::TxInputsHash => {
cip509_metadatum.txn_inputs_hash =
txn_inputs_hash =
TxInputHash::try_from(decode_bytes(d, "CIP509 txn inputs hash")?)
.map_err(|_| {
decode::Error::message("Invalid data size of TxInputsHash")
})?;
},
Cip509IntIdentifier::PreviousTxId => {
let prv_tx_hash: [u8; 32] = decode_bytes(d, "CIP509 previous tx ID")?
let hash: [u8; 32] = decode_bytes(d, "CIP509 previous tx ID")?
.try_into()
.map_err(|_| {
decode::Error::message("Invalid data size of PreviousTxId")
})?;
cip509_metadatum.prv_tx_id = Some(Hash::from(prv_tx_hash));
decode::Error::message("Invalid data size of PreviousTxId")
})?;
prv_tx_id = Some(Hash::from(hash));
},
Cip509IntIdentifier::ValidationSignature => {
let validation_signature = decode_bytes(d, "CIP509 validation signature")?;
if validation_signature.is_empty() || validation_signature.len() > 64 {
return Err(decode::Error::message(
"Invalid data size of ValidationSignature",
));
}
cip509_metadatum.validation_signature = validation_signature;
let signature = decode_bytes(d, "CIP509 validation signature")?;
validation_signature = signature;
},
}
} else {
// Handle the x509 chunks 10 11 12
let x509_chunks = X509Chunks::decode(d, ctx)?;
cip509_metadatum.x509_chunks = x509_chunks;
// Technically it is possible to store multiple copies (or different instances) of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for miscommunication, storing multiple violate the CDDL spec, so only 1 is allowed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you were clear, perhaps it is a misunderstanding from my side. What I wanted to say with this comment is that it is possible to produce metadata with multiple different types, but it indeed would violate the specification.

// metadata, but it isn't allowed. See this link for more details:
// https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md#keys-10-11-or-12---x509-chunked-data
if metadata.is_some() {
return Err(decode::Error::message(
"Only one instance of the chunked metadata should be present",
));
}
metadata = Some(x509_chunks.into());
}
}
Ok(cip509_metadatum)

let metadata =
metadata.ok_or_else(|| decode::Error::message("Missing metadata in CIP509"))?;
let validation_signature = validation_signature
.try_into()
.map_err(|e| decode::Error::message(format!("Invalid validation signature: {e:?}")))?;

Ok(Self {
purpose,
txn_inputs_hash,
prv_tx_id,
metadata,
validation_signature,
})
}
}

Expand Down Expand Up @@ -179,16 +214,14 @@ impl Cip509 {
let mut is_valid_stake_public_key = true;
let mut is_valid_payment_key = true;
let mut is_valid_signing_key = true;
if let Some(role_set) = &self.x509_chunks.0.role_set {
// Validate only role 0
for role in role_set {
if role.role_number == 0 {
is_valid_stake_public_key =
validate_stake_public_key(self, txn, validation_report).unwrap_or(false);
is_valid_payment_key =
validate_payment_key(txn, role, validation_report).unwrap_or(false);
is_valid_signing_key = validate_role_singing_key(role, validation_report);
}
// Validate only role 0
for role in &self.metadata.role_set {
if role.role_number == 0 {
is_valid_stake_public_key =
validate_stake_public_key(self, txn, validation_report).unwrap_or(false);
is_valid_payment_key =
validate_payment_key(txn, role, validation_report).unwrap_or(false);
is_valid_signing_key = validate_role_singing_key(role, validation_report);
}
}
Cip509Validation {
Expand Down
128 changes: 60 additions & 68 deletions rust/rbac-registration/src/cardano/cip509/rbac/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,43 @@ use role_data::RoleData;
use strum_macros::FromRepr;

use super::types::cert_key_hash::CertKeyHash;
use crate::utils::decode_helper::{
decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len,
use crate::{
cardano::cip509::utils::Cip0134UriSet,
utils::decode_helper::{
decode_any, decode_array_len, decode_bytes, decode_helper, decode_map_len,
},
};

/// Cip509 RBAC metadata.
#[derive(Debug, PartialEq, Clone, Default)]
///
/// See [this document] for more details.
///
/// [this document]: https://github.com/input-output-hk/catalyst-CIPs/tree/x509-role-registration-metadata/CIP-XXXX
#[derive(Debug, PartialEq, Clone)]
pub struct Cip509RbacMetadata {
/// Optional list of x509 certificates.
pub x509_certs: Option<Vec<X509DerCert>>,
/// Optional list of c509 certificates.
/// The value can be either the c509 certificate or c509 metadatum reference.
pub c509_certs: Option<Vec<C509Cert>>,
/// Optional list of Public keys.
pub pub_keys: Option<Vec<SimplePublicKeyType>>,
/// Optional list of revocation list.
pub revocation_list: Option<Vec<CertKeyHash>>,
/// Optional list of role data.
pub role_set: Option<Vec<RoleData>>,
/// A potentially empty list of x509 certificates.
pub x509_certs: Vec<X509DerCert>,
/// A potentially empty list of c509 certificates.
pub c509_certs: Vec<C509Cert>,
/// A set of URIs contained in both x509 and c509 certificates.
///
/// URIs from different certificate types are stored separately and certificate
/// indexes are preserved too.
///
/// This field isn't present in the encoded format and is populated by processing both
/// `x509_certs` and `c509_certs` fields.
pub certificate_uris: Cip0134UriSet,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this certificate_uris is too specific for Cip509Metadata (specific to role 0)
Might be useful to add it in RegistrationChain.
What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I'm missing something, but certificates be present in the next registrations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DIscussed: we decided to proceed with this approach for now because it isn't going to be merged directly to the main branch. But we will consider this again during the final review.

/// A list of public keys that can be used instead of storing full certificates.
///
/// Check [this section] to understand the how certificates and the public keys list
/// are related.
///
/// [this section]: https://github.com/input-output-hk/catalyst-CIPs/tree/x509-role-registration-metadata/CIP-XXXX#storing-certificates-and-public-key
pub pub_keys: Vec<SimplePublicKeyType>,
/// A potentially empty list of revoked certificates.
pub revocation_list: Vec<CertKeyHash>,
/// A potentially empty list of role data.
pub role_set: Vec<RoleData>,
/// Optional map of purpose key data.
/// Empty map if no purpose key data is present.
pub purpose_key_data: HashMap<u16, Vec<u8>>,
Expand All @@ -60,86 +79,59 @@ pub enum Cip509RbacMetadataInt {
RoleSet = 100,
}

impl Cip509RbacMetadata {
/// Create a new instance of `Cip509RbacMetadata`.
pub(crate) fn new() -> Self {
Self {
x509_certs: None,
c509_certs: None,
pub_keys: None,
revocation_list: None,
role_set: None,
purpose_key_data: HashMap::new(),
}
}

/// Set the x509 certificates.
fn set_x509_certs(&mut self, x509_certs: Vec<X509DerCert>) {
self.x509_certs = Some(x509_certs);
}

/// Set the c509 certificates.
fn set_c509_certs(&mut self, c509_certs: Vec<C509Cert>) {
self.c509_certs = Some(c509_certs);
}

/// Set the public keys.
fn set_pub_keys(&mut self, pub_keys: Vec<SimplePublicKeyType>) {
self.pub_keys = Some(pub_keys);
}

/// Set the revocation list.
fn set_revocation_list(&mut self, revocation_list: Vec<CertKeyHash>) {
self.revocation_list = Some(revocation_list);
}

/// Set the role data set.
fn set_role_set(&mut self, role_set: Vec<RoleData>) {
self.role_set = Some(role_set);
}
}

impl Decode<'_, ()> for Cip509RbacMetadata {
fn decode(d: &mut Decoder, ctx: &mut ()) -> Result<Self, decode::Error> {
let map_len = decode_map_len(d, "Cip509RbacMetadata")?;

let mut x509_rbac_metadata = Cip509RbacMetadata::new();
let mut x509_certs = Vec::new();
let mut c509_certs = Vec::new();
let mut pub_keys = Vec::new();
let mut revocation_list = Vec::new();
let mut role_set = Vec::new();
let mut purpose_key_data = HashMap::new();

for _ in 0..map_len {
let key: u16 = decode_helper(d, "key in Cip509RbacMetadata", ctx)?;
if let Some(key) = Cip509RbacMetadataInt::from_repr(key) {
match key {
Cip509RbacMetadataInt::X509Certs => {
let x509_certs = decode_array_rbac(d, "x509 certificate")?;
x509_rbac_metadata.set_x509_certs(x509_certs);
x509_certs = decode_array_rbac(d, "x509 certificate")?;
},
Cip509RbacMetadataInt::C509Certs => {
let c509_certs = decode_array_rbac(d, "c509 certificate")?;
x509_rbac_metadata.set_c509_certs(c509_certs);
c509_certs = decode_array_rbac(d, "c509 certificate")?;
},
Cip509RbacMetadataInt::PubKeys => {
let pub_keys = decode_array_rbac(d, "public keys")?;
x509_rbac_metadata.set_pub_keys(pub_keys);
pub_keys = decode_array_rbac(d, "public keys")?;
},
Cip509RbacMetadataInt::RevocationList => {
let revocation_list = decode_revocation_list(d)?;
x509_rbac_metadata.set_revocation_list(revocation_list);
revocation_list = decode_revocation_list(d)?;
},
Cip509RbacMetadataInt::RoleSet => {
let role_set = decode_array_rbac(d, "role set")?;
x509_rbac_metadata.set_role_set(role_set);
role_set = decode_array_rbac(d, "role set")?;
},
}
} else {
if !(FIRST_PURPOSE_KEY..=LAST_PURPOSE_KEY).contains(&key) {
return Err(decode::Error::message(format!("Invalid purpose key set, should be with the range {FIRST_PURPOSE_KEY} - {LAST_PURPOSE_KEY}")));
}
x509_rbac_metadata
.purpose_key_data
.insert(key, decode_any(d, "purpose key")?);

purpose_key_data.insert(key, decode_any(d, "purpose key")?);
}
}
Ok(x509_rbac_metadata)

let certificate_uris = Cip0134UriSet::new(&x509_certs, &c509_certs).map_err(|e| {
decode::Error::message(format!("Unable to parse URIs from certificates: {e:?}"))
})?;

Ok(Self {
x509_certs,
c509_certs,
certificate_uris,
pub_keys,
revocation_list,
role_set,
purpose_key_data,
})
}
}

Expand Down
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
Expand Up @@ -2,3 +2,7 @@

pub mod cert_key_hash;
pub mod tx_input_hash;

pub use validation_signature::ValidationSignature;

mod validation_signature;
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Transaction input hash type

/// Transaction input hash representing in 16 bytes.
/// A 16-byte hash of the transaction inputs field.
///
/// This type is described [here].
///
/// [here]: https://github.com/input-output-hk/catalyst-CIPs/blob/x509-envelope-metadata/CIP-XXXX/README.md#key-1-txn-inputs-hash
#[derive(Debug, PartialEq, Clone, Default)]
pub struct TxInputHash([u8; 16]);

Expand Down
Loading
Loading