From 256c3aa6334fdfeaf484042df535d39ecbe878eb Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 17 Sep 2025 12:26:42 +0400 Subject: [PATCH 1/2] update SignatureKidRule initialisation, include admin roles --- .../src/signers/roles.rs | 39 ++++++++++++++++++- rust/signed_doc/Cargo.toml | 2 +- rust/signed_doc/src/providers.rs | 18 +++++---- rust/signed_doc/src/validator/mod.rs | 4 +- rust/signed_doc/src/validator/rules/mod.rs | 6 +-- .../src/validator/rules/signature.rs | 8 ++-- .../src/validator/rules/signature_kid.rs | 33 ++++++++++++---- 7 files changed, 83 insertions(+), 27 deletions(-) diff --git a/rust/catalyst-signed-doc-spec/src/signers/roles.rs b/rust/catalyst-signed-doc-spec/src/signers/roles.rs index fc428060e72..56faa4e5006 100644 --- a/rust/catalyst-signed-doc-spec/src/signers/roles.rs +++ b/rust/catalyst-signed-doc-spec/src/signers/roles.rs @@ -4,13 +4,16 @@ #[derive(serde::Deserialize)] #[allow(clippy::missing_docs_in_private_items)] pub struct Roles { - pub user: Vec, + #[serde(default)] + pub user: Vec, + #[serde(default)] + pub admin: Vec, } /// Role definition #[derive(serde::Deserialize)] #[allow(clippy::missing_docs_in_private_items)] -pub enum Role { +pub enum UserRole { /// Role 0 - A registered User / Voter - Base Role Registered, /// Registered for posting proposals @@ -18,3 +21,35 @@ pub enum Role { /// Registered as a rep for voting purposes. Representative, } + +#[derive(serde::Deserialize)] +#[allow(clippy::missing_docs_in_private_items)] +pub enum AdminRole { + /// Root Certificate Authority role. + #[serde(rename = "Root CA")] + RootCA, + /// Brand Certificate Authority role. + #[serde(rename = "Brand CA")] + BrandCA, + /// Campaign Certificate Authority role. + #[serde(rename = "Campaign CA")] + CampaignCA, + /// Category Certificate Authority role. + #[serde(rename = "Category CA")] + CategoryCA, + /// Root Admin role. + #[serde(rename = "Root Admin")] + RootAdmin, + /// Brand Admin role. + #[serde(rename = "Brand Admin")] + BrandAdmin, + /// Campaign Admin role. + #[serde(rename = "Campaign Admin")] + CampaignAdmin, + /// Category Admin role. + #[serde(rename = "Category Admin")] + CategoryAdmin, + /// Moderator role. + #[serde(rename = "Moderator")] + Moderator, +} diff --git a/rust/signed_doc/Cargo.toml b/rust/signed_doc/Cargo.toml index 6f1be7c9797..0c921e5745e 100644 --- a/rust/signed_doc/Cargo.toml +++ b/rust/signed_doc/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true workspace = true [dependencies] -catalyst-types = { version = "0.0.7", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "catalyst-types/v0.0.7" } +catalyst-types = { version = "0.0.8", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "catalyst-types/v0.0.8" } cbork-utils = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cbork-utils-v0.0.2" } catalyst-signed-doc-macro = { version = "0.0.1", path = "../catalyst-signed-doc-macro" } diff --git a/rust/signed_doc/src/providers.rs b/rust/signed_doc/src/providers.rs index 968fdad858a..49eb4aeedcb 100644 --- a/rust/signed_doc/src/providers.rs +++ b/rust/signed_doc/src/providers.rs @@ -7,10 +7,12 @@ use ed25519_dalek::VerifyingKey; use crate::{CatalystSignedDocument, DocumentRef}; -/// `VerifyingKey` Provider trait -pub trait VerifyingKeyProvider: Send + Sync { - /// Try to get `VerifyingKey` - fn try_get_key( +/// `CatalystId` Provider trait +pub trait CatalystIdProvider: Send + Sync { + /// Try to get `VerifyingKey` by the provided `CatalystId` and corresponding `RoleId` + /// and `KeyRotation` Return `None` if the provided `CatalystId` with the + /// corresponding `RoleId` and `KeyRotation` has not been registered. + fn try_get_registered_key( &self, kid: &CatalystId, ) -> impl Future>> + Send; @@ -48,8 +50,8 @@ pub mod tests { use std::{collections::HashMap, time::Duration}; use super::{ - CatalystId, CatalystSignedDocument, CatalystSignedDocumentProvider, VerifyingKey, - VerifyingKeyProvider, + CatalystId, CatalystIdProvider, CatalystSignedDocument, CatalystSignedDocumentProvider, + VerifyingKey, }; use crate::{DocLocator, DocumentRef}; @@ -123,8 +125,8 @@ pub mod tests { } } - impl VerifyingKeyProvider for TestCatalystProvider { - async fn try_get_key( + impl CatalystIdProvider for TestCatalystProvider { + async fn try_get_registered_key( &self, kid: &CatalystId, ) -> anyhow::Result> { diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs index 3cc32b846b7..ff4f6e04d07 100644 --- a/rust/signed_doc/src/validator/mod.rs +++ b/rust/signed_doc/src/validator/mod.rs @@ -17,7 +17,7 @@ use crate::{ PROPOSAL_COMMENT_FORM_TEMPLATE, PROPOSAL_FORM_TEMPLATE, PROPOSAL_SUBMISSION_ACTION, }, metadata::DocType, - providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider}, + providers::{CatalystIdProvider, CatalystSignedDocumentProvider}, validator::rules::{CollaboratorsRule, SignatureRule, TemplateRule}, CatalystSignedDocument, ContentEncoding, ContentType, }; @@ -196,7 +196,7 @@ pub async fn validate( provider: &Provider, ) -> anyhow::Result where - Provider: CatalystSignedDocumentProvider + VerifyingKeyProvider, + Provider: CatalystSignedDocumentProvider + CatalystIdProvider, { let Ok(doc_type) = doc.doc_type() else { doc.report().missing_field( diff --git a/rust/signed_doc/src/validator/rules/mod.rs b/rust/signed_doc/src/validator/rules/mod.rs index 2d8496deae6..58f56dce992 100644 --- a/rust/signed_doc/src/validator/rules/mod.rs +++ b/rust/signed_doc/src/validator/rules/mod.rs @@ -4,7 +4,7 @@ use futures::FutureExt; use crate::{ - providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider}, + providers::{CatalystIdProvider, CatalystSignedDocumentProvider}, CatalystSignedDocument, }; @@ -80,7 +80,7 @@ impl Rules { provider: &Provider, ) -> anyhow::Result where - Provider: CatalystSignedDocumentProvider + VerifyingKeyProvider, + Provider: CatalystSignedDocumentProvider + CatalystIdProvider, { let rules = [ self.id.check(doc, provider).boxed(), @@ -139,7 +139,7 @@ impl Rules { section: SectionRule::NotSpecified, collaborators: CollaboratorsRule::NotSpecified, content: ContentRule::new(&doc_spec.payload)?, - kid: SignatureKidRule::new(&doc_spec.signers.roles), + kid: SignatureKidRule::new(&doc_spec.signers.roles)?, signature: SignatureRule { mutlisig: false }, original_author: OriginalAuthorRule, }; diff --git a/rust/signed_doc/src/validator/rules/signature.rs b/rust/signed_doc/src/validator/rules/signature.rs index 15bf43b7f0a..1bfedc4f47c 100644 --- a/rust/signed_doc/src/validator/rules/signature.rs +++ b/rust/signed_doc/src/validator/rules/signature.rs @@ -4,7 +4,7 @@ use anyhow::Context; use catalyst_types::problem_report::ProblemReport; use crate::{ - providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider}, + providers::{CatalystIdProvider, CatalystSignedDocumentProvider}, signature::{tbs_data, Signature}, CatalystSignedDocument, }; @@ -28,7 +28,7 @@ impl SignatureRule { provider: &Provider, ) -> anyhow::Result where - Provider: CatalystSignedDocumentProvider + VerifyingKeyProvider, + Provider: CatalystSignedDocumentProvider + CatalystIdProvider, { if doc.signatures().is_empty() { doc.report().other( @@ -74,11 +74,11 @@ async fn validate_signature( report: &ProblemReport, ) -> anyhow::Result where - Provider: VerifyingKeyProvider, + Provider: CatalystIdProvider, { let kid = sign.kid(); - let Some(pk) = provider.try_get_key(kid).await? else { + let Some(pk) = provider.try_get_registered_key(kid).await? else { report.other( &format!("Missing public key for {kid}."), "During public key extraction", diff --git a/rust/signed_doc/src/validator/rules/signature_kid.rs b/rust/signed_doc/src/validator/rules/signature_kid.rs index 9b96a7a9ef0..04ccdba1c34 100644 --- a/rust/signed_doc/src/validator/rules/signature_kid.rs +++ b/rust/signed_doc/src/validator/rules/signature_kid.rs @@ -1,6 +1,6 @@ //! Catalyst Signed Document COSE signature `kid` (Catalyst Id) role validation -use catalyst_signed_doc_spec::signers::roles::{Role, Roles}; +use catalyst_signed_doc_spec::signers::roles::{AdminRole, Roles, UserRole}; use catalyst_types::catalyst_id::role_index::RoleId; use crate::CatalystSignedDocument; @@ -14,19 +14,38 @@ pub(crate) struct SignatureKidRule { impl SignatureKidRule { /// Generating `SignatureKidRule` from specs - pub(crate) fn new(spec: &Roles) -> Self { - let allowed_roles = spec + pub(crate) fn new(spec: &Roles) -> anyhow::Result { + let allowed_roles: Vec<_> = spec .user .iter() .map(|v| { match v { - Role::Registered => RoleId::Role0, - Role::Proposer => RoleId::Proposer, - Role::Representative => RoleId::DelegatedRepresentative, + UserRole::Registered => RoleId::Role0, + UserRole::Proposer => RoleId::Proposer, + UserRole::Representative => RoleId::DelegatedRepresentative, } }) + .chain(spec.admin.iter().map(|v| { + match v { + AdminRole::RootCA => RoleId::RootCA, + AdminRole::BrandCA => RoleId::BrandCA, + AdminRole::CampaignCA => RoleId::CampaignCA, + AdminRole::CategoryCA => RoleId::CategoryCA, + AdminRole::RootAdmin => RoleId::RootAdmin, + AdminRole::BrandAdmin => RoleId::BrandAdmin, + AdminRole::CampaignAdmin => RoleId::CampaignAdmin, + AdminRole::CategoryAdmin => RoleId::CategoryAdmin, + AdminRole::Moderator => RoleId::Moderator, + } + })) .collect(); - Self { allowed_roles } + + anyhow::ensure!( + !allowed_roles.is_empty(), + "A list of allowed roles cannot be empty" + ); + + Ok(Self { allowed_roles }) } /// Field validation rule From 85895775482879b4a7d48faa8a9f54eec0092959 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Wed, 17 Sep 2025 12:39:05 +0400 Subject: [PATCH 2/2] fix --- rust/signed_doc/src/validator/mod.rs | 6 +++--- rust/signed_doc/src/validator/rules/signature_kid.rs | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs index ff4f6e04d07..d7f42823d09 100644 --- a/rust/signed_doc/src/validator/mod.rs +++ b/rust/signed_doc/src/validator/mod.rs @@ -58,7 +58,7 @@ fn proposal_rule() -> Rules { collaborators: CollaboratorsRule::NotSpecified, content: ContentRule::NotNil, kid: SignatureKidRule { - allowed_roles: vec![RoleId::Proposer], + allowed_roles: [RoleId::Proposer].into_iter().collect(), }, signature: SignatureRule { mutlisig: false }, original_author: OriginalAuthorRule, @@ -105,7 +105,7 @@ fn proposal_comment_rule() -> Rules { collaborators: CollaboratorsRule::NotSpecified, content: ContentRule::NotNil, kid: SignatureKidRule { - allowed_roles: vec![RoleId::Role0], + allowed_roles: [RoleId::Role0].into_iter().collect(), }, signature: SignatureRule { mutlisig: false }, original_author: OriginalAuthorRule, @@ -158,7 +158,7 @@ fn proposal_submission_action_rule() -> Rules { collaborators: CollaboratorsRule::NotSpecified, content: ContentRule::StaticSchema(ContentSchema::Json(proposal_action_json_schema)), kid: SignatureKidRule { - allowed_roles: vec![RoleId::Proposer], + allowed_roles: [RoleId::Proposer].into_iter().collect(), }, signature: SignatureRule { mutlisig: false }, original_author: OriginalAuthorRule, diff --git a/rust/signed_doc/src/validator/rules/signature_kid.rs b/rust/signed_doc/src/validator/rules/signature_kid.rs index 04ccdba1c34..c9b6b1521cb 100644 --- a/rust/signed_doc/src/validator/rules/signature_kid.rs +++ b/rust/signed_doc/src/validator/rules/signature_kid.rs @@ -1,5 +1,7 @@ //! Catalyst Signed Document COSE signature `kid` (Catalyst Id) role validation +use std::collections::HashSet; + use catalyst_signed_doc_spec::signers::roles::{AdminRole, Roles, UserRole}; use catalyst_types::catalyst_id::role_index::RoleId; @@ -9,13 +11,13 @@ use crate::CatalystSignedDocument; #[derive(Debug)] pub(crate) struct SignatureKidRule { /// expected `RoleId` values for the `kid` field - pub(crate) allowed_roles: Vec, + pub(crate) allowed_roles: HashSet, } impl SignatureKidRule { /// Generating `SignatureKidRule` from specs pub(crate) fn new(spec: &Roles) -> anyhow::Result { - let allowed_roles: Vec<_> = spec + let allowed_roles: HashSet<_> = spec .user .iter() .map(|v| { @@ -92,7 +94,9 @@ mod tests { #[tokio::test] async fn signature_kid_rule_test() { let mut rule = SignatureKidRule { - allowed_roles: vec![RoleId::Role0, RoleId::DelegatedRepresentative], + allowed_roles: [RoleId::Role0, RoleId::DelegatedRepresentative] + .into_iter() + .collect(), }; let sk = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng); @@ -111,7 +115,7 @@ mod tests { assert!(rule.check(&doc).await.unwrap()); - rule.allowed_roles = vec![RoleId::Proposer]; + rule.allowed_roles = [RoleId::Proposer].into_iter().collect(); assert!(!rule.check(&doc).await.unwrap()); } }