From 35f83253556771c6a683374b792b85c80ecb3497 Mon Sep 17 00:00:00 2001 From: bkioshn <35752733+bkioshn@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:17:44 +0700 Subject: [PATCH] feat: generate c509 certificate + `Extensions` msg fields (#518) * feat: setup c509 lib * fix: update toml file * feat: add earthfile * fix: remove blank line * docs: Add CatalystDataGatewayRepository docs (#388) * test: Fix tests after cat-gateway update. * docs: Add `CatalystDataGatewayRepository` usage examples. * docs: Move docs from README to code comment. * test: Update `CatalystDataGatewayRepository` tests. * test: Use Fake instead of Mock. * chore: Fix Markdown errors. * chore: Explicit use of `HttpStatus` codes. * adds one more worker and compression for catgateway logs (#400) * feat: Collect flutter code coverage (#404) * test: Fix tests after cat-gateway update. * docs: Add `CatalystDataGatewayRepository` usage examples. * docs: Move docs from README to code comment. * test: Update `CatalystDataGatewayRepository` tests. * test: Use Fake instead of Mock. * chore: Fix Markdown errors. * chore: Explicit use of `HttpStatus` codes. * chore(deps-dev): bump vite in /utilities/wallet-tester (#397) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.6 to 5.1.7. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.1.7/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.1.7/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj <38898766+apskhem@users.noreply.github.com> * feat: Get slot number by provided date-time endpoint (#387) * fix CardanoStakeAddress error handling * refactor, add sync_state_get endpoint * refactor types * refactor * add block_hash validation * wip * wip * wip * wip * add check_network fn * fix * fix schematisis test * try * wip * try * try * try * try * wip * try * try * fix * update Network * add test_utxo test * try * fix * try * fix * wip * fix * fix docket-compose.yml file * try * try * fix * try * try * try * try * wip * fix * wip * try * try * wip * try * try * revert * wip * wip * wip * fix * fix * fix * remove mithril_snapshot loader * wip * wip * wip * wip * wip * wip * wip * add stake addr bech32 encode utils function * wip * wip * update indexing of the utxo data * fix spelling * wip * wip * finish utxo test * fix deny * fix check * fix * fix * update earthly builder versions * wip * ignore test_utxo.py in CI * dont ignore tests * add date_time_to_slot_number_get endpoint * add sql queries * fix * update slot info, fix follower indexing block time issue * add previous slot info field * fix * refactor * fix sync_state_get * wip * fix check * try * fix * finish slot_info test, fix queries * fix * cleanup * wip * wip * wip * feat: RBAC Documentation Drafting (#332) * chore: wip * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * Update 0005-flutter-app.md * docs(docs): Use latest docs builders and fix concepts page * docs(cips): Start drafting the CIPS for milestone 2 * docs(cips): More text for RBAC metadata draft * docs(cips): WIP updates to draft cip for role registration * docs(cips): define draft specification for a ULID cbor tag * docs(cips): Further WIP edits to RBAC * docs(cips): fix ulid spec binary encoding reference * docs(cips): Add a tag to the epoch time. * docs(cips): Add CBOR tag cip for ED25519-BIP32 Keys, Derivation paths and Signatures * docs(cips): Properly define the field tags to use where known, and clean up Stake Address specification. * docs(cips): Fix nonce so its reliable without needing blockchain data * docs(cips): updates * docs(docs): Add CDDL definition for POC x509 envelope metadata * fix(vscode): update vscode extension recommendations * docs(cips): rbac x509 envelope fix * docs(cips): wip updates to high level docs * docs(cips): Add overview of cardano transaction processign and data * docs(cips): update cardano block to be complete for clarity * docs(cips): fix layout engine * docs(cips): wip cddl for envelope metadata * docs(cips): Add cddl specs and diagrams for x509 rbac registration work * docs(cips): Add full transaction/metadata relationship diagram * refactor(cips): reorganize documentation ready for drafting descriptive prose about the formats and uses * docs(cips): add cip draft for catalyst roles using the x509-rbac standard * docs(cips): Add c509 cddl with restrictions and enhancements for plutus usage * docs(cips): Metadata envelope specification draft complete * Update docs/src/catalyst-standards/draft-cips/c509-plutus-restricted-certificate/c509-cert-plutus-restricted.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/catalyst-standards/draft-cips/c509-plutus-restricted-certificate/c509-cert-plutus-restricted.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/catalyst-standards/draft-cips/x509-role-registration-metadata/x509-roles.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/catalyst-standards/draft-cips/x509-role-registration-metadata/x509-roles.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/catalyst-standards/draft-cips/x509-envelope-metadata/x509-envelope.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/catalyst-standards/draft-cips/x509-envelope-metadata/x509-envelope.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/catalyst-standards/draft-cips/x509-envelope-metadata/x509-envelope.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update docs/src/catalyst-standards/draft-cips/c509-plutus-restricted-certificate/c509-cert-plutus-restricted.cddl Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * docs(cips): Fix time and algorithm comments * build(frontend): Relax flutter/dart version requirements to last minor release * docs(cips): wip * fix(cips): rename draft x509 envelope CIP so its easier to identify * docs(cips): WIP updates to x509 roles * fix(cips): rename RBAC definition CIP draft so its easier to identify * docs(cips): x509 certificate registration format fully defined * docs(cips): Document the restricted plutus subset. * docs(cips): Add document detailing how CIP-30 is used to sign the transaction * fix(cips): remove trailing spaces * fix(cips): Fix line lengths * fix(cips): Correct spelling * fix(cips): spelling * fix(frontend): revert changes to flutter/dart versions * fix(frontend): more flutter/dart version corrections * fix(frontend): Revert flutter files to same as main branch * fix(frontend): revert more flutter .yml files to those in main * fix(cips): Fix links between files * docs(cips): Add catalyst specific role registration documentation * docs(spelling): fix spelling --------- Co-authored-by: minikin Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * ci: configure static analysis & code formatting check * style: formatting * fix: revert browser installation scripts * style: format code * ci: optimize directions include in repo-catalyst-voices-all artifact to include only needed ones * refactor: remove empty tests * ci: add melos script to generate test reports * ci: melos script to generate test report * style: revert previously generated files formatting * style: format files * ci: update melos to exclude generated code form code coverage * ci: cleanup build script * ci: generate multiple junit test reports and save them at /test_reports * ci: depend on melos analyze instead of custom command * docs: improve melos docs * ci: remove unused melos scripts * ci: format files in test & integration_test directories * ci: break code to make sure CI will report failure for demonstration purposes * style: fix lint issues * ci: change WORKDIR after creating the user to make sure it will be owned by that user * ci: restore root user * Revert "Merge branch 'main' into feat/collect-flutter-code-coverage" This reverts commit d0f66b2c1e7228141009ea153b35b488bf7922b1, reversing changes made to 39ce4017c3624a807b93116633ac1b249f2d86bd. * style: format code * ci: revert test-unit target name --------- Signed-off-by: dependabot[bot] Co-authored-by: Lucio Baglione Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj <38898766+apskhem@users.noreply.github.com> Co-authored-by: Alex Pozhylenkov Co-authored-by: Steven Johnson Co-authored-by: minikin Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * refactor: update schema_validation check (#414) * update schema_validation check * wip * fix * fix: update wasm testing * fix: remove clippy toml * fix: update cargo toml * fix: spelling * fix: revert test report * chore: add comment * chore: update message * fix: update cargo.toml * chore: fix format * fix: linter * fix: update cargo toml * fix: update cargo toml * fix: update project dic * fix: remove wasm-pack install * feat: add config * fix: earthly for c509 * feat: add regex dependency * feat: implement c509 type name * fix: clean up lib.rs * fix: add c509 cert module * feat: add x690 der * feat: wip TLV extract value * chore: add comment on TLV length * fix (wip): update der parser * fix (wip): update der parser * fix (wip): update der parser * fix (wip): update der parser * fix (wip): dependencies * feat(wip): add c509 enum * feat(wip): add cbor encode helper * feat(wip): add c509 cert helper * fix(wip): modify lib * feat(wip): add subject public key encoder * fix: add thiserror * fix: cbor encode biguint and type name * fix: cbor encode time * fix: cleanup * fix: add oid crate * feat: add extensions * feat: add alt name * chore: remove fixme * fix: clean up c509 enum * fix: rewrite cbor encoder * fix: move c509 crate folder * feat: add gitignore * fix: cargo toml * fix: cbor encoder * fix: remove der encoding * feat: add eid encoder * fix: ssp encoder * feat: create genc509cert function * feat: add sigalgo registry * fix: altname type visibility * fix: altname encoder * feat: add extension encoding func * fix: lib.rs * fix: gen c509 function * fix: cbor encoder * fix: alt name * fix: extensions * docs: update c509 docs * fix: name * chore: remove unnecessary comments * fix: macro definitions location * fix: earthly version and cspell * fix: update rust config file to match cat-ci stdcfg * fix: macro definitions location * feat: add oid encode/decode * fix: naming and test * fix: naming * fix: implement encode and decode trait * fix: test * feat: add OID PEN encode and decode * fix: merge * fix: merge * wip * fix: pen decode and encode * fix: rewrite PEN encode and decode * chore: add comments * fix: update c509 extensions * fix: add once cell * fix: oid registry and oid extension implementation * fix (wip): extension value encoding * fix: value encode and test * fix: add decode extension * fix: extension decode value * fix: delete files * fix(wip): handle extensions * fix: disable extensions encode and decode * fix: cargo toml and add num-derive * fix(wip): refactor tables * feat(wip): add encode decode general name * fix: name and type * fix(wip): decode and encode generalnames * fix(wip): gn value encode decode * fix: remove unused crate * fix: gns encode and decode * fix: remove unused table * fix: oid lifetime to static * fix: create extensions mod * fix: general name visibility * feat: add alt name * feat: add alternative name encode and decode * fix: minor fixes + cleanup * chore: use super in test * fix: remove unused ctx type C * fix(wip): extensions * feat: add strum crate * fix: tables * fix: extensions * fix: general name * fix: table * fix: alt name * fix: extensions * fix: refactor * feat(wip): add eid * chore: spelling * chore: spelling * chore: revert back to main * chore: restore file in main * fix: update rust stdconfig and earthfile * fix: earthfile * fix: earthfile * fix: earthfile * fix: wrong earthfile * fix: doc link * Update catalyst-gateway-crates/c509-certificate/src/c509_extensions/mod.rs Co-authored-by: Apisit Ritreungroj <38898766+apskhem@users.noreply.github.com> * Update catalyst-gateway-crates/c509-certificate/src/c509_extensions/mod.rs Co-authored-by: Apisit Ritreungroj <38898766+apskhem@users.noreply.github.com> * bump cat-ci * bump deny.toml * fix fmt * fix machete * fix * fix: minor fix and update doc * chore: code format * bump cat-ci * update build * fix: doc * test: new cliipy ci fix * test: new cliipy ci fix fix cargo toml * fix: orphan module * fix: remove EID * fix: comment out wasm pack build * fix: add chaining * fix: linter * fix: PR comment * fix: move code section * chore: fix name and comment * fix: format * test: add zero extensions * fix: format * fix: gn type naming * chore: remove unused file * test: add test case for multiples gn type * fix: dns gn decode special case * fix: format * fix: data tables * fix: alt name test case * fix: cspell * fix: table functions * fix: format * chore: fix test comment --------- Signed-off-by: dependabot[bot] Co-authored-by: Lucio Baglione Co-authored-by: Stefano Cunego <93382903+kukkok3@users.noreply.github.com> Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj <38898766+apskhem@users.noreply.github.com> Co-authored-by: Alex Pozhylenkov Co-authored-by: Steven Johnson Co-authored-by: minikin Co-authored-by: Steven Johnson --- .config/dictionaries/project.dic | 10 + .../c509-certificate/.cargo/config.toml | 93 +++++ .../c509-certificate/.config/nextest.toml | 49 +++ .../c509-certificate/.gitignore | 11 + .../c509-certificate/Cargo.toml | 62 ++++ .../c509-certificate/Earthfile | 35 ++ .../c509-certificate/clippy.toml | 1 + .../c509-certificate/deny.toml | 117 +++++++ .../c509-certificate/rust-toolchain.toml | 3 + .../c509-certificate/rustfmt.toml | 68 ++++ .../src/c509_extensions/alt_name.rs | 159 +++++++++ .../src/c509_extensions/extension/data.rs | 115 +++++++ .../src/c509_extensions/extension/mod.rs | 308 +++++++++++++++++ .../src/c509_extensions/mod.rs | 223 ++++++++++++ .../src/c509_general_names/data.rs | 132 ++++++++ .../src/c509_general_names/general_name.rs | 319 ++++++++++++++++++ .../src/c509_general_names/mod.rs | 167 +++++++++ .../other_name_hw_module.rs | 53 +++ .../c509-certificate/src/c509_oid.rs | 212 ++++++++++++ .../c509-certificate/src/lib.rs | 59 ++++ .../c509-certificate/src/tables.rs | 78 +++++ catalyst-gateway/deny.toml | 2 +- 22 files changed, 2275 insertions(+), 1 deletion(-) create mode 100644 catalyst-gateway-crates/c509-certificate/.cargo/config.toml create mode 100644 catalyst-gateway-crates/c509-certificate/.config/nextest.toml create mode 100644 catalyst-gateway-crates/c509-certificate/.gitignore create mode 100644 catalyst-gateway-crates/c509-certificate/Cargo.toml create mode 100644 catalyst-gateway-crates/c509-certificate/Earthfile create mode 100644 catalyst-gateway-crates/c509-certificate/clippy.toml create mode 100644 catalyst-gateway-crates/c509-certificate/deny.toml create mode 100644 catalyst-gateway-crates/c509-certificate/rust-toolchain.toml create mode 100644 catalyst-gateway-crates/c509-certificate/rustfmt.toml create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_extensions/alt_name.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/data.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/mod.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_extensions/mod.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_general_names/data.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_general_names/general_name.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_general_names/mod.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_general_names/other_name_hw_module.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/c509_oid.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/lib.rs create mode 100644 catalyst-gateway-crates/c509-certificate/src/tables.rs diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index 27ddb8c3c9b..c120bab63e7 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -4,7 +4,9 @@ addrr adminer afinet androidx +anypolicy appspot +Arissara Arbritrary ARGB asmjs @@ -16,6 +18,8 @@ autorecalculates autoresizing backendpython bech +bimap +bindgen bkioshn bluefireteam BROTLI @@ -25,7 +29,9 @@ Catalyst CBOR cborg CEST +cdylib cfbundle +Chotivichit chromedriver chrono ciphertext @@ -116,6 +122,7 @@ metadatums metamap mgrybyk miniaturizable +minicbor mithril mitigations moderations @@ -130,6 +137,7 @@ NDEBUG netifas netkey nextest +OCSP Oleksandr onboarded oneshot @@ -144,6 +152,7 @@ plpgsql podfile podhelper postcss +Precertificate preprod projectcatalyst Prokhorenko @@ -167,6 +176,7 @@ rustc rustdoc rustdocflags rustflags +rustfmt rustls rxdart saibatizoku diff --git a/catalyst-gateway-crates/c509-certificate/.cargo/config.toml b/catalyst-gateway-crates/c509-certificate/.cargo/config.toml new file mode 100644 index 00000000000..2764f1df4e2 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/.cargo/config.toml @@ -0,0 +1,93 @@ +# Use MOLD linker where possible, but ONLY in CI applicable targets. + +# Configure how Docker container targets build. + +# If you want to customize these targets for a local build, then customize them in your: +# $CARGO_HOME/config.toml +# NOT in the project itself. +# These targets are ONLY the targets used by CI and inside docker builds. + +# DO NOT remove `"-C", "target-feature=+crt-static"` from the rustflags for these targets. + +# Should be the default to have fully static rust programs in CI +[target.x86_64-unknown-linux-musl] +linker = "clang" +rustflags = [ + "-C", "link-arg=-fuse-ld=/usr/bin/mold", + "-C", "target-feature=-crt-static" +] + +# Should be the default to have fully static rust programs in CI +[target.aarch64-unknown-linux-musl] +linker = "clang" +rustflags = [ + "-C", "link-arg=-fuse-ld=/usr/bin/mold", + "-C", "target-feature=-crt-static" +] + +[build] +rustflags = [] +rustdocflags = [ + "--enable-index-page", + "-Z", + "unstable-options", +] + +[profile.dev] +opt-level = 1 +debug = true +debug-assertions = true +overflow-checks = true +lto = false +panic = "unwind" +incremental = true +codegen-units = 256 + +[profile.release] +opt-level = 3 +debug = false +debug-assertions = false +overflow-checks = false +lto = "thin" +panic = "unwind" +incremental = false +codegen-units = 16 + +[profile.test] +opt-level = 3 +debug = true +lto = false +debug-assertions = true +incremental = true +codegen-units = 256 + +[profile.bench] +opt-level = 3 +debug = false +debug-assertions = false +overflow-checks = false +lto = "thin" +incremental = false +codegen-units = 16 + +[alias] +lint = "clippy --all-targets" +lintfix = "clippy --all-targets --fix --allow-dirty" +lint-vscode = "clippy --message-format=json-diagnostic-rendered-ansi --all-targets" + +docs = "doc --release --no-deps --document-private-items --bins --lib --examples" +# nightly docs build broken... when they are'nt we can enable these docs... --unit-graph --timings=html,json -Z unstable-options" +testunit = "nextest run --release --bins --lib --tests --benches --no-fail-fast -P ci" +testcov = "llvm-cov nextest --release --bins --lib --tests --benches --no-fail-fast -P ci" +testdocs = "test --doc --release" + +# Rust formatting, MUST be run with +nightly +fmtchk = "fmt -- --check -v --color=always" +fmtfix = "fmt -- -v" + +[term] +quiet = false # whether cargo output is quiet +verbose = false # whether cargo provides verbose output +color = "auto" # whether cargo colorizes output use `CARGO_TERM_COLOR="off"` to disable. +progress.when = "never" # whether cargo shows progress bar +progress.width = 80 # width of progress bar \ No newline at end of file diff --git a/catalyst-gateway-crates/c509-certificate/.config/nextest.toml b/catalyst-gateway-crates/c509-certificate/.config/nextest.toml new file mode 100644 index 00000000000..be3673830bb --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/.config/nextest.toml @@ -0,0 +1,49 @@ +# cspell: words scrollability testcase +[store] +# The directory under the workspace root at which nextest-related files are +# written. Profile-specific storage is currently written to dir/. +# dir = "target/nextest" + +[profile.default] +# Print out output for failing tests as soon as they fail, and also at the end +# of the run (for easy scrollability). +failure-output = "immediate-final" + +# Do not cancel the test run on the first failure. +fail-fast = true + +status-level = "all" +final-status-level = "all" + +[profile.ci] +# Print out output for failing tests as soon as they fail, and also at the end +# of the run (for easy scrollability). +failure-output = "immediate-final" +# Do not cancel the test run on the first failure. +fail-fast = false + +status-level = "all" +final-status-level = "all" + + +[profile.ci.junit] +# Output a JUnit report into the given file inside 'store.dir/'. +# If unspecified, JUnit is not written out. + +path = "junit.xml" + +# The name of the top-level "report" element in JUnit report. If aggregating +# reports across different test runs, it may be useful to provide separate names +# for each report. +report-name = "nextest" + +# Whether standard output and standard error for passing tests should be stored in the JUnit report. +# Output is stored in the and elements of the element. +store-success-output = true + +# Whether standard output and standard error for failing tests should be stored in the JUnit report. +# Output is stored in the and elements of the element. +# +# Note that if a description can be extracted from the output, it is always stored in the +# element. +store-failure-output = true diff --git a/catalyst-gateway-crates/c509-certificate/.gitignore b/catalyst-gateway-crates/c509-certificate/.gitignore new file mode 100644 index 00000000000..c1b3e696893 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/.gitignore @@ -0,0 +1,11 @@ +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb \ No newline at end of file diff --git a/catalyst-gateway-crates/c509-certificate/Cargo.toml b/catalyst-gateway-crates/c509-certificate/Cargo.toml new file mode 100644 index 00000000000..f596dff281f --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "c509-certificate" +description = "c509 certificate implementation" +keywords = ["cardano", "catalyst", "c509 certificate", "certificate", "x509"] +version = "0.0.1" +authors = [ + "Arissara Chotivichit " +] +homepage = "https://input-output-hk.github.io/catalyst-voices" +repository = "https://github.com/input-output-hk/catalyst-voices" +license = "MIT OR Apache-2.0" +edition = "2021" + +[lints.rust] +warnings = "deny" +missing_docs = "deny" +let_underscore_drop = "deny" +non_ascii_idents = "deny" +single_use_lifetimes = "deny" +trivial_casts = "deny" +trivial_numeric_casts = "deny" + +[lints.rustdoc] +broken_intra_doc_links = "deny" +invalid_codeblock_attributes = "deny" +invalid_html_tags = "deny" +invalid_rust_codeblocks = "deny" +bare_urls = "deny" +unescaped_backticks = "deny" + +[lints.clippy] +pedantic = { level = "deny", priority = -1 } +unwrap_used = "deny" +expect_used = "deny" +exit = "deny" +get_unwrap = "deny" +index_refutable_slice = "deny" +indexing_slicing = "deny" +match_on_vec_items = "deny" +match_wild_err_arm = "deny" +missing_panics_doc = "deny" +panic = "deny" +string_slice = "deny" +unchecked_duration_subtraction = "deny" +unreachable = "deny" +missing_docs_in_private_items = "deny" + +[dependencies] +wasm-bindgen = "0.2" +minicbor = { version = "0.24", features = ["std"] } +hex = "0.4.3" +oid = "0.2.1" +oid-registry = "0.7.0" +asn1-rs = "0.6.0" +anyhow = "1.0.86" +bimap = "0.6.3" +once_cell = "1.19.0" +strum = "0.26.3" +strum_macros = "0.26.3" + +[package.metadata.cargo-machete] +ignored = ["strum"] diff --git a/catalyst-gateway-crates/c509-certificate/Earthfile b/catalyst-gateway-crates/c509-certificate/Earthfile new file mode 100644 index 00000000000..57d036136ce --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/Earthfile @@ -0,0 +1,35 @@ +VERSION 0.8 + +IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.1.15 AS rust-ci + +# builder : Set up our target toolchains, and copy our files. +builder: + DO rust-ci+SETUP + + COPY --dir .cargo .config Cargo.* clippy.toml deny.toml rustfmt.toml src . + + +check: + FROM +builder + + DO rust-ci+EXECUTE --cmd="/scripts/std_checks.py" + +build: + FROM +builder + + DO rust-ci+EXECUTE \ + --cmd="/scripts/std_build.py" \ + --args1="--libs=c509-certificate" + + # RUN wasm-pack build --target web + +package: + FROM +build + + # AS LOCAL is for local testing only, will be removed in the future + SAVE ARTIFACT ./pkg c509-certificate-binding + +# # local-build: Build the service and save it locally +# local-build: +# FROM +build +# SAVE ARTIFACT c509-certificate-binding AS LOCAL ./pkg \ No newline at end of file diff --git a/catalyst-gateway-crates/c509-certificate/clippy.toml b/catalyst-gateway-crates/c509-certificate/clippy.toml new file mode 100644 index 00000000000..6933b816419 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/clippy.toml @@ -0,0 +1 @@ +allow-expect-in-tests = true diff --git a/catalyst-gateway-crates/c509-certificate/deny.toml b/catalyst-gateway-crates/c509-certificate/deny.toml new file mode 100644 index 00000000000..d290f243fdc --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/deny.toml @@ -0,0 +1,117 @@ +# cspell: words msvc, wasip, RUSTSEC, rustls, libssh, reqwest, tinyvec, Leay, webpki + +[graph] +# cargo-deny is really only ever intended to run on the "normal" tier-1 targets +targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "wasm32-unknown-unknown", + "wasm32-wasip1", + "wasm32-wasip2", +] + +[advisories] +version = 2 +ignore = [ + { id = "RUSTSEC-2020-0168", reason = "`mach` is used by wasmtime and we have no control over that." }, + { id = "RUSTSEC-2021-0145", reason = "we don't target windows, and don;t use a custom global allocator." }, +] + +[bans] +multiple-versions = "warn" +wildcards = 'deny' +deny = [ + # { crate = "git2", use-instead = "gix" }, + { crate = "openssl", use-instead = "rustls" }, + { crate = "openssl-sys", use-instead = "rustls" }, + "libssh2-sys", + # { crate = "cmake", use-instead = "cc" }, + # { crate = "windows", reason = "bloated and unnecessary", use-instead = "ideally inline bindings, practically, windows-sys" }, +] +skip = [ + # { crate = "bitflags@1.3.2", reason = "https://github.com/seanmonstar/reqwest/pull/2130 should be in the next version" }, + # { crate = "winnow@0.5.40", reason = "gix 0.59 was yanked, see https://github.com/Byron/gitoxide/issues/1309" }, + # { crate = "heck@0.4.1", reason = "strum_macros uses this old version" }, + # { crate = "base64@0.21.7", reason = "gix-transport pulls in this old version, as well as a newer version via reqwest" }, + # { crate = "byte-array-literalsase64@0.21.7", reason = "gix-transport pulls in this old version, as well as a newer version via reqwest" }, +] +skip-tree = [ + { crate = "windows-sys@0.48.0", reason = "a foundational crate for many that bumps far too frequently to ever have a shared version" }, +] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" + +# List of URLs for allowed Git repositories +allow-git = [ + "https://github.com/input-output-hk/hermes.git", + "https://github.com/input-output-hk/catalyst-pallas.git", + "https://github.com/bytecodealliance/wasmtime", + "https://github.com/aldanor/hdf5-rust", +] + +[licenses] +version = 2 +# Don't warn if a listed license isn't found +unused-allowed-license="allow" +# We want really high confidence when inferring licenses from text +confidence-threshold = 0.93 +allow = [ + "MIT", + "Apache-2.0", + "Unicode-DFS-2016", + "BSD-3-Clause", + "BSD-2-Clause", + "BlueOak-1.0.0", + "Apache-2.0 WITH LLVM-exception", + "CC0-1.0", + "ISC", + "Unicode-3.0", + "MPL-2.0", +] +exceptions = [ + #{ allow = ["Zlib"], crate = "tinyvec" }, + #{ allow = ["Unicode-DFS-2016"], crate = "unicode-ident" }, + #{ allow = ["OpenSSL"], crate = "ring" }, +] + +[[licenses.clarify]] +crate = "byte-array-literals" +expression = "Apache-2.0 WITH LLVM-exception" +license-files = [{ path = "../../../LICENSE", hash = 0x001c7e6c }] + +[[licenses.clarify]] +crate = "hdf5-src" +expression = "MIT" +license-files = [{ path = "../LICENSE-MIT", hash = 0x001c7e6c }] + +[[licenses.clarify]] +crate = "ring" +expression = "MIT" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + +# SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses +# https://spdx.org/licenses/OpenSSL.html +# ISC - Both BoringSSL and ring use this for their new files +# MIT - "Files in third_party/ have their own licenses, as described therein. The MIT +# license, for third_party/fiat, which, unlike other third_party directories, is +# compiled into non-test libraries, is included below." +# OpenSSL - Obviously +#expression = "ISC AND MIT AND OpenSSL" +#license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + +#[[licenses.clarify]] +#crate = "webpki" +#expression = "ISC" +#license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] + +# Actually "ISC-style" +#[[licenses.clarify]] +#crate = "rustls-webpki" +#expression = "ISC" +#license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] \ No newline at end of file diff --git a/catalyst-gateway-crates/c509-certificate/rust-toolchain.toml b/catalyst-gateway-crates/c509-certificate/rust-toolchain.toml new file mode 100644 index 00000000000..08feed89d60 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.78" +profile = "default" \ No newline at end of file diff --git a/catalyst-gateway-crates/c509-certificate/rustfmt.toml b/catalyst-gateway-crates/c509-certificate/rustfmt.toml new file mode 100644 index 00000000000..1a0573b222b --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/rustfmt.toml @@ -0,0 +1,68 @@ +# Enable unstable features: +# * imports_indent +# * imports_layout +# * imports_granularity +# * group_imports +# * reorder_impl_items +# * trailing_comma +# * where_single_line +# * wrap_comments +# * comment_width +# * blank_lines_upper_bound +# * condense_wildcard_suffixes +# * force_multiline_blocks +# * format_code_in_doc_comments +# * format_generated_files +# * hex_literal_case +# * inline_attribute_width +# * normalize_comments +# * normalize_doc_attributes +# * overflow_delimited_expr +unstable_features = true + +# Compatibility: +edition = "2021" + +# Tabs & spaces - Defaults, listed for clarity +tab_spaces = 4 +hard_tabs = false + +# Commas. +trailing_comma = "Vertical" +match_block_trailing_comma = true + +# General width constraints. +max_width = 100 + +# Comments: +normalize_comments = true +normalize_doc_attributes = true +wrap_comments = true +comment_width = 90 # small excess is okay but prefer 80 +format_code_in_doc_comments = true +format_generated_files = false + +# Imports. +imports_indent = "Block" +imports_layout = "Mixed" +group_imports = "StdExternalCrate" +reorder_imports = true +imports_granularity = "Crate" + +# Arguments: +use_small_heuristics = "Default" +fn_params_layout = "Compressed" +overflow_delimited_expr = true +where_single_line = true + +# Misc: +inline_attribute_width = 0 +blank_lines_upper_bound = 1 +reorder_impl_items = true +use_field_init_shorthand = true +force_multiline_blocks = true +condense_wildcard_suffixes = true +hex_literal_case = "Upper" + +# Ignored files: +ignore = [] \ No newline at end of file diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_extensions/alt_name.rs b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/alt_name.rs new file mode 100644 index 00000000000..451bfa55ccb --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/alt_name.rs @@ -0,0 +1,159 @@ +//! C509 Alternative Name uses for Subject Alternative Name extension and +//! Issuer Alternative Name extension. + +use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; + +use crate::c509_general_names::{ + general_name::{GeneralName, GeneralNameTypeRegistry, GeneralNameValue}, + GeneralNames, +}; + +/// Alternative Name extension. +/// Can be interpreted as a `GeneralNames / text` +#[derive(Debug, Clone, PartialEq)] +pub struct AlternativeName(GeneralNamesOrText); + +impl AlternativeName { + /// Create a new instance of `AlternativeName` given value. + #[must_use] + pub fn new(value: GeneralNamesOrText) -> Self { + Self(value) + } +} + +impl Encode<()> for AlternativeName { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + self.0.encode(e, ctx) + } +} + +impl Decode<'_, ()> for AlternativeName { + fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { + GeneralNamesOrText::decode(d, ctx).map(AlternativeName::new) + } +} + +// ------------------GeneralNamesOrText-------------------- + +/// Enum for type that can be a `GeneralNames` or a text use in `AlternativeName`. +#[derive(Debug, Clone, PartialEq)] +pub enum GeneralNamesOrText { + /// A value of `GeneralNames`. + GeneralNames(GeneralNames), + /// A text string. + Text(String), +} + +impl Encode<()> for GeneralNamesOrText { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + match self { + GeneralNamesOrText::GeneralNames(gns) => { + let gn = gns + .get_gns() + .first() + .ok_or(minicbor::encode::Error::message("GeneralNames is empty"))?; + // Check whether there is only 1 item in the array which is a DNSName + if gns.get_gns().len() == 1 && gn.get_gn_type().is_dns_name() { + gn.get_gn_value().encode(e, ctx)?; + } else { + gns.encode(e, ctx)?; + } + }, + GeneralNamesOrText::Text(text) => { + e.str(text)?; + }, + } + Ok(()) + } +} + +impl Decode<'_, ()> for GeneralNamesOrText { + fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { + match d.datatype()? { + // If it is a string it is a GeneralNames with only 1 DNSName + minicbor::data::Type::String => { + let gn_dns = GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text(d.str()?.to_string()), + ); + let mut gns = GeneralNames::new(); + gns.add_gn(gn_dns); + Ok(GeneralNamesOrText::GeneralNames(gns)) + }, + minicbor::data::Type::Array => { + Ok(GeneralNamesOrText::GeneralNames(GeneralNames::decode( + d, ctx, + )?)) + }, + _ => { + Err(minicbor::decode::Error::message( + "Invalid type for AlternativeName", + )) + }, + } + } +} + +// ------------------Test---------------------- + +#[cfg(test)] +mod test_alt_name { + use super::*; + use crate::c509_general_names::general_name::{ + GeneralName, GeneralNameTypeRegistry, GeneralNameValue, + }; + + #[test] + fn encode_only_dns() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + let mut gns = GeneralNames::new(); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text("example.com".to_string()), + )); + let alt_name = AlternativeName::new(GeneralNamesOrText::GeneralNames(gns)); + alt_name + .encode(&mut encoder, &mut ()) + .expect("Failed to encode AlternativeName"); + // "example.com": 0x6b6578616d706c652e636f6d + assert_eq!(hex::encode(buffer.clone()), "6b6578616d706c652e636f6d"); + + let mut decoder = Decoder::new(&buffer); + let decoded_alt_name = AlternativeName::decode(&mut decoder, &mut ()) + .expect("Failed to decode Alternative Name"); + assert_eq!(decoded_alt_name, alt_name); + } + + #[test] + fn encode_decode_text() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let alt_name = AlternativeName::new(GeneralNamesOrText::Text("example.com".to_string())); + alt_name + .encode(&mut encoder, &mut ()) + .expect("Failed to encode AlternativeName"); + // "example.com": 0x6b6578616d706c652e636f6d + assert_eq!(hex::encode(buffer.clone()), "6b6578616d706c652e636f6d"); + + // If only text, it should be GeneralNames with only 1 DNSName + let mut gns = GeneralNames::new(); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text("example.com".to_string()), + )); + + let mut decoder = Decoder::new(&buffer); + let decoded_alt_name = AlternativeName::decode(&mut decoder, &mut ()) + .expect("Failed to decode Alternative Name"); + assert_eq!( + decoded_alt_name, + AlternativeName::new(GeneralNamesOrText::GeneralNames(gns)) + ); + } +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/data.rs b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/data.rs new file mode 100644 index 00000000000..9bf2b5a8fc8 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/data.rs @@ -0,0 +1,115 @@ +//! Extension data provides a necessary information for encoding and decoding of C509 +//! Extension. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! Section 9.4 C509 Extensions Registry for more information. + +// cspell: words Evt + +use std::collections::HashMap; + +use anyhow::Error; +use asn1_rs::{oid, Oid}; +use once_cell::sync::Lazy; + +use super::ExtensionValueType; +use crate::tables::IntegerToOidTable; + +/// Type of `Extension` data +/// Int | OID | Type | Name +type ExtensionDataTuple = (i16, Oid<'static>, ExtensionValueType, &'static str); + +/// Create a type alias for `ExtensionValueType` +type Evt = ExtensionValueType; + +/// `Extension` data table +#[rustfmt::skip] +const EXTENSION_DATA: [ExtensionDataTuple; 25] = [ + // Int | OID | Type | Name + ( 1, oid!(2.5.29 .14), Evt::Bytes, "Subject Key Identifier"), + ( 2, oid!(2.5.29 .15), Evt::Int, "Key Usage"), + ( 3, oid!(2.5.29 .17), Evt::AlternativeName, "Subject Alternative Name"), + ( 4, oid!(2.5.29 .19), Evt::Int, "Basic Constraints"), + ( 5, oid!(2.5.29 .31), Evt::Unsupported, "CRL Distribution Points"), + ( 6, oid!(2.5.29 .32), Evt::Unsupported, "Certificate Policies"), + ( 7, oid!(2.5.29 .35), Evt::Unsupported, "Authority Key Identifier"), + ( 8, oid!(2.5.29 .37), Evt::Unsupported, "Extended Key Usage"), + ( 9, oid!(1.3.6 .1 .5 .5 .7 .1 .1), Evt::Unsupported, "Authority Information Access"), + (10, oid!(1.3.6 .1 .4 .1 .11129 .2 .4 .2), Evt::Unsupported, "Signed Certificate Timestamp List"), + (24, oid!(2.5.29 .9), Evt::Unsupported, "Subject Directory Attributes"), + (25, oid!(2.5.29 .18), Evt::AlternativeName, "Issuer Alternative Name"), + (26, oid!(2.5.29 .30), Evt::Unsupported, "Name Constraints"), + (27, oid!(2.5.29 .33), Evt::Unsupported, "Policy Mappings"), + (28, oid!(2.5.29 .36), Evt::Unsupported, "Policy Constraints"), + (29, oid!(2.5.29 .46), Evt::Unsupported, "Freshest CRL"), + (30, oid!(2.5.29 .54), Evt::Int, "Inhibit anyPolicy"), + (31, oid!(1.3.6 .1 .5 .5 .7 .1 .11), Evt::Unsupported, "Subject Information Access"), + (32, oid!(1.3.6 .1 .5 .5 .7 .1 .7), Evt::Unsupported, "IP Resources"), + (33, oid!(1.3.6 .1 .5 .5 .7 .1 .7), Evt::Unsupported, "AS Resource"), + (34, oid!(1.3.6 .1 .5 .5 .7 .1 .28), Evt::Unsupported, "IP Resources v2"), + (35, oid!(1.3.6 .1 .5 .5 .7 .1 .29), Evt::Unsupported, "AS Resources v2"), + (36, oid!(1.3.6 .1 .5 .5 .7 .1 .2), Evt::Unsupported, "Biometric Information"), + (37, oid!(1.3.6 .1 .4 .1 .11129 .2 .4 .4), Evt::Unsupported, "Precertificate Signing Certificate"), + (38, oid!(1.3.6 .1 .5 .5 .7 .48 .1 .5), Evt::Unsupported, "OCSP No Check"), +]; + +/// A struct of data that contains lookup tables for `Extension`. +pub(crate) struct ExtensionData { + /// A table of integer to OID, provide a bidirectional lookup. + int_to_oid_table: IntegerToOidTable, + /// A table of integer to `ExtensionValueType`, provide a lookup for `Extension` value + /// type. + int_to_type_table: HashMap, +} + +impl ExtensionData { + /// Get the `IntegerToOidTable`. + pub(crate) fn get_int_to_oid_table(&self) -> &IntegerToOidTable { + &self.int_to_oid_table + } + + /// Get the `int_to_type_table` + pub(crate) fn get_int_to_type_table(&self) -> &HashMap { + &self.int_to_type_table + } +} + +/// Define static lookup for extensions table +static EXTENSIONS_TABLES: Lazy = Lazy::new(|| { + let mut int_to_oid_table = IntegerToOidTable::new(); + let mut int_to_type_table = HashMap::::new(); + + for data in EXTENSION_DATA { + int_to_oid_table.add(data.0, data.1); + int_to_type_table.insert(data.0, data.2); + } + + ExtensionData { + int_to_oid_table, + int_to_type_table, + } +}); + +/// Static reference to the `ExtensionData` lookup table. +pub(crate) static EXTENSIONS_LOOKUP: &Lazy = &EXTENSIONS_TABLES; + +/// Get the OID from the int value. +pub(crate) fn get_oid_from_int(i: i16) -> Result, Error> { + EXTENSIONS_TABLES + .get_int_to_oid_table() + .get_map() + .get_by_left(&i) + .ok_or(Error::msg(format!( + "OID not found in the extension registry table given int {i}" + ))) + .cloned() +} + +/// Get the extension value type from the int value. +pub(crate) fn get_extension_type_from_int(i: i16) -> Result { + EXTENSIONS_TABLES + .get_int_to_type_table() + .get(&i) + .ok_or(Error::msg(format!( + "Extension value type not found in the extension registry table given int {i}" + ))) + .cloned() +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/mod.rs b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/mod.rs new file mode 100644 index 00000000000..bb099acd2c8 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/extension/mod.rs @@ -0,0 +1,308 @@ +//! C509 Extension use to construct an Extensions message field for C509 Certificate. + +mod data; +use std::fmt::Debug; + +use asn1_rs::Oid; +use data::{get_extension_type_from_int, get_oid_from_int, EXTENSIONS_LOOKUP}; +use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; +use strum_macros::EnumDiscriminants; + +use super::alt_name::AlternativeName; +use crate::c509_oid::{C509oid, C509oidRegistered}; + +/// A struct of C509 `Extension` +#[derive(Debug, Clone, PartialEq)] +pub struct Extension { + /// The registered OID of the `Extension`. + registered_oid: C509oidRegistered, + /// The critical flag of the `Extension` negative if critical is true, otherwise + /// positive. + critical: bool, + /// The value of the `Extension` in `ExtensionValue`. + value: ExtensionValue, +} + +impl Extension { + /// Create a new instance of `Extension` using `OID` and value. + #[must_use] + pub fn new(oid: Oid<'static>, value: ExtensionValue, critical: bool) -> Self { + Self { + registered_oid: C509oidRegistered::new(oid, EXTENSIONS_LOOKUP.get_int_to_oid_table()) + .pen_encoded(), + critical, + value, + } + } + + /// Get the value of the `Extension` in `ExtensionValue`. + #[must_use] + pub fn get_value(&self) -> &ExtensionValue { + &self.value + } + + /// Get the critical flag of the `Extension`. + #[must_use] + pub fn get_critical(&self) -> bool { + self.critical + } + + /// Get the registered OID of the `Extension`. + #[must_use] + pub fn get_registered_oid(&self) -> &C509oidRegistered { + &self.registered_oid + } +} + +impl Encode<()> for Extension { + // Extension can be encoded as: + // - (extensionID: int, extensionValue: any) + // - (extensionID: ~oid, ? critical: true, extensionValue: bytes) + // - (extensionID: pen, ? critical: true, extensionValue: bytes) + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + // Handle CBOR int based on OID mapping + if let Some(&mapped_oid) = self + .registered_oid + .get_table() + .get_map() + .get_by_right(&self.registered_oid.get_c509_oid().get_oid()) + { + // Determine encoded OID value based on critical flag + let encoded_oid = if self.critical { + -mapped_oid + } else { + mapped_oid + }; + e.i16(encoded_oid)?; + } else { + // Handle unwrapped CBOR OID or CBOR PEN + self.registered_oid.get_c509_oid().encode(e, ctx)?; + if self.critical { + e.bool(self.critical)?; + } + } + // Encode the extension value + self.value.encode(e, ctx)?; + Ok(()) + } +} + +impl Decode<'_, ()> for Extension { + fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { + match d.datatype()? { + // Check whether OID is an int + // Even the encoding is i16, the minicbor decoder doesn't know what type we encoded, + // so need to check every possible type. + minicbor::data::Type::U8 + | minicbor::data::Type::U16 + | minicbor::data::Type::I8 + | minicbor::data::Type::I16 => { + let int_value = d.i16()?; + // OID can be negative due to critical flag, so need absolute the value + let abs_int_value = int_value.abs(); + let oid = + get_oid_from_int(abs_int_value).map_err(minicbor::decode::Error::message)?; + let value_type = get_extension_type_from_int(abs_int_value) + .map_err(minicbor::decode::Error::message)?; + + // Decode extension value + let extension_value = ExtensionValue::decode(d, &mut value_type.get_type())?; + Ok(Extension::new( + oid.to_owned(), + extension_value, + int_value.is_negative(), + )) + }, + _ => { + // Handle unwrapped CBOR OID or CBOR PEN + let c509_oid = C509oid::decode(d, ctx)?; + // Critical flag is optional, so if exist, this mean we have to decode it + let critical = if d.datatype()? == minicbor::data::Type::Bool { + d.bool()? + } else { + false + }; + + // Decode bytes for extension value + let extension_value = ExtensionValue::Bytes(d.bytes()?.to_vec()); + + Ok(Extension::new( + c509_oid.get_oid(), + extension_value, + critical, + )) + }, + } + } +} + +// -----------------ExtensionValue------------------------ + +/// Trait for `ExtensionValueType` +trait ExtensionValueTypeTrait { + /// Get the type of the `ExtensionValueType`. + fn get_type(&self) -> ExtensionValueType; +} + +/// An enum of possible value types for `Extension`. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, PartialEq, EnumDiscriminants)] +#[strum_discriminants(name(ExtensionValueType))] +pub enum ExtensionValue { + /// An Integer in the range [-2^64, 2^64-1] + Int(i64), + /// A bytes. + Bytes(Vec), + /// An Alternative Name. + AlternativeName(AlternativeName), + /// An unsupported value. + Unsupported, +} + +impl ExtensionValueTypeTrait for ExtensionValueType { + fn get_type(&self) -> ExtensionValueType { + *self + } +} + +impl Encode<()> for ExtensionValue { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + match self { + ExtensionValue::Int(value) => { + e.i64(*value)?; + }, + ExtensionValue::Bytes(value) => { + e.bytes(value)?; + }, + ExtensionValue::AlternativeName(value) => { + value.encode(e, ctx)?; + }, + ExtensionValue::Unsupported => { + return Err(minicbor::encode::Error::message( + "Cannot encode unsupported Extension value", + )); + }, + } + Ok(()) + } +} + +impl Decode<'_, C> for ExtensionValue +where C: ExtensionValueTypeTrait + Debug +{ + fn decode(d: &mut Decoder<'_>, ctx: &mut C) -> Result { + match ctx.get_type() { + ExtensionValueType::Int => { + let value = d.i64()?; + Ok(ExtensionValue::Int(value)) + }, + ExtensionValueType::Bytes => { + let value = d.bytes()?.to_vec(); + Ok(ExtensionValue::Bytes(value)) + }, + ExtensionValueType::AlternativeName => { + let value = AlternativeName::decode(d, &mut ())?; + Ok(ExtensionValue::AlternativeName(value)) + }, + ExtensionValueType::Unsupported => { + Err(minicbor::decode::Error::message( + "Cannot decode Unsupported extension value", + )) + }, + } + } +} + +// ------------------Test---------------------- + +#[cfg(test)] +mod test_extension { + use asn1_rs::oid; + + use super::*; + + #[test] + fn int_oid_inhibit_anypolicy_value_unsigned_int() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let ext = Extension::new(oid!(2.5.29 .54), ExtensionValue::Int(2), false); + ext.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extension"); + // Inhibit anyPolicy : 0x181e + // 2 : 0x02 + assert_eq!(hex::encode(buffer.clone()), "181e02"); + + let mut decoder = Decoder::new(&buffer); + let decoded_ext = + Extension::decode(&mut decoder, &mut ()).expect("Failed to decode Extension"); + assert_eq!(decoded_ext, ext); + } + + #[test] + fn unwrapped_oid_critical_key_usage_value_int() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let ext = Extension::new(oid!(2.5.29 .15), ExtensionValue::Int(-1), true); + ext.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extension"); + // Key Usage with critical true: 0x21 + // -1 : 0x20 + assert_eq!(hex::encode(buffer.clone()), "2120"); + + let mut decoder = Decoder::new(&buffer); + let decoded_ext = + Extension::decode(&mut decoder, &mut ()).expect("Failed to decode Extension"); + assert_eq!(decoded_ext, ext); + } + + #[test] + fn oid_unwrapped_value_bytes_string() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + // Not PEN OID and not in the registry table + // Value should be bytes + let ext = Extension::new( + oid!(2.16.840 .1 .101 .3 .4 .2 .1), + ExtensionValue::Bytes("test".as_bytes().to_vec()), + false, + ); + ext.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extension"); + // OID : 0x49608648016503040201 + // "test".as_bytes() : 0x4474657374 + assert_eq!( + hex::encode(buffer.clone()), + "496086480165030402014474657374" + ); + + let mut decoder = Decoder::new(&buffer); + let decoded_ext = + Extension::decode(&mut decoder, &mut ()).expect("Failed to decode Extension"); + assert_eq!(decoded_ext, ext); + } + + #[test] + fn encode_decode_mismatch_type() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + // Subject Key Identifier should be bytes + let ext = Extension::new(oid!(2.5.29 .14), ExtensionValue::Int(2), false); + ext.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extension"); + // SubjectKeyIdentifier : 0x01 + // 2 : 0x02 + assert_eq!(hex::encode(buffer.clone()), "0102"); + + let mut decoder = Decoder::new(&buffer); + // Decode should fail, because rely on the int value + Extension::decode(&mut decoder, &mut ()).expect_err("Failed to decode Extension"); + } +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_extensions/mod.rs b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/mod.rs new file mode 100644 index 00000000000..b29a6f88ab0 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_extensions/mod.rs @@ -0,0 +1,223 @@ +//! C509 Extension as a part of `TBSCertificate` used in C509 Certificate. +//! Extension fallback of C509 OID extension +//! Given OID if not found in the registered OID table, it will be encoded as a PEN OID. +//! If the OID is not a PEN OID, it will be encoded as an unwrapped OID. +//! +//! ```cddl +//! Extensions and Extension can be encoded as the following: +//! Extensions = [ * Extension ] / int +//! Extension = ( extensionID: int, extensionValue: any ) // +//! ( extensionID: ~oid, ? critical: true, +//! extensionValue: bytes ) // +//! ( extensionID: pen, ? critical: true, +//! extensionValue: bytes ) +//! ``` +//! +//! For more information about Extensions, +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) + +pub mod alt_name; +pub mod extension; + +use std::fmt::Debug; + +use asn1_rs::{oid, Oid}; +use extension::{Extension, ExtensionValue}; +use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; + +/// OID of `KeyUsage` extension +static KEY_USAGE_OID: Oid<'static> = oid!(2.5.29 .15); + +/// A struct of C509 Extensions containing a vector of `Extension`. +#[derive(Debug, Clone, PartialEq)] +pub struct Extensions(Vec); + +impl Default for Extensions { + fn default() -> Self { + Self::new() + } +} + +impl Extensions { + /// Create a new instance of `Extensions` as empty vector. + #[must_use] + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Add an `Extension` to the `Extensions`. + pub fn add_ext(&mut self, extension: Extension) { + self.0.push(extension); + } +} + +impl Encode<()> for Extensions { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + // If there is only one extension and it is KeyUsage, encode as int + // encoding as absolute value of the second int and the sign of the first int + if let Some(extension) = self.0.first() { + if self.0.len() == 1 + && extension.get_registered_oid().get_c509_oid().get_oid() == KEY_USAGE_OID + { + match extension.get_value() { + ExtensionValue::Int(value) => { + let ku_value = if extension.get_critical() { + -value + } else { + *value + }; + e.i64(ku_value)?; + return Ok(()); + }, + _ => { + return Err(minicbor::encode::Error::message( + "KeyUsage extension value should be an integer", + )); + }, + } + } + } + // Else handle the array of `Extension` + e.array(self.0.len() as u64)?; + for extension in &self.0 { + extension.encode(e, ctx)?; + } + Ok(()) + } +} + +impl Decode<'_, ()> for Extensions { + fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result { + // If only KeyUsage is in the extension -> will only contain an int + if d.datatype()? == minicbor::data::Type::U8 || d.datatype()? == minicbor::data::Type::I8 { + // Check if it's a negative number (critical extension) + let critical = d.datatype()? == minicbor::data::Type::I8; + // Note that 'KeyUsage' BIT STRING is interpreted as an unsigned integer, + // so we can absolute the value + let value = d.i64()?.abs(); + + let extension_value = ExtensionValue::Int(value); + let mut extensions = Extensions::new(); + extensions.add_ext(Extension::new( + KEY_USAGE_OID.clone(), + extension_value, + critical, + )); + return Ok(extensions); + } + // Handle array of extensions + let len = d + .array()? + .ok_or_else(|| minicbor::decode::Error::message("Failed to get array length"))?; + let mut extensions = Extensions::new(); + for _ in 0..len { + let extension = Extension::decode(d, &mut ())?; + extensions.add_ext(extension); + } + + Ok(extensions) + } +} + +// ------------------Test---------------------- + +#[cfg(test)] +mod test_extensions { + use super::*; + + #[test] + fn one_extension_key_usage() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let mut exts = Extensions::new(); + exts.add_ext(Extension::new( + oid!(2.5.29 .15), + ExtensionValue::Int(2), + false, + )); + exts.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extensions"); + // 1 extension + // value 2 : 0x02 + assert_eq!(hex::encode(buffer.clone()), "02"); + + let mut decoder = Decoder::new(&buffer); + let decoded_exts = + Extensions::decode(&mut decoder, &mut ()).expect("Failed to decode Extensions"); + assert_eq!(decoded_exts, exts); + } + + #[test] + fn one_extension_key_usage_set_critical() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let mut exts = Extensions::new(); + exts.add_ext(Extension::new( + oid!(2.5.29 .15), + ExtensionValue::Int(2), + true, + )); + exts.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extensions"); + // 1 extension + // value -2 : 0x21 + assert_eq!(hex::encode(buffer.clone()), "21"); + + let mut decoder = Decoder::new(&buffer); + let decoded_exts = + Extensions::decode(&mut decoder, &mut ()).expect("Failed to decode Extensions"); + assert_eq!(decoded_exts, exts); + } + + #[test] + fn multiple_extensions() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let mut exts = Extensions::new(); + exts.add_ext(Extension::new( + oid!(2.5.29 .15), + ExtensionValue::Int(2), + false, + )); + + exts.add_ext(Extension::new( + oid!(2.5.29 .14), + ExtensionValue::Bytes([1, 2, 3, 4].to_vec()), + false, + )); + exts.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extensions"); + + // 2 extensions (array of 2): 0x82 + // KeyUsage with value 2: 0x0202 + // SubjectKeyIdentifier with value [1,2,3,4]: 0x0401020304 + assert_eq!(hex::encode(buffer.clone()), "820202014401020304"); + + let mut decoder = Decoder::new(&buffer); + let decoded_exts = + Extensions::decode(&mut decoder, &mut ()).expect("Failed to decode Extensions"); + assert_eq!(decoded_exts, exts); + } + + #[test] + fn zero_extensions() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let exts = Extensions::new(); + exts.encode(&mut encoder, &mut ()) + .expect("Failed to encode Extensions"); + assert_eq!(hex::encode(buffer.clone()), "80"); + + let mut decoder = Decoder::new(&buffer); + // Extensions can have 0 length + let decoded_exts = + Extensions::decode(&mut decoder, &mut ()).expect("Failed to decode Extensions"); + assert_eq!(decoded_exts, exts); + } +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_general_names/data.rs b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/data.rs new file mode 100644 index 00000000000..72cc7e906fd --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/data.rs @@ -0,0 +1,132 @@ +//! General Name data provides a necessary information for encoding and decoding of C509 +//! General Name. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! Section 9.9 C509 General Names Registry for more information. + +// cspell: words Gntr Gnvt + +use std::collections::HashMap; + +use anyhow::Error; +use bimap::BiMap; +use once_cell::sync::Lazy; + +use super::general_name::{GeneralNameTypeRegistry, GeneralNameValueType}; +use crate::tables::{IntTable, TableTrait}; + +/// Type of `GeneralName` data. +/// Int | Name | Type +type GeneralNameDataTuple = (i16, GeneralNameTypeRegistry, GeneralNameValueType); + +/// Create a type alias for `GeneralNameTypeRegistry` +type Gntr = GeneralNameTypeRegistry; +/// Create a type alias for `GeneralNameValueType` +type Gnvt = GeneralNameValueType; + +/// `GeneralName` data table. +#[rustfmt::skip] +const GENERAL_NAME_DATA: [GeneralNameDataTuple; 10] = [ + // Int | Name | Type + (-3, Gntr::OtherNameBundleEID, Gnvt::Unsupported), + (-2, Gntr::OtherNameSmtpUTF8Mailbox, Gnvt::Text), + (-1, Gntr::OtherNameHardwareModuleName, Gnvt::OtherNameHWModuleName), + (0, Gntr::OtherName, Gnvt::OtherNameHWModuleName), + (1, Gntr::Rfc822Name, Gnvt::Text), + (2, Gntr::DNSName, Gnvt::Text), + (4, Gntr::DirectoryName, Gnvt::Unsupported), + (6, Gntr::UniformResourceIdentifier, Gnvt::Text), + (7, Gntr::IPAddress, Gnvt::Bytes), + (8, Gntr::RegisteredID, Gnvt::Oid), +]; + +/// A struct of data that contains lookup table for `GeneralName`. +pub(crate) struct GeneralNameData { + /// A table of integer to `GeneralNameTypeRegistry`, provide a bidirectional lookup. + int_to_name_table: IntegerToGNTable, + /// A table of integer to `GeneralNameValueType`, provide a lookup for the type of + /// `GeneralName` value. + int_to_type_table: HashMap, +} + +impl GeneralNameData { + /// Get the `int_to_name_table`. + pub(crate) fn get_int_to_name_table(&self) -> &IntegerToGNTable { + &self.int_to_name_table + } + + /// Get the `int_to_type_table`. + pub(crate) fn get_int_to_type_table(&self) -> &HashMap { + &self.int_to_type_table + } +} + +/// A struct of integer to `GeneralNameTypeRegistry` table. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct IntegerToGNTable(IntTable); + +impl IntegerToGNTable { + /// Create a new instance of `IntegerToGNTable`. + pub(crate) fn new() -> Self { + Self(IntTable::::new()) + } + + /// Add a new integer to `GeneralNameTypeRegistry` map table. + pub(crate) fn add(&mut self, k: i16, v: GeneralNameTypeRegistry) { + self.0.add(k, v); + } + + /// Get the map table of integer to `GeneralNameTypeRegistry`. + pub(crate) fn get_map(&self) -> &BiMap { + self.0.get_map() + } +} + +/// Define static lookup for general names table +static GENERAL_NAME_TABLES: Lazy = Lazy::new(|| { + let mut int_to_name_table = IntegerToGNTable::new(); + let mut int_to_type_table = HashMap::new(); + + for data in GENERAL_NAME_DATA { + int_to_name_table.add(data.0, data.1); + int_to_type_table.insert(data.0, data.2); + } + + GeneralNameData { + int_to_name_table, + int_to_type_table, + } +}); + +/// Get the general name from the int value. +pub(crate) fn get_gn_from_int(i: i16) -> Result { + GENERAL_NAME_TABLES + .get_int_to_name_table() + .get_map() + .get_by_left(&i) + .ok_or(Error::msg(format!( + "GeneralName not found in the general name registry table given int {i}" + ))) + .cloned() +} + +/// Get the int value from the general name. +pub(crate) fn get_int_from_gn(gn: GeneralNameTypeRegistry) -> Result { + GENERAL_NAME_TABLES + .get_int_to_name_table() + .get_map() + .get_by_right(&gn) + .ok_or(Error::msg(format!( + "Int value not found in the general name registry table given GeneralName {gn:?}" + ))) + .cloned() +} + +/// Get the general name value type from the int value. +pub(crate) fn get_gn_value_type_from_int(i: i16) -> Result { + GENERAL_NAME_TABLES + .get_int_to_type_table() + .get(&i) + .ok_or(Error::msg(format!( + "General name value type not found in the general name registry table given {i}" + ))) + .cloned() +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_general_names/general_name.rs b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/general_name.rs new file mode 100644 index 00000000000..56da450b1b5 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/general_name.rs @@ -0,0 +1,319 @@ +//! C509 General Name +//! +//! For more information about `GeneralName`, +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) + +use std::fmt::Debug; + +use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; +use strum_macros::{EnumDiscriminants, EnumIs}; + +use super::{ + data::{get_gn_from_int, get_gn_value_type_from_int, get_int_from_gn}, + other_name_hw_module::OtherNameHardwareModuleName, +}; +use crate::c509_oid::C509oid; + +/// A struct represents a `GeneralName`. +/// ```cddl +/// GeneralName = ( GeneralNameType : int, GeneralNameValue : any ) +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct GeneralName { + /// A registered general name type. + gn_type: GeneralNameTypeRegistry, + /// A general name value. + value: GeneralNameValue, +} + +#[allow(dead_code)] +impl GeneralName { + /// Create a new instance of `GeneralName`. + #[must_use] + pub fn new(gn_type: GeneralNameTypeRegistry, value: GeneralNameValue) -> Self { + Self { gn_type, value } + } + + /// Get the `GeneralName` type. + #[must_use] + pub fn get_gn_type(&self) -> &GeneralNameTypeRegistry { + &self.gn_type + } + + /// Get the value of the `GeneralName` in `GeneralNameValue`. + #[must_use] + pub fn get_gn_value(&self) -> &GeneralNameValue { + &self.value + } +} + +impl Encode<()> for GeneralName { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + // Encode GeneralNameType as int + let i = get_int_from_gn(self.gn_type).map_err(minicbor::encode::Error::message)?; + e.i16(i)?; + // Encode GeneralNameValue as its type + self.value.encode(e, ctx)?; + Ok(()) + } +} + +impl Decode<'_, ()> for GeneralName { + fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result { + if minicbor::data::Type::U8 == d.datatype()? || minicbor::data::Type::I8 == d.datatype()? { + let i = d.i16()?; + let gn = get_gn_from_int(i).map_err(minicbor::decode::Error::message)?; + let value_type = + get_gn_value_type_from_int(i).map_err(minicbor::decode::Error::message)?; + Ok(GeneralName::new( + gn, + GeneralNameValue::decode(d, &mut value_type.get_type())?, + )) + } else { + // GeneralName is not type int + Err(minicbor::decode::Error::message( + "GeneralName id type invalid, expected int", + )) + } + } +} + +// -----------------GeneralNameTypeRegistry------------------------ + +/// Enum of `GeneralName` registered in table Section 9.9 C509. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Copy, PartialEq, Clone, Eq, Hash, EnumIs)] +pub enum GeneralNameTypeRegistry { + /// An otherName with `BundleEID`. + OtherNameBundleEID, // EID + /// An otherName with `SmtpUTF8Mailbox`. + OtherNameSmtpUTF8Mailbox, + /// An otherName with `HardwareModuleName`. + OtherNameHardwareModuleName, + /// An otherName. + OtherName, + /// A rfc822Name. + Rfc822Name, + /// A dNSName. + DNSName, + /// A directoryName. + DirectoryName, // Name + /// A uniformResourceIdentifier. + UniformResourceIdentifier, + /// An iPAddress. + IPAddress, + /// A registeredID. + RegisteredID, +} + +// -------------------GeneralNameValue---------------------- + +/// An enum of possible value types for `GeneralName`. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, PartialEq, Clone, Eq, Hash, EnumDiscriminants)] +#[strum_discriminants(name(GeneralNameValueType))] +pub enum GeneralNameValue { + /// A text string. + Text(String), + /// A otherName + hardwareModuleName. + OtherNameHWModuleName(OtherNameHardwareModuleName), + /// A bytes. + Bytes(Vec), + /// An OID + Oid(C509oid), + /// An unsupported value. + Unsupported, +} + +/// Trait for `GeneralNameValueType` +trait GeneralNameValueTrait { + /// Get the type of the `GeneralNameValueType`. + fn get_type(&self) -> GeneralNameValueType; +} + +impl GeneralNameValueTrait for GeneralNameValueType { + fn get_type(&self) -> GeneralNameValueType { + *self + } +} + +impl Encode<()> for GeneralNameValue { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + match self { + GeneralNameValue::Text(value) => { + e.str(value)?; + }, + GeneralNameValue::Bytes(value) => { + e.bytes(value)?; + }, + GeneralNameValue::Oid(value) => { + value.encode(e, ctx)?; + }, + GeneralNameValue::OtherNameHWModuleName(value) => { + value.encode(e, ctx)?; + }, + GeneralNameValue::Unsupported => { + return Err(minicbor::encode::Error::message( + "Cannot encode unsupported GeneralName value", + )) + }, + }; + Ok(()) + } +} +impl Decode<'_, C> for GeneralNameValue +where C: GeneralNameValueTrait + Debug +{ + fn decode(d: &mut Decoder<'_>, ctx: &mut C) -> Result { + match ctx.get_type() { + GeneralNameValueType::Text => { + let value = d.str()?.to_string(); + Ok(GeneralNameValue::Text(value)) + }, + GeneralNameValueType::Bytes => { + let value = d.bytes()?.to_vec(); + Ok(GeneralNameValue::Bytes(value)) + }, + GeneralNameValueType::Oid => { + let value = C509oid::decode(d, &mut ())?; + Ok(GeneralNameValue::Oid(value)) + }, + GeneralNameValueType::OtherNameHWModuleName => { + let value = OtherNameHardwareModuleName::decode(d, &mut ())?; + Ok(GeneralNameValue::OtherNameHWModuleName(value)) + }, + GeneralNameValueType::Unsupported => { + Err(minicbor::decode::Error::message( + "Cannot decode Unsupported GeneralName value", + )) + }, + } + } +} + +// ------------------Test---------------------- + +#[cfg(test)] +mod test_general_name { + use std::net::Ipv4Addr; + + use asn1_rs::oid; + + use super::*; + + #[test] + fn encode_decode_text() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let gn = GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text("example.com".to_string()), + ); + gn.encode(&mut encoder, &mut ()) + .expect("Failed to encode GeneralName"); + // DNSName: 0x02 + // "example.com": 0x6b6578616d706c652e636f6d + assert_eq!(hex::encode(buffer.clone()), "026b6578616d706c652e636f6d"); + + let mut decoder = Decoder::new(&buffer); + let gn_decoded = + GeneralName::decode(&mut decoder, &mut ()).expect("Failed to decode GeneralName"); + assert_eq!(gn_decoded, gn); + } + + #[test] + fn encode_decode_hw_module_name() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let hw = OtherNameHardwareModuleName::new(oid!(2.16.840 .1 .101 .3 .4 .2 .1), vec![ + 0x01, 0x02, 0x03, 0x04, + ]); + let gn = GeneralName::new( + GeneralNameTypeRegistry::OtherNameHardwareModuleName, + GeneralNameValue::OtherNameHWModuleName(hw), + ); + gn.encode(&mut encoder, &mut ()) + .expect("Failed to encode GeneralName"); + // OtherNameHardwareModuleName: 0x20 + // [ ~oid, bytes ] = 0x82496086480165030402014401020304 + assert_eq!( + hex::encode(buffer.clone()), + "2082496086480165030402014401020304" + ); + + let mut decoder = Decoder::new(&buffer); + let gn_decoded = + GeneralName::decode(&mut decoder, &mut ()).expect("Failed to decode GeneralName"); + assert_eq!(gn_decoded, gn); + } + + #[test] + fn encode_decode_ip() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let ipv4 = Ipv4Addr::new(192, 168, 1, 1); + let gn = GeneralName::new( + GeneralNameTypeRegistry::IPAddress, + GeneralNameValue::Bytes(ipv4.octets().to_vec()), + ); + + gn.encode(&mut encoder, &mut ()) + .expect("Failed to encode GeneralName"); + // IPAddress: 0x07 + // 192.168.1.1 bytes: 0x44c0a8010 + assert_eq!(hex::encode(buffer.clone()), "0744c0a80101"); + + let mut decoder = Decoder::new(&buffer); + let gn_decoded = + GeneralName::decode(&mut decoder, &mut ()).expect("Failed to decode GeneralName"); + assert_eq!(gn_decoded, gn); + } + + #[test] + fn encode_decode_oid() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let gn = GeneralName::new( + GeneralNameTypeRegistry::RegisteredID, + GeneralNameValue::Oid(C509oid::new(oid!(2.16.840 .1 .101 .3 .4 .2 .1))), + ); + gn.encode(&mut encoder, &mut ()) + .expect("Failed to encode GeneralName"); + // RegisteredID: 0x08 + // oid: 49608648016503040201 + assert_eq!(hex::encode(buffer.clone()), "0849608648016503040201"); + + let mut decoder = Decoder::new(&buffer); + let gn_decoded = + GeneralName::decode(&mut decoder, &mut ()).expect("Failed to decode GeneralName"); + assert_eq!(gn_decoded, gn); + } + + #[test] + fn encode_decode_mismatch_type() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let gn = GeneralName::new( + GeneralNameTypeRegistry::OtherNameSmtpUTF8Mailbox, + GeneralNameValue::Oid(C509oid::new(oid!(2.16.840 .1 .101 .3 .4 .2 .1))), + ); + gn.encode(&mut encoder, &mut ()) + .expect("Failed to encode GeneralName"); + // OtherNameSmtpUTF8Mailbox: 0x21 + // oid: 49608648016503040201 + assert_eq!(hex::encode(buffer.clone()), "2149608648016503040201"); + + let mut decoder = Decoder::new(&buffer); + // Decode should fail, because rely on the int value + GeneralName::decode(&mut decoder, &mut ()).expect_err("Failed to decode GeneralName"); + } +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_general_names/mod.rs b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/mod.rs new file mode 100644 index 00000000000..69d762bc25e --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/mod.rs @@ -0,0 +1,167 @@ +//! C509 General Names +//! +//! For more information about `GeneralNames`, +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) + +mod data; +pub mod general_name; +mod other_name_hw_module; + +use general_name::GeneralName; +use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; + +/// A struct represents an array of `GeneralName`. +/// +/// ```cddl +/// GeneralNames = [ + GeneralName ] +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct GeneralNames(Vec); + +impl Default for GeneralNames { + fn default() -> Self { + Self::new() + } +} + +impl GeneralNames { + /// Create a new instance of `GeneralNames` as empty vector. + #[must_use] + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Add a new `GeneralName` to the `GeneralNames`. + pub fn add_gn(&mut self, gn: GeneralName) { + self.0.push(gn); + } + + /// Get the a vector of `GeneralName`. + pub(crate) fn get_gns(&self) -> &Vec { + &self.0 + } +} + +impl Encode<()> for GeneralNames { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + if self.0.is_empty() { + return Err(minicbor::encode::Error::message( + "GeneralNames should not be empty", + )); + } + e.array(self.0.len() as u64)?; + for gn in &self.0 { + gn.encode(e, ctx)?; + } + Ok(()) + } +} + +impl Decode<'_, ()> for GeneralNames { + fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { + let len = d.array()?.ok_or(minicbor::decode::Error::message( + "GeneralNames should be an array", + ))?; + let mut gn = GeneralNames::new(); + for _ in 0..len { + gn.add_gn(GeneralName::decode(d, ctx)?); + } + Ok(gn) + } +} + +// ------------------Test---------------------- + +#[cfg(test)] +mod test_general_names { + + use std::net::Ipv4Addr; + + use asn1_rs::oid; + use general_name::{GeneralNameTypeRegistry, GeneralNameValue}; + use other_name_hw_module::OtherNameHardwareModuleName; + + use super::*; + use crate::c509_oid::C509oid; + + #[test] + fn encode_decode_gns() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let mut gns = GeneralNames::new(); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text("example.com".to_string()), + )); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::OtherNameHardwareModuleName, + GeneralNameValue::OtherNameHWModuleName(OtherNameHardwareModuleName::new( + oid!(2.16.840 .1 .101 .3 .4 .2 .1), + vec![0x01, 0x02, 0x03, 0x04], + )), + )); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::IPAddress, + GeneralNameValue::Bytes(Ipv4Addr::new(192, 168, 1, 1).octets().to_vec()), + )); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::RegisteredID, + GeneralNameValue::Oid(C509oid::new(oid!(2.16.840 .1 .101 .3 .4 .2 .1))), + )); + gns.encode(&mut encoder, &mut ()) + .expect("Failed to encode GeneralNames"); + // Array of 4 GeneralName: 0x84 + assert_eq!(hex::encode(buffer.clone()), "84026b6578616d706c652e636f6d20824960864801650304020144010203040744c0a801010849608648016503040201"); + + let mut decoder = Decoder::new(&buffer); + let gns_decoded = + GeneralNames::decode(&mut decoder, &mut ()).expect("Failed to decode GeneralName"); + assert_eq!(gns_decoded, gns); + } + + #[test] + fn encode_decode_gns_with_same_gn_type() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let mut gns = GeneralNames::new(); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text("example.com".to_string()), + )); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text("example.com".to_string()), + )); + gns.add_gn(GeneralName::new( + GeneralNameTypeRegistry::DNSName, + GeneralNameValue::Text("example.com".to_string()), + )); + gns.encode(&mut encoder, &mut ()) + .expect("Failed to encode GeneralNames"); + // Array of 3 GeneralName: 0x83 + // DNSName with "example.com": 0x026b6578616d706c652e636f6d + assert_eq!( + hex::encode(buffer.clone()), + "83026b6578616d706c652e636f6d026b6578616d706c652e636f6d026b6578616d706c652e636f6d" + ); + + let mut decoder = Decoder::new(&buffer); + let gns_decoded = + GeneralNames::decode(&mut decoder, &mut ()).expect("Failed to decode GeneralName"); + assert_eq!(gns_decoded, gns); + } + + #[test] + fn encode_decode_gns_empty() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + + let gns = GeneralNames::new(); + gns.encode(&mut encoder, &mut ()) + .expect_err("GeneralNames should not be empty"); + } +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_general_names/other_name_hw_module.rs b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/other_name_hw_module.rs new file mode 100644 index 00000000000..700ae952982 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_general_names/other_name_hw_module.rs @@ -0,0 +1,53 @@ +//! `OtherNameHardwareModuleName`, special type for `hardwareModuleName` type of +//! otherName. When 'otherName + hardwareModuleName' is used, then `[ ~oid, bytes ]` is +//! used to contain the pair ( hwType, hwSerialNum ) directly as specified in +//! [RFC4108](https://datatracker.ietf.org/doc/rfc4108/) + +use asn1_rs::Oid; +use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; + +use crate::c509_oid::C509oid; + +/// A struct represents the hardwareModuleName type of otherName. +/// Containing a pair of ( hwType, hwSerialNum ) as mentioned in +/// [RFC4108](https://datatracker.ietf.org/doc/rfc4108/) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct OtherNameHardwareModuleName { + /// The hardware type OID. + hw_type: C509oid, + /// The hardware serial number represent in bytes. + hw_serial_num: Vec, +} + +impl OtherNameHardwareModuleName { + /// Create a new instance of `OtherNameHardwareModuleName`. + pub fn new(hw_type: Oid<'static>, hw_serial_num: Vec) -> Self { + Self { + hw_type: C509oid::new(hw_type), + hw_serial_num, + } + } +} + +impl Encode<()> for OtherNameHardwareModuleName { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + e.array(2)?; + self.hw_type.encode(e, ctx)?; + e.bytes(&self.hw_serial_num)?; + Ok(()) + } +} + +impl<'a> Decode<'a, ()> for OtherNameHardwareModuleName { + fn decode(d: &mut Decoder<'a>, ctx: &mut ()) -> Result { + d.array()?; + let hw_type = C509oid::decode(d, ctx)?; + let hw_serial_num = d.bytes()?.to_vec(); + Ok(OtherNameHardwareModuleName::new( + hw_type.get_oid(), + hw_serial_num, + )) + } +} diff --git a/catalyst-gateway-crates/c509-certificate/src/c509_oid.rs b/catalyst-gateway-crates/c509-certificate/src/c509_oid.rs new file mode 100644 index 00000000000..07f7dae81cd --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/c509_oid.rs @@ -0,0 +1,212 @@ +//! C509 OID provides an encoding and decoding of C509 Object Identifier (OID). +//! Please refer to [RFC9090](https://datatracker.ietf.org/doc/rfc9090/) for OID encoding +//! Please refer to [CDDL Wrapping](https://datatracker.ietf.org/doc/html/rfc8610#section-3.7) +//! for unwrapped types. + +use anyhow::Result; +use asn1_rs::oid; +use minicbor::{data::Tag, decode, encode::Write, Decode, Decoder, Encode, Encoder}; +use oid_registry::Oid; + +use crate::tables::IntegerToOidTable; + +/// IANA Private Enterprise Number (PEN) OID prefix. +const PEN_PREFIX: Oid<'static> = oid!(1.3.6 .1 .4 .1); + +/// Tag number representing IANA Private Enterprise Number (PEN) OID. +const OID_PEN_TAG: u64 = 112; + +/// A strut of C509 OID with Registered Integer Encoding/Decoding. +#[derive(Debug, Clone, PartialEq)] +pub struct C509oidRegistered { + /// The `C509oid`. + oid: C509oid, + /// The registration table. + registration_table: &'static IntegerToOidTable, +} + +impl C509oidRegistered { + /// Create a new instance of `C509oidRegistered`. + pub(crate) fn new(oid: Oid<'static>, table: &'static IntegerToOidTable) -> Self { + Self { + oid: C509oid::new(oid), + registration_table: table, + } + } + + /// Is PEN Encoding supported for this OID. + /// Depends on each registration table. + pub(crate) fn pen_encoded(mut self) -> Self { + self.oid.pen_supported = true; + self + } + + /// Get the `C509oid`. + pub(crate) fn get_c509_oid(&self) -> C509oid { + self.oid.clone() + } + + /// Get the registration table. + pub(crate) fn get_table(&self) -> &'static IntegerToOidTable { + self.registration_table + } +} + +// ----------------------------------------- + +/// A struct represent an instance of `C509oid`. +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub struct C509oid { + /// The OID. + oid: Oid<'static>, + /// The flag to indicate whether PEN encoding is supported. + pen_supported: bool, +} + +impl C509oid { + /// Create an new instance of `C509oid`. + /// Default value of PEN flag is false + #[must_use] + pub fn new(oid: Oid<'static>) -> Self { + Self { + oid, + pen_supported: false, + } + } + + /// Is PEN Encoding supported for this OID + pub(crate) fn pen_encoded(mut self) -> Self { + self.pen_supported = true; + self + } + + /// Get the underlying OID of the `C509oid` + #[must_use] + pub fn get_oid(self) -> Oid<'static> { + self.oid.clone() + } +} + +impl Encode<()> for C509oid { + /// Encode an OID + /// If `pen_supported` flag is set, and OID start with a valid `PEN_PREFIX`, + /// it is encoded as PEN (Private Enterprise Number) + /// else encode as an unwrapped OID (~oid) - as bytes string without tag. + /// + /// # Returns + /// + /// A vector of bytes containing the CBOR encoded OID. + /// If the encoding fails, it will return an error. + fn encode( + &self, e: &mut Encoder, _ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + // Check if PEN encoding is supported and the OID starts with the PEN prefix. + if self.pen_supported && self.oid.starts_with(&PEN_PREFIX) { + // Set the CBOR tag. + e.tag(Tag::new(OID_PEN_TAG))?; + // Convert OID originally store as [u8] to [u64] + // This process is necessary to get the correct OID + // For example given - 1.3.6 .1 .4 .1.4.999 + // This OID will be stored as [u8] - [43, 6, 1, 4, 1, 4, 135, 103] + // The first 2 integer has a special encoding formula where, + // values is computed using X * 40 + Y (See RFC9090 for more info) + // The number 999 exceed the 225 limit (max of u8), so it will be encoded as 2 bytes + let raw_oid: Vec = + self.oid + .iter() + .map(Iterator::collect) + .ok_or(minicbor::encode::Error::message( + "Failed to collect OID components from iterator", + ))?; + let raw_pen_prefix: Vec = PEN_PREFIX.iter().map(Iterator::collect).ok_or( + minicbor::encode::Error::message("Failed to collect OID components from iterator"), + )?; + // relative_oid is OID that follows PEN_PREFIX (relative to PEN_PREFIX) + // Use the [u64] PEN prefix length to extract the relative OID + let oid_slice = + raw_oid + .get(raw_pen_prefix.len()..) + .ok_or(minicbor::encode::Error::message( + "Failed to get a OID slice", + ))?; + let relative_oid = Oid::from_relative(oid_slice) + .map_err(|_| minicbor::encode::Error::message("Failed to get a relative OID"))?; + return e.bytes(relative_oid.as_bytes())?.ok(); + } + let oid_bytes = self.oid.as_bytes(); + e.bytes(oid_bytes)?.ok() + } +} + +impl Decode<'_, ()> for C509oid { + /// Decode an OID + /// If the data to be decoded is a `Tag`, and the tag is an `OID_PEN_TAG`, + /// then decode the OID as Private Enterprise Number (PEN) OID. + /// else decode the OID as unwrapped OID (~oid) - as bytes string without tag. + + /// # Returns + /// + /// A C509oid instance. + /// If the decoding fails, it will return an error. + fn decode(d: &mut Decoder, _ctx: &mut ()) -> Result { + if (minicbor::data::Type::Tag == d.datatype()?) && (Tag::new(OID_PEN_TAG) == d.tag()?) { + let oid_bytes = d.bytes()?; + // raw_oid contains the whole OID which stored in bytes + let mut raw_oid = Vec::new(); + raw_oid.extend_from_slice(PEN_PREFIX.as_bytes()); + raw_oid.extend_from_slice(oid_bytes); + // Convert the raw_oid to Oid + let oid = Oid::new(raw_oid.into()); + return Ok(C509oid::new(oid).pen_encoded()); + } + // Not a PEN Relative OID, so treat as a normal OID + let oid_bytes = d.bytes()?; + let oid = Oid::new(oid_bytes.to_owned().into()); + Ok(C509oid::new(oid)) + } +} + +// ----------------------------------------- + +#[cfg(test)] +mod test_c509_oid { + + use super::*; + + // Test reference 3.1. Encoding of the SHA-256 OID + // https://datatracker.ietf.org/doc/rfc9090/ + #[test] + fn encode_decode_unwrapped() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + let oid = C509oid::new(oid!(2.16.840 .1 .101 .3 .4 .2 .1)); + oid.encode(&mut encoder, &mut ()) + .expect("Failed to encode OID"); + assert_eq!(hex::encode(buffer.clone()), "49608648016503040201"); + + let mut decoder = Decoder::new(&buffer); + let decoded_oid = C509oid::decode(&mut decoder, &mut ()).expect("Failed to decode OID"); + assert_eq!(decoded_oid, oid); + } + + #[test] + fn encode_decode_pen() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + let oid = C509oid::new(oid!(1.3.6 .1 .4 .1 .1 .1 .29)).pen_encoded(); + oid.encode(&mut encoder, &mut ()) + .expect("Failed to encode OID"); + assert_eq!(hex::encode(buffer.clone()), "d8704301011d"); + + let mut decoder = Decoder::new(&buffer); + let decoded_oid = C509oid::decode(&mut decoder, &mut ()).expect("Failed to decode OID"); + assert_eq!(decoded_oid, oid); + } + + #[test] + fn partial_equal() { + let oid1 = C509oid::new(oid_registry::OID_HASH_SHA1); + let oid2 = C509oid::new(oid!(1.3.14 .3 .2 .26)); + assert_eq!(oid1, oid2); + } +} diff --git a/catalyst-gateway-crates/c509-certificate/src/lib.rs b/catalyst-gateway-crates/c509-certificate/src/lib.rs new file mode 100644 index 00000000000..066482a57c4 --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/lib.rs @@ -0,0 +1,59 @@ +//! CBOR Encoded X.509 Certificate (C509 Certificate) library +//! +//! This crate provides a library for generating C509 Certificates. +//! The function is exposed to Javascript through wasm-bindgen. +//! +//! Please refer to the [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) for more information. + +use wasm_bindgen::prelude::wasm_bindgen; +pub mod c509_extensions; +pub mod c509_general_names; +pub mod c509_oid; +mod tables; + +/// Generates an unsigned C509 certificate from the provided `TbsCertificate`. +/// +/// C509 certificate contains 2 parts +/// 1. TBSCertificate +/// 2. issuerSignatureValue +/// In order to generate an unsigned C509 certificate, the TBS Certificate must be +/// provided. Then the unsigned C509 certificate will then be used to calculate the +/// issuerSignatureValue. +/// +/// # Arguments +/// +/// * `tbs_cert` - The `TbsCertificate` is the TBS Certificate containing +/// * c509CertificateType: A certificate type, whether 0 a natively signed C509 +/// certificate following X.509 v3 or 1 a CBOR re-encoded X.509 v3 DER certificate. +/// * certificateSerialNumber: A unique serial number for the certificate. +/// * issuer: The entity that issued the certificate. +/// * validityNotBefore: The duration for which the Certificate Authority (CA) +/// guarantees it will retain information regarding the certificate's status on which +/// the period begins. +/// * validityNotAfter: The duration for which the Certificate Authority (CA) +/// guarantees it will retain information regarding the certificate's status on which +/// the period ends. +/// * subject: The entity associated with the public key stored in the subject public +/// key field. +/// * subjectPublicKeyAlgorithm: The algorithm that the public key is used, +/// * subjectPublicKey: The public key of the subject. +/// * extensions: A list of extensions defined for X.509 v3 certificate, providing +/// additional attributes for users or public keys, and for managing relationships +/// between Certificate Authorities (CAs). +/// * issuerSignatureAlgorithm: The algorithm used to sign the certificate (must be the +/// algorithm uses to create IssuerSignatureValue). +/// +/// # Returns +/// +/// A `Vec` containing the generated unsigned C509 certificate. +/// +/// # Remarks +/// +/// This function relies on the `c509_cert` module's `generate_unsigned_c509_cert` +/// function for the actual generation process. +#[wasm_bindgen] +#[must_use] +pub fn generate_unsigned_c509_cert() -> Vec { + // c509_cert::generate_unsigned_c509_cert(tbs_cert) + todo!() +} diff --git a/catalyst-gateway-crates/c509-certificate/src/tables.rs b/catalyst-gateway-crates/c509-certificate/src/tables.rs new file mode 100644 index 00000000000..67e83fde4fd --- /dev/null +++ b/catalyst-gateway-crates/c509-certificate/src/tables.rs @@ -0,0 +1,78 @@ +//! A bimap table for bidirectional lookup. + +use std::hash::Hash; + +use asn1_rs::Oid; +use bimap::BiMap; + +/// A trait that represents a table structure with key-value pairs. +/// +/// # Type Parameters +/// +/// * `K` - The type of the keys in the table. +/// * `V` - The type of the values in the table. +pub(crate) trait TableTrait { + /// Create new instance of the map table. + fn new() -> Self; + /// Add the key-value pair to the map table. + fn add(&mut self, k: K, v: V); + /// Get the bimap of the map table. + fn get_map(&self) -> &BiMap; +} + +// ----------------------------------------- + +/// A struct that represents a table mapping integers to any type that +/// implements `Eq` and `Hash`. +/// i16 is used because the int value in C509 certificate registry can be -256 to 255. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct IntTable { + /// A bimap table for bidirectional lookup where it map between i16 and other type. + map: BiMap, +} + +impl TableTrait for IntTable { + /// Create new instance of `IntTable`. + fn new() -> Self { + Self { map: BiMap::new() } + } + + /// Add the key-value pair to the map table. + fn add(&mut self, k: i16, v: T) { + self.map.insert(k, v); + } + + /// Get the bimap of the map table. + fn get_map(&self) -> &BiMap { + &self.map + } +} + +// ----------------------------------------- + +/// A struct represents a table of integer to OID. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct IntegerToOidTable { + /// A table of integer to OID, provide a bidirectional lookup. + table: IntTable>, +} + +#[allow(dead_code)] +impl IntegerToOidTable { + /// Create new instance of `IntegerToOidTable`. + pub(crate) fn new() -> Self { + Self { + table: IntTable::>::new(), + } + } + + /// Add the key-value pair to the map table. + pub(crate) fn add(&mut self, k: i16, v: Oid<'static>) { + self.table.add(k, v); + } + + /// Get the bimap of the map table. + pub(crate) fn get_map(&self) -> &BiMap> { + self.table.get_map() + } +} diff --git a/catalyst-gateway/deny.toml b/catalyst-gateway/deny.toml index 9b7adc91ce6..7e6bd8d8121 100644 --- a/catalyst-gateway/deny.toml +++ b/catalyst-gateway/deny.toml @@ -113,4 +113,4 @@ license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] #[[licenses.clarify]] #crate = "rustls-webpki" #expression = "ISC" -#license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] +#license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] \ No newline at end of file