• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / catalyst-libs / 14167028834

31 Mar 2025 08:11AM UTC coverage: 65.173% (+0.1%) from 65.037%
14167028834

push

github

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

* feat(rbac-registration): record each role data fields

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): comment

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): syntax

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): role data record

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): remove key ref usize conversion

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): move  update rbac to its own file

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): add extract pk from cert or pk + rename

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): move all key extraction from cat-voice

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): add more function to get the actual pk from cert

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): typo + format

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): move cert_or_pk to its own file

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): return ref

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): add structured error

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(rbac-registration): change box to arc

Signed-off-by: bkioshn <bkioshn@gmail.com>

---------

Signed-off-by: bkioshn <bkioshn@gmail.com>
Co-authored-by: Steven Johnson <stevenj@users.noreply.github.com>

291 of 430 new or added lines in 5 files covered. (67.67%)

30 existing lines in 2 files now uncovered.

10427 of 15999 relevant lines covered (65.17%)

2674.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.88
/rust/signed_doc/src/validator/mod.rs
1
//! Catalyst Signed Documents validation logic
2

3
pub(crate) mod rules;
4
pub(crate) mod utils;
5

6
use std::{collections::HashMap, sync::LazyLock};
7

8
use catalyst_types::{id_uri::IdUri, problem_report::ProblemReport, uuid::Uuid};
9
use coset::{CoseSign, CoseSignature};
10
use rules::{
11
    CategoryRule, ContentEncodingRule, ContentTypeRule, RefRule, ReplyRule, Rules, SectionRule,
12
    TemplateRule,
13
};
14

15
use crate::{
16
    doc_types::{
17
        COMMENT_DOCUMENT_UUID_TYPE, COMMENT_TEMPLATE_UUID_TYPE, PROPOSAL_ACTION_DOCUMENT_UUID_TYPE,
18
        PROPOSAL_DOCUMENT_UUID_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE,
19
    },
20
    providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider},
21
    CatalystSignedDocument, ContentEncoding, ContentType,
22
};
23

24
/// A table representing a full set or validation rules per document id.
25
static DOCUMENT_RULES: LazyLock<HashMap<Uuid, Rules>> = LazyLock::new(document_rules_init);
26

27
/// `DOCUMENT_RULES` initialization function
28
#[allow(clippy::expect_used)]
29
fn document_rules_init() -> HashMap<Uuid, Rules> {
10✔
30
    let mut document_rules_map = HashMap::new();
10✔
31

10✔
32
    let proposal_document_rules = Rules {
10✔
33
        content_type: ContentTypeRule {
10✔
34
            exp: ContentType::Json,
10✔
35
        },
10✔
36
        content_encoding: ContentEncodingRule {
10✔
37
            exp: ContentEncoding::Brotli,
10✔
38
            optional: false,
10✔
39
        },
10✔
40
        template: TemplateRule::Specified {
10✔
41
            exp_template_type: PROPOSAL_TEMPLATE_UUID_TYPE
10✔
42
                .try_into()
10✔
43
                .expect("Must be a valid UUID V4"),
10✔
44
        },
10✔
45
        category: CategoryRule::Specified { optional: true },
10✔
46
        doc_ref: RefRule::NotSpecified,
10✔
47
        reply: ReplyRule::NotSpecified,
10✔
48
        section: SectionRule::NotSpecified,
10✔
49
    };
10✔
50
    document_rules_map.insert(PROPOSAL_DOCUMENT_UUID_TYPE, proposal_document_rules);
10✔
51

10✔
52
    let comment_document_rules = Rules {
10✔
53
        content_type: ContentTypeRule {
10✔
54
            exp: ContentType::Json,
10✔
55
        },
10✔
56
        content_encoding: ContentEncodingRule {
10✔
57
            exp: ContentEncoding::Brotli,
10✔
58
            optional: false,
10✔
59
        },
10✔
60
        template: TemplateRule::Specified {
10✔
61
            exp_template_type: COMMENT_TEMPLATE_UUID_TYPE
10✔
62
                .try_into()
10✔
63
                .expect("Must be a valid UUID V4"),
10✔
64
        },
10✔
65
        doc_ref: RefRule::Specified {
10✔
66
            exp_ref_type: PROPOSAL_DOCUMENT_UUID_TYPE
10✔
67
                .try_into()
10✔
68
                .expect("Must be a valid UUID V4"),
10✔
69
            optional: false,
10✔
70
        },
10✔
71
        reply: ReplyRule::Specified {
10✔
72
            exp_reply_type: COMMENT_DOCUMENT_UUID_TYPE
10✔
73
                .try_into()
10✔
74
                .expect("Must be a valid UUID V4"),
10✔
75
            optional: true,
10✔
76
        },
10✔
77
        section: SectionRule::Specified { optional: true },
10✔
78
        category: CategoryRule::NotSpecified,
10✔
79
    };
10✔
80
    document_rules_map.insert(COMMENT_DOCUMENT_UUID_TYPE, comment_document_rules);
10✔
81

10✔
82
    let proposal_submission_action_rules = Rules {
10✔
83
        content_type: ContentTypeRule {
10✔
84
            exp: ContentType::Json,
10✔
85
        },
10✔
86
        content_encoding: ContentEncodingRule {
10✔
87
            exp: ContentEncoding::Brotli,
10✔
88
            optional: false,
10✔
89
        },
10✔
90
        template: TemplateRule::NotSpecified,
10✔
91
        category: CategoryRule::Specified { optional: true },
10✔
92
        doc_ref: RefRule::Specified {
10✔
93
            exp_ref_type: PROPOSAL_DOCUMENT_UUID_TYPE
10✔
94
                .try_into()
10✔
95
                .expect("Must be a valid UUID V4"),
10✔
96
            optional: false,
10✔
97
        },
10✔
98
        reply: ReplyRule::NotSpecified,
10✔
99
        section: SectionRule::NotSpecified,
10✔
100
    };
10✔
101

10✔
102
    document_rules_map.insert(
10✔
103
        PROPOSAL_ACTION_DOCUMENT_UUID_TYPE,
10✔
104
        proposal_submission_action_rules,
10✔
105
    );
10✔
106

10✔
107
    document_rules_map
10✔
108
}
10✔
109

110
/// A comprehensive document type based validation of the `CatalystSignedDocument`.
111
/// Return true if all signatures are valid, otherwise return false.
112
///
113
/// # Errors
114
/// If `provider` returns error, fails fast throwing that error.
115
pub async fn validate<Provider>(
9✔
116
    doc: &CatalystSignedDocument, provider: &Provider,
9✔
117
) -> anyhow::Result<bool>
9✔
118
where Provider: CatalystSignedDocumentProvider {
9✔
119
    let Ok(doc_type) = doc.doc_type() else {
9✔
UNCOV
120
        doc.report().missing_field(
×
UNCOV
121
            "type",
×
UNCOV
122
            "Can't get a document type during the validation process",
×
123
        );
×
124
        return Ok(false);
×
125
    };
126

127
    let Some(rules) = DOCUMENT_RULES.get(&doc_type.uuid()) else {
9✔
UNCOV
128
        doc.report().invalid_value(
×
UNCOV
129
            "`type`",
×
UNCOV
130
            &doc.doc_type()?.to_string(),
×
UNCOV
131
            "Must be a known document type value",
×
UNCOV
132
            "Unsupported document type",
×
UNCOV
133
        );
×
UNCOV
134
        return Ok(false);
×
135
    };
136
    rules.check(doc, provider).await
9✔
137
}
9✔
138

139
/// Verify document signatures.
140
/// Return true if all signatures are valid, otherwise return false.
141
///
142
/// # Errors
143
/// If `provider` returns error, fails fast throwing that error.
144
pub async fn validate_signatures(
6✔
145
    doc: &CatalystSignedDocument, provider: &impl VerifyingKeyProvider,
6✔
146
) -> anyhow::Result<bool> {
6✔
147
    let Ok(cose_sign) = doc.as_cose_sign() else {
6✔
UNCOV
148
        doc.report().other(
×
UNCOV
149
            "Cannot build a COSE sign object",
×
UNCOV
150
            "During encoding signed document as COSE SIGN",
×
UNCOV
151
        );
×
UNCOV
152
        return Ok(false);
×
153
    };
154

155
    let sign_rules = doc
6✔
156
        .signatures()
6✔
157
        .cose_signatures_with_kids()
6✔
158
        .map(|(signature, kid)| {
14✔
159
            validate_signature(&cose_sign, signature, kid, provider, doc.report())
14✔
160
        });
14✔
161

162
    let res = futures::future::join_all(sign_rules)
6✔
163
        .await
6✔
164
        .into_iter()
6✔
165
        .collect::<anyhow::Result<Vec<_>>>()?
6✔
166
        .iter()
6✔
167
        .all(|res| *res);
10✔
168

6✔
169
    Ok(res)
6✔
170
}
6✔
171

172
/// A single signature validation function
173
async fn validate_signature<Provider>(
14✔
174
    cose_sign: &CoseSign, signature: &CoseSignature, kid: &IdUri, provider: &Provider,
14✔
175
    report: &ProblemReport,
14✔
176
) -> anyhow::Result<bool>
14✔
177
where
14✔
178
    Provider: VerifyingKeyProvider,
14✔
179
{
14✔
180
    let Some(pk) = provider.try_get_key(kid).await? else {
14✔
181
        report.other(
8✔
182
            &format!("Missing public key for {kid}."),
8✔
183
            "During public key extraction",
8✔
184
        );
8✔
185
        return Ok(false);
8✔
186
    };
187

188
    let tbs_data = cose_sign.tbs_data(&[], signature);
6✔
189
    let Ok(signature_bytes) = signature.signature.as_slice().try_into() else {
6✔
UNCOV
190
        report.invalid_value(
×
UNCOV
191
            "cose signature",
×
UNCOV
192
            &format!("{}", signature.signature.len()),
×
UNCOV
193
            &format!("must be {}", ed25519_dalek::Signature::BYTE_SIZE),
×
UNCOV
194
            "During encoding cose signature to bytes",
×
UNCOV
195
        );
×
UNCOV
196
        return Ok(false);
×
197
    };
198

199
    let signature = ed25519_dalek::Signature::from_bytes(signature_bytes);
6✔
200
    if pk.verify_strict(&tbs_data, &signature).is_err() {
6✔
UNCOV
201
        report.functional_validation(
×
UNCOV
202
            &format!("Verification failed for signature with Key ID {kid}"),
×
UNCOV
203
            "During signature validation with verifying key",
×
UNCOV
204
        );
×
UNCOV
205
        return Ok(false);
×
206
    }
6✔
207

6✔
208
    Ok(true)
6✔
209
}
14✔
210

211
#[cfg(test)]
212
mod tests {
213
    use super::*;
214

215
    #[test]
216
    fn document_rules_init_test() {
1✔
217
        document_rules_init();
1✔
218
    }
1✔
219
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc