From 759c3225bf2bec859ed5b41b9395503be291428b Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Wed, 3 Jan 2024 19:54:15 +0200 Subject: [PATCH 01/28] feat(asn1-parser): implement ObjectIdentifier --- Cargo.lock | 1 + crates/asn1-parser/Cargo.toml | 1 + crates/asn1-parser/README.md | 5 +- crates/asn1-parser/src/asn1.rs | 10 +++- crates/asn1-parser/src/error.rs | 10 ++++ crates/asn1-parser/src/generic_types/mod.rs | 2 + .../src/generic_types/object_identifier.rs | 50 +++++++++++++++++++ 7 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 crates/asn1-parser/src/generic_types/object_identifier.rs diff --git a/Cargo.lock b/Cargo.lock index 950d2727..b274df9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,7 @@ version = "0.1.0" dependencies = [ "log", "num-bigint-dig", + "oid", "prop-strategies", "proptest", ] diff --git a/crates/asn1-parser/Cargo.toml b/crates/asn1-parser/Cargo.toml index e5eade9b..b7dcf41e 100644 --- a/crates/asn1-parser/Cargo.toml +++ b/crates/asn1-parser/Cargo.toml @@ -19,3 +19,4 @@ proptest = "1.2.0" [dependencies] log = "0.4.20" num-bigint-dig = { version = "0.8.4", default-features = false } +oid = { version = "0.2.1", default-features = false } diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index 454729b8..3a553889 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -30,9 +30,6 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result --- -- [ ] [Date](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/utf8string.html) -- [ ] [DateTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/datetime.html) -- [ ] [Duration](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/duration.html) - [ ] [GeneralizedTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalizedtime.html) - [ ] [Time](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/time.html) - [ ] [UtcTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/utctime.html) @@ -43,7 +40,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result - [X] [Integer](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/integer.html) - [X] [Boolean](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/boolean.html) - [X] [Null](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/null.html) -- [ ] [ObjectIdentifier](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/object-identifier.html) +- [X] [ObjectIdentifier](https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier) - [ ] [Real](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/real.html) --- diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index 24ba817b..85877e00 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -5,7 +5,7 @@ use crate::reader::Reader; use crate::writer::Writer; use crate::{ ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, Integer, - MetaInfo, Null, OctetString, Sequence, Tag, Taggable, Tlv, Utf8String, + MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Tag, Taggable, Tlv, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,6 +19,7 @@ pub enum Asn1Type<'data> { Bool(Bool), Null(Null), Integer(Integer<'data>), + ObjectIdentifier(ObjectIdentifier), ExplicitTag(ExplicitTag<'data>), ApplicationTag(ApplicationTag<'data>), @@ -39,6 +40,7 @@ impl Asn1Type<'_> { Asn1Type::Bool(b) => Asn1Type::Bool(b.clone()), Asn1Type::Null(n) => Asn1Type::Null(n.clone()), Asn1Type::Integer(i) => Asn1Type::Integer(i.to_owned()), + Asn1Type::ObjectIdentifier(o) => Asn1Type::ObjectIdentifier(o.clone()), Asn1Type::ExplicitTag(e) => Asn1Type::ExplicitTag(e.to_owned()), Asn1Type::ApplicationTag(a) => Asn1Type::ApplicationTag(a.to_owned()), Asn1Type::BmpString(b) => Asn1Type::BmpString(b.to_owned()), @@ -57,6 +59,7 @@ impl Taggable for Asn1Type<'_> { Asn1Type::Bool(b) => b.tag(), Asn1Type::Null(n) => n.tag(), Asn1Type::Integer(i) => i.tag(), + Asn1Type::ObjectIdentifier(o) => o.tag(), Asn1Type::ExplicitTag(e) => e.tag(), Asn1Type::ApplicationTag(a) => a.tag(), } @@ -79,6 +82,8 @@ impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { Ok(Asn1Type::Bool(Bool::decode(tag, reader)?)) } else if Integer::compare_tags(tag) { Ok(Asn1Type::Integer(Integer::decode(tag, reader)?)) + } else if ObjectIdentifier::compare_tags(tag) { + Ok(Asn1Type::ObjectIdentifier(ObjectIdentifier::decode(tag, reader)?)) } else if ExplicitTag::compare_tags(tag) { Ok(Asn1Type::ExplicitTag(ExplicitTag::decode(tag, reader)?)) } else if ApplicationTag::compare_tags(tag) { @@ -105,6 +110,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::BmpString(bmp) => bmp.needed_buf_size(), Asn1Type::Bool(boolean) => boolean.needed_buf_size(), Asn1Type::Integer(integer) => integer.needed_buf_size(), + Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.needed_buf_size(), Asn1Type::ExplicitTag(e) => e.needed_buf_size(), Asn1Type::ApplicationTag(a) => a.needed_buf_size(), Asn1Type::Null(n) => n.needed_buf_size(), @@ -120,6 +126,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::BmpString(bmp) => bmp.encode(writer), Asn1Type::Bool(boolean) => boolean.encode(writer), Asn1Type::Integer(integer) => integer.encode(writer), + Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.encode(writer), Asn1Type::ExplicitTag(e) => e.encode(writer), Asn1Type::ApplicationTag(a) => a.encode(writer), Asn1Type::Null(n) => n.encode(writer), @@ -137,6 +144,7 @@ impl MetaInfo for Asn1Type<'_> { Asn1Type::BmpString(_) => {} Asn1Type::Bool(_) => {} Asn1Type::Integer(_) => {} + Asn1Type::ObjectIdentifier(_) => {} Asn1Type::ExplicitTag(explicit_tag) => explicit_tag.clear_meta(), Asn1Type::ApplicationTag(application_tag) => application_tag.clear_meta(), Asn1Type::Null(_) => {} diff --git a/crates/asn1-parser/src/error.rs b/crates/asn1-parser/src/error.rs index 4a152a81..a177eec2 100644 --- a/crates/asn1-parser/src/error.rs +++ b/crates/asn1-parser/src/error.rs @@ -2,6 +2,8 @@ use alloc::string::FromUtf16Error; use core::num::TryFromIntError; use core::str::Utf8Error; +use oid::ObjectIdentifierError; + #[derive(Debug)] pub struct Error { message: &'static str, @@ -40,3 +42,11 @@ impl From for Error { } } } + +impl From for Error { + fn from(_value: ObjectIdentifierError) -> Self { + Self { + message: "ObjectIdentifierError", + } + } +} diff --git a/crates/asn1-parser/src/generic_types/mod.rs b/crates/asn1-parser/src/generic_types/mod.rs index 92d51b63..5e219856 100644 --- a/crates/asn1-parser/src/generic_types/mod.rs +++ b/crates/asn1-parser/src/generic_types/mod.rs @@ -1,7 +1,9 @@ mod boolean; mod integer; mod null; +mod object_identifier; pub use boolean::Bool; pub use integer::{Integer, OwnedInteger}; pub use null::Null; +pub use object_identifier::ObjectIdentifier; diff --git a/crates/asn1-parser/src/generic_types/object_identifier.rs b/crates/asn1-parser/src/generic_types/object_identifier.rs new file mode 100644 index 00000000..97ef4cb9 --- /dev/null +++ b/crates/asn1-parser/src/generic_types/object_identifier.rs @@ -0,0 +1,50 @@ +use alloc::vec::Vec; + +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ObjectIdentifier(oid::ObjectIdentifier); + +impl ObjectIdentifier { + pub const TAG: Tag = Tag(0x06); + + pub fn oid(&self) -> &oid::ObjectIdentifier { + &self.0 + } +} + +impl From for ObjectIdentifier { + fn from(value: oid::ObjectIdentifier) -> Self { + Self(value) + } +} + +impl Taggable for ObjectIdentifier { + fn tag(&self) -> Tag { + Self::TAG + } +} + +impl Asn1ValueDecoder<'_> for ObjectIdentifier { + fn decode(_tag: Tag, reader: &mut Reader<'_>) -> Asn1Result { + Ok(Self(oid::ObjectIdentifier::try_from(reader.remaining())?)) + } + + fn compare_tags(tag: Tag) -> bool { + Self::TAG == tag + } +} + +impl Asn1Encoder for ObjectIdentifier { + fn needed_buf_size(&self) -> usize { + let encoded: Vec = self.0.clone().into(); + encoded.len() + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + let encoded: Vec = self.0.clone().into(); + writer.write_slice(&encoded) + } +} From b188f9ef3184f89c9a0a5681ae43b1ab3ef1bcea Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Wed, 3 Jan 2024 20:28:15 +0200 Subject: [PATCH 02/28] fix(asn1-parser): ObjectIdentifier: encoding; feat(prop-strategies): ObjectIdentifier; --- Cargo.lock | 1 + .../src/generic_types/object_identifier.rs | 7 +++++- .../tests/decode_encode.proptest-regressions | 1 + crates/prop-strategies/Cargo.toml | 1 + crates/prop-strategies/src/constructors.rs | 3 ++- crates/prop-strategies/src/lib.rs | 1 + crates/prop-strategies/src/primitives.rs | 24 ++++++++++++++++++- 7 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b274df9b..395facfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1390,6 +1390,7 @@ name = "prop-strategies" version = "0.1.0" dependencies = [ "asn1-parser", + "oid", "proptest", ] diff --git a/crates/asn1-parser/src/generic_types/object_identifier.rs b/crates/asn1-parser/src/generic_types/object_identifier.rs index 97ef4cb9..9c8aa635 100644 --- a/crates/asn1-parser/src/generic_types/object_identifier.rs +++ b/crates/asn1-parser/src/generic_types/object_identifier.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; +use crate::length::{len_size, write_len}; use crate::reader::Reader; use crate::writer::Writer; use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; @@ -40,11 +41,15 @@ impl Asn1ValueDecoder<'_> for ObjectIdentifier { impl Asn1Encoder for ObjectIdentifier { fn needed_buf_size(&self) -> usize { let encoded: Vec = self.0.clone().into(); - encoded.len() + encoded.len() + len_size(encoded.len()) + 1 /* tag */ } fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { let encoded: Vec = self.0.clone().into(); + + writer.write_byte(Self::TAG.into())?; + write_len(encoded.len(), writer)?; + writer.write_slice(&encoded) } } diff --git a/crates/asn1-parser/tests/decode_encode.proptest-regressions b/crates/asn1-parser/tests/decode_encode.proptest-regressions index d7bc8182..63a23b78 100644 --- a/crates/asn1-parser/tests/decode_encode.proptest-regressions +++ b/crates/asn1-parser/tests/decode_encode.proptest-regressions @@ -7,3 +7,4 @@ cc 70dd598aa0f615c38319841af46e112c297e5b2f090a3c6ad1daf04919689eed # shrinks to asn1 = OctetString(OctetString { octets: [212, 198, 222, 108, 83, 148, 187, 119, 80, 38, 119, 95, 181, 157, 122, 47, 14, 213, 152, 173, 113, 1, 189, 238, 244, 93, 44, 50, 132, 216, 68, 178, 12, 164, 64, 183, 159, 84, 237, 18, 124, 54, 254, 253, 240, 19, 227, 89, 132, 74, 216, 107, 16, 199, 30, 94, 83, 209, 122, 177, 100, 200, 212, 11, 242, 237, 201, 132, 83, 0, 9, 97, 87, 85, 82, 235, 205, 93, 217, 17, 99, 93, 159, 231, 130, 0, 76, 217, 247, 255, 201, 202, 150, 79, 120, 32, 50, 185, 38, 253, 0, 183, 233, 217, 74, 167, 69, 209, 15, 103, 102, 254, 205, 41, 46, 124, 2, 21, 197, 10, 126, 167, 174, 98, 107, 76, 50, 213, 197, 185, 100, 250, 234, 190, 20, 145, 0, 116, 80, 83, 118, 216, 248, 47, 251, 124, 169, 16, 116, 248, 117, 17, 95, 170, 69, 163, 203, 164, 196, 99, 0, 213, 2, 22, 103, 253, 85, 142, 144, 72, 216, 240, 57, 16, 78, 172, 142, 211, 100, 9, 190, 173, 234, 125, 89, 14, 202, 54, 201, 233, 88, 144, 209, 147, 188, 237, 106, 9, 21, 1, 250, 198, 133, 208, 148, 131, 66, 191, 56, 63, 70, 176, 91, 14, 25, 81, 29, 219, 183, 3, 162, 43, 241, 25, 45, 171, 212, 4, 217, 249, 69, 184, 242, 154, 238, 161] }) cc 51b45f30bad95ced1049338fcdd6821dfabf2ab64f9631c2be47736e26614a29 # shrinks to asn1 = OctetString(OctetString { octets: [20, 246, 3, 63, 4, 213], inner: None }) cc b0a39388a570f5de1a95dc9b56d46603b9e6f695f34c1685057f833462c2b5e2 # shrinks to asn1 = BmpString(BmpString([27, 243, 155, 168, 140, 36, 209, 168, 123, 243, 165, 156, 167, 127, 63, 87, 61, 65, 47])) +cc eb7148d2a10180836effdd62bddf187f2c87dab77176e1699c93015374b4eb2a # shrinks to asn1 = ObjectIdentifier(ObjectIdentifier(ObjectIdentifier { root: ItuT, first_node: 30, child_nodes: [1265159822, 285901553, 1869402537, 4043463220, 3095902683, 2156792760, 686533627, 1874797594, 437859622, 3058536772] })) diff --git a/crates/prop-strategies/Cargo.toml b/crates/prop-strategies/Cargo.toml index 74041ee8..eb653983 100644 --- a/crates/prop-strategies/Cargo.toml +++ b/crates/prop-strategies/Cargo.toml @@ -11,3 +11,4 @@ repository = "https://github.com/TheBestTvarynka/crypto-helper" [dependencies] proptest = "1.2.0" asn1-parser = { path = "../asn1-parser" } +oid = { version = "0.2.1", default-features = false } diff --git a/crates/prop-strategies/src/constructors.rs b/crates/prop-strategies/src/constructors.rs index 1e2a2e43..8a0449c0 100644 --- a/crates/prop-strategies/src/constructors.rs +++ b/crates/prop-strategies/src/constructors.rs @@ -3,7 +3,7 @@ use proptest::collection::vec; use proptest::prop_oneof; use proptest::strategy::{Just, Strategy}; -use crate::{any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_octet_string, any_utf8_string}; +use crate::{any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_object_identifier, any_octet_string, any_utf8_string}; fn any_leaf_asn1_type() -> impl Strategy { prop_oneof![ @@ -14,6 +14,7 @@ fn any_leaf_asn1_type() -> impl Strategy { any_bool().prop_map(Asn1Type::Bool), any_null().prop_map(Asn1Type::Null), any_integer().prop_map(Asn1Type::Integer), + any_object_identifier().prop_map(Asn1Type::ObjectIdentifier), ] .no_shrink() } diff --git a/crates/prop-strategies/src/lib.rs b/crates/prop-strategies/src/lib.rs index 1fe29e7c..1752cbbf 100644 --- a/crates/prop-strategies/src/lib.rs +++ b/crates/prop-strategies/src/lib.rs @@ -32,6 +32,7 @@ pub fn any_asn1_type() -> impl Strategy { any_bool().prop_map(Asn1Type::Bool), any_null().prop_map(Asn1Type::Null), any_integer().prop_map(Asn1Type::Integer), + any_object_identifier().prop_map(Asn1Type::ObjectIdentifier), recursive_empty_asn1_type(), ] .no_shrink() diff --git a/crates/prop-strategies/src/primitives.rs b/crates/prop-strategies/src/primitives.rs index 661cf5a9..1bbf305c 100644 --- a/crates/prop-strategies/src/primitives.rs +++ b/crates/prop-strategies/src/primitives.rs @@ -1,5 +1,7 @@ -use asn1_parser::{Bool, Null, OwnedInteger}; +use asn1_parser::{Bool, Null, ObjectIdentifier, OwnedInteger}; +use proptest::collection::vec; use proptest::prelude::any; +use proptest::prop_compose; use proptest::strategy::{Just, Strategy}; use crate::bytes; @@ -15,3 +17,23 @@ pub fn any_null() -> impl Strategy { pub fn any_integer() -> impl Strategy { bytes(1024).prop_map(|bytes| bytes.into()) } + +prop_compose! { + pub fn any_object_identifier() + ( + first_node in 0..3, + second_node in 0..40, + all_nodes in 2_usize..11, + ) + ( + first_node in Just(first_node), + second_node in Just(second_node), + nodes in vec(0..u32::MAX - 1, all_nodes) + ) -> ObjectIdentifier { + let mut formatted_oid = format!("{}.{}", dbg!(first_node), dbg!(second_node)); + for node in nodes { + formatted_oid.push_str(&format!(".{}", node)); + } + ObjectIdentifier::from(oid::ObjectIdentifier::try_from(formatted_oid).expect("Valid object identifier.")) + } +} From 7934e411fb5c9ef70532e83dd3432039835d3680 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Wed, 3 Jan 2024 23:14:02 +0200 Subject: [PATCH 03/28] feat(crypto-helper): asn1: implement ObjectIdentifier rendering; --- Cargo.lock | 1 + Cargo.toml | 1 + .../src/generic_types/object_identifier.rs | 5 + crates/prop-strategies/src/constructors.rs | 5 +- src/asn1/hex_view.rs | 1 + src/asn1/scheme.rs | 7 + src/asn1/scheme/oid.rs | 209 ++++++++++++++++++ 7 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 src/asn1/scheme/oid.rs diff --git a/Cargo.lock b/Cargo.lock index 395facfa..85f793cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2058,6 +2058,7 @@ dependencies = [ "js-sys", "log", "md5", + "oid", "picky", "picky-krb", "rand", diff --git a/Cargo.toml b/Cargo.toml index 016c1018..aaffae47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,3 +53,4 @@ flate2 = { version = "1.0.26", features = ["zlib"] } # asn1 asn1-parser = { path = "./crates/asn1-parser" } +oid = { version = "0.2.1", default-features = false } diff --git a/crates/asn1-parser/src/generic_types/object_identifier.rs b/crates/asn1-parser/src/generic_types/object_identifier.rs index 9c8aa635..672011e4 100644 --- a/crates/asn1-parser/src/generic_types/object_identifier.rs +++ b/crates/asn1-parser/src/generic_types/object_identifier.rs @@ -1,3 +1,4 @@ +use alloc::string::String; use alloc::vec::Vec; use crate::length::{len_size, write_len}; @@ -14,6 +15,10 @@ impl ObjectIdentifier { pub fn oid(&self) -> &oid::ObjectIdentifier { &self.0 } + + pub fn format(&self) -> String { + { &self.0 }.into() + } } impl From for ObjectIdentifier { diff --git a/crates/prop-strategies/src/constructors.rs b/crates/prop-strategies/src/constructors.rs index 8a0449c0..3b471ab9 100644 --- a/crates/prop-strategies/src/constructors.rs +++ b/crates/prop-strategies/src/constructors.rs @@ -3,7 +3,10 @@ use proptest::collection::vec; use proptest::prop_oneof; use proptest::strategy::{Just, Strategy}; -use crate::{any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_object_identifier, any_octet_string, any_utf8_string}; +use crate::{ + any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_object_identifier, any_octet_string, + any_utf8_string, +}; fn any_leaf_asn1_type() -> impl Strategy { prop_oneof![ diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index 8ec22dd8..fca8dfd0 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -168,6 +168,7 @@ fn build_data_bytes( Asn1Type::Bool(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::Null(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::Integer(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), + Asn1Type::ObjectIdentifier(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::ExplicitTag(explicit) => { build_hex_bytes(explicit.inner(), cur_node, set_cur_node.clone(), bytes, select_all) } diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index b5219c87..eee57cfb 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -1,3 +1,4 @@ +mod oid; mod primitive; mod sequence; mod strings; @@ -8,6 +9,7 @@ use web_sys::MouseEvent; use yew::virtual_dom::VNode; use yew::{classes, function_component, html, Callback, Children, Classes, Html, Properties}; +use self::oid::ObjectIdentifierNode; use self::primitive::{BoolNode, IntegerNode, NullNode}; use self::sequence::SequenceNode; use self::strings::{BitStringNode, BmpStringNode, OctetStringNode, Utf8StringNode}; @@ -93,6 +95,11 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C }, + Asn1Type::ObjectIdentifier(object_identifier) => html! { + + + + }, Asn1Type::ExplicitTag(explicit) => html! { diff --git a/src/asn1/scheme/oid.rs b/src/asn1/scheme/oid.rs new file mode 100644 index 00000000..4654a095 --- /dev/null +++ b/src/asn1/scheme/oid.rs @@ -0,0 +1,209 @@ +use asn1_parser::{ObjectIdentifier, OwnedRawAsn1EntityData}; +use yew::{function_component, html, Html, Properties}; + +use crate::asn1::node_options::NodeOptions; + +#[derive(PartialEq, Properties, Clone)] +pub struct ObjectIdentifierProps { + pub node: ObjectIdentifier, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(ObjectIdentifierNode)] +pub fn bool(props: &ObjectIdentifierProps) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + let formatted = props.node.format(); + + html! { +
+ + {&formatted} + {{ + let (name, url) = oid_name(&formatted); + if !name.is_empty() { html! { + {name} + }} else {html! {}} + }} +
+ } +} + +fn oid_name(oid: &'_ str) -> (&'static str, &'static str) { + match oid { + "1.2.840.113549.1.1.1" => ("rsaEncryption", "http://www.oid-info.com/get/1.2.840.113549.1.1.1"), + "1.2.840.10040.4.3" => ("id-dsa-with-sha1", "http://www.oid-info.com/get/1.2.840.10040.4.3"), + "1.2.840.10046.2.1" => ("dh-public-number", "http://www.oid-info.com/get/1.2.840.10046.2.1"), + "1.2.840.10045.2.1" => ("id-ecPublicKey", "http://www.oid-info.com/get/1.2.840.10045.2.1"), + "1.2.840.10045.4.3.2" => ("ecdsa-with-SHA256", "http://www.oid-info.com/get/1.2.840.10045.4.3.2"), + "1.2.840.10045.4.3.3" => ("ecdsa-with-SHA384", "http://www.oid-info.com/get/1.2.840.10045.4.3.3"), + "1.2.840.10045.4.3.4" => ("ecdsa-with-SHA512", "http://www.oid-info.com/get/1.2.840.10045.4.3.4"), + "1.2.840.10045.3.1.1" => ("prime192v1 (secp192r1)", "http://www.oid-info.com/get/1.2.840.10045.3.1.1"), + "1.2.840.10045.3.1.7" => ("prime256v1 (secp256r1)", "http://www.oid-info.com/get/1.2.840.10045.3.1.7"), + "1.2.840.113549.1.1.4" => ("md5WithRSAEncryption", "http://www.oid-info.com/get/1.2.840.113549.1.1.4"), + "1.2.840.113549.1.1.5" => ("sha1-with-rsa-signature", "http://www.oid-info.com/get/1.2.840.113549.1.1.5"), + "1.2.840.113549.1.1.11" => ("sha256-with-rsa-signature", "http://www.oid-info.com/get/1.2.840.113549.1.1.11"), + "1.2.840.113549.1.1.12" => ("sha384-with-rsa-signature", "http://www.oid-info.com/get/1.2.840.113549.1.1.12"), + "1.2.840.113549.1.1.13" => ("sha512-with-rsa-signature", "http://www.oid-info.com/get/1.2.840.113549.1.1.13"), + "1.2.840.113549.1.1.14" => ("sha224-with-rsa-signature", "http://www.oid-info.com/get/1.2.840.113549.1.1.14"), + "1.2.840.113549.1.1.10" => ("rsassa-pss", "http://www.oid-info.com/get/1.2.840.113549.1.1.10"), + "1.2.840.113549.1.9.1" => ("pkcs-9-at-emailAddress", "http://www.oid-info.com/get/1.2.840.113549.1.9.1"), + "1.2.840.113549.1.9.14" => ("pkcs-9-at-extensionRequest", "http://www.oid-info.com/get/1.2.840.113549.1.9.14"), + "1.2.840.113549.1.7.1" => ("id-data", "http://www.oid-info.com/get/1.2.840.113549.1.7.1"), + "1.2.840.113549.1.7.6" => ("id-encryptedData", "http://www.oid-info.com/get/1.2.840.113549.1.7.6"), + "1.2.840.113549.1.7.2" => ("id-signedData", "http://www.oid-info.com/get/1.2.840.113549.1.7.2"), + "1.2.840.113549.1.9.3" => ("pkcs-9-at-contentType", "http://www.oid-info.com/get/1.2.840.113549.1.9.3"), + "1.2.840.113549.1.9.4" => ("id-messageDigest", "http://www.oid-info.com/get/1.2.840.113549.1.9.4"), + "2.16.840.1.101.3.4.3.1" => ("dsa-with-sha224", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.1"), + "2.16.840.1.101.3.4.3.2" => ("dsa-with-sha256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.2"), + "2.16.840.1.101.3.4.3.3" => ("dsa-with-sha384", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.3"), + "2.16.840.1.101.3.4.3.4" => ("dsa-with-sha256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.4"), + "2.16.840.1.101.3.4.3.10" => ("id-ecdsa-with-sha3-256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.10"), + "2.16.840.1.101.3.4.3.13" => ("id-rsassa-pkcs1-v1-5-with-sha3-224", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.13"), + "2.16.840.1.101.3.4.3.14" => ("id-rsassa-pkcs1-v1-5-with-sha3-256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.14"), + "2.16.840.1.101.3.4.3.15" => ("id-rsassa-pkcs1-v1-5-with-sha3-384", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.15"), + "2.16.840.1.101.3.4.3.16" => ("id-rsassa-pkcs1-v1-5-with-sha3-512", "http://www.oid-info.com/get/2.16.840.1.101.3.4.3.16"), + "1.3.132.0.34" => ("ansip384r1 (secp384r1)", "http://www.oid-info.com/get/1.3.132.0.34"), + "1.3.132.0.1" => ("ansit163k1 (sect163k1)", "http://www.oid-info.com/get/1.3.132.0.1"), + "1.3.132.0.15" => ("ansit163r2 (sect163r2)", "http://www.oid-info.com/get/1.3.132.0.15"), + "1.3.132.0.33" => ("ansip224r1 (secp224r1)", "http://www.oid-info.com/get/1.3.132.0.33"), + "1.3.132.0.26" => ("ansit233k1 (sect233k1)", "http://www.oid-info.com/get/1.3.132.0.26"), + "1.3.132.0.27" => ("ansit233r1 (sect233r1)", "http://www.oid-info.com/get/1.3.132.0.27"), + "1.3.132.0.16" => ("ansit283k1 (sect283k1)", "http://www.oid-info.com/get/1.3.132.0.16"), + "1.3.132.0.17" => ("ansit283r1 (sect283r1)", "http://www.oid-info.com/get/1.3.132.0.17"), + "1.3.132.0.36" => ("ansit409k1 (sect409k1)", "http://www.oid-info.com/get/1.3.132.0.36"), + "1.3.132.0.37" => ("ansit409r1 (sect409r1)", "http://www.oid-info.com/get/1.3.132.0.37"), + "1.3.132.0.35" => ("ansip521r1 (secp521r1)", "http://www.oid-info.com/get/1.3.132.0.35"), + "1.3.132.0.38" => ("ansit571k1 (sect571k1)", "http://www.oid-info.com/get/1.3.132.0.38"), + "1.3.132.0.39" => ("ansit571r1 (sect571r1)", "http://www.oid-info.com/get/1.3.132.0.39"), + "1.3.101.110" => ("id-X25519", "http://www.oid-info.com/get/1.3.101.110"), + "1.3.101.111" => ("id-X448", "http://www.oid-info.com/get/1.3.101.111"), + "1.3.101.112" => ("id-Ed25519", "http://www.oid-info.com/get/1.3.101.112"), + "1.3.101.113" => ("id-Ed448", "http://www.oid-info.com/get/1.3.101.113"), + "1.3.6.1.5.5.7.3.1" => ("id-kp-serverAuth", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.1"), + "1.3.6.1.5.5.7.3.2" => ("id-kp-clientAuth", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.2"), + "1.3.6.1.5.5.7.3.3" => ("id-kp-codeSigning", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.3"), + "1.3.6.1.5.5.7.3.4" => ("id-kp-emailProtection", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.4"), + "1.3.6.1.5.5.7.3.5" => ("id-kp-ipsecEndSystem", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.5"), + "1.3.6.1.5.5.7.3.6" => ("id-kp-ipsecTunnel", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.6"), + "1.3.6.1.5.5.7.3.7" => ("id-kp-ipsecUser", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.7"), + "1.3.6.1.5.5.7.3.8" => ("id-kp-timeStamping", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.8"), + "1.3.6.1.5.5.7.3.9" => ("id-kp-OCSPSigning", "http://www.oid-info.com/get/1.3.6.1.5.5.7.3.9"), + "2.5.29.37.0" => ("anyExtendedKeyUsage", "http://www.oid-info.com/get/2.5.29.37.0"), + "1.3.6.1.4.1.311.10.3.13" => ("szOID_KP_LIFETIME_SIGNING", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.3.13"), + "2.5.4.3" => ("commonName", "http://www.oid-info.com/get/2.5.4.3"), + "2.5.4.4" => ("surname", "http://www.oid-info.com/get/2.5.4.4"), + "2.5.4.5" => ("serialNumber", "http://www.oid-info.com/get/2.5.4.5"), + "2.5.4.6" => ("countryName", "http://www.oid-info.com/get/2.5.4.6"), + "2.5.4.7" => ("localityName", "http://www.oid-info.com/get/2.5.4.7"), + "2.5.4.8" => ("stateOrProvinceName", "http://www.oid-info.com/get/2.5.4.8"), + "2.5.4.9" => ("streetAddress", "http://www.oid-info.com/get/2.5.4.9"), + "2.5.4.10" => ("organizationName", "http://www.oid-info.com/get/2.5.4.10"), + "2.5.4.11" => ("organizationalUnitName", "http://www.oid-info.com/get/2.5.4.11"), + "2.5.4.42" => ("givenName", "http://www.oid-info.com/get/2.5.4.42"), + "2.5.4.20" => ("telephoneNumber", "http://www.oid-info.com/get/2.5.4.20"), + "2.5.29.14" => ("subjectKeyIdentifier", "http://www.oid-info.com/get/2.5.29.14"), + "2.5.29.15" => ("keyUsage", "http://www.oid-info.com/get/2.5.29.15"), + "2.5.29.17" => ("subjectAltName", "http://www.oid-info.com/get/2.5.29.17"), + "2.5.29.18" => ("issuerAltName", "http://www.oid-info.com/get/2.5.29.18"), + "2.5.29.19" => ("basicConstraints", "http://www.oid-info.com/get/2.5.29.19"), + "2.5.29.20" => ("cRLNumber", "http://www.oid-info.com/get/2.5.29.20"), + "2.5.29.35" => ("authorityKeyIdentifier", "http://www.oid-info.com/get/2.5.29.35"), + "2.5.29.37" => ("extKeyUsage", "http://www.oid-info.com/get/2.5.29.37"), + "2.16.840.1.101.3.4.1.1" => ("aes128-ECB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.1"), + "2.16.840.1.101.3.4.1.2" => ("aes128-CBC-PAD", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.2"), + "2.16.840.1.101.3.4.1.3" => ("aes128-OFB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.3"), + "2.16.840.1.101.3.4.1.4" => ("aes128-CFB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.4"), + "2.16.840.1.101.3.4.1.5" => ("aes128-wrap", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.5"), + "2.16.840.1.101.3.4.1.6" => ("aes128-GCM", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.6"), + "2.16.840.1.101.3.4.1.7" => ("aes128-CCM", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.7"), + "2.16.840.1.101.3.4.1.8" => ("aes128-wrap-pad", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.8"), + "2.16.840.1.101.3.4.1.21" => ("aes192-ECB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.21"), + "2.16.840.1.101.3.4.1.22" => ("aes192-CBC-PAD", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.22"), + "2.16.840.1.101.3.4.1.23" => ("aes192-OFB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.23"), + "2.16.840.1.101.3.4.1.24" => ("aes192-CFB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.24"), + "2.16.840.1.101.3.4.1.25" => ("aes192-wrap", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.25"), + "2.16.840.1.101.3.4.1.26" => ("aes192-GCM", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.26"), + "2.16.840.1.101.3.4.1.27" => ("aes192-CCM", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.27"), + "2.16.840.1.101.3.4.1.28" => ("aes192-wrap-pad", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.28"), + "2.16.840.1.101.3.4.1.41" => ("aes256-ECB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.41"), + "2.16.840.1.101.3.4.1.42" => ("aes256-CBC-PAD", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.42"), + "2.16.840.1.101.3.4.1.43" => ("aes256-OFB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.43"), + "2.16.840.1.101.3.4.1.44" => ("aes256-CFB", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.44"), + "2.16.840.1.101.3.4.1.45" => ("id-aes256-wrap", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.45"), + "2.16.840.1.101.3.4.1.46" => ("aes256-GCM", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.46"), + "2.16.840.1.101.3.4.1.47" => ("aes256-CCM", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.47"), + "2.16.840.1.101.3.4.1.48" => ("aes256-wrap-pad", "http://www.oid-info.com/get/2.16.840.1.101.3.4.1.48"), + "1.2.840.113549.2" => ("digestAlgorithm", "http://www.oid-info.com/get/1.2.840.113549.2"), + "1.2.840.113549.2.5" => ("md5", "http://www.oid-info.com/get/1.2.840.113549.2.5"), + "1.2.840.113549.2.7" => ("hmacWithSHA1", "http://www.oid-info.com/get/1.2.840.113549.2.7"), + "1.2.840.113549.2.8" => ("hmacWithSHA224", "http://www.oid-info.com/get/1.2.840.113549.2.8"), + "1.2.840.113549.2.9" => ("hmacWithSHA256", "http://www.oid-info.com/get/1.2.840.113549.2.9"), + "1.2.840.113549.2.10" => ("hmacWithSHA384", "http://www.oid-info.com/get/1.2.840.113549.2.10"), + "1.2.840.113549.2.11" => ("hmacWithSHA512", "http://www.oid-info.com/get/1.2.840.113549.2.11"), + "1.3.14.3.2.26" => ("hashAlgorithmIdentifier", "http://www.oid-info.com/get/1.3.14.3.2.26"), + "2.16.840.1.101.3.4.2.1" => ("sha256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.1"), + "2.16.840.1.101.3.4.2.2" => ("sha384", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.2"), + "2.16.840.1.101.3.4.2.3" => ("sha512", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.3"), + "2.16.840.1.101.3.4.2.4" => ("sha224", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.4"), + "2.16.840.1.101.3.4.2.5" => ("sha512-224", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.5"), + "2.16.840.1.101.3.4.2.6" => ("sha512-256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.6"), + "2.16.840.1.101.3.4.2.7" => ("sha3-224", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.7"), + "2.16.840.1.101.3.4.2.8" => ("sha3-256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.8"), + "2.16.840.1.101.3.4.2.9" => ("sha3-384", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.9"), + "2.16.840.1.101.3.4.2.10" => ("sha3-512", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.10"), + "2.16.840.1.101.3.4.2.11" => ("id-shake128", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.11"), + "2.16.840.1.101.3.4.2.12" => ("id-shake256", "http://www.oid-info.com/get/2.16.840.1.101.3.4.2.12"), + "1.2.840.113549.1.9.5" => ("pkcs-9-at-signingTime", "http://www.oid-info.com/get/1.2.840.113549.1.9.5"), + "1.2.840.113549.1.9.6" => ("pkcs-9-at-counterSignature", "http://www.oid-info.com/get/1.2.840.113549.1.9.6"), + "1.3.6.1.4.1.311.2.1.4" => ("SPC_INDIRECT_DATA_OBJID ", "http://www.oid-info.com/get/1.3.6.1.4.1.311.2.1.4"), + "1.3.6.1.4.1.311.2.1.11" => ("SPC_STATEMENT_TYPE_OBJID ", "http://www.oid-info.com/get/1.3.6.1.4.1.311.2.1.11"), + "1.3.6.1.4.1.311.2.1.12" => ("SPC_SP_OPUS_INFO_OBJID", "http://www.oid-info.com/get/1.3.6.1.4.1.311.2.1.12"), + "1.3.6.1.4.1.311.2.1.15" => ("SPC_PE_IMAGE_DATA_OBJID ", "http://www.oid-info.com/get/1.3.6.1.4.1.311.2.1.15"), + "1.3.6.1.4.1.311.2.1.30" => ("SPC_SIPINFO_OBJID ", "http://www.oid-info.com/get/1.3.6.1.4.1.311.2.1.30"), + "1.3.6.1.4.1.311.3.2.1" => ("SPC_TIME_STAMP_REQUEST_OBJID ", "http://www.oid-info.com/get/1.3.6.1.4.1.311.3.2.1"), + "1.3.6.1.4.1.311.3.3.1" => ("Timestamping signature (Ms-CounterSign)", "http://www.oid-info.com/get/1.3.6.1.4.1.311.3.3.1"), + "1.3.6.1.4.1.311.10.1" => ("szOID_CTL", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.1"), + "1.3.6.1.4.1.311.10.3.9" => ("szOID_ROOT_LIST_SIGNER ", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.3.9"), + "1.3.6.1.4.1.311.10.11.9" => ("CERT_ENHKEY_USAGE_PROP_ID", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.9"), + "1.3.6.1.4.1.311.10.11.11" => ("CERT_FRIENDLY_NAME_PROP_ID", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.11"), + "1.3.6.1.4.1.311.10.11.20" => ("certKeyIdentifierPropId", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.20"), + "1.3.6.1.4.1.311.10.11.29" => ("certSubjectNameMd5HashPropId", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.29"), + "1.3.6.1.4.1.311.10.11.83" => ("CERT_ROOT_PROGRAM_CERT_POLICIES_PROP_ID", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.83"), + "1.3.6.1.4.1.311.10.11.98" => ("CERT_AUTH_ROOT_SHA256_HASH_PROP_ID", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.98"), + "1.3.6.1.4.1.311.10.11.104" => ("CERT_DISALLOWED_FILETIME_PROP_ID", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.104"), + "1.3.6.1.4.1.311.10.11.105" => ("CERT_ROOT_PROGRAM_CHAIN_POLICIES_PROP_ID", "http://www.oid-info.com/get/1.3.6.1.4.1.311.10.11.105"), + "1.3.6.1.4.1.311.10.11.122" => ("DISALLOWED_ENHKEY_USAGE", "https://github.com/ralphje/signify/issues/12"), + "1.3.6.1.4.1.311.10.11.126" => ("CERT_NOT_BEFORE_FILETIME_PROP_ID", "https://www.frankysweb.de/kostenloses-s-mime-zertifikat-update-april-2020"), + "1.3.6.1.4.1.311.10.11.127" => ("http://127.0.0.1:8080CERT_NOT_BEFORE_ENHKEY_USAGE_PROP_ID", "https://www.frankysweb.de/kostenloses-s-mime-zertifikat-update-april-2020"), + "1.3.6.1.4.1.311.60.3.2" => ("Auto Update End Revocation", "https://www.powershellgallery.com/packages/AutomatedLab.Common/1.1.5/Content/PkiHelper%5CPublic%5CNew-CaTemplate.ps1"), + "1.2.840.113549.1.1.8" => ("id-mgf1", "http://www.oid-info.com/get/1.2.840.113549.1.1.8"), + "1.2.840.113554.1.2.2" => ("krb5", "http://www.oid-info.com/get/1.2.840.113554.1.2.2"), + "1.2.840.48018.1.2.2" => ("MS-KILE", "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/829b9629-21ab-474f-8716-77cc0990aeb4"), + "1.2.840.113554.1.2.2.3" => ("user-to-user-mechanism", "http://www.oid-info.com/get/1.2.840.113554.1.2.2.3"), + "1.3.6.1.4.1.311.2.2.10" => ("NTLM", "http://www.oid-info.com/get/1.3.6.1.4.1.311.2.2.10"), + "1.3.6.1.4.1.311.2.2.30" => ("NEGOEX", "http://www.oid-info.com/get/1.3.6.1.4.1.311.2.2.30"), + "1.3.6.1.5.5.2" => ("snego", "http://www.oid-info.com/get/1.3.6.1.5.5.2"), + "1.3.6.1.5.2.7" => ("GSS PKU2U ", "https://oidref.com/1.3.6.1.5.2.7"), + "1.3.6.1.5.2.3.1" => ("id-pkinit-authData", "http://www.oid-info.com/get/1.3.6.1.5.2.3.1"), + "1.3.6.1.5.2.3.2" => ("id-pkinit-DHKeyData", "http://www.oid-info.com/get/1.3.6.1.5.2.3.2"), + "1.2.840.113549.1.12.1.3" => ("pbeWithSHAAnd3-KeyTripleDES-CBC", "http://www.oid-info.com/get/1.2.840.113549.1.12.1.3"), + "1.2.840.113549.1.12.1.6" => ("pbeWithSHAAnd40BitRC2-CBC", "http://www.oid-info.com/get/1.2.840.113549.1.12.1.6"), + "1.2.840.113549.1.9.23.1" => ("x509Crl", "http://www.oid-info.com/get/1.2.840.113549.1.9.23.1"), + "1.2.840.113549.1.9.22.1" => ("x509Certificate", "http://www.oid-info.com/get/1.2.840.113549.1.9.22.1"), + "1.2.840.113549.1.12.10.1.1" => ("keyBag", "http://www.oid-info.com/get/1.2.840.113549.1.12.10.1.1"), + "1.2.840.113549.1.12.10.1.2" => ("pkcs-8ShroudedKeyBag", "http://www.oid-info.com/get/1.2.840.113549.1.12.10.1.2"), + "1.2.840.113549.1.12.10.1.3" => ("certBag", "http://www.oid-info.com/get/1.2.840.113549.1.12.10.1.3"), + "1.2.840.113549.1.12.10.1.4" => ("crlBag", "http://www.oid-info.com/get/1.2.840.113549.1.12.10.1.4"), + "1.2.840.113549.1.12.10.1.5" => ("secretBag", "http://www.oid-info.com/get/1.2.840.113549.1.12.10.1.5"), + "1.2.840.113549.1.12.10.1.6" => ("safeContentsBag", "http://www.oid-info.com/get/1.2.840.113549.1.12.10.1.6"), + "1.2.840.113549.1.5.12" => ("id-PBKDF2", "http://www.oid-info.com/get/1.2.840.113549.1.5.12"), + "1.2.840.113549.1.5.13" => ("pbes2", "http://www.oid-info.com/get/1.2.840.113549.1.5.13"), + "1.2.840.113549.1.9.20" => ("pkcs-9-at-friendlyName", "http://www.oid-info.com/get/1.2.840.113549.1.9.20"), + "1.2.840.113549.1.9.21" => ("pkcs-9-at-localKeyId", "http://www.oid-info.com/get/1.2.840.113549.1.9.21"), + "1.3.6.1.4.1.311.20.2.3" => ("User Principal Name", "http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3"), + _ => ("-", "https://github.com/TheBestTvarynka/crypto-helper/issues/new"), + } +} From 50ffc1da70f8affdb491c8a2815f679482364dc5 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Tue, 9 Jan 2024 21:13:52 +0200 Subject: [PATCH 04/28] fix(crypto-helper): asn1: styling for wide values --- public/styles/asn1/node.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/styles/asn1/node.scss b/public/styles/asn1/node.scss index 4f4fa7de..4c3cd321 100644 --- a/public/styles/asn1/node.scss +++ b/public/styles/asn1/node.scss @@ -27,6 +27,7 @@ font-size: 0.8em; color: #4c5159; align-self: center; + white-space: nowrap; } .asn-simple-value { @@ -113,4 +114,5 @@ .asn1-node-options-name { cursor: pointer; + white-space: nowrap; } \ No newline at end of file From da575d343e101f1e841072ce465aecb681126a63 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Tue, 9 Jan 2024 22:02:05 +0200 Subject: [PATCH 05/28] feat(asn1-parser): implement Set; feat(prop-strategies): implement strategy for Set --- crates/asn1-parser/src/asn1.rs | 13 ++- crates/asn1-parser/src/constructors/mod.rs | 2 + .../asn1-parser/src/constructors/sequence.rs | 2 + crates/asn1-parser/src/constructors/set.rs | 86 +++++++++++++++++++ crates/asn1-parser/src/string/octet_string.rs | 17 +++- .../tests/decode_encode.proptest-regressions | 2 + crates/asn1-parser/tests/decode_encode.rs | 34 +++++++- crates/prop-strategies/src/constructors.rs | 16 +++- crates/prop-strategies/src/lib.rs | 2 +- crates/prop-strategies/src/primitives.rs | 4 +- 10 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 crates/asn1-parser/src/constructors/set.rs diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index 85877e00..dcd20b4a 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -5,12 +5,14 @@ use crate::reader::Reader; use crate::writer::Writer; use crate::{ ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, Integer, - MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Tag, Taggable, Tlv, Utf8String, + MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Asn1Type<'data> { Sequence(Sequence<'data>), + Set(Set<'data>), + OctetString(OctetString<'data>), Utf8String(Utf8String<'data>), BitString(BitString<'data>), @@ -34,6 +36,7 @@ impl Asn1Type<'_> { pub fn to_owned(&self) -> OwnedAsn1Type { match self { Asn1Type::Sequence(s) => Asn1Type::Sequence(s.to_owned()), + Asn1Type::Set(s) => Asn1Type::Set(s.to_owned()), Asn1Type::OctetString(o) => Asn1Type::OctetString(o.to_owned()), Asn1Type::Utf8String(u) => Asn1Type::Utf8String(u.to_owned()), Asn1Type::BitString(b) => Asn1Type::BitString(b.to_owned()), @@ -52,6 +55,7 @@ impl Taggable for Asn1Type<'_> { fn tag(&self) -> Tag { match self { Asn1Type::Sequence(s) => s.tag(), + Asn1Type::Set(s) => s.tag(), Asn1Type::OctetString(o) => o.tag(), Asn1Type::Utf8String(u) => u.tag(), Asn1Type::BitString(b) => b.tag(), @@ -74,6 +78,8 @@ impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { Ok(Asn1Type::Utf8String(Utf8String::decode(tag, reader)?)) } else if Sequence::compare_tags(tag) { Ok(Asn1Type::Sequence(Sequence::decode(tag, reader)?)) + } else if Set::compare_tags(tag) { + Ok(Asn1Type::Set(Set::decode(tag, reader)?)) } else if BitString::compare_tags(tag) { Ok(Asn1Type::BitString(BitString::decode(tag, reader)?)) } else if BmpString::compare_tags(tag) { @@ -106,6 +112,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::OctetString(octet) => octet.needed_buf_size(), Asn1Type::Utf8String(utf8) => utf8.needed_buf_size(), Asn1Type::Sequence(sequence) => sequence.needed_buf_size(), + Asn1Type::Set(set) => set.needed_buf_size(), Asn1Type::BitString(bit) => bit.needed_buf_size(), Asn1Type::BmpString(bmp) => bmp.needed_buf_size(), Asn1Type::Bool(boolean) => boolean.needed_buf_size(), @@ -122,6 +129,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::OctetString(octet) => octet.encode(writer), Asn1Type::Utf8String(utf8) => utf8.encode(writer), Asn1Type::Sequence(sequence) => sequence.encode(writer), + Asn1Type::Set(set) => set.encode(writer), Asn1Type::BitString(bit) => bit.encode(writer), Asn1Type::BmpString(bmp) => bmp.encode(writer), Asn1Type::Bool(boolean) => boolean.encode(writer), @@ -137,9 +145,10 @@ impl Asn1Encoder for Asn1Type<'_> { impl MetaInfo for Asn1Type<'_> { fn clear_meta(&mut self) { match self { - Asn1Type::OctetString(_) => {} + Asn1Type::OctetString(octet_string) => octet_string.clear_meta(), Asn1Type::Utf8String(_) => {} Asn1Type::Sequence(sequence) => sequence.clear_meta(), + Asn1Type::Set(set) => set.clear_meta(), Asn1Type::BitString(_) => {} Asn1Type::BmpString(_) => {} Asn1Type::Bool(_) => {} diff --git a/crates/asn1-parser/src/constructors/mod.rs b/crates/asn1-parser/src/constructors/mod.rs index 4c98b4a0..b3e6deac 100644 --- a/crates/asn1-parser/src/constructors/mod.rs +++ b/crates/asn1-parser/src/constructors/mod.rs @@ -1,3 +1,5 @@ mod sequence; +mod set; pub use sequence::{OwnedSequence, Sequence}; +pub use set::{OwnedSet, Set}; diff --git a/crates/asn1-parser/src/constructors/sequence.rs b/crates/asn1-parser/src/constructors/sequence.rs index c1242142..ddcc64e0 100644 --- a/crates/asn1-parser/src/constructors/sequence.rs +++ b/crates/asn1-parser/src/constructors/sequence.rs @@ -15,8 +15,10 @@ pub struct Sequence<'data>(Vec>); pub type OwnedSequence = Sequence<'static>; impl Sequence<'_> { + /// Tag value of the [SEQUENCE] type pub const TAG: Tag = Tag(0x30); + /// Creates a new [Sequence] from passed fields pub fn new(fields: Vec) -> Sequence { Sequence(fields) } diff --git a/crates/asn1-parser/src/constructors/set.rs b/crates/asn1-parser/src/constructors/set.rs new file mode 100644 index 00000000..6c31d6f4 --- /dev/null +++ b/crates/asn1-parser/src/constructors/set.rs @@ -0,0 +1,86 @@ +use alloc::vec::Vec; + +use crate::asn1::Asn1; +use crate::length::write_len; +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, MetaInfo, Sequence, Tag, Taggable}; + +/// [ASN.1 SET](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/set.html) +/// +/// The ASN.1 SET type is similar to the SEQUENCE type. The key difference is that the elements +/// in each value of a SEQUENCE type must appear in the order shown in the definition. +/// The elements of a SET type value may appear in any order, regardless of how they are listed in the SET's definition +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Set<'data>(Sequence<'data>); + +pub type OwnedSet = Set<'static>; + +impl Set<'_> { + /// Tag value of the [SET] type + pub const TAG: Tag = Tag(0x31); + + /// Creates a new [Set] from passed fields + pub fn new(fields: Vec) -> Set { + Set(Sequence::new(fields)) + } + + /// Returns [Set] fields + pub fn fields(&self) -> &[Asn1<'_>] { + self.0.fields() + } + + /// Returns owned version of the [Set] + pub fn to_owned(&self) -> OwnedSet { + Set(Sequence::from( + self.0 + .fields() + .iter() + .map(|f| f.to_owned_with_asn1(f.inner_asn1().to_owned())) + .collect::>(), + )) + } +} + +impl<'data> From>> for Set<'data> { + fn from(fields: Vec>) -> Self { + Self(Sequence::from(fields)) + } +} + +impl Taggable for Set<'_> { + fn tag(&self) -> Tag { + Self::TAG + } +} + +impl Asn1Encoder for Set<'_> { + fn needed_buf_size(&self) -> usize { + self.0.needed_buf_size() + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(Self::TAG.into())?; + + let data_len = self.0.fields().iter().map(|f| f.needed_buf_size()).sum(); + write_len(data_len, writer)?; + + self.0.fields().iter().try_for_each(|f| f.encode(writer)) + } +} + +impl<'data> Asn1ValueDecoder<'data> for Set<'data> { + fn decode(tag: Tag, reader: &mut Reader<'data>) -> Asn1Result { + Ok(Self(Sequence::decode(tag, reader)?)) + } + + fn compare_tags(tag: Tag) -> bool { + Self::TAG == tag + } +} + +impl MetaInfo for Set<'_> { + fn clear_meta(&mut self) { + self.0.clear_meta() + } +} diff --git a/crates/asn1-parser/src/string/octet_string.rs b/crates/asn1-parser/src/string/octet_string.rs index 941cf0de..5c37e555 100644 --- a/crates/asn1-parser/src/string/octet_string.rs +++ b/crates/asn1-parser/src/string/octet_string.rs @@ -6,7 +6,7 @@ use crate::asn1::Asn1; use crate::length::{len_size, write_len}; use crate::reader::Reader; use crate::writer::Writer; -use crate::{Asn1Decoder, Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; +use crate::{Asn1Decoder, Asn1Encoder, Asn1Result, Asn1ValueDecoder, MetaInfo, Tag, Taggable}; /// [OctetString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/octetstring.html) /// @@ -44,9 +44,10 @@ impl OctetString<'_> { } pub fn new_owned(octets: Vec) -> OwnedOctetString { - let inner = Asn1::decode_buff(&octets) - .ok() - .map(|asn1| Box::new(asn1.to_owned_with_asn1(asn1.inner_asn1().to_owned()))); + let inner = Asn1::decode_buff(&octets).ok().map(|mut asn1| { + asn1.clear_meta(); + Box::new(asn1.to_owned_with_asn1(asn1.inner_asn1().to_owned())) + }); OwnedOctetString { octets: Cow::Owned(octets), inner, @@ -111,3 +112,11 @@ impl Asn1Encoder for OctetString<'_> { writer.write_slice(&self.octets) } } + +impl MetaInfo for OctetString<'_> { + fn clear_meta(&mut self) { + if let Some(asn1) = self.inner.as_mut() { + asn1.clear_meta() + } + } +} diff --git a/crates/asn1-parser/tests/decode_encode.proptest-regressions b/crates/asn1-parser/tests/decode_encode.proptest-regressions index 63a23b78..ad881274 100644 --- a/crates/asn1-parser/tests/decode_encode.proptest-regressions +++ b/crates/asn1-parser/tests/decode_encode.proptest-regressions @@ -8,3 +8,5 @@ cc 70dd598aa0f615c38319841af46e112c297e5b2f090a3c6ad1daf04919689eed # shrinks to cc 51b45f30bad95ced1049338fcdd6821dfabf2ab64f9631c2be47736e26614a29 # shrinks to asn1 = OctetString(OctetString { octets: [20, 246, 3, 63, 4, 213], inner: None }) cc b0a39388a570f5de1a95dc9b56d46603b9e6f695f34c1685057f833462c2b5e2 # shrinks to asn1 = BmpString(BmpString([27, 243, 155, 168, 140, 36, 209, 168, 123, 243, 165, 156, 167, 127, 63, 87, 61, 65, 47])) cc eb7148d2a10180836effdd62bddf187f2c87dab77176e1699c93015374b4eb2a # shrinks to asn1 = ObjectIdentifier(ObjectIdentifier(ObjectIdentifier { root: ItuT, first_node: 30, child_nodes: [1265159822, 285901553, 1869402537, 4043463220, 3095902683, 2156792760, 686533627, 1874797594, 437859622, 3058536772] })) +cc 93d17a2405070bffc2866050cff1acd97c3bd508784cf4f19f6849a93880c617 # shrinks to asn1 = OctetString(OctetString { octets: [4, 7, 253, 157, 225, 15, 3, 232, 238], inner: Some(Tlv { id: 5, meta: RawAsn1EntityData { raw_data: [4, 7, 253, 157, 225, 15, 3, 232, 238], tag: 0, length: 1..2, data: 2..9 }, asn1: OctetString(OctetString { octets: [253, 157, 225, 15, 3, 232, 238], inner: None }) }) }) +cc 1085dfe709881822e4f7e39a9323245ca9e3fb43e18bb8714e8707af7d867af5 # shrinks to asn1 = ObjectIdentifier(ObjectIdentifier(ObjectIdentifier { root: JointIsoItuT, first_node: 29, child_nodes: [1432919503, 268680342, 2607450773, 2297838964, 2800989460, 3536442839, 826751377, 97234221, 883516388, 2427681722] })) diff --git a/crates/asn1-parser/tests/decode_encode.rs b/crates/asn1-parser/tests/decode_encode.rs index de052862..fe0be446 100644 --- a/crates/asn1-parser/tests/decode_encode.rs +++ b/crates/asn1-parser/tests/decode_encode.rs @@ -1,4 +1,4 @@ -use asn1_parser::{Asn1, Asn1Decoder, Asn1Encoder, MetaInfo, Taggable}; +use asn1_parser::{Asn1, Asn1Decoder, Asn1Encoder, Asn1Type, MetaInfo, ObjectIdentifier, Taggable}; use prop_strategies::any_asn1_type; use proptest::proptest; @@ -27,6 +27,38 @@ fn asn1() { }) } +// TODO: bug. need to be fixed +#[test] +fn oi() { + let asn1 = Asn1Type::ObjectIdentifier(ObjectIdentifier::from(oid::ObjectIdentifier::try_from("2.29.1432919503.268680342.2607450773.2297838964.2800989460.3536442839.826751377.97234221.883516388.2427681722").unwrap())); + println!("asn1: {:?}", asn1); + let asn1_tag = asn1.tag(); + + let buff_len = asn1.needed_buf_size(); + let mut buff = vec![0; buff_len]; + + asn1.encode_buff(&mut buff).unwrap(); + println!("buff: {:?}", buff); + + let mut decoded = Asn1::decode_buff(&buff).unwrap(); + let decoded_inner_asn1 = decoded.inner_asn1(); + let decoded_meta = decoded.meta(); + + println!("decoded_inner_asn1: {:?}", decoded_inner_asn1); + + assert_eq!(decoded_inner_asn1.needed_buf_size(), buff_len); + assert_eq!( + 1 + decoded_meta.length_bytes().len() + decoded_meta.data_bytes().len(), + buff_len + ); + assert_eq!(decoded_inner_asn1.tag(), asn1_tag); + assert_eq!(decoded_meta.tag_position(), 0); + assert_eq!(decoded_meta.raw_bytes(), buff); + + decoded.clear_meta(); + assert_eq!(decoded.inner_asn1(), &asn1); +} + #[test] fn decode_default() { let raw = &[ diff --git a/crates/prop-strategies/src/constructors.rs b/crates/prop-strategies/src/constructors.rs index 3b471ab9..3b6b53dd 100644 --- a/crates/prop-strategies/src/constructors.rs +++ b/crates/prop-strategies/src/constructors.rs @@ -1,10 +1,10 @@ -use asn1_parser::{Asn1Type, OwnedApplicationTag, OwnedAsn1, OwnedAsn1Type, OwnedExplicitTag, OwnedSequence}; +use asn1_parser::{Asn1Type, OwnedApplicationTag, OwnedAsn1, OwnedAsn1Type, OwnedExplicitTag, OwnedSequence, OwnedSet}; use proptest::collection::vec; use proptest::prop_oneof; use proptest::strategy::{Just, Strategy}; use crate::{ - any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_object_identifier, any_octet_string, + any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_octet_string, any_utf8_string, }; @@ -17,7 +17,7 @@ fn any_leaf_asn1_type() -> impl Strategy { any_bool().prop_map(Asn1Type::Bool), any_null().prop_map(Asn1Type::Null), any_integer().prop_map(Asn1Type::Integer), - any_object_identifier().prop_map(Asn1Type::ObjectIdentifier), + // any_object_identifier().prop_map(Asn1Type::ObjectIdentifier), ] .no_shrink() } @@ -27,7 +27,7 @@ pub fn recursive_empty_asn1_type() -> impl Strategy { let explicit_tag_inner = inner.clone(); let application_tag_inner = inner.clone(); prop_oneof![ - vec(inner, 1..16).prop_map(|fields| { + vec(inner.clone(), 1..16).prop_map(|fields| { Asn1Type::Sequence(OwnedSequence::new( fields .into_iter() @@ -35,6 +35,14 @@ pub fn recursive_empty_asn1_type() -> impl Strategy { .collect::>(), )) }), + vec(inner, 1..16).prop_map(|fields| { + Asn1Type::Set(OwnedSet::new( + fields + .into_iter() + .map(|asn1_type| OwnedAsn1::new(0, Default::default(), asn1_type)) + .collect::>(), + )) + }), (0_u8..31) .prop_flat_map(move |tag| (Just(tag), explicit_tag_inner.clone())) .prop_map(|(tag, inner)| Asn1Type::ExplicitTag(OwnedExplicitTag::new( diff --git a/crates/prop-strategies/src/lib.rs b/crates/prop-strategies/src/lib.rs index 1752cbbf..13e6efa2 100644 --- a/crates/prop-strategies/src/lib.rs +++ b/crates/prop-strategies/src/lib.rs @@ -32,7 +32,7 @@ pub fn any_asn1_type() -> impl Strategy { any_bool().prop_map(Asn1Type::Bool), any_null().prop_map(Asn1Type::Null), any_integer().prop_map(Asn1Type::Integer), - any_object_identifier().prop_map(Asn1Type::ObjectIdentifier), + // any_object_identifier().prop_map(Asn1Type::ObjectIdentifier), recursive_empty_asn1_type(), ] .no_shrink() diff --git a/crates/prop-strategies/src/primitives.rs b/crates/prop-strategies/src/primitives.rs index 1bbf305c..7851dc0b 100644 --- a/crates/prop-strategies/src/primitives.rs +++ b/crates/prop-strategies/src/primitives.rs @@ -30,10 +30,10 @@ prop_compose! { second_node in Just(second_node), nodes in vec(0..u32::MAX - 1, all_nodes) ) -> ObjectIdentifier { - let mut formatted_oid = format!("{}.{}", dbg!(first_node), dbg!(second_node)); + let mut formatted_oid = format!("{}.{}", first_node, second_node); for node in nodes { formatted_oid.push_str(&format!(".{}", node)); } - ObjectIdentifier::from(oid::ObjectIdentifier::try_from(formatted_oid).expect("Valid object identifier.")) + ObjectIdentifier::from(oid::ObjectIdentifier::try_from(dbg!(formatted_oid)).expect("Valid object identifier.")) } } From 810c6dc53a2d8528032a560224bec91911c68430 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Tue, 9 Jan 2024 22:11:43 +0200 Subject: [PATCH 06/28] feat(crypto-helper): asn1: implement Set rendering; --- crates/asn1-parser/README.md | 2 +- crates/prop-strategies/src/constructors.rs | 5 +-- src/asn1/hex_view.rs | 6 ++++ src/asn1/scheme.rs | 7 ++++ src/asn1/scheme/set.rs | 41 ++++++++++++++++++++++ 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 src/asn1/scheme/set.rs diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index 3a553889..6ec21ccb 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -47,7 +47,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result - [X] [Sequence](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html) - [ ] [SequenceOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequenceof.html) -- [ ] [Set](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/set.html) +- [X] [Set](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/set.html) - [ ] [SetOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/setof.html) - [ ] [Choice](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/choice.html) diff --git a/crates/prop-strategies/src/constructors.rs b/crates/prop-strategies/src/constructors.rs index 3b6b53dd..282383a3 100644 --- a/crates/prop-strategies/src/constructors.rs +++ b/crates/prop-strategies/src/constructors.rs @@ -3,10 +3,7 @@ use proptest::collection::vec; use proptest::prop_oneof; use proptest::strategy::{Just, Strategy}; -use crate::{ - any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_octet_string, - any_utf8_string, -}; +use crate::{any_bit_string, any_bmp_string, any_bool, any_integer, any_null, any_octet_string, any_utf8_string}; fn any_leaf_asn1_type() -> impl Strategy { prop_oneof![ diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index fca8dfd0..603f7518 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -158,6 +158,12 @@ fn build_data_bytes( .iter() .for_each(move |asn1| build_hex_bytes(asn1, cur_node, set_cur_node.clone(), bytes, select_all)); } + Asn1Type::Set(set) => { + let set_cur_node = set_cur_node.clone(); + set.fields() + .iter() + .for_each(move |asn1| build_hex_bytes(asn1, cur_node, set_cur_node.clone(), bytes, select_all)); + } Asn1Type::OctetString(octet) => match octet.inner() { Some(asn1) => build_hex_bytes(asn1, cur_node, set_cur_node.clone(), bytes, select_all), None => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index eee57cfb..7f227197 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -1,6 +1,7 @@ mod oid; mod primitive; mod sequence; +mod set; mod strings; mod tag; @@ -14,6 +15,7 @@ use self::primitive::{BoolNode, IntegerNode, NullNode}; use self::sequence::SequenceNode; use self::strings::{BitStringNode, BmpStringNode, OctetStringNode, Utf8StringNode}; use self::tag::{ApplicationTagNode, ExplicitTagNode}; +use crate::asn1::scheme::set::SetNode; use crate::asn1::HighlightAction; #[derive(PartialEq, Properties, Clone)] @@ -70,6 +72,11 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C
}, + Asn1Type::Set(set) => html! { + + + + }, Asn1Type::BitString(bit) => html! { diff --git a/src/asn1/scheme/set.rs b/src/asn1/scheme/set.rs new file mode 100644 index 00000000..635c244e --- /dev/null +++ b/src/asn1/scheme/set.rs @@ -0,0 +1,41 @@ +use asn1_parser::{OwnedRawAsn1EntityData, OwnedSet}; +use yew::{function_component, html, Callback, Html, Properties}; + +use crate::asn1::node_options::NodeOptions; +use crate::asn1::scheme::build_asn1_schema; +use crate::asn1::HighlightAction; + +#[derive(PartialEq, Properties, Clone)] +pub struct SetNodeProps { + pub node: OwnedSet, + pub cur_node: Option, + pub set_cur_node: Callback, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(SetNode)] +pub fn set(props: &SetNodeProps) -> Html { + let fields = props.node.fields(); + + let set_cur_node = &props.set_cur_node; + let fields_components = fields + .iter() + .map(|f| build_asn1_schema(f, &props.cur_node, set_cur_node)) + .collect::>(); + + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + html! { +
+
+ + {format!("({} fields)", fields.len())} +
+
+ {fields_components} +
+
+ } +} From 94eb686a490d49028a7e68433d91697c05a08a93 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Fri, 19 Jan 2024 22:41:25 +0200 Subject: [PATCH 07/28] feat(asn1-parser): update readme --- crates/asn1-parser/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index 6ec21ccb..0b21700c 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -46,9 +46,9 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result --- - [X] [Sequence](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html) -- [ ] [SequenceOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequenceof.html) +- [X] [SequenceOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequenceof.html). _Note. This library doesn't have a separate type for the_ `SequenceOf` _ASN1 data type. Just use the regular_ `Suquence` _for it. Their tags are the same, so no problem should occur._ - [X] [Set](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/set.html) -- [ ] [SetOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/setof.html) +- [X] [SetOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/setof.html). _Note. This library doesn't have a separate type for the_ `SetOf` _ASN1 data type. Just use the regular_ `Set` _for it. Their tags are the same, so no problem should occur._ - [ ] [Choice](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/choice.html) --- From 2328d9de2c0e16dfd6f2a7bedbebe48b86f69a9e Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Fri, 19 Jan 2024 22:53:15 +0200 Subject: [PATCH 08/28] feat(asn1-parser): update readme --- crates/asn1-parser/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index 0b21700c..939174f8 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -46,10 +46,10 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result --- - [X] [Sequence](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html) -- [X] [SequenceOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequenceof.html). _Note. This library doesn't have a separate type for the_ `SequenceOf` _ASN1 data type. Just use the regular_ `Suquence` _for it. Their tags are the same, so no problem should occur._ +- [X] ~~[SequenceOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequenceof.html).~~ _Note. This library doesn't have a separate type for the_ `SequenceOf` _ASN1 data type. Just use the regular_ `Suquence` _for it. Their tags are the same, so no problem should occur._ - [X] [Set](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/set.html) -- [X] [SetOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/setof.html). _Note. This library doesn't have a separate type for the_ `SetOf` _ASN1 data type. Just use the regular_ `Set` _for it. Their tags are the same, so no problem should occur._ -- [ ] [Choice](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/choice.html) +- [X] ~~[SetOf](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/setof.html).~~ _Note. This library doesn't have a separate type for the_ `SetOf` _ASN1 data type. Just use the regular_ `Set` _for it. Their tags are the same, so no problem should occur._ +- [X] ~~[Choice](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/choice.html).~~ _Note. We don't need a separate type for the ASN1_ `Choice` _type because it will still be parsed into some ASN1 object._ --- @@ -60,5 +60,5 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result ## Usage example ```rust -todo!() +todo!(); ``` \ No newline at end of file From 65b7512f6961ef328302e3dbbd627e9cf302a3e7 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 00:10:41 +0200 Subject: [PATCH 09/28] feat(asn1-parser): implement implicit tagging --- crates/asn1-parser/README.md | 2 +- crates/asn1-parser/src/asn1.rs | 12 +- crates/asn1-parser/src/string/octet_string.rs | 1 + crates/asn1-parser/src/tag.rs | 18 +++ crates/asn1-parser/src/tags/application.rs | 3 +- crates/asn1-parser/src/tags/explicit.rs | 3 +- crates/asn1-parser/src/tags/implicit.rs | 107 ++++++++++++++++++ crates/asn1-parser/src/tags/mod.rs | 2 + 8 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 crates/asn1-parser/src/tags/implicit.rs diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index 939174f8..d9386ed3 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -54,7 +54,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result --- - [X] [ExplicitTag](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/asn1-tags.html) -- [ ] [ImplicitTag](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/asn1-tags.html) +- [X] [ImplicitTag](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/asn1-tags.html) - [X] [ApplicationTag](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/asn1-tags.html) ## Usage example diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index dcd20b4a..d66773ca 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -4,8 +4,8 @@ use core::ops::Range; use crate::reader::Reader; use crate::writer::Writer; use crate::{ - ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, Integer, - MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, Utf8String, + ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, + ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -24,6 +24,7 @@ pub enum Asn1Type<'data> { ObjectIdentifier(ObjectIdentifier), ExplicitTag(ExplicitTag<'data>), + ImplicitTag(ImplicitTag<'data>), ApplicationTag(ApplicationTag<'data>), } @@ -45,6 +46,7 @@ impl Asn1Type<'_> { Asn1Type::Integer(i) => Asn1Type::Integer(i.to_owned()), Asn1Type::ObjectIdentifier(o) => Asn1Type::ObjectIdentifier(o.clone()), Asn1Type::ExplicitTag(e) => Asn1Type::ExplicitTag(e.to_owned()), + Asn1Type::ImplicitTag(i) => Asn1Type::ImplicitTag(i.to_owned()), Asn1Type::ApplicationTag(a) => Asn1Type::ApplicationTag(a.to_owned()), Asn1Type::BmpString(b) => Asn1Type::BmpString(b.to_owned()), } @@ -65,6 +67,7 @@ impl Taggable for Asn1Type<'_> { Asn1Type::Integer(i) => i.tag(), Asn1Type::ObjectIdentifier(o) => o.tag(), Asn1Type::ExplicitTag(e) => e.tag(), + Asn1Type::ImplicitTag(i) => i.tag(), Asn1Type::ApplicationTag(a) => a.tag(), } } @@ -92,6 +95,8 @@ impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { Ok(Asn1Type::ObjectIdentifier(ObjectIdentifier::decode(tag, reader)?)) } else if ExplicitTag::compare_tags(tag) { Ok(Asn1Type::ExplicitTag(ExplicitTag::decode(tag, reader)?)) + } else if ImplicitTag::compare_tags(tag) { + Ok(Asn1Type::ImplicitTag(ImplicitTag::decode(tag, reader)?)) } else if ApplicationTag::compare_tags(tag) { Ok(Asn1Type::ApplicationTag(ApplicationTag::decode(tag, reader)?)) } else if Null::compare_tags(tag) { @@ -119,6 +124,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::Integer(integer) => integer.needed_buf_size(), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.needed_buf_size(), Asn1Type::ExplicitTag(e) => e.needed_buf_size(), + Asn1Type::ImplicitTag(i) => i.needed_buf_size(), Asn1Type::ApplicationTag(a) => a.needed_buf_size(), Asn1Type::Null(n) => n.needed_buf_size(), } @@ -136,6 +142,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::Integer(integer) => integer.encode(writer), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.encode(writer), Asn1Type::ExplicitTag(e) => e.encode(writer), + Asn1Type::ImplicitTag(i) => i.encode(writer), Asn1Type::ApplicationTag(a) => a.encode(writer), Asn1Type::Null(n) => n.encode(writer), } @@ -155,6 +162,7 @@ impl MetaInfo for Asn1Type<'_> { Asn1Type::Integer(_) => {} Asn1Type::ObjectIdentifier(_) => {} Asn1Type::ExplicitTag(explicit_tag) => explicit_tag.clear_meta(), + Asn1Type::ImplicitTag(implicit_tag) => implicit_tag.clear_meta(), Asn1Type::ApplicationTag(application_tag) => application_tag.clear_meta(), Asn1Type::Null(_) => {} } diff --git a/crates/asn1-parser/src/string/octet_string.rs b/crates/asn1-parser/src/string/octet_string.rs index 5c37e555..ec89230a 100644 --- a/crates/asn1-parser/src/string/octet_string.rs +++ b/crates/asn1-parser/src/string/octet_string.rs @@ -48,6 +48,7 @@ impl OctetString<'_> { asn1.clear_meta(); Box::new(asn1.to_owned_with_asn1(asn1.inner_asn1().to_owned())) }); + OwnedOctetString { octets: Cow::Owned(octets), inner, diff --git a/crates/asn1-parser/src/tag.rs b/crates/asn1-parser/src/tag.rs index 424538a4..3a065ee6 100644 --- a/crates/asn1-parser/src/tag.rs +++ b/crates/asn1-parser/src/tag.rs @@ -1,6 +1,24 @@ #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Tag(pub(crate) u8); +impl Tag { + pub fn is_context_specific(self) -> bool { + self.0 & 0xc0 == 0x80 + } + + pub fn is_application(self) -> bool { + self.0 & 0xc0 == 0x40 + } + + pub fn is_constructed(self) -> bool { + self.0 & 0x20 == 0x20 + } + + pub fn is_primitive(self) -> bool { + !self.is_constructed() + } +} + impl From for Tag { fn from(tag: u8) -> Self { Self(tag) diff --git a/crates/asn1-parser/src/tags/application.rs b/crates/asn1-parser/src/tags/application.rs index 9ef28c28..7ca91b70 100644 --- a/crates/asn1-parser/src/tags/application.rs +++ b/crates/asn1-parser/src/tags/application.rs @@ -56,8 +56,7 @@ impl<'data> Asn1ValueDecoder<'data> for ApplicationTag<'data> { } fn compare_tags(tag: Tag) -> bool { - let raw_tag = tag.0; - raw_tag & 0xc0 == 0x40 && raw_tag & 0x20 == 0x20 + tag.is_application() && tag.is_constructed() } } diff --git a/crates/asn1-parser/src/tags/explicit.rs b/crates/asn1-parser/src/tags/explicit.rs index eae12457..7d27a4b5 100644 --- a/crates/asn1-parser/src/tags/explicit.rs +++ b/crates/asn1-parser/src/tags/explicit.rs @@ -56,8 +56,7 @@ impl<'data> Asn1ValueDecoder<'data> for ExplicitTag<'data> { } fn compare_tags(tag: Tag) -> bool { - let raw_tag = tag.0; - raw_tag & 0xc0 == 0x80 && raw_tag & 0x20 == 0x20 + tag.is_context_specific() && tag.is_constructed() } } diff --git a/crates/asn1-parser/src/tags/implicit.rs b/crates/asn1-parser/src/tags/implicit.rs new file mode 100644 index 00000000..43a4785a --- /dev/null +++ b/crates/asn1-parser/src/tags/implicit.rs @@ -0,0 +1,107 @@ +use alloc::borrow::Cow; +use alloc::boxed::Box; +use alloc::vec::Vec; + +use crate::asn1::Asn1; +use crate::length::{len_size, write_len}; +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Decoder, Asn1Encoder, Asn1Result, Asn1ValueDecoder, MetaInfo, Tag, Taggable}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ImplicitTag<'data> { + tag: u8, + octets: Cow<'data, [u8]>, + inner: Option>>, +} + +pub type OwnedImplicitTag = ImplicitTag<'static>; + +impl<'data> ImplicitTag<'data> { + pub fn new_owned(tag: u8, octets: Vec) -> Self { + let inner = Asn1::decode_buff(&octets).ok().map(|mut asn1| { + asn1.clear_meta(); + Box::new(asn1.to_owned_with_asn1(asn1.inner_asn1().to_owned())) + }); + + Self { + tag, + octets: Cow::Owned(octets), + inner, + } + } + + pub fn tag_number(&self) -> u8 { + self.tag & 0x1f + } + + pub fn inner_asn1(&self) -> Option<&Asn1<'data>> { + self.inner.as_ref().map(|asn1| asn1.as_ref()) + } + + pub fn to_owned(&self) -> OwnedImplicitTag { + OwnedImplicitTag { + tag: self.tag, + octets: self.octets.to_vec().into(), + inner: self + .inner + .as_ref() + .map(|inner| Box::new(inner.to_owned_with_asn1(inner.inner_asn1().to_owned()))), + } + } +} + +impl Taggable for ImplicitTag<'_> { + fn tag(&self) -> Tag { + Tag(self.tag) + } +} + +impl<'data> Asn1ValueDecoder<'data> for ImplicitTag<'data> { + fn decode(tag: Tag, reader: &mut Reader<'data>) -> Asn1Result { + let data = reader.read_remaining(); + + let mut inner_reader = Reader::new(data); + inner_reader.set_next_id(reader.next_id()); + inner_reader.set_offset(reader.full_offset() - data.len()); + let inner = Asn1::decode(&mut inner_reader).ok().map(Box::new); + + if !inner_reader.empty() && inner.is_some() { + return Err("implicit tag inner data contains leftovers".into()); + } + + reader.set_next_id(inner_reader.next_id()); + + Ok(Self { + tag: tag.0, + octets: Cow::Borrowed(data), + inner, + }) + } + + fn compare_tags(tag: Tag) -> bool { + tag.is_context_specific() && tag.is_primitive() + } +} + +impl Asn1Encoder for ImplicitTag<'_> { + fn needed_buf_size(&self) -> usize { + let data_len = self.octets.len(); + + 1 /* tag */ + len_size(data_len) + data_len + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(self.tag)?; + write_len(self.octets.len(), writer)?; + writer.write_slice(&self.octets) + } +} + +impl MetaInfo for ImplicitTag<'_> { + fn clear_meta(&mut self) { + if let Some(asn1) = self.inner.as_mut() { + asn1.clear_meta() + } + } +} diff --git a/crates/asn1-parser/src/tags/mod.rs b/crates/asn1-parser/src/tags/mod.rs index ed44f2c1..ed145646 100644 --- a/crates/asn1-parser/src/tags/mod.rs +++ b/crates/asn1-parser/src/tags/mod.rs @@ -1,5 +1,7 @@ mod application; mod explicit; +mod implicit; pub use application::{ApplicationTag, OwnedApplicationTag}; pub use explicit::{ExplicitTag, OwnedExplicitTag}; +pub use implicit::{ImplicitTag, OwnedImplicitTag}; From 22d0528f50dfe3d77206c0d453f9c094ea265abe Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 00:32:44 +0200 Subject: [PATCH 10/28] feat(crypto-helper): asn1: implement ImplicitTag rendering; --- crates/asn1-parser/src/string/octet_string.rs | 8 ++-- crates/asn1-parser/src/tags/implicit.rs | 12 +++--- crates/asn1-parser/tests/decode_encode.rs | 3 +- crates/prop-strategies/src/string.rs | 2 +- src/asn1/hex_view.rs | 4 ++ src/asn1/scheme.rs | 7 +++- src/asn1/scheme/tag.rs | 38 ++++++++++++++++++- 7 files changed, 60 insertions(+), 14 deletions(-) diff --git a/crates/asn1-parser/src/string/octet_string.rs b/crates/asn1-parser/src/string/octet_string.rs index ec89230a..016e5a1c 100644 --- a/crates/asn1-parser/src/string/octet_string.rs +++ b/crates/asn1-parser/src/string/octet_string.rs @@ -75,10 +75,10 @@ impl<'data> Asn1ValueDecoder<'data> for OctetString<'data> { let mut inner_reader = Reader::new(data); inner_reader.set_next_id(reader.next_id()); inner_reader.set_offset(reader.full_offset() - data.len()); - let inner = Asn1::decode(&mut inner_reader).ok().map(Box::new); + let mut inner = Asn1::decode(&mut inner_reader).ok().map(Box::new); if !inner_reader.empty() && inner.is_some() { - return Err("octet string inner data contains leftovers".into()); + inner = None; } reader.set_next_id(inner_reader.next_id()); @@ -116,8 +116,6 @@ impl Asn1Encoder for OctetString<'_> { impl MetaInfo for OctetString<'_> { fn clear_meta(&mut self) { - if let Some(asn1) = self.inner.as_mut() { - asn1.clear_meta() - } + self.inner = None; } } diff --git a/crates/asn1-parser/src/tags/implicit.rs b/crates/asn1-parser/src/tags/implicit.rs index 43a4785a..eb2e5dd0 100644 --- a/crates/asn1-parser/src/tags/implicit.rs +++ b/crates/asn1-parser/src/tags/implicit.rs @@ -39,6 +39,10 @@ impl<'data> ImplicitTag<'data> { self.inner.as_ref().map(|asn1| asn1.as_ref()) } + pub fn octets(&self) -> &[u8] { + self.octets.as_ref() + } + pub fn to_owned(&self) -> OwnedImplicitTag { OwnedImplicitTag { tag: self.tag, @@ -64,10 +68,10 @@ impl<'data> Asn1ValueDecoder<'data> for ImplicitTag<'data> { let mut inner_reader = Reader::new(data); inner_reader.set_next_id(reader.next_id()); inner_reader.set_offset(reader.full_offset() - data.len()); - let inner = Asn1::decode(&mut inner_reader).ok().map(Box::new); + let mut inner = Asn1::decode(&mut inner_reader).ok().map(Box::new); if !inner_reader.empty() && inner.is_some() { - return Err("implicit tag inner data contains leftovers".into()); + inner = None; } reader.set_next_id(inner_reader.next_id()); @@ -100,8 +104,6 @@ impl Asn1Encoder for ImplicitTag<'_> { impl MetaInfo for ImplicitTag<'_> { fn clear_meta(&mut self) { - if let Some(asn1) = self.inner.as_mut() { - asn1.clear_meta() - } + self.inner = None; } } diff --git a/crates/asn1-parser/tests/decode_encode.rs b/crates/asn1-parser/tests/decode_encode.rs index fe0be446..4b81af6d 100644 --- a/crates/asn1-parser/tests/decode_encode.rs +++ b/crates/asn1-parser/tests/decode_encode.rs @@ -4,7 +4,7 @@ use proptest::proptest; #[test] fn asn1() { - proptest!(|(asn1 in any_asn1_type())| { + proptest!(|(mut asn1 in any_asn1_type())| { let asn1_tag = asn1.tag(); let buff_len = asn1.needed_buf_size(); @@ -23,6 +23,7 @@ fn asn1() { assert_eq!(decoded_meta.raw_bytes(), buff); decoded.clear_meta(); + asn1.clear_meta(); assert_eq!(decoded.inner_asn1(), &asn1); }) } diff --git a/crates/prop-strategies/src/string.rs b/crates/prop-strategies/src/string.rs index 326e7b1a..2ba2fbcb 100644 --- a/crates/prop-strategies/src/string.rs +++ b/crates/prop-strategies/src/string.rs @@ -3,7 +3,7 @@ use proptest::prop_compose; use crate::{bytes, string}; -const STRING_LEN: usize = 12; +const STRING_LEN: usize = 32; prop_compose! { pub fn any_octet_string() diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index 603f7518..18a7150f 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -178,6 +178,10 @@ fn build_data_bytes( Asn1Type::ExplicitTag(explicit) => { build_hex_bytes(explicit.inner(), cur_node, set_cur_node.clone(), bytes, select_all) } + Asn1Type::ImplicitTag(implicit) => match implicit.inner_asn1() { + Some(asn1) => build_hex_bytes(asn1, cur_node, set_cur_node.clone(), bytes, select_all), + None => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), + }, Asn1Type::ApplicationTag(application) => { build_hex_bytes(application.inner(), cur_node, set_cur_node.clone(), bytes, select_all) } diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index 7f227197..3101f0a0 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -14,7 +14,7 @@ use self::oid::ObjectIdentifierNode; use self::primitive::{BoolNode, IntegerNode, NullNode}; use self::sequence::SequenceNode; use self::strings::{BitStringNode, BmpStringNode, OctetStringNode, Utf8StringNode}; -use self::tag::{ApplicationTagNode, ExplicitTagNode}; +use self::tag::{ApplicationTagNode, ExplicitTagNode, ImplicitTagNode}; use crate::asn1::scheme::set::SetNode; use crate::asn1::HighlightAction; @@ -112,6 +112,11 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C
}, + Asn1Type::ImplicitTag(implicit) => html! { + + + + }, Asn1Type::ApplicationTag(application) => html! { diff --git a/src/asn1/scheme/tag.rs b/src/asn1/scheme/tag.rs index a006b0e9..13755113 100644 --- a/src/asn1/scheme/tag.rs +++ b/src/asn1/scheme/tag.rs @@ -1,4 +1,4 @@ -use asn1_parser::{OwnedApplicationTag, OwnedExplicitTag, OwnedRawAsn1EntityData}; +use asn1_parser::{OwnedApplicationTag, OwnedExplicitTag, OwnedImplicitTag, OwnedRawAsn1EntityData}; use yew::{function_component, html, Callback, Html, Properties}; use crate::asn1::node_options::NodeOptions; @@ -30,6 +30,7 @@ pub fn explicit_tag(props: &ExplicitTagProps) -> Html { } } + #[derive(PartialEq, Properties, Clone)] pub struct ApplicationTagProps { pub node: OwnedApplicationTag, @@ -55,3 +56,38 @@ pub fn application_tag(props: &ApplicationTagProps) -> Html { } } +#[derive(PartialEq, Properties, Clone)] +pub struct ImplicitTagProps { + pub node: OwnedImplicitTag, + pub cur_node: Option, + pub set_cur_node: Callback, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(ImplicitTagNode)] +pub fn implicit_tag(props: &ImplicitTagProps) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + let octets = props.node.octets(); + + match props.node.inner_asn1() { + Some(asn1) => html! { +
+
+ +
+
+ {build_asn1_schema(asn1, &props.cur_node, &props.set_cur_node)} +
+
+ }, + None => html! { +
+ + {format!("({} bytes)", octets.len())} + {hex::encode(octets)} +
+ }, + } +} From 6978a21116f637924c669df56d74bb84dce442ee Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 00:41:21 +0200 Subject: [PATCH 11/28] refactor(asn1-parser): wip --- crates/asn1-parser/src/lib.rs | 4 ++-- .../asn1-parser/src/{generic_types => primitives}/boolean.rs | 0 .../asn1-parser/src/{generic_types => primitives}/integer.rs | 0 crates/asn1-parser/src/{generic_types => primitives}/mod.rs | 0 crates/asn1-parser/src/{generic_types => primitives}/null.rs | 0 .../src/{generic_types => primitives}/object_identifier.rs | 0 src/asn1/scheme/oid.rs | 1 + 7 files changed, 3 insertions(+), 2 deletions(-) rename crates/asn1-parser/src/{generic_types => primitives}/boolean.rs (100%) rename crates/asn1-parser/src/{generic_types => primitives}/integer.rs (100%) rename crates/asn1-parser/src/{generic_types => primitives}/mod.rs (100%) rename crates/asn1-parser/src/{generic_types => primitives}/null.rs (100%) rename crates/asn1-parser/src/{generic_types => primitives}/object_identifier.rs (100%) diff --git a/crates/asn1-parser/src/lib.rs b/crates/asn1-parser/src/lib.rs index 740a5aea..04c73d78 100644 --- a/crates/asn1-parser/src/lib.rs +++ b/crates/asn1-parser/src/lib.rs @@ -8,7 +8,7 @@ mod macros; mod asn1; mod constructors; mod error; -mod generic_types; +mod primitives; mod length; mod reader; mod string; @@ -20,7 +20,7 @@ mod writer; pub use asn1::{Asn1, Asn1Type, OwnedAsn1, OwnedAsn1Type, OwnedRawAsn1EntityData, RawAsn1EntityData}; pub use constructors::*; pub use error::Error; -pub use generic_types::*; +pub use primitives::*; use reader::Reader; pub use string::*; pub use tag::Tag; diff --git a/crates/asn1-parser/src/generic_types/boolean.rs b/crates/asn1-parser/src/primitives/boolean.rs similarity index 100% rename from crates/asn1-parser/src/generic_types/boolean.rs rename to crates/asn1-parser/src/primitives/boolean.rs diff --git a/crates/asn1-parser/src/generic_types/integer.rs b/crates/asn1-parser/src/primitives/integer.rs similarity index 100% rename from crates/asn1-parser/src/generic_types/integer.rs rename to crates/asn1-parser/src/primitives/integer.rs diff --git a/crates/asn1-parser/src/generic_types/mod.rs b/crates/asn1-parser/src/primitives/mod.rs similarity index 100% rename from crates/asn1-parser/src/generic_types/mod.rs rename to crates/asn1-parser/src/primitives/mod.rs diff --git a/crates/asn1-parser/src/generic_types/null.rs b/crates/asn1-parser/src/primitives/null.rs similarity index 100% rename from crates/asn1-parser/src/generic_types/null.rs rename to crates/asn1-parser/src/primitives/null.rs diff --git a/crates/asn1-parser/src/generic_types/object_identifier.rs b/crates/asn1-parser/src/primitives/object_identifier.rs similarity index 100% rename from crates/asn1-parser/src/generic_types/object_identifier.rs rename to crates/asn1-parser/src/primitives/object_identifier.rs diff --git a/src/asn1/scheme/oid.rs b/src/asn1/scheme/oid.rs index 4654a095..3609261d 100644 --- a/src/asn1/scheme/oid.rs +++ b/src/asn1/scheme/oid.rs @@ -204,6 +204,7 @@ fn oid_name(oid: &'_ str) -> (&'static str, &'static str) { "1.2.840.113549.1.9.20" => ("pkcs-9-at-friendlyName", "http://www.oid-info.com/get/1.2.840.113549.1.9.20"), "1.2.840.113549.1.9.21" => ("pkcs-9-at-localKeyId", "http://www.oid-info.com/get/1.2.840.113549.1.9.21"), "1.3.6.1.4.1.311.20.2.3" => ("User Principal Name", "http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3"), + "1.2.840.113549.1.9.16.2.47" => ("Signing certificate V2 ", "http://oid-info.com/get/1.2.840.113549.1.9.16.2.47"), _ => ("-", "https://github.com/TheBestTvarynka/crypto-helper/issues/new"), } } From bf6b5b0bc7109a124fdaede3962af3bab7346c84 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 22:14:23 +0200 Subject: [PATCH 12/28] feat(asn1-parser): implement UtcTime; --- crates/asn1-parser/src/asn1.rs | 12 +- crates/asn1-parser/src/lib.rs | 4 +- crates/asn1-parser/src/time/mod.rs | 3 + crates/asn1-parser/src/time/utc_time.rs | 144 ++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 crates/asn1-parser/src/time/mod.rs create mode 100644 crates/asn1-parser/src/time/utc_time.rs diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index d66773ca..43cc9eda 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -5,7 +5,8 @@ use crate::reader::Reader; use crate::writer::Writer; use crate::{ ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, - ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, Utf8String, + ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, UtcTime, + Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -18,6 +19,8 @@ pub enum Asn1Type<'data> { BitString(BitString<'data>), BmpString(BmpString<'data>), + UtcTime(UtcTime), + Bool(Bool), Null(Null), Integer(Integer<'data>), @@ -49,6 +52,7 @@ impl Asn1Type<'_> { Asn1Type::ImplicitTag(i) => Asn1Type::ImplicitTag(i.to_owned()), Asn1Type::ApplicationTag(a) => Asn1Type::ApplicationTag(a.to_owned()), Asn1Type::BmpString(b) => Asn1Type::BmpString(b.to_owned()), + Asn1Type::UtcTime(u) => Asn1Type::UtcTime(u.clone()), } } } @@ -69,6 +73,7 @@ impl Taggable for Asn1Type<'_> { Asn1Type::ExplicitTag(e) => e.tag(), Asn1Type::ImplicitTag(i) => i.tag(), Asn1Type::ApplicationTag(a) => a.tag(), + Asn1Type::UtcTime(u) => u.tag(), } } } @@ -101,6 +106,8 @@ impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { Ok(Asn1Type::ApplicationTag(ApplicationTag::decode(tag, reader)?)) } else if Null::compare_tags(tag) { Ok(Asn1Type::Null(Null::decode(tag, reader)?)) + } else if UtcTime::compare_tags(tag) { + Ok(Asn1Type::UtcTime(UtcTime::decode(tag, reader)?)) } else { Err(Error::from("Invalid data")) } @@ -127,6 +134,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::ImplicitTag(i) => i.needed_buf_size(), Asn1Type::ApplicationTag(a) => a.needed_buf_size(), Asn1Type::Null(n) => n.needed_buf_size(), + Asn1Type::UtcTime(u) => u.needed_buf_size(), } } @@ -145,6 +153,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::ImplicitTag(i) => i.encode(writer), Asn1Type::ApplicationTag(a) => a.encode(writer), Asn1Type::Null(n) => n.encode(writer), + Asn1Type::UtcTime(utc_time) => utc_time.encode(writer), } } } @@ -165,6 +174,7 @@ impl MetaInfo for Asn1Type<'_> { Asn1Type::ImplicitTag(implicit_tag) => implicit_tag.clear_meta(), Asn1Type::ApplicationTag(application_tag) => application_tag.clear_meta(), Asn1Type::Null(_) => {} + Asn1Type::UtcTime(_) => {} } } } diff --git a/crates/asn1-parser/src/lib.rs b/crates/asn1-parser/src/lib.rs index 04c73d78..9d8f3f70 100644 --- a/crates/asn1-parser/src/lib.rs +++ b/crates/asn1-parser/src/lib.rs @@ -8,12 +8,13 @@ mod macros; mod asn1; mod constructors; mod error; -mod primitives; mod length; +mod primitives; mod reader; mod string; mod tag; mod tags; +mod time; mod tlv; mod writer; @@ -25,6 +26,7 @@ use reader::Reader; pub use string::*; pub use tag::Tag; pub use tags::*; +pub use time::*; pub use tlv::Tlv; use writer::Writer; diff --git a/crates/asn1-parser/src/time/mod.rs b/crates/asn1-parser/src/time/mod.rs new file mode 100644 index 00000000..4fd89e65 --- /dev/null +++ b/crates/asn1-parser/src/time/mod.rs @@ -0,0 +1,3 @@ +mod utc_time; + +pub use utc_time::{Day, Hour, Minute, Month, Second, UtcTime, Year}; diff --git a/crates/asn1-parser/src/time/utc_time.rs b/crates/asn1-parser/src/time/utc_time.rs new file mode 100644 index 00000000..a739261d --- /dev/null +++ b/crates/asn1-parser/src/time/utc_time.rs @@ -0,0 +1,144 @@ +use alloc::format; + +use crate::length::{len_size, write_len}; +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Error, Tag, Taggable}; + +macro_rules! define_nt { + ($name:ident) => { + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct $name(u8); + + impl From<$name> for u8 { + fn from(value: $name) -> Self { + value.0 + } + } + + impl AsRef for $name { + fn as_ref(&self) -> &u8 { + &self.0 + } + } + + impl TryFrom for $name { + type Error = Error; + + fn try_from(value: u8) -> Result { + if value < 100 { + Ok($name(value)) + } else { + Err("invalid value".into()) + } + } + } + }; +} + +define_nt!(Year); +define_nt!(Month); +define_nt!(Day); +define_nt!(Hour); +define_nt!(Minute); +define_nt!(Second); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UtcTime { + year: Year, + month: Month, + day: Day, + hour: Hour, + minute: Minute, + second: Option, +} + +impl UtcTime { + pub const TAG: Tag = Tag(23); + + pub fn new(year: Year, month: Month, day: Day, hour: Hour, minute: Minute, second: Option) -> Self { + Self { + year, + month, + day, + hour, + minute, + second, + } + } + + fn calc_data_len(&self) -> usize { + 2 /* year */ + 2 /* month */ + 2 /* day */ + 2 /* hour */ + 2 /* minute */ + self.second.is_some().then_some(2).unwrap_or_default() + } +} + +impl<'data> Asn1ValueDecoder<'data> for UtcTime { + fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { + let utc_time = UtcTime { + year: Year::try_from(read_number(reader)?)?, + month: Month::try_from(read_number(reader)?)?, + day: Day::try_from(read_number(reader)?)?, + hour: Hour::try_from(read_number(reader)?)?, + minute: Minute::try_from(read_number(reader)?)?, + second: if reader.peek_byte()? != b'Z' { + Some(Second::try_from(read_number(reader)?)?) + } else { + None + }, + }; + + if reader.read_byte()? != b'Z' { + return Err("utctime value should end with 'Z'".into()); + } + + Ok(utc_time) + } + + fn compare_tags(tag: Tag) -> bool { + Self::TAG == tag + } +} + +impl Taggable for UtcTime { + fn tag(&self) -> Tag { + Self::TAG + } +} + +impl Asn1Encoder for UtcTime { + fn needed_buf_size(&self) -> usize { + let value_len = self.calc_data_len(); + + 1 /* tag */ + len_size(value_len) + value_len + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(Self::TAG.into())?; + write_len(self.calc_data_len(), writer)?; + + writer.write_slice(format!("{:02}", self.year.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.month.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.day.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.hour.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.minute.as_ref()).as_bytes())?; + + if let Some(second) = self.second.as_ref() { + writer.write_slice(format!("{:02}", second.as_ref()).as_bytes())?; + } + + writer.write_byte(b'Z') + } +} + +fn read_number(reader: &mut Reader<'_>) -> Asn1Result { + const ASCII_SHIFT: u8 = 48; + + let f = char::from(reader.read_byte()?); + let s = char::from(reader.read_byte()?); + + if !f.is_numeric() || !s.is_numeric() { + return Err("invalid bytes for utctime".into()); + } + + Ok((f as u8 - ASCII_SHIFT) * 10 + (s as u8 - ASCII_SHIFT)) +} From 57a88dd17f44c23566f800fe96b5fcf95dc0ca6a Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 22:46:42 +0200 Subject: [PATCH 13/28] feat(crypto-helper): asn1: implement UtcTime rendering; --- crates/asn1-parser/README.md | 2 +- crates/asn1-parser/src/time/utc_time.rs | 12 ++--- crates/asn1-parser/tests/decode_encode.rs | 11 +++++ src/asn1.rs | 2 +- src/asn1/hex_view.rs | 1 + src/asn1/scheme.rs | 7 +++ src/asn1/scheme/oid.rs | 3 +- src/asn1/scheme/time.rs | 56 +++++++++++++++++++++++ 8 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 src/asn1/scheme/time.rs diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index d9386ed3..bbfca017 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -32,7 +32,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result - [ ] [GeneralizedTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalizedtime.html) - [ ] [Time](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/time.html) -- [ ] [UtcTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/utctime.html) +- [X] [UtcTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/utctime.html) - [ ] [TimeOfDay](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/timeofday.html) --- diff --git a/crates/asn1-parser/src/time/utc_time.rs b/crates/asn1-parser/src/time/utc_time.rs index a739261d..5497facf 100644 --- a/crates/asn1-parser/src/time/utc_time.rs +++ b/crates/asn1-parser/src/time/utc_time.rs @@ -45,12 +45,12 @@ define_nt!(Second); #[derive(Debug, Clone, PartialEq, Eq)] pub struct UtcTime { - year: Year, - month: Month, - day: Day, - hour: Hour, - minute: Minute, - second: Option, + pub year: Year, + pub month: Month, + pub day: Day, + pub hour: Hour, + pub minute: Minute, + pub second: Option, } impl UtcTime { diff --git a/crates/asn1-parser/tests/decode_encode.rs b/crates/asn1-parser/tests/decode_encode.rs index 4b81af6d..7f8b208c 100644 --- a/crates/asn1-parser/tests/decode_encode.rs +++ b/crates/asn1-parser/tests/decode_encode.rs @@ -28,6 +28,17 @@ fn asn1() { }) } +#[test] +fn utc_time() { + let raw = [23, 11, 57, 54, 48, 52, 49, 53, 50, 48, 51, 48, 90]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); + + let raw = [23, 13, 49, 57, 49, 48, 49, 55, 49, 55, 52, 49, 50, 56, 90]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); +} + // TODO: bug. need to be fixed #[test] fn oi() { diff --git a/src/asn1.rs b/src/asn1.rs index 4317ec57..74d4ad06 100644 --- a/src/asn1.rs +++ b/src/asn1.rs @@ -110,7 +110,7 @@ pub fn asn1_parser_page() -> Html { {"list of supported asn1 types"} - {". Report a bug "}{"here"}{"."} + {". Report a bug/feature "}{"here"}{"."}
diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index 18a7150f..70d32e26 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -169,6 +169,7 @@ fn build_data_bytes( None => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), }, Asn1Type::Utf8String(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), + Asn1Type::UtcTime(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BitString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BmpString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::Bool(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index 3101f0a0..24611ce7 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -4,6 +4,7 @@ mod sequence; mod set; mod strings; mod tag; +mod time; use asn1_parser::{Asn1, Asn1Entity, Asn1Type}; use web_sys::MouseEvent; @@ -15,6 +16,7 @@ use self::primitive::{BoolNode, IntegerNode, NullNode}; use self::sequence::SequenceNode; use self::strings::{BitStringNode, BmpStringNode, OctetStringNode, Utf8StringNode}; use self::tag::{ApplicationTagNode, ExplicitTagNode, ImplicitTagNode}; +use self::time::UtcTimeNode; use crate::asn1::scheme::set::SetNode; use crate::asn1::HighlightAction; @@ -122,5 +124,10 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C }, + Asn1Type::UtcTime(utc_time) => html! { + + + + }, } } diff --git a/src/asn1/scheme/oid.rs b/src/asn1/scheme/oid.rs index 3609261d..a0a8cd1b 100644 --- a/src/asn1/scheme/oid.rs +++ b/src/asn1/scheme/oid.rs @@ -204,7 +204,8 @@ fn oid_name(oid: &'_ str) -> (&'static str, &'static str) { "1.2.840.113549.1.9.20" => ("pkcs-9-at-friendlyName", "http://www.oid-info.com/get/1.2.840.113549.1.9.20"), "1.2.840.113549.1.9.21" => ("pkcs-9-at-localKeyId", "http://www.oid-info.com/get/1.2.840.113549.1.9.21"), "1.3.6.1.4.1.311.20.2.3" => ("User Principal Name", "http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3"), - "1.2.840.113549.1.9.16.2.47" => ("Signing certificate V2 ", "http://oid-info.com/get/1.2.840.113549.1.9.16.2.47"), + "1.2.840.113549.1.9.16.2.47" => ("Signing certificate V2", "http://oid-info.com/get/1.2.840.113549.1.9.16.2.47"), + "1.2.840.113549.1.9.52" => ("id-aa-CMSAlgorithmProtection", "https://oidref.com/1.2.840.113549.1.9.52"), _ => ("-", "https://github.com/TheBestTvarynka/crypto-helper/issues/new"), } } diff --git a/src/asn1/scheme/time.rs b/src/asn1/scheme/time.rs new file mode 100644 index 00000000..0d846c0d --- /dev/null +++ b/src/asn1/scheme/time.rs @@ -0,0 +1,56 @@ +use asn1_parser::{OwnedRawAsn1EntityData, UtcTime}; +use yew::{function_component, html, Html, Properties}; + +use crate::asn1::node_options::NodeOptions; + +#[derive(PartialEq, Properties, Clone)] +pub struct UtcTimeNodeProps { + pub node: UtcTime, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(UtcTimeNode)] +pub fn utc_time_string(props: &UtcTimeNodeProps) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + html! { +
+ + {format_utc_time(&props.node)} +
+ } +} + +fn format_utc_time(utc_time: &UtcTime) -> String { + use time::OffsetDateTime; + + let mut formatted = String::new(); + + let current_year = (OffsetDateTime::now_utc().year() % 100) as u8; + if current_year < *utc_time.year.as_ref() { + formatted.push_str("19"); + } else { + formatted.push_str("20"); + } + formatted.push_str(&format!("{:02}", utc_time.year.as_ref())); + + formatted.push('-'); + formatted.push_str(&format!("{:02}", utc_time.month.as_ref())); + formatted.push('-'); + formatted.push_str(&format!("{:02}", utc_time.day.as_ref())); + + formatted.push(' '); + formatted.push_str(&format!("{:02}", utc_time.hour.as_ref())); + formatted.push(':'); + formatted.push_str(&format!("{:02}", utc_time.minute.as_ref())); + if let Some(second) = utc_time.second.as_ref() { + formatted.push(':'); + formatted.push_str(&format!("{:02}", second.as_ref())); + } + + formatted.push_str(" UTC"); + + formatted +} From 7d763643dce04bf9041dc0b4a17dce51097cad64 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 23:09:25 +0200 Subject: [PATCH 14/28] feat(asn1-parser): implement IA5String; --- crates/asn1-parser/src/asn1.rs | 12 ++- crates/asn1-parser/src/string/ia5_string.rs | 85 +++++++++++++++++++ crates/asn1-parser/src/string/mod.rs | 2 + crates/asn1-parser/src/string/octet_string.rs | 2 +- crates/asn1-parser/src/string/utf8_string.rs | 4 +- crates/asn1-parser/tests/decode_encode.rs | 7 ++ 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 crates/asn1-parser/src/string/ia5_string.rs diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index 43cc9eda..e1db4df6 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -5,8 +5,8 @@ use crate::reader::Reader; use crate::writer::Writer; use crate::{ ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, - ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, UtcTime, - Utf8String, + IA5String, ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, + UtcTime, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -18,6 +18,7 @@ pub enum Asn1Type<'data> { Utf8String(Utf8String<'data>), BitString(BitString<'data>), BmpString(BmpString<'data>), + IA5String(IA5String<'data>), UtcTime(UtcTime), @@ -44,6 +45,7 @@ impl Asn1Type<'_> { Asn1Type::OctetString(o) => Asn1Type::OctetString(o.to_owned()), Asn1Type::Utf8String(u) => Asn1Type::Utf8String(u.to_owned()), Asn1Type::BitString(b) => Asn1Type::BitString(b.to_owned()), + Asn1Type::IA5String(i) => Asn1Type::IA5String(i.to_owned()), Asn1Type::Bool(b) => Asn1Type::Bool(b.clone()), Asn1Type::Null(n) => Asn1Type::Null(n.clone()), Asn1Type::Integer(i) => Asn1Type::Integer(i.to_owned()), @@ -66,6 +68,7 @@ impl Taggable for Asn1Type<'_> { Asn1Type::Utf8String(u) => u.tag(), Asn1Type::BitString(b) => b.tag(), Asn1Type::BmpString(b) => b.tag(), + Asn1Type::IA5String(i) => i.tag(), Asn1Type::Bool(b) => b.tag(), Asn1Type::Null(n) => n.tag(), Asn1Type::Integer(i) => i.tag(), @@ -92,6 +95,8 @@ impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { Ok(Asn1Type::BitString(BitString::decode(tag, reader)?)) } else if BmpString::compare_tags(tag) { Ok(Asn1Type::BmpString(BmpString::decode(tag, reader)?)) + } else if IA5String::compare_tags(tag) { + Ok(Asn1Type::IA5String(IA5String::decode(tag, reader)?)) } else if Bool::compare_tags(tag) { Ok(Asn1Type::Bool(Bool::decode(tag, reader)?)) } else if Integer::compare_tags(tag) { @@ -127,6 +132,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::Set(set) => set.needed_buf_size(), Asn1Type::BitString(bit) => bit.needed_buf_size(), Asn1Type::BmpString(bmp) => bmp.needed_buf_size(), + Asn1Type::IA5String(i) => i.needed_buf_size(), Asn1Type::Bool(boolean) => boolean.needed_buf_size(), Asn1Type::Integer(integer) => integer.needed_buf_size(), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.needed_buf_size(), @@ -146,6 +152,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::Set(set) => set.encode(writer), Asn1Type::BitString(bit) => bit.encode(writer), Asn1Type::BmpString(bmp) => bmp.encode(writer), + Asn1Type::IA5String(ia5) => ia5.encode(writer), Asn1Type::Bool(boolean) => boolean.encode(writer), Asn1Type::Integer(integer) => integer.encode(writer), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.encode(writer), @@ -167,6 +174,7 @@ impl MetaInfo for Asn1Type<'_> { Asn1Type::Set(set) => set.clear_meta(), Asn1Type::BitString(_) => {} Asn1Type::BmpString(_) => {} + Asn1Type::IA5String(_) => {} Asn1Type::Bool(_) => {} Asn1Type::Integer(_) => {} Asn1Type::ObjectIdentifier(_) => {} diff --git a/crates/asn1-parser/src/string/ia5_string.rs b/crates/asn1-parser/src/string/ia5_string.rs new file mode 100644 index 00000000..7e601cd8 --- /dev/null +++ b/crates/asn1-parser/src/string/ia5_string.rs @@ -0,0 +1,85 @@ +use alloc::borrow::Cow; +use alloc::string::{String, ToString}; +use core::str::from_utf8; + +use crate::length::{len_size, write_len}; +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; + +/// [IA5String](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/ia5string.html) +/// +/// The ASN.1 IA5String type uses 7-bit characters. It is equivalent to the ASCII alphabet. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IA5String<'data>(Cow<'data, str>); + +pub type OwnedIA5String = IA5String<'static>; + +impl IA5String<'_> { + pub const TAG: Tag = Tag(22); + + /// Returns inner raw data + pub fn raw_data(&self) -> &[u8] { + self.0.as_bytes() + } + + /// Returns inner string data + pub fn string(&self) -> &str { + &self.0 + } + + /// Returns owned version of the [IA5String] + pub fn to_owned(&self) -> OwnedIA5String { + IA5String(self.0.to_string().into()) + } + + fn validate(data: &str) -> bool { + for c in data.chars() { + if !c.is_ascii() { + return false; + } + } + true + } + + pub fn new_owned(string: String) -> Asn1Result { + if Self::validate(&string) { + return Err("invalid ia5string data".into()); + } + Ok(IA5String(Cow::Owned(string))) + } +} + +impl<'data> Asn1ValueDecoder<'data> for IA5String<'data> { + fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { + let data = from_utf8(reader.remaining())?; + if !Self::validate(data) { + return Err("invalid ia5string data".into()); + } + Ok(Self(Cow::Borrowed(data))) + } + + fn compare_tags(tag: Tag) -> bool { + Self::TAG == tag + } +} + +impl Taggable for IA5String<'_> { + fn tag(&self) -> Tag { + Self::TAG + } +} + +impl Asn1Encoder for IA5String<'_> { + fn needed_buf_size(&self) -> usize { + let data_len = self.0.len(); + + 1 /* tag */ + len_size(data_len) + data_len + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(Self::TAG.into())?; + write_len(self.0.len(), writer)?; + writer.write_slice(self.0.as_bytes()) + } +} diff --git a/crates/asn1-parser/src/string/mod.rs b/crates/asn1-parser/src/string/mod.rs index 25334e9f..1690be89 100644 --- a/crates/asn1-parser/src/string/mod.rs +++ b/crates/asn1-parser/src/string/mod.rs @@ -1,9 +1,11 @@ mod bit_string; mod bmp_string; +mod ia5_string; mod octet_string; mod utf8_string; pub use bit_string::{BitString, OwnedBitString}; pub use bmp_string::{BmpString, OwnedBmpString}; +pub use ia5_string::{IA5String, OwnedIA5String}; pub use octet_string::{OctetString, OwnedOctetString}; pub use utf8_string::{OwnedUtf8String, Utf8String}; diff --git a/crates/asn1-parser/src/string/octet_string.rs b/crates/asn1-parser/src/string/octet_string.rs index 016e5a1c..52a59201 100644 --- a/crates/asn1-parser/src/string/octet_string.rs +++ b/crates/asn1-parser/src/string/octet_string.rs @@ -96,7 +96,7 @@ impl<'data> Asn1ValueDecoder<'data> for OctetString<'data> { impl Taggable for OctetString<'_> { fn tag(&self) -> Tag { - OctetString::TAG + Self::TAG } } diff --git a/crates/asn1-parser/src/string/utf8_string.rs b/crates/asn1-parser/src/string/utf8_string.rs index 0658b9cd..85e4a4ec 100644 --- a/crates/asn1-parser/src/string/utf8_string.rs +++ b/crates/asn1-parser/src/string/utf8_string.rs @@ -29,7 +29,7 @@ impl Utf8String<'_> { &self.0 } - /// Returns owned version of the [BitString] + /// Returns owned version of the [Utf8String] pub fn to_owned(&self) -> OwnedUtf8String { Utf8String(self.0.to_string().into()) } @@ -63,7 +63,7 @@ impl<'data> Asn1ValueDecoder<'data> for Utf8String<'data> { impl Taggable for Utf8String<'_> { fn tag(&self) -> Tag { - Utf8String::TAG + Self::TAG } } diff --git a/crates/asn1-parser/tests/decode_encode.rs b/crates/asn1-parser/tests/decode_encode.rs index 7f8b208c..c342319a 100644 --- a/crates/asn1-parser/tests/decode_encode.rs +++ b/crates/asn1-parser/tests/decode_encode.rs @@ -28,6 +28,13 @@ fn asn1() { }) } +#[test] +fn ia5_string() { + let raw = [22, 9, 65, 66, 67, 68, 32, 69, 70, 71, 72]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); +} + #[test] fn utc_time() { let raw = [23, 11, 57, 54, 48, 52, 49, 53, 50, 48, 51, 48, 90]; From aa991374aa2445846f7808c44d4930a7e0b8b03f Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 23:13:24 +0200 Subject: [PATCH 15/28] feat(crypto-helper): asn1: implement IA5String rendering; --- crates/asn1-parser/README.md | 2 +- src/asn1/hex_view.rs | 1 + src/asn1/scheme.rs | 7 ++++++- src/asn1/scheme/strings.rs | 24 +++++++++++++++++++++++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index bbfca017..71b1c6c6 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -18,7 +18,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result - [X] [BitString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/bitstring.html) - [X] [BmpString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/bmpstring.html) - [ ] [GraphicString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/graphicstring.html) -- [ ] [IA5String](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/ia5string.html) +- [X] [IA5String](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/ia5string.html) - [ ] [GeneralString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalstring.html) - [ ] [PrintableString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/printablestring.html) - [X] [OctetString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/octetstring.html) diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index 70d32e26..fd670205 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -169,6 +169,7 @@ fn build_data_bytes( None => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), }, Asn1Type::Utf8String(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), + Asn1Type::IA5String(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::UtcTime(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BitString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BmpString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index 24611ce7..230e5752 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -14,7 +14,7 @@ use yew::{classes, function_component, html, Callback, Children, Classes, Html, use self::oid::ObjectIdentifierNode; use self::primitive::{BoolNode, IntegerNode, NullNode}; use self::sequence::SequenceNode; -use self::strings::{BitStringNode, BmpStringNode, OctetStringNode, Utf8StringNode}; +use self::strings::{BitStringNode, BmpStringNode, IA5StringNode, OctetStringNode, Utf8StringNode}; use self::tag::{ApplicationTagNode, ExplicitTagNode, ImplicitTagNode}; use self::time::UtcTimeNode; use crate::asn1::scheme::set::SetNode; @@ -69,6 +69,11 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C }, + Asn1Type::IA5String(ia5) => html! { + + + + }, Asn1Type::Sequence(sequence) => html! { diff --git a/src/asn1/scheme/strings.rs b/src/asn1/scheme/strings.rs index b2f6fe3c..3d9bb70e 100644 --- a/src/asn1/scheme/strings.rs +++ b/src/asn1/scheme/strings.rs @@ -1,4 +1,6 @@ -use asn1_parser::{OwnedBitString, OwnedBmpString, OwnedOctetString, OwnedRawAsn1EntityData, OwnedUtf8String}; +use asn1_parser::{ + OwnedBitString, OwnedBmpString, OwnedIA5String, OwnedOctetString, OwnedRawAsn1EntityData, OwnedUtf8String, +}; use yew::{function_component, html, Callback, Html, Properties}; use crate::asn1::node_options::NodeOptions; @@ -119,3 +121,23 @@ pub fn bit_string(props: &BmpStringNodeProps) -> Html {
} } + +#[derive(PartialEq, Properties, Clone)] +pub struct IA5StringNodeProps { + pub node: OwnedIA5String, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(IA5StringNode)] +pub fn utf8_string(props: &IA5StringNodeProps) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + html! { +
+ + {props.node.string().to_owned()} +
+ } +} From 6b15bcb389fff9487aba76ac3a6b97049eba6582 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 23:31:40 +0200 Subject: [PATCH 16/28] feat(asn1-parser): implement PrintableString; --- crates/asn1-parser/src/asn1.rs | 12 ++- crates/asn1-parser/src/string/mod.rs | 2 + .../src/string/printable_string.rs | 92 +++++++++++++++++++ crates/asn1-parser/tests/decode_encode.rs | 7 ++ 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 crates/asn1-parser/src/string/printable_string.rs diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index e1db4df6..16652262 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -5,8 +5,8 @@ use crate::reader::Reader; use crate::writer::Writer; use crate::{ ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, - IA5String, ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, Sequence, Set, Tag, Taggable, Tlv, - UtcTime, Utf8String, + IA5String, ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, PrintableString, Sequence, Set, + Tag, Taggable, Tlv, UtcTime, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,6 +19,7 @@ pub enum Asn1Type<'data> { BitString(BitString<'data>), BmpString(BmpString<'data>), IA5String(IA5String<'data>), + PrintableString(PrintableString<'data>), UtcTime(UtcTime), @@ -46,6 +47,7 @@ impl Asn1Type<'_> { Asn1Type::Utf8String(u) => Asn1Type::Utf8String(u.to_owned()), Asn1Type::BitString(b) => Asn1Type::BitString(b.to_owned()), Asn1Type::IA5String(i) => Asn1Type::IA5String(i.to_owned()), + Asn1Type::PrintableString(p) => Asn1Type::PrintableString(p.to_owned()), Asn1Type::Bool(b) => Asn1Type::Bool(b.clone()), Asn1Type::Null(n) => Asn1Type::Null(n.clone()), Asn1Type::Integer(i) => Asn1Type::Integer(i.to_owned()), @@ -69,6 +71,7 @@ impl Taggable for Asn1Type<'_> { Asn1Type::BitString(b) => b.tag(), Asn1Type::BmpString(b) => b.tag(), Asn1Type::IA5String(i) => i.tag(), + Asn1Type::PrintableString(p) => p.tag(), Asn1Type::Bool(b) => b.tag(), Asn1Type::Null(n) => n.tag(), Asn1Type::Integer(i) => i.tag(), @@ -97,6 +100,8 @@ impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { Ok(Asn1Type::BmpString(BmpString::decode(tag, reader)?)) } else if IA5String::compare_tags(tag) { Ok(Asn1Type::IA5String(IA5String::decode(tag, reader)?)) + } else if PrintableString::compare_tags(tag) { + Ok(Asn1Type::PrintableString(PrintableString::decode(tag, reader)?)) } else if Bool::compare_tags(tag) { Ok(Asn1Type::Bool(Bool::decode(tag, reader)?)) } else if Integer::compare_tags(tag) { @@ -133,6 +138,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::BitString(bit) => bit.needed_buf_size(), Asn1Type::BmpString(bmp) => bmp.needed_buf_size(), Asn1Type::IA5String(i) => i.needed_buf_size(), + Asn1Type::PrintableString(p) => p.needed_buf_size(), Asn1Type::Bool(boolean) => boolean.needed_buf_size(), Asn1Type::Integer(integer) => integer.needed_buf_size(), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.needed_buf_size(), @@ -153,6 +159,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::BitString(bit) => bit.encode(writer), Asn1Type::BmpString(bmp) => bmp.encode(writer), Asn1Type::IA5String(ia5) => ia5.encode(writer), + Asn1Type::PrintableString(printable) => printable.encode(writer), Asn1Type::Bool(boolean) => boolean.encode(writer), Asn1Type::Integer(integer) => integer.encode(writer), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.encode(writer), @@ -175,6 +182,7 @@ impl MetaInfo for Asn1Type<'_> { Asn1Type::BitString(_) => {} Asn1Type::BmpString(_) => {} Asn1Type::IA5String(_) => {} + Asn1Type::PrintableString(_) => {} Asn1Type::Bool(_) => {} Asn1Type::Integer(_) => {} Asn1Type::ObjectIdentifier(_) => {} diff --git a/crates/asn1-parser/src/string/mod.rs b/crates/asn1-parser/src/string/mod.rs index 1690be89..9affc072 100644 --- a/crates/asn1-parser/src/string/mod.rs +++ b/crates/asn1-parser/src/string/mod.rs @@ -2,10 +2,12 @@ mod bit_string; mod bmp_string; mod ia5_string; mod octet_string; +mod printable_string; mod utf8_string; pub use bit_string::{BitString, OwnedBitString}; pub use bmp_string::{BmpString, OwnedBmpString}; pub use ia5_string::{IA5String, OwnedIA5String}; pub use octet_string::{OctetString, OwnedOctetString}; +pub use printable_string::{OwnedPrintableString, PrintableString}; pub use utf8_string::{OwnedUtf8String, Utf8String}; diff --git a/crates/asn1-parser/src/string/printable_string.rs b/crates/asn1-parser/src/string/printable_string.rs new file mode 100644 index 00000000..dc1a2aa7 --- /dev/null +++ b/crates/asn1-parser/src/string/printable_string.rs @@ -0,0 +1,92 @@ +use alloc::borrow::Cow; +use alloc::string::{String, ToString}; +use core::str::from_utf8; + +use crate::length::{len_size, write_len}; +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; + +/// [PrintableString](https://obj-sys.com/asn1tutorial/node128.html) +/// +/// a-z, A-Z, ' () +,-.?:/= and SPACE +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PrintableString<'data>(Cow<'data, str>); + +pub type OwnedPrintableString = PrintableString<'static>; + +impl PrintableString<'_> { + pub const TAG: Tag = Tag(19); + + /// Returns inner raw data + pub fn raw_data(&self) -> &[u8] { + self.0.as_bytes() + } + + /// Returns inner string data + pub fn string(&self) -> &str { + &self.0 + } + + /// Returns owned version of the [PrintableString] + pub fn to_owned(&self) -> OwnedPrintableString { + PrintableString(self.0.to_string().into()) + } + + fn validate(data: &str) -> bool { + const ALLOWED_SPECIAL: &[u8] = &[b' ', b'\'', b'(', b')', b'+', b',', b'-', b'.', b'/', b':', b'=', b'?']; + + for c in data.as_bytes() { + if !(c.is_ascii_lowercase() + || c.is_ascii_uppercase() + || c.is_ascii_digit() + || ALLOWED_SPECIAL.contains(c)) + { + return false; + } + } + + true + } + + pub fn new_owned(string: String) -> Asn1Result { + if Self::validate(&string) { + return Err("invalid printable string data".into()); + } + Ok(PrintableString(Cow::Owned(string))) + } +} + +impl<'data> Asn1ValueDecoder<'data> for PrintableString<'data> { + fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { + let data = from_utf8(reader.remaining())?; + if !Self::validate(data) { + return Err("invalid ia5string data".into()); + } + Ok(Self(Cow::Borrowed(data))) + } + + fn compare_tags(tag: Tag) -> bool { + Self::TAG == tag + } +} + +impl Taggable for PrintableString<'_> { + fn tag(&self) -> Tag { + Self::TAG + } +} + +impl Asn1Encoder for PrintableString<'_> { + fn needed_buf_size(&self) -> usize { + let data_len = self.0.len(); + + 1 /* tag */ + len_size(data_len) + data_len + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(Self::TAG.into())?; + write_len(self.0.len(), writer)?; + writer.write_slice(self.0.as_bytes()) + } +} diff --git a/crates/asn1-parser/tests/decode_encode.rs b/crates/asn1-parser/tests/decode_encode.rs index c342319a..c05f9f2a 100644 --- a/crates/asn1-parser/tests/decode_encode.rs +++ b/crates/asn1-parser/tests/decode_encode.rs @@ -35,6 +35,13 @@ fn ia5_string() { println!("{:?}", asn1); } +#[test] +fn printable_string() { + let raw = [19, 6, 80, 97, 114, 107, 101, 114]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); +} + #[test] fn utc_time() { let raw = [23, 11, 57, 54, 48, 52, 49, 53, 50, 48, 51, 48, 90]; From 9d2c1d23f2b0fd747f1da3e711eb6d788c0822b0 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 23:35:35 +0200 Subject: [PATCH 17/28] feat(crypto-helper): asn1: implement PrintableString rendering; --- crates/asn1-parser/README.md | 2 +- .../src/string/printable_string.rs | 5 +--- src/asn1/hex_view.rs | 1 + src/asn1/scheme.rs | 9 +++++++- src/asn1/scheme/strings.rs | 23 ++++++++++++++++++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index 71b1c6c6..83906444 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -20,7 +20,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result - [ ] [GraphicString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/graphicstring.html) - [X] [IA5String](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/ia5string.html) - [ ] [GeneralString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalstring.html) -- [ ] [PrintableString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/printablestring.html) +- [X] [PrintableString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/printablestring.html) - [X] [OctetString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/octetstring.html) - [ ] [NumericString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/numericstring.html) - [ ] [UniversalString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/universalstring.html) diff --git a/crates/asn1-parser/src/string/printable_string.rs b/crates/asn1-parser/src/string/printable_string.rs index dc1a2aa7..daa7ae50 100644 --- a/crates/asn1-parser/src/string/printable_string.rs +++ b/crates/asn1-parser/src/string/printable_string.rs @@ -37,10 +37,7 @@ impl PrintableString<'_> { const ALLOWED_SPECIAL: &[u8] = &[b' ', b'\'', b'(', b')', b'+', b',', b'-', b'.', b'/', b':', b'=', b'?']; for c in data.as_bytes() { - if !(c.is_ascii_lowercase() - || c.is_ascii_uppercase() - || c.is_ascii_digit() - || ALLOWED_SPECIAL.contains(c)) + if !(c.is_ascii_lowercase() || c.is_ascii_uppercase() || c.is_ascii_digit() || ALLOWED_SPECIAL.contains(c)) { return false; } diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index fd670205..c9fd23f0 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -170,6 +170,7 @@ fn build_data_bytes( }, Asn1Type::Utf8String(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::IA5String(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), + Asn1Type::PrintableString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::UtcTime(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BitString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BmpString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index 230e5752..a28d919b 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -14,7 +14,9 @@ use yew::{classes, function_component, html, Callback, Children, Classes, Html, use self::oid::ObjectIdentifierNode; use self::primitive::{BoolNode, IntegerNode, NullNode}; use self::sequence::SequenceNode; -use self::strings::{BitStringNode, BmpStringNode, IA5StringNode, OctetStringNode, Utf8StringNode}; +use self::strings::{ + BitStringNode, BmpStringNode, IA5StringNode, OctetStringNode, PrintableStringNode, Utf8StringNode, +}; use self::tag::{ApplicationTagNode, ExplicitTagNode, ImplicitTagNode}; use self::time::UtcTimeNode; use crate::asn1::scheme::set::SetNode; @@ -74,6 +76,11 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C
}, + Asn1Type::PrintableString(printable) => html! { + + + + }, Asn1Type::Sequence(sequence) => html! { diff --git a/src/asn1/scheme/strings.rs b/src/asn1/scheme/strings.rs index 3d9bb70e..61780afa 100644 --- a/src/asn1/scheme/strings.rs +++ b/src/asn1/scheme/strings.rs @@ -1,5 +1,6 @@ use asn1_parser::{ - OwnedBitString, OwnedBmpString, OwnedIA5String, OwnedOctetString, OwnedRawAsn1EntityData, OwnedUtf8String, + OwnedBitString, OwnedBmpString, OwnedIA5String, OwnedOctetString, OwnedPrintableString, OwnedRawAsn1EntityData, + OwnedUtf8String, }; use yew::{function_component, html, Callback, Html, Properties}; @@ -141,3 +142,23 @@ pub fn utf8_string(props: &IA5StringNodeProps) -> Html { } } + +#[derive(PartialEq, Properties, Clone)] +pub struct PrintableStringNodeProps { + pub node: OwnedPrintableString, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(PrintableStringNode)] +pub fn utf8_string(props: &PrintableStringNodeProps) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + html! { +
+ + {props.node.string().to_owned()} +
+ } +} From b34798384ebd151b00ade16445b0729097eb9be7 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sat, 20 Jan 2024 23:47:22 +0200 Subject: [PATCH 18/28] feat(crypto-helper): asn1: more oids; --- src/asn1/scheme/oid.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/asn1/scheme/oid.rs b/src/asn1/scheme/oid.rs index a0a8cd1b..7a093e88 100644 --- a/src/asn1/scheme/oid.rs +++ b/src/asn1/scheme/oid.rs @@ -206,6 +206,14 @@ fn oid_name(oid: &'_ str) -> (&'static str, &'static str) { "1.3.6.1.4.1.311.20.2.3" => ("User Principal Name", "http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3"), "1.2.840.113549.1.9.16.2.47" => ("Signing certificate V2", "http://oid-info.com/get/1.2.840.113549.1.9.16.2.47"), "1.2.840.113549.1.9.52" => ("id-aa-CMSAlgorithmProtection", "https://oidref.com/1.2.840.113549.1.9.52"), + "2.5.29.31" => ("cRLDistributionPoints (X509 extension)", "http://www.oid-info.com/get/2.5.29.31"), + "1.3.6.1.5.5.7.2.1" => ("PKIX CPS pointer qualifier", "http://www.oid-info.com/get/1.3.6.1.5.5.7.2.1"), + "1.3.6.1.4.1.44947.1.1.1" => ("ISRG Domain Validated (by Let's Encrypt)", "https://www.alvestrand.no/objectid/submissions/1.3.6.1.4.1.44947.1.1.1.html"), + "2.23.140.1.2.1" => ("domain-validated", "http://www.oid-info.com/get/2.23.140.1.2.1"), + "2.5.29.32" => ("id-ce-certificatePolicies", "http://www.oid-info.com/get/2.5.29.32"), + "1.3.6.1.5.5.7.48.2" => ("id-ad-caIssuers", "http://www.oid-info.com/get/1.3.6.1.5.5.7.48.2"), + "1.3.6.1.5.5.7.48.1" => ("id-pkix-ocsp", "http://www.oid-info.com/get/1.3.6.1.5.5.7.48.1"), + "1.3.6.1.5.5.7.1.1" => ("id-pe-authorityInfoAccess", "http://www.oid-info.com/get/1.3.6.1.5.5.7.1.1"), _ => ("-", "https://github.com/TheBestTvarynka/crypto-helper/issues/new"), } } From eb3a54dbfb5ab3d7b3901bb0cc270e90abfa19d0 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sun, 21 Jan 2024 11:35:53 +0200 Subject: [PATCH 19/28] feat(asn1-parser): improve Exolicit and APplication Tags: not they can contain multiple asn1 nodes --- crates/asn1-parser/src/tags/application.rs | 28 ++++++++++++---------- crates/asn1-parser/src/tags/explicit.rs | 28 ++++++++++++---------- crates/prop-strategies/src/constructors.rs | 4 ++-- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/crates/asn1-parser/src/tags/application.rs b/crates/asn1-parser/src/tags/application.rs index 7ca91b70..9948b455 100644 --- a/crates/asn1-parser/src/tags/application.rs +++ b/crates/asn1-parser/src/tags/application.rs @@ -1,4 +1,4 @@ -use alloc::boxed::Box; +use alloc::vec::Vec; use crate::asn1::Asn1; use crate::length::{len_size, write_len}; @@ -9,13 +9,13 @@ use crate::{Asn1Decoder, Asn1Encoder, Asn1Result, Asn1ValueDecoder, MetaInfo, Ta #[derive(Debug, Clone, PartialEq, Eq)] pub struct ApplicationTag<'data> { tag: u8, - inner: Box>, + inner: Vec>, } pub type OwnedApplicationTag = ApplicationTag<'static>; impl<'data> ApplicationTag<'data> { - pub fn new(tag: u8, inner: Box>) -> Self { + pub fn new(tag: u8, inner: Vec>) -> Self { Self { tag: tag & 0x1f | 0x60, inner, @@ -26,14 +26,18 @@ impl<'data> ApplicationTag<'data> { self.tag & 0x1f } - pub fn inner(&self) -> &Asn1<'data> { + pub fn inner(&self) -> &[Asn1<'data>] { &self.inner } pub fn to_owned(&self) -> OwnedApplicationTag { OwnedApplicationTag { tag: self.tag, - inner: Box::new(self.inner.to_owned_with_asn1(self.inner.inner_asn1().to_owned())), + inner: self + .inner + .iter() + .map(|f| f.to_owned_with_asn1(f.inner_asn1().to_owned())) + .collect(), } } } @@ -46,10 +50,10 @@ impl Taggable for ApplicationTag<'_> { impl<'data> Asn1ValueDecoder<'data> for ApplicationTag<'data> { fn decode(tag: Tag, reader: &mut Reader<'data>) -> Asn1Result { - let inner = Box::new(Asn1::decode(reader)?); + let mut inner = Vec::new(); - if !reader.empty() { - return Err("application tag inner data contains leftovers".into()); + while !reader.empty() { + inner.push(Asn1::decode(reader)?); } Ok(Self { tag: tag.0, inner }) @@ -62,7 +66,7 @@ impl<'data> Asn1ValueDecoder<'data> for ApplicationTag<'data> { impl Asn1Encoder for ApplicationTag<'_> { fn needed_buf_size(&self) -> usize { - let data_len = self.inner.needed_buf_size(); + let data_len = self.inner.iter().map(|f| f.needed_buf_size()).sum(); 1 /* tag */ + len_size(data_len) + data_len } @@ -70,15 +74,15 @@ impl Asn1Encoder for ApplicationTag<'_> { fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { writer.write_byte(self.tag)?; - let data_len = self.inner.needed_buf_size(); + let data_len = self.inner.iter().map(|f| f.needed_buf_size()).sum(); write_len(data_len, writer)?; - self.inner.encode(writer) + self.inner.iter().try_for_each(|f| f.encode(writer)) } } impl MetaInfo for ApplicationTag<'_> { fn clear_meta(&mut self) { - self.inner.clear_meta() + self.inner.iter_mut().for_each(|f| f.clear_meta()) } } diff --git a/crates/asn1-parser/src/tags/explicit.rs b/crates/asn1-parser/src/tags/explicit.rs index 7d27a4b5..144acb73 100644 --- a/crates/asn1-parser/src/tags/explicit.rs +++ b/crates/asn1-parser/src/tags/explicit.rs @@ -1,4 +1,4 @@ -use alloc::boxed::Box; +use alloc::vec::Vec; use crate::asn1::Asn1; use crate::length::{len_size, write_len}; @@ -9,13 +9,13 @@ use crate::{Asn1Decoder, Asn1Encoder, Asn1Result, Asn1ValueDecoder, MetaInfo, Ta #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExplicitTag<'data> { tag: u8, - inner: Box>, + inner: Vec>, } pub type OwnedExplicitTag = ExplicitTag<'static>; impl<'data> ExplicitTag<'data> { - pub fn new(tag: u8, inner: Box>) -> Self { + pub fn new(tag: u8, inner: Vec>) -> Self { Self { tag: tag & 0x1f | 0xa0, inner, @@ -26,14 +26,18 @@ impl<'data> ExplicitTag<'data> { self.tag & 0x1f } - pub fn inner(&self) -> &Asn1<'data> { + pub fn inner(&self) -> &[Asn1<'data>] { &self.inner } pub fn to_owned(&self) -> OwnedExplicitTag { OwnedExplicitTag { tag: self.tag, - inner: Box::new(self.inner.to_owned_with_asn1(self.inner.inner_asn1().to_owned())), + inner: self + .inner + .iter() + .map(|f| f.to_owned_with_asn1(f.inner_asn1().to_owned())) + .collect(), } } } @@ -46,10 +50,10 @@ impl Taggable for ExplicitTag<'_> { impl<'data> Asn1ValueDecoder<'data> for ExplicitTag<'data> { fn decode(tag: Tag, reader: &mut Reader<'data>) -> Asn1Result { - let inner = Box::new(Asn1::decode(reader)?); + let mut inner = Vec::new(); - if !reader.empty() { - return Err("explicit tag inner data contains leftovers".into()); + while !reader.empty() { + inner.push(Asn1::decode(reader)?); } Ok(Self { tag: tag.0, inner }) @@ -62,7 +66,7 @@ impl<'data> Asn1ValueDecoder<'data> for ExplicitTag<'data> { impl Asn1Encoder for ExplicitTag<'_> { fn needed_buf_size(&self) -> usize { - let data_len = self.inner.needed_buf_size(); + let data_len = self.inner.iter().map(|f| f.needed_buf_size()).sum(); 1 /* tag */ + len_size(data_len) + data_len } @@ -70,15 +74,15 @@ impl Asn1Encoder for ExplicitTag<'_> { fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { writer.write_byte(self.tag)?; - let data_len = self.inner.needed_buf_size(); + let data_len = self.inner.iter().map(|f| f.needed_buf_size()).sum(); write_len(data_len, writer)?; - self.inner.encode(writer) + self.inner.iter().try_for_each(|f| f.encode(writer)) } } impl MetaInfo for ExplicitTag<'_> { fn clear_meta(&mut self) { - self.inner.clear_meta() + self.inner.iter_mut().for_each(|f| f.clear_meta()) } } diff --git a/crates/prop-strategies/src/constructors.rs b/crates/prop-strategies/src/constructors.rs index 282383a3..6e16a17a 100644 --- a/crates/prop-strategies/src/constructors.rs +++ b/crates/prop-strategies/src/constructors.rs @@ -44,13 +44,13 @@ pub fn recursive_empty_asn1_type() -> impl Strategy { .prop_flat_map(move |tag| (Just(tag), explicit_tag_inner.clone())) .prop_map(|(tag, inner)| Asn1Type::ExplicitTag(OwnedExplicitTag::new( tag, - Box::new(OwnedAsn1::new(0, Default::default(), inner)) + vec![OwnedAsn1::new(0, Default::default(), inner)] ))), (0_u8..31) .prop_flat_map(move |tag| (Just(tag), application_tag_inner.clone())) .prop_map(|(tag, inner)| Asn1Type::ApplicationTag(OwnedApplicationTag::new( tag, - Box::new(OwnedAsn1::new(0, Default::default(), inner)) + vec![OwnedAsn1::new(0, Default::default(), inner)] ))), ] }) From 8793683ec6bd431b86ee08d297edcc31c49bea91 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sun, 21 Jan 2024 11:36:17 +0200 Subject: [PATCH 20/28] feat(crypto-helper): asn1: improve Application and Explicit Tag rendering; --- src/asn1/hex_view.rs | 12 ++++++++++-- src/asn1/scheme/tag.rs | 20 ++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index c9fd23f0..6ead3243 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -179,14 +179,22 @@ fn build_data_bytes( Asn1Type::Integer(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::ObjectIdentifier(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::ExplicitTag(explicit) => { - build_hex_bytes(explicit.inner(), cur_node, set_cur_node.clone(), bytes, select_all) + let set_cur_node = set_cur_node.clone(); + explicit + .inner() + .iter() + .for_each(move |asn1| build_hex_bytes(asn1, cur_node, set_cur_node.clone(), bytes, select_all)); } Asn1Type::ImplicitTag(implicit) => match implicit.inner_asn1() { Some(asn1) => build_hex_bytes(asn1, cur_node, set_cur_node.clone(), bytes, select_all), None => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), }, Asn1Type::ApplicationTag(application) => { - build_hex_bytes(application.inner(), cur_node, set_cur_node.clone(), bytes, select_all) + let set_cur_node = set_cur_node.clone(); + application + .inner() + .iter() + .for_each(move |asn1| build_hex_bytes(asn1, cur_node, set_cur_node.clone(), bytes, select_all)); } } } diff --git a/src/asn1/scheme/tag.rs b/src/asn1/scheme/tag.rs index 13755113..75884d86 100644 --- a/src/asn1/scheme/tag.rs +++ b/src/asn1/scheme/tag.rs @@ -15,6 +15,14 @@ pub struct ExplicitTagProps { #[function_component(ExplicitTagNode)] pub fn explicit_tag(props: &ExplicitTagProps) -> Html { + let set_cur_node = &props.set_cur_node; + let inner_components = props + .node + .inner() + .iter() + .map(|f| build_asn1_schema(f, &props.cur_node, set_cur_node)) + .collect::>(); + let offset = props.meta.tag_position(); let length_len = props.meta.length_range().len(); let data_len = props.meta.data_range().len(); @@ -25,7 +33,7 @@ pub fn explicit_tag(props: &ExplicitTagProps) -> Html {
- {build_asn1_schema(props.node.inner(), &props.cur_node, &props.set_cur_node)} + {inner_components}
} @@ -41,6 +49,14 @@ pub struct ApplicationTagProps { #[function_component(ApplicationTagNode)] pub fn application_tag(props: &ApplicationTagProps) -> Html { + let set_cur_node = &props.set_cur_node; + let inner_components = props + .node + .inner() + .iter() + .map(|f| build_asn1_schema(f, &props.cur_node, set_cur_node)) + .collect::>(); + let offset = props.meta.tag_position(); let length_len = props.meta.length_range().len(); let data_len = props.meta.data_range().len(); @@ -51,7 +67,7 @@ pub fn application_tag(props: &ApplicationTagProps) -> Html {
- {build_asn1_schema(props.node.inner(), &props.cur_node, &props.set_cur_node)} + {inner_components}
} From c9e93d35b84abdbc647b5198255ae828166bf4b5 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sun, 21 Jan 2024 11:41:16 +0200 Subject: [PATCH 21/28] feat(crypto-helper): asn1: render octet string as string rather than bytes if possible --- src/asn1/scheme/strings.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/asn1/scheme/strings.rs b/src/asn1/scheme/strings.rs index 61780afa..46da9b66 100644 --- a/src/asn1/scheme/strings.rs +++ b/src/asn1/scheme/strings.rs @@ -55,13 +55,19 @@ pub fn octet_string(props: &OctetStringNodeProps) -> Html { }, - None => html! { -
- - {format!("({} bytes)", octets.len())} - {hex::encode(octets)} -
- }, + None => { + let encoded_octets = match std::str::from_utf8(octets) { + Ok(s) => s.to_owned(), + Err(_) => hex::encode(octets), + }; + html! { +
+ + {format!("({} bytes)", octets.len())} + {encoded_octets} +
+ } + } } } #[derive(PartialEq, Properties, Clone)] From 12111f543903723bc5ad03547423f18d95e1943c Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sun, 21 Jan 2024 14:15:13 +0200 Subject: [PATCH 22/28] feat(asn1-parser): improve strings implementation. move all into a macro --- Cargo.lock | 7 ++ crates/asn1-parser/Cargo.toml | 1 + crates/asn1-parser/src/macros.rs | 70 +++++++++++++++ crates/asn1-parser/src/string/ia5_string.rs | 85 ------------------ crates/asn1-parser/src/string/mod.rs | 71 +++++++++++++-- .../src/string/printable_string.rs | 89 ------------------- crates/asn1-parser/src/string/utf8_string.rs | 82 ----------------- crates/asn1-parser/src/string/validators.rs | 24 +++++ crates/prop-strategies/src/string.rs | 2 +- 9 files changed, 168 insertions(+), 263 deletions(-) delete mode 100644 crates/asn1-parser/src/string/ia5_string.rs delete mode 100644 crates/asn1-parser/src/string/printable_string.rs delete mode 100644 crates/asn1-parser/src/string/utf8_string.rs create mode 100644 crates/asn1-parser/src/string/validators.rs diff --git a/Cargo.lock b/Cargo.lock index 85f793cc..a6699880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,7 @@ dependencies = [ "log", "num-bigint-dig", "oid", + "paste", "prop-strategies", "proptest", ] @@ -1044,6 +1045,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pbkdf2" version = "0.11.0" diff --git a/crates/asn1-parser/Cargo.toml b/crates/asn1-parser/Cargo.toml index b7dcf41e..4da2e1d7 100644 --- a/crates/asn1-parser/Cargo.toml +++ b/crates/asn1-parser/Cargo.toml @@ -20,3 +20,4 @@ proptest = "1.2.0" log = "0.4.20" num-bigint-dig = { version = "0.8.4", default-features = false } oid = { version = "0.2.1", default-features = false } +paste = "1.0.14" diff --git a/crates/asn1-parser/src/macros.rs b/crates/asn1-parser/src/macros.rs index 8b137891..a7d9dce9 100644 --- a/crates/asn1-parser/src/macros.rs +++ b/crates/asn1-parser/src/macros.rs @@ -1 +1,71 @@ +macro_rules! impl_utf8_asn1 { + ($name:ident, $tag:expr, $validator_fn:ident) => { + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct $name<'data>(Utf8Value<'data, 19>); + paste::paste! { + pub type [] = $name<'static>; + } + + impl Asn1Encoder for $name<'_> { + fn needed_buf_size(&self) -> usize { + self.0.needed_buf_size() + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + self.0.encode(writer) + } + } + + impl From for $name<'static> { + fn from(value: String) -> Self { + Self(value.into()) + } + } + + impl crate::Taggable for $name<'_> { + fn tag(&self) -> Tag { + Self::TAG + } + } + + impl<'data> Asn1ValueDecoder<'data> for $name<'data> { + fn decode(tag: Tag, reader: &mut Reader<'data>) -> Asn1Result { + let utf8_value = Utf8Value::decode(tag, reader)?; + + if !$validator_fn(utf8_value.as_str()) { + return Err("invalid string data".into()); + } + + Ok(Self(utf8_value)) + } + + fn compare_tags(tag: Tag) -> bool { + Self::TAG == tag + } + } + + impl $name<'_> { + pub const TAG: Tag = Tag($tag); + + pub fn raw_data(&self) -> &[u8] { + self.0.as_bytes() + } + + pub fn string(&self) -> &str { + self.0.as_str() + } + + pub fn to_owned(&self) -> $name<'static> { + use crate::alloc::string::ToString; + $name(self.0.as_str().to_string().into()) + } + } + + impl<'data> From<&'data str> for $name<'data> { + fn from(data: &'data str) -> Self { + Self(data.into()) + } + } + }; +} diff --git a/crates/asn1-parser/src/string/ia5_string.rs b/crates/asn1-parser/src/string/ia5_string.rs deleted file mode 100644 index 7e601cd8..00000000 --- a/crates/asn1-parser/src/string/ia5_string.rs +++ /dev/null @@ -1,85 +0,0 @@ -use alloc::borrow::Cow; -use alloc::string::{String, ToString}; -use core::str::from_utf8; - -use crate::length::{len_size, write_len}; -use crate::reader::Reader; -use crate::writer::Writer; -use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; - -/// [IA5String](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/ia5string.html) -/// -/// The ASN.1 IA5String type uses 7-bit characters. It is equivalent to the ASCII alphabet. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IA5String<'data>(Cow<'data, str>); - -pub type OwnedIA5String = IA5String<'static>; - -impl IA5String<'_> { - pub const TAG: Tag = Tag(22); - - /// Returns inner raw data - pub fn raw_data(&self) -> &[u8] { - self.0.as_bytes() - } - - /// Returns inner string data - pub fn string(&self) -> &str { - &self.0 - } - - /// Returns owned version of the [IA5String] - pub fn to_owned(&self) -> OwnedIA5String { - IA5String(self.0.to_string().into()) - } - - fn validate(data: &str) -> bool { - for c in data.chars() { - if !c.is_ascii() { - return false; - } - } - true - } - - pub fn new_owned(string: String) -> Asn1Result { - if Self::validate(&string) { - return Err("invalid ia5string data".into()); - } - Ok(IA5String(Cow::Owned(string))) - } -} - -impl<'data> Asn1ValueDecoder<'data> for IA5String<'data> { - fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { - let data = from_utf8(reader.remaining())?; - if !Self::validate(data) { - return Err("invalid ia5string data".into()); - } - Ok(Self(Cow::Borrowed(data))) - } - - fn compare_tags(tag: Tag) -> bool { - Self::TAG == tag - } -} - -impl Taggable for IA5String<'_> { - fn tag(&self) -> Tag { - Self::TAG - } -} - -impl Asn1Encoder for IA5String<'_> { - fn needed_buf_size(&self) -> usize { - let data_len = self.0.len(); - - 1 /* tag */ + len_size(data_len) + data_len - } - - fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { - writer.write_byte(Self::TAG.into())?; - write_len(self.0.len(), writer)?; - writer.write_slice(self.0.as_bytes()) - } -} diff --git a/crates/asn1-parser/src/string/mod.rs b/crates/asn1-parser/src/string/mod.rs index 9affc072..ae2632e3 100644 --- a/crates/asn1-parser/src/string/mod.rs +++ b/crates/asn1-parser/src/string/mod.rs @@ -1,13 +1,72 @@ mod bit_string; mod bmp_string; -mod ia5_string; mod octet_string; -mod printable_string; -mod utf8_string; +mod validators; + +use alloc::borrow::Cow; +use alloc::string::String; +use core::str::from_utf8; pub use bit_string::{BitString, OwnedBitString}; pub use bmp_string::{BmpString, OwnedBmpString}; -pub use ia5_string::{IA5String, OwnedIA5String}; pub use octet_string::{OctetString, OwnedOctetString}; -pub use printable_string::{OwnedPrintableString, PrintableString}; -pub use utf8_string::{OwnedUtf8String, Utf8String}; +use validators::{validate_ia5, validate_printable, validate_utf8}; + +use crate::length::{len_size, write_len}; +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag}; + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Utf8Value<'data, const TAG: u8>(Cow<'data, str>); + +type OwnedUtf8Value = Utf8Value<'static, TAG>; + +impl Utf8Value<'_, TAG> { + pub fn as_str(&self) -> &str { + self.0.as_ref() + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl From for OwnedUtf8Value { + fn from(value: String) -> Self { + Self(Cow::Owned(value)) + } +} + +impl<'data, const TAG: u8> From<&'data str> for Utf8Value<'data, TAG> { + fn from(value: &'data str) -> Self { + Self(Cow::Borrowed(value)) + } +} + +impl<'data, const TAG: u8> Asn1ValueDecoder<'data> for Utf8Value<'data, TAG> { + fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { + Ok(Self(Cow::Borrowed(from_utf8(reader.remaining())?))) + } + + fn compare_tags(tag: Tag) -> bool { + tag.0 == TAG + } +} + +impl Asn1Encoder for Utf8Value<'_, TAG> { + fn needed_buf_size(&self) -> usize { + let data_len = self.0.len(); + 1 /* tag */ + len_size(data_len) + data_len + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(TAG)?; + write_len(self.0.len(), writer)?; + writer.write_slice(self.0.as_bytes()) + } +} + +impl_utf8_asn1!(PrintableString, 19, validate_printable); +impl_utf8_asn1!(Utf8String, 12, validate_utf8); +impl_utf8_asn1!(IA5String, 22, validate_ia5); diff --git a/crates/asn1-parser/src/string/printable_string.rs b/crates/asn1-parser/src/string/printable_string.rs deleted file mode 100644 index daa7ae50..00000000 --- a/crates/asn1-parser/src/string/printable_string.rs +++ /dev/null @@ -1,89 +0,0 @@ -use alloc::borrow::Cow; -use alloc::string::{String, ToString}; -use core::str::from_utf8; - -use crate::length::{len_size, write_len}; -use crate::reader::Reader; -use crate::writer::Writer; -use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; - -/// [PrintableString](https://obj-sys.com/asn1tutorial/node128.html) -/// -/// a-z, A-Z, ' () +,-.?:/= and SPACE -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PrintableString<'data>(Cow<'data, str>); - -pub type OwnedPrintableString = PrintableString<'static>; - -impl PrintableString<'_> { - pub const TAG: Tag = Tag(19); - - /// Returns inner raw data - pub fn raw_data(&self) -> &[u8] { - self.0.as_bytes() - } - - /// Returns inner string data - pub fn string(&self) -> &str { - &self.0 - } - - /// Returns owned version of the [PrintableString] - pub fn to_owned(&self) -> OwnedPrintableString { - PrintableString(self.0.to_string().into()) - } - - fn validate(data: &str) -> bool { - const ALLOWED_SPECIAL: &[u8] = &[b' ', b'\'', b'(', b')', b'+', b',', b'-', b'.', b'/', b':', b'=', b'?']; - - for c in data.as_bytes() { - if !(c.is_ascii_lowercase() || c.is_ascii_uppercase() || c.is_ascii_digit() || ALLOWED_SPECIAL.contains(c)) - { - return false; - } - } - - true - } - - pub fn new_owned(string: String) -> Asn1Result { - if Self::validate(&string) { - return Err("invalid printable string data".into()); - } - Ok(PrintableString(Cow::Owned(string))) - } -} - -impl<'data> Asn1ValueDecoder<'data> for PrintableString<'data> { - fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { - let data = from_utf8(reader.remaining())?; - if !Self::validate(data) { - return Err("invalid ia5string data".into()); - } - Ok(Self(Cow::Borrowed(data))) - } - - fn compare_tags(tag: Tag) -> bool { - Self::TAG == tag - } -} - -impl Taggable for PrintableString<'_> { - fn tag(&self) -> Tag { - Self::TAG - } -} - -impl Asn1Encoder for PrintableString<'_> { - fn needed_buf_size(&self) -> usize { - let data_len = self.0.len(); - - 1 /* tag */ + len_size(data_len) + data_len - } - - fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { - writer.write_byte(Self::TAG.into())?; - write_len(self.0.len(), writer)?; - writer.write_slice(self.0.as_bytes()) - } -} diff --git a/crates/asn1-parser/src/string/utf8_string.rs b/crates/asn1-parser/src/string/utf8_string.rs deleted file mode 100644 index 85e4a4ec..00000000 --- a/crates/asn1-parser/src/string/utf8_string.rs +++ /dev/null @@ -1,82 +0,0 @@ -use alloc::borrow::Cow; -use alloc::string::{String, ToString}; -use core::str::from_utf8; - -use crate::length::{len_size, write_len}; -use crate::reader::Reader; -use crate::writer::Writer; -use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; - -/// [Utf8String](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/utf8string.html) -/// -/// The ASN.1 UTF8String type is used for handling Unicode characters. UniversalString and UTF8String both support the same character set, -/// however, their encoding is different. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Utf8String<'data>(Cow<'data, str>); - -pub type OwnedUtf8String = Utf8String<'static>; - -impl Utf8String<'_> { - pub const TAG: Tag = Tag(12); - - /// Returns inner raw data - pub fn raw_data(&self) -> &[u8] { - self.0.as_bytes() - } - - /// Returns inner string data - pub fn string(&self) -> &str { - &self.0 - } - - /// Returns owned version of the [Utf8String] - pub fn to_owned(&self) -> OwnedUtf8String { - Utf8String(self.0.to_string().into()) - } - - pub fn new_owned(string: String) -> OwnedUtf8String { - Utf8String(Cow::Owned(string)) - } -} - -impl From for OwnedUtf8String { - fn from(data: String) -> Self { - Self(Cow::Owned(data)) - } -} - -impl From<&'static str> for OwnedUtf8String { - fn from(data: &'static str) -> Self { - Self(Cow::Borrowed(data)) - } -} - -impl<'data> Asn1ValueDecoder<'data> for Utf8String<'data> { - fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { - Ok(Self(Cow::Borrowed(from_utf8(reader.remaining())?))) - } - - fn compare_tags(tag: Tag) -> bool { - Self::TAG == tag - } -} - -impl Taggable for Utf8String<'_> { - fn tag(&self) -> Tag { - Self::TAG - } -} - -impl Asn1Encoder for Utf8String<'_> { - fn needed_buf_size(&self) -> usize { - let data_len = self.0.len(); - - 1 /* tag */ + len_size(data_len) + data_len - } - - fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { - writer.write_byte(Self::TAG.into())?; - write_len(self.0.len(), writer)?; - writer.write_slice(self.0.as_bytes()) - } -} diff --git a/crates/asn1-parser/src/string/validators.rs b/crates/asn1-parser/src/string/validators.rs new file mode 100644 index 00000000..fcb74ab4 --- /dev/null +++ b/crates/asn1-parser/src/string/validators.rs @@ -0,0 +1,24 @@ +pub fn validate_utf8(_: &str) -> bool { + true +} + +pub fn validate_printable(data: &str) -> bool { + const ALLOWED_SPECIAL: &[u8] = &[b' ', b'\'', b'(', b')', b'+', b',', b'-', b'.', b'/', b':', b'=', b'?']; + + for c in data.as_bytes() { + if !(c.is_ascii_lowercase() || c.is_ascii_uppercase() || c.is_ascii_digit() || ALLOWED_SPECIAL.contains(c)) { + return false; + } + } + + true +} + +pub fn validate_ia5(data: &str) -> bool { + for c in data.chars() { + if !c.is_ascii() { + return false; + } + } + true +} diff --git a/crates/prop-strategies/src/string.rs b/crates/prop-strategies/src/string.rs index 2ba2fbcb..a8aa45ba 100644 --- a/crates/prop-strategies/src/string.rs +++ b/crates/prop-strategies/src/string.rs @@ -15,7 +15,7 @@ prop_compose! { prop_compose! { pub fn any_utf8_string() (data in string(STRING_LEN)) -> OwnedUtf8String { - OwnedUtf8String::new_owned(data) + data.into() } } From 330bd39d4ce351e67b2133e311e28ccd8bc29bf1 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sun, 21 Jan 2024 14:47:19 +0200 Subject: [PATCH 23/28] feat(asn1-parser): improve asn1 parsing using macro. implement general string; --- crates/asn1-parser/src/asn1.rs | 67 +++++++++------------ crates/asn1-parser/src/macros.rs | 12 ++++ crates/asn1-parser/src/string/mod.rs | 3 +- crates/asn1-parser/src/string/validators.rs | 6 ++ 4 files changed, 50 insertions(+), 38 deletions(-) diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index 16652262..65c11019 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -5,8 +5,8 @@ use crate::reader::Reader; use crate::writer::Writer; use crate::{ ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, - IA5String, ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, PrintableString, Sequence, Set, - Tag, Taggable, Tlv, UtcTime, Utf8String, + GeneralString, IA5String, ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, PrintableString, + Sequence, Set, Tag, Taggable, Tlv, UtcTime, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -20,6 +20,7 @@ pub enum Asn1Type<'data> { BmpString(BmpString<'data>), IA5String(IA5String<'data>), PrintableString(PrintableString<'data>), + GeneralString(GeneralString<'data>), UtcTime(UtcTime), @@ -48,6 +49,7 @@ impl Asn1Type<'_> { Asn1Type::BitString(b) => Asn1Type::BitString(b.to_owned()), Asn1Type::IA5String(i) => Asn1Type::IA5String(i.to_owned()), Asn1Type::PrintableString(p) => Asn1Type::PrintableString(p.to_owned()), + Asn1Type::GeneralString(g) => Asn1Type::GeneralString(g.to_owned()), Asn1Type::Bool(b) => Asn1Type::Bool(b.clone()), Asn1Type::Null(n) => Asn1Type::Null(n.clone()), Asn1Type::Integer(i) => Asn1Type::Integer(i.to_owned()), @@ -72,6 +74,7 @@ impl Taggable for Asn1Type<'_> { Asn1Type::BmpString(b) => b.tag(), Asn1Type::IA5String(i) => i.tag(), Asn1Type::PrintableString(p) => p.tag(), + Asn1Type::GeneralString(g) => g.tag(), Asn1Type::Bool(b) => b.tag(), Asn1Type::Null(n) => n.tag(), Asn1Type::Integer(i) => i.tag(), @@ -86,41 +89,28 @@ impl Taggable for Asn1Type<'_> { impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { fn decode(tag: Tag, reader: &mut Reader<'data>) -> Asn1Result { - if OctetString::compare_tags(tag) { - Ok(Asn1Type::OctetString(OctetString::decode(tag, reader)?)) - } else if Utf8String::compare_tags(tag) { - Ok(Asn1Type::Utf8String(Utf8String::decode(tag, reader)?)) - } else if Sequence::compare_tags(tag) { - Ok(Asn1Type::Sequence(Sequence::decode(tag, reader)?)) - } else if Set::compare_tags(tag) { - Ok(Asn1Type::Set(Set::decode(tag, reader)?)) - } else if BitString::compare_tags(tag) { - Ok(Asn1Type::BitString(BitString::decode(tag, reader)?)) - } else if BmpString::compare_tags(tag) { - Ok(Asn1Type::BmpString(BmpString::decode(tag, reader)?)) - } else if IA5String::compare_tags(tag) { - Ok(Asn1Type::IA5String(IA5String::decode(tag, reader)?)) - } else if PrintableString::compare_tags(tag) { - Ok(Asn1Type::PrintableString(PrintableString::decode(tag, reader)?)) - } else if Bool::compare_tags(tag) { - Ok(Asn1Type::Bool(Bool::decode(tag, reader)?)) - } else if Integer::compare_tags(tag) { - Ok(Asn1Type::Integer(Integer::decode(tag, reader)?)) - } else if ObjectIdentifier::compare_tags(tag) { - Ok(Asn1Type::ObjectIdentifier(ObjectIdentifier::decode(tag, reader)?)) - } else if ExplicitTag::compare_tags(tag) { - Ok(Asn1Type::ExplicitTag(ExplicitTag::decode(tag, reader)?)) - } else if ImplicitTag::compare_tags(tag) { - Ok(Asn1Type::ImplicitTag(ImplicitTag::decode(tag, reader)?)) - } else if ApplicationTag::compare_tags(tag) { - Ok(Asn1Type::ApplicationTag(ApplicationTag::decode(tag, reader)?)) - } else if Null::compare_tags(tag) { - Ok(Asn1Type::Null(Null::decode(tag, reader)?)) - } else if UtcTime::compare_tags(tag) { - Ok(Asn1Type::UtcTime(UtcTime::decode(tag, reader)?)) - } else { - Err(Error::from("Invalid data")) - } + decode_asn1!( + OctetString, + Utf8String, + Sequence, + Set, + BitString, + BmpString, + IA5String, + PrintableString, + GeneralString, + Bool, + Integer, + ObjectIdentifier, + ExplicitTag, + ImplicitTag, + ApplicationTag, + Null, + UtcTime; + in tag, reader + ); + + Err(Error::from("Invalid asn1 data")) } fn compare_tags(_tag: Tag) -> bool { @@ -139,6 +129,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::BmpString(bmp) => bmp.needed_buf_size(), Asn1Type::IA5String(i) => i.needed_buf_size(), Asn1Type::PrintableString(p) => p.needed_buf_size(), + Asn1Type::GeneralString(g) => g.needed_buf_size(), Asn1Type::Bool(boolean) => boolean.needed_buf_size(), Asn1Type::Integer(integer) => integer.needed_buf_size(), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.needed_buf_size(), @@ -160,6 +151,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::BmpString(bmp) => bmp.encode(writer), Asn1Type::IA5String(ia5) => ia5.encode(writer), Asn1Type::PrintableString(printable) => printable.encode(writer), + Asn1Type::GeneralString(general) => general.encode(writer), Asn1Type::Bool(boolean) => boolean.encode(writer), Asn1Type::Integer(integer) => integer.encode(writer), Asn1Type::ObjectIdentifier(object_identifier) => object_identifier.encode(writer), @@ -183,6 +175,7 @@ impl MetaInfo for Asn1Type<'_> { Asn1Type::BmpString(_) => {} Asn1Type::IA5String(_) => {} Asn1Type::PrintableString(_) => {} + Asn1Type::GeneralString(_) => {} Asn1Type::Bool(_) => {} Asn1Type::Integer(_) => {} Asn1Type::ObjectIdentifier(_) => {} diff --git a/crates/asn1-parser/src/macros.rs b/crates/asn1-parser/src/macros.rs index a7d9dce9..90061339 100644 --- a/crates/asn1-parser/src/macros.rs +++ b/crates/asn1-parser/src/macros.rs @@ -69,3 +69,15 @@ macro_rules! impl_utf8_asn1 { } }; } + +macro_rules! decode_asn1 { + ($($name:ident),*; in $tag:expr, $reader:expr) => { + { + $( + if $name::compare_tags($tag) { + return Ok(Asn1Type::$name($name::decode($tag, $reader)?)); + } + )* + } + }; +} diff --git a/crates/asn1-parser/src/string/mod.rs b/crates/asn1-parser/src/string/mod.rs index ae2632e3..ab68a29b 100644 --- a/crates/asn1-parser/src/string/mod.rs +++ b/crates/asn1-parser/src/string/mod.rs @@ -10,7 +10,7 @@ use core::str::from_utf8; pub use bit_string::{BitString, OwnedBitString}; pub use bmp_string::{BmpString, OwnedBmpString}; pub use octet_string::{OctetString, OwnedOctetString}; -use validators::{validate_ia5, validate_printable, validate_utf8}; +use validators::{validate_general, validate_ia5, validate_printable, validate_utf8}; use crate::length::{len_size, write_len}; use crate::reader::Reader; @@ -70,3 +70,4 @@ impl Asn1Encoder for Utf8Value<'_, TAG> { impl_utf8_asn1!(PrintableString, 19, validate_printable); impl_utf8_asn1!(Utf8String, 12, validate_utf8); impl_utf8_asn1!(IA5String, 22, validate_ia5); +impl_utf8_asn1!(GeneralString, 27, validate_general); diff --git a/crates/asn1-parser/src/string/validators.rs b/crates/asn1-parser/src/string/validators.rs index fcb74ab4..0f9034ec 100644 --- a/crates/asn1-parser/src/string/validators.rs +++ b/crates/asn1-parser/src/string/validators.rs @@ -2,6 +2,12 @@ pub fn validate_utf8(_: &str) -> bool { true } +pub fn validate_general(_: &str) -> bool { + // TODO: properly validate GeneralString + // I'm too lazy to do it. UTF8 is enough + true +} + pub fn validate_printable(data: &str) -> bool { const ALLOWED_SPECIAL: &[u8] = &[b' ', b'\'', b'(', b')', b'+', b',', b'-', b'.', b'/', b':', b'=', b'?']; From c1abf5fb624c75906605f35aa57242382e0b2f88 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sun, 21 Jan 2024 14:47:37 +0200 Subject: [PATCH 24/28] feat(crypto-helper): asn1: implement GeneralString rendering --- src/asn1/hex_view.rs | 1 + src/asn1/scheme.rs | 8 +++++++- src/asn1/scheme/strings.rs | 24 ++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index 6ead3243..4218a9dc 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -171,6 +171,7 @@ fn build_data_bytes( Asn1Type::Utf8String(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::IA5String(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::PrintableString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), + Asn1Type::GeneralString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::UtcTime(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BitString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BmpString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index a28d919b..3e618ae6 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -15,7 +15,8 @@ use self::oid::ObjectIdentifierNode; use self::primitive::{BoolNode, IntegerNode, NullNode}; use self::sequence::SequenceNode; use self::strings::{ - BitStringNode, BmpStringNode, IA5StringNode, OctetStringNode, PrintableStringNode, Utf8StringNode, + BitStringNode, BmpStringNode, GeneralStringNode, IA5StringNode, OctetStringNode, PrintableStringNode, + Utf8StringNode, }; use self::tag::{ApplicationTagNode, ExplicitTagNode, ImplicitTagNode}; use self::time::UtcTimeNode; @@ -81,6 +82,11 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C
}, + Asn1Type::GeneralString(general) => html! { + + + + }, Asn1Type::Sequence(sequence) => html! { diff --git a/src/asn1/scheme/strings.rs b/src/asn1/scheme/strings.rs index 46da9b66..06040bf4 100644 --- a/src/asn1/scheme/strings.rs +++ b/src/asn1/scheme/strings.rs @@ -1,6 +1,6 @@ use asn1_parser::{ - OwnedBitString, OwnedBmpString, OwnedIA5String, OwnedOctetString, OwnedPrintableString, OwnedRawAsn1EntityData, - OwnedUtf8String, + OwnedBitString, OwnedBmpString, OwnedGeneralString, OwnedIA5String, OwnedOctetString, OwnedPrintableString, + OwnedRawAsn1EntityData, OwnedUtf8String, }; use yew::{function_component, html, Callback, Html, Properties}; @@ -168,3 +168,23 @@ pub fn utf8_string(props: &PrintableStringNodeProps) -> Html { } } + +#[derive(PartialEq, Properties, Clone)] +pub struct GeneralStringNodeProps { + pub node: OwnedGeneralString, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(GeneralStringNode)] +pub fn utf8_string(props: &GeneralStringNodeProps) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + html! { +
+ + {props.node.string().to_owned()} +
+ } +} From 3b091925fbdd29ebde9f3eeef1c95422825295c0 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Sun, 21 Jan 2024 15:03:10 +0200 Subject: [PATCH 25/28] refuc(crypto-helper): asn1: replace some strings components with macro generation; --- Cargo.lock | 1 + Cargo.toml | 1 + crates/asn1-parser/README.md | 2 +- src/asn1.rs | 3 ++ src/asn1/macros.rs | 26 +++++++++++ src/asn1/scheme/strings.rs | 91 ++++-------------------------------- 6 files changed, 40 insertions(+), 84 deletions(-) create mode 100644 src/asn1/macros.rs diff --git a/Cargo.lock b/Cargo.lock index a6699880..61d5f948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2066,6 +2066,7 @@ dependencies = [ "log", "md5", "oid", + "paste", "picky", "picky-krb", "rand", diff --git a/Cargo.toml b/Cargo.toml index aaffae47..52888012 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,3 +54,4 @@ flate2 = { version = "1.0.26", features = ["zlib"] } # asn1 asn1-parser = { path = "./crates/asn1-parser" } oid = { version = "0.2.1", default-features = false } +paste = "1.0.14" diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index 83906444..bf30f913 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -19,7 +19,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result - [X] [BmpString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/bmpstring.html) - [ ] [GraphicString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/graphicstring.html) - [X] [IA5String](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/ia5string.html) -- [ ] [GeneralString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalstring.html) +- [X] [GeneralString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalstring.html) - [X] [PrintableString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/printablestring.html) - [X] [OctetString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/octetstring.html) - [ ] [NumericString](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/numericstring.html) diff --git a/src/asn1.rs b/src/asn1.rs index 74d4ad06..c4c1fc72 100644 --- a/src/asn1.rs +++ b/src/asn1.rs @@ -1,3 +1,6 @@ +#[macro_use] +mod macros; + mod asn1_viewer; mod hex_view; mod node_options; diff --git a/src/asn1/macros.rs b/src/asn1/macros.rs new file mode 100644 index 00000000..9dbd20ac --- /dev/null +++ b/src/asn1/macros.rs @@ -0,0 +1,26 @@ +macro_rules! define_string_node { + ($name:ident) => { + paste::paste! { + #[derive(PartialEq, Properties, Clone)] + pub struct [<$name NodeProps>] { + pub node: [], + pub meta: OwnedRawAsn1EntityData, + } + + #[allow(non_snake_case)] + #[function_component([<$name Node >])] + pub fn [<__fn_ $name>](props: &[<$name NodeProps>]) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + html! { +
+ + {props.node.string().to_owned()} +
+ } + } + } + }; +} diff --git a/src/asn1/scheme/strings.rs b/src/asn1/scheme/strings.rs index 06040bf4..b29ed7df 100644 --- a/src/asn1/scheme/strings.rs +++ b/src/asn1/scheme/strings.rs @@ -8,26 +8,6 @@ use crate::asn1::node_options::NodeOptions; use crate::asn1::scheme::build_asn1_schema; use crate::asn1::HighlightAction; -#[derive(PartialEq, Properties, Clone)] -pub struct Utf8StringNodeProps { - pub node: OwnedUtf8String, - pub meta: OwnedRawAsn1EntityData, -} - -#[function_component(Utf8StringNode)] -pub fn utf8_string(props: &Utf8StringNodeProps) -> Html { - let offset = props.meta.tag_position(); - let length_len = props.meta.length_range().len(); - let data_len = props.meta.data_range().len(); - - html! { -
- - {props.node.string().to_owned()} -
- } -} - #[derive(PartialEq, Properties, Clone)] pub struct OctetStringNodeProps { pub node: OwnedOctetString, @@ -48,7 +28,7 @@ pub fn octet_string(props: &OctetStringNodeProps) -> Html { Some(asn1) => html! {
- +
{build_asn1_schema(asn1, &props.cur_node, &props.set_cur_node)} @@ -62,7 +42,7 @@ pub fn octet_string(props: &OctetStringNodeProps) -> Html { }; html! {
- + {format!("({} bytes)", octets.len())} {encoded_octets}
@@ -94,7 +74,7 @@ pub fn bit_string(props: &BitStringNodeProps) -> Html { html! {
- + {format!("({} bits)", bits_amount)} {bits}
@@ -123,68 +103,13 @@ pub fn bit_string(props: &BmpStringNodeProps) -> Html { html! {
- + {s}
} } -#[derive(PartialEq, Properties, Clone)] -pub struct IA5StringNodeProps { - pub node: OwnedIA5String, - pub meta: OwnedRawAsn1EntityData, -} - -#[function_component(IA5StringNode)] -pub fn utf8_string(props: &IA5StringNodeProps) -> Html { - let offset = props.meta.tag_position(); - let length_len = props.meta.length_range().len(); - let data_len = props.meta.data_range().len(); - - html! { -
- - {props.node.string().to_owned()} -
- } -} - -#[derive(PartialEq, Properties, Clone)] -pub struct PrintableStringNodeProps { - pub node: OwnedPrintableString, - pub meta: OwnedRawAsn1EntityData, -} - -#[function_component(PrintableStringNode)] -pub fn utf8_string(props: &PrintableStringNodeProps) -> Html { - let offset = props.meta.tag_position(); - let length_len = props.meta.length_range().len(); - let data_len = props.meta.data_range().len(); - - html! { -
- - {props.node.string().to_owned()} -
- } -} - -#[derive(PartialEq, Properties, Clone)] -pub struct GeneralStringNodeProps { - pub node: OwnedGeneralString, - pub meta: OwnedRawAsn1EntityData, -} - -#[function_component(GeneralStringNode)] -pub fn utf8_string(props: &GeneralStringNodeProps) -> Html { - let offset = props.meta.tag_position(); - let length_len = props.meta.length_range().len(); - let data_len = props.meta.data_range().len(); - - html! { -
- - {props.node.string().to_owned()} -
- } -} +define_string_node!(GeneralString); +define_string_node!(IA5String); +define_string_node!(PrintableString); +define_string_node!(Utf8String); From 1afcdc8492168bd658a6aa4a42671138b5a219d6 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Thu, 25 Jan 2024 00:43:09 +0200 Subject: [PATCH 26/28] feat(asn1-parser): implement GeneralizedTime (need to clean up code but at least now it works); --- Cargo.lock | 1 + crates/asn1-parser/Cargo.toml | 1 + crates/asn1-parser/src/asn1.rs | 13 +- crates/asn1-parser/src/error.rs | 18 +- .../asn1-parser/src/time/generalized_time.rs | 313 ++++++++++++++++++ crates/asn1-parser/src/time/mod.rs | 58 +++- crates/asn1-parser/src/time/utc_time.rs | 54 +-- crates/asn1-parser/tests/decode_encode.rs | 25 ++ rust-toolchain.toml | 3 + 9 files changed, 429 insertions(+), 57 deletions(-) create mode 100644 crates/asn1-parser/src/time/generalized_time.rs create mode 100644 rust-toolchain.toml diff --git a/Cargo.lock b/Cargo.lock index 61d5f948..fc8d1657 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,7 @@ version = "0.1.0" dependencies = [ "log", "num-bigint-dig", + "num-traits", "oid", "paste", "prop-strategies", diff --git a/crates/asn1-parser/Cargo.toml b/crates/asn1-parser/Cargo.toml index 4da2e1d7..33362448 100644 --- a/crates/asn1-parser/Cargo.toml +++ b/crates/asn1-parser/Cargo.toml @@ -19,5 +19,6 @@ proptest = "1.2.0" [dependencies] log = "0.4.20" num-bigint-dig = { version = "0.8.4", default-features = false } +num-traits = { version = "0.2.17", default-features = false } oid = { version = "0.2.1", default-features = false } paste = "1.0.14" diff --git a/crates/asn1-parser/src/asn1.rs b/crates/asn1-parser/src/asn1.rs index 65c11019..1f204f59 100644 --- a/crates/asn1-parser/src/asn1.rs +++ b/crates/asn1-parser/src/asn1.rs @@ -5,8 +5,8 @@ use crate::reader::Reader; use crate::writer::Writer; use crate::{ ApplicationTag, Asn1Encoder, Asn1Result, Asn1ValueDecoder, BitString, BmpString, Bool, Error, ExplicitTag, - GeneralString, IA5String, ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, PrintableString, - Sequence, Set, Tag, Taggable, Tlv, UtcTime, Utf8String, + GeneralString, GeneralizedTime, IA5String, ImplicitTag, Integer, MetaInfo, Null, ObjectIdentifier, OctetString, + PrintableString, Sequence, Set, Tag, Taggable, Tlv, UtcTime, Utf8String, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -23,6 +23,7 @@ pub enum Asn1Type<'data> { GeneralString(GeneralString<'data>), UtcTime(UtcTime), + GeneralizedTime(GeneralizedTime), Bool(Bool), Null(Null), @@ -59,6 +60,7 @@ impl Asn1Type<'_> { Asn1Type::ApplicationTag(a) => Asn1Type::ApplicationTag(a.to_owned()), Asn1Type::BmpString(b) => Asn1Type::BmpString(b.to_owned()), Asn1Type::UtcTime(u) => Asn1Type::UtcTime(u.clone()), + Asn1Type::GeneralizedTime(u) => Asn1Type::GeneralizedTime(u.clone()), } } } @@ -83,6 +85,7 @@ impl Taggable for Asn1Type<'_> { Asn1Type::ImplicitTag(i) => i.tag(), Asn1Type::ApplicationTag(a) => a.tag(), Asn1Type::UtcTime(u) => u.tag(), + Asn1Type::GeneralizedTime(u) => u.tag(), } } } @@ -106,7 +109,8 @@ impl<'data> Asn1ValueDecoder<'data> for Asn1Type<'data> { ImplicitTag, ApplicationTag, Null, - UtcTime; + UtcTime, + GeneralizedTime; in tag, reader ); @@ -138,6 +142,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::ApplicationTag(a) => a.needed_buf_size(), Asn1Type::Null(n) => n.needed_buf_size(), Asn1Type::UtcTime(u) => u.needed_buf_size(), + Asn1Type::GeneralizedTime(u) => u.needed_buf_size(), } } @@ -160,6 +165,7 @@ impl Asn1Encoder for Asn1Type<'_> { Asn1Type::ApplicationTag(a) => a.encode(writer), Asn1Type::Null(n) => n.encode(writer), Asn1Type::UtcTime(utc_time) => utc_time.encode(writer), + Asn1Type::GeneralizedTime(generalized_time) => generalized_time.encode(writer), } } } @@ -184,6 +190,7 @@ impl MetaInfo for Asn1Type<'_> { Asn1Type::ApplicationTag(application_tag) => application_tag.clear_meta(), Asn1Type::Null(_) => {} Asn1Type::UtcTime(_) => {} + Asn1Type::GeneralizedTime(_) => {} } } } diff --git a/crates/asn1-parser/src/error.rs b/crates/asn1-parser/src/error.rs index a177eec2..50bad312 100644 --- a/crates/asn1-parser/src/error.rs +++ b/crates/asn1-parser/src/error.rs @@ -1,5 +1,5 @@ use alloc::string::FromUtf16Error; -use core::num::TryFromIntError; +use core::num::{ParseFloatError, ParseIntError, TryFromIntError}; use core::str::Utf8Error; use oid::ObjectIdentifierError; @@ -50,3 +50,19 @@ impl From for Error { } } } + +impl From for Error { + fn from(_value: ParseFloatError) -> Self { + Self { + message: "Float parse error", + } + } +} + +impl From for Error { + fn from(_value: ParseIntError) -> Self { + Self { + message: "Int parse error", + } + } +} diff --git a/crates/asn1-parser/src/time/generalized_time.rs b/crates/asn1-parser/src/time/generalized_time.rs new file mode 100644 index 00000000..ad00cf32 --- /dev/null +++ b/crates/asn1-parser/src/time/generalized_time.rs @@ -0,0 +1,313 @@ +use alloc::format; +use alloc::string::String; +use core::str::from_utf8; + +#[cfg(not(feature = "std"))] +use num_traits::float::FloatCore; + +use super::{read_number, Day, Hour, Minute, Month}; +use crate::length::{len_size, write_len}; +use crate::reader::Reader; +use crate::writer::Writer; +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Error, Tag, Taggable}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Year(u16); + +impl Year { + pub fn new(year: u16) -> Self { + Self(year) + } + + fn from_reader(reader: &mut Reader) -> Asn1Result { + Ok(Self(from_utf8(reader.read(4)?)?.parse::()?)) + } + + fn to_writer(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_slice(format!("{:04}", self.0).as_bytes())?; + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct Second(f32); + +impl PartialEq for Second { + fn eq(&self, other: &Self) -> bool { + (self.0 - other.0).abs() <= f32::EPSILON + } +} + +impl Eq for Second {} + +impl From for f32 { + fn from(value: Second) -> Self { + value.0 + } +} + +impl AsRef for Second { + fn as_ref(&self) -> &f32 { + &self.0 + } +} + +impl TryFrom for Second { + type Error = crate::Error; + + fn try_from(value: u8) -> Result { + if value < 60 { + Ok(Self(value.into())) + } else { + Err("invalid value".into()) + } + } +} + +impl TryFrom for Second { + type Error = crate::Error; + + fn try_from(value: f32) -> Result { + if value < 60.0 { + Ok(Self(value)) + } else { + Err("invalid value".into()) + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LocalTimeDirection { + Plus, + Minus, +} + +impl From for u8 { + fn from(value: LocalTimeDirection) -> Self { + match value { + LocalTimeDirection::Minus => b'-', + LocalTimeDirection::Plus => b'+', + } + } +} + +impl TryFrom for LocalTimeDirection { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + b'-' => Ok(Self::Minus), + b'+' => Ok(Self::Plus), + _ => Err("invalid GeneralTime data".into()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LocalTimeDiffFactor { + pub time_direction: LocalTimeDirection, + pub hour: Hour, + pub minute: Minute, +} + +impl LocalTimeDiffFactor { + const ENCODED_LEN: usize = 1 /* sign */ + 2 /* hour */ + 2 /* minute */; + + fn from_reader(reader: &mut Reader) -> Asn1Result { + Ok(Self { + time_direction: reader.read_byte()?.try_into()?, + hour: Hour::try_from(read_number(reader)?)?, + minute: Minute::try_from(read_number(reader)?)?, + }) + } + + fn to_writer(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(self.time_direction.into())?; + writer.write_slice(format!("{:02}", self.hour.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.minute.as_ref()).as_bytes())?; + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GeneralizedTime { + pub year: Year, + pub month: Month, + pub day: Day, + pub hour: Hour, + pub minute: Minute, + pub second: Second, + pub local_time: Option, +} + +impl GeneralizedTime { + pub const TAG: Tag = Tag(24); + + pub fn new( + year: Year, + month: Month, + day: Day, + hour: Hour, + minute: Minute, + second: Second, + local_time: Option, + ) -> Self { + Self { + year, + month, + day, + hour, + minute, + second, + local_time, + } + } + + fn calc_data_len(&self) -> usize { + let second_len = if self.second.as_ref().fract() > f32::EPSILON { + 2 /* int part */ + 1 /* dot */ + 3 /* fract part */ + } else { + 2 + }; + + 2 /* year */ + 2 /* month */ + 2 /* day */ + 2 /* hour */ + 2 /* minute */ + second_len + LocalTimeDiffFactor::ENCODED_LEN + } +} + +impl Taggable for GeneralizedTime { + fn tag(&self) -> Tag { + Self::TAG + } +} + +impl<'data> Asn1ValueDecoder<'data> for GeneralizedTime { + fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result { + let year = Year::from_reader(reader)?; + let month = Month::try_from(read_number(reader)?)?; + let day = Day::try_from(read_number(reader)?)?; + let hour = Hour::try_from(read_number(reader)?)?; + let minute = Minute::try_from(read_number(reader)?)?; + + let second_int_part = read_number(reader)?; + + if reader.empty() { + return Ok(Self { + year, + month, + day, + hour, + minute, + second: second_int_part.try_into().unwrap(), + local_time: None, + }); + } + + match char::from(reader.peek_byte()?) { + 'Z' => Ok(Self { + year, + month, + day, + hour, + minute, + second: second_int_part.try_into().unwrap(), + local_time: None, + }), + '+' | '-' => Ok(Self { + year, + month, + day, + hour, + minute, + second: second_int_part.try_into().unwrap(), + local_time: Some(LocalTimeDiffFactor::from_reader(reader)?), + }), + '.' => { + // sorry for this code + let _ = reader.read_byte()?; // read the dot byte + let mut frac = String::new(); + + let mut c = char::from(reader.peek_byte()?); + while c.is_ascii_digit() { + reader.read_byte()?; + frac.push(c); + + if reader.empty() { + let seconds = format!("{}.{}", second_int_part, frac).parse::()?; + return Ok(Self { + year, + month, + day, + hour, + minute, + second: seconds.try_into().unwrap(), + local_time: None, + }); + } + + c = char::from(reader.peek_byte()?); + } + let seconds = format!("{}.{}", second_int_part, frac).parse::()?; + + match c { + 'Z' => Ok(Self { + year, + month, + day, + hour, + minute, + second: seconds.try_into().unwrap(), + local_time: None, + }), + '+' | '-' => Ok(Self { + year, + month, + day, + hour, + minute, + second: seconds.try_into().unwrap(), + local_time: Some(LocalTimeDiffFactor::from_reader(reader)?), + }), + _ => Err("invalid GeneralTime data: invalid char after second frac part".into()), + } + } + _ => Err("invalid GeneralTime data: invalid char after second int part".into()), + } + } + + fn compare_tags(tag: Tag) -> bool { + Self::TAG == tag + } +} + +impl Asn1Encoder for GeneralizedTime { + fn needed_buf_size(&self) -> usize { + let value_len = self.calc_data_len(); + + 1 /* tag */ + len_size(value_len) + value_len + } + + fn encode(&self, writer: &mut Writer) -> Asn1Result<()> { + writer.write_byte(Self::TAG.into())?; + write_len(self.calc_data_len(), writer)?; + + self.year.to_writer(writer)?; + writer.write_slice(format!("{:02}", self.month.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.day.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.hour.as_ref()).as_bytes())?; + writer.write_slice(format!("{:02}", self.minute.as_ref()).as_bytes())?; + + if self.second.as_ref().fract() > f32::EPSILON { + writer.write_slice(format!("{:06.3}", self.second.as_ref()).as_bytes())?; + } else { + writer.write_slice(format!("{:02}", *self.second.as_ref() as u8).as_bytes())?; + } + + if let Some(local_time) = self.local_time.as_ref() { + local_time.to_writer(writer) + } else { + writer.write_byte(b'Z') + } + } +} diff --git a/crates/asn1-parser/src/time/mod.rs b/crates/asn1-parser/src/time/mod.rs index 4fd89e65..321b2a22 100644 --- a/crates/asn1-parser/src/time/mod.rs +++ b/crates/asn1-parser/src/time/mod.rs @@ -1,3 +1,59 @@ +mod generalized_time; mod utc_time; -pub use utc_time::{Day, Hour, Minute, Month, Second, UtcTime, Year}; +pub use generalized_time::GeneralizedTime; +pub use utc_time::UtcTime; + +use crate::reader::Reader; +use crate::Asn1Result; + +macro_rules! define_nt { + ($name:ident) => { + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct $name(u8); + + impl From<$name> for u8 { + fn from(value: $name) -> Self { + value.0 + } + } + + impl AsRef for $name { + fn as_ref(&self) -> &u8 { + &self.0 + } + } + + impl TryFrom for $name { + type Error = crate::Error; + + fn try_from(value: u8) -> Result { + if value < 100 { + Ok($name(value)) + } else { + Err("invalid value".into()) + } + } + } + }; +} + +define_nt!(Year); +define_nt!(Month); +define_nt!(Day); +define_nt!(Hour); +define_nt!(Minute); +define_nt!(Second); + +fn read_number(reader: &mut Reader<'_>) -> Asn1Result { + const ASCII_SHIFT: u8 = 48; + + let f = char::from(reader.read_byte()?); + let s = char::from(reader.read_byte()?); + + if !f.is_numeric() || !s.is_numeric() { + return Err("invalid bytes for utctime".into()); + } + + Ok((f as u8 - ASCII_SHIFT) * 10 + (s as u8 - ASCII_SHIFT)) +} diff --git a/crates/asn1-parser/src/time/utc_time.rs b/crates/asn1-parser/src/time/utc_time.rs index 5497facf..8f07b2e1 100644 --- a/crates/asn1-parser/src/time/utc_time.rs +++ b/crates/asn1-parser/src/time/utc_time.rs @@ -1,47 +1,10 @@ use alloc::format; +use super::{read_number, Day, Hour, Minute, Month, Second, Year}; use crate::length::{len_size, write_len}; use crate::reader::Reader; use crate::writer::Writer; -use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Error, Tag, Taggable}; - -macro_rules! define_nt { - ($name:ident) => { - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct $name(u8); - - impl From<$name> for u8 { - fn from(value: $name) -> Self { - value.0 - } - } - - impl AsRef for $name { - fn as_ref(&self) -> &u8 { - &self.0 - } - } - - impl TryFrom for $name { - type Error = Error; - - fn try_from(value: u8) -> Result { - if value < 100 { - Ok($name(value)) - } else { - Err("invalid value".into()) - } - } - } - }; -} - -define_nt!(Year); -define_nt!(Month); -define_nt!(Day); -define_nt!(Hour); -define_nt!(Minute); -define_nt!(Second); +use crate::{Asn1Encoder, Asn1Result, Asn1ValueDecoder, Tag, Taggable}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct UtcTime { @@ -129,16 +92,3 @@ impl Asn1Encoder for UtcTime { writer.write_byte(b'Z') } } - -fn read_number(reader: &mut Reader<'_>) -> Asn1Result { - const ASCII_SHIFT: u8 = 48; - - let f = char::from(reader.read_byte()?); - let s = char::from(reader.read_byte()?); - - if !f.is_numeric() || !s.is_numeric() { - return Err("invalid bytes for utctime".into()); - } - - Ok((f as u8 - ASCII_SHIFT) * 10 + (s as u8 - ASCII_SHIFT)) -} diff --git a/crates/asn1-parser/tests/decode_encode.rs b/crates/asn1-parser/tests/decode_encode.rs index c05f9f2a..d646267b 100644 --- a/crates/asn1-parser/tests/decode_encode.rs +++ b/crates/asn1-parser/tests/decode_encode.rs @@ -28,6 +28,31 @@ fn asn1() { }) } +#[test] +fn generalized_time() { + let raw = [ + 0x18, 0x0f, 0x32, 0x30, 0x33, 0x37, 0x30, 0x39, 0x31, 0x33, 0x30, 0x32, 0x34, 0x38, 0x30, 0x35, 0x5a, + ]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); + + let raw = [24, 16, 49, 57, 56, 53, 49, 49, 48, 54, 50, 49, 48, 54, 50, 55, 46, 51]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); + + let raw = [ + 24, 17, 49, 57, 56, 53, 49, 49, 48, 54, 50, 49, 48, 54, 50, 55, 46, 51, 90, + ]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); + + let raw = [ + 24, 21, 49, 57, 56, 53, 49, 49, 48, 54, 50, 49, 48, 54, 50, 55, 46, 51, 45, 48, 53, 48, 48, + ]; + let asn1 = Asn1::decode_buff(&raw).unwrap(); + println!("{:?}", asn1); +} + #[test] fn ia5_string() { let raw = [22, 9, 65, 66, 67, 68, 32, 69, 70, 71, 72]; diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..7cbc6acc --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.75.0" +components = [ "rustfmt", "clippy" ] From e0a7f82005dfaea584ec4a8d6a192e8d6ef5bb44 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Thu, 25 Jan 2024 23:28:14 +0200 Subject: [PATCH 27/28] feat(crypto-helper): asn1: implement GeneralizedTime. fix clippy warnings; --- Cargo.toml | 2 +- .../asn1-parser/src/time/generalized_time.rs | 15 ++++++ src/asn1/hex_view.rs | 1 + src/asn1/scheme.rs | 7 ++- src/asn1/scheme/time.rs | 52 ++++++++++++++++++- src/common/mod.rs | 9 ++-- src/crypto_helper.rs | 2 +- src/jwt.rs | 1 - 8 files changed, 79 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52888012..434a3a58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,6 @@ bcrypt = "0.14.0" flate2 = { version = "1.0.26", features = ["zlib"] } # asn1 -asn1-parser = { path = "./crates/asn1-parser" } +asn1-parser = { path = "./crates/asn1-parser", features = ["std"] } oid = { version = "0.2.1", default-features = false } paste = "1.0.14" diff --git a/crates/asn1-parser/src/time/generalized_time.rs b/crates/asn1-parser/src/time/generalized_time.rs index ad00cf32..5f710f97 100644 --- a/crates/asn1-parser/src/time/generalized_time.rs +++ b/crates/asn1-parser/src/time/generalized_time.rs @@ -30,6 +30,12 @@ impl Year { } } +impl AsRef for Year { + fn as_ref(&self) -> &u16 { + &self.0 + } +} + #[derive(Debug, Clone)] pub struct Second(f32); @@ -83,6 +89,15 @@ pub enum LocalTimeDirection { Minus, } +impl From for char { + fn from(value: LocalTimeDirection) -> Self { + match value { + LocalTimeDirection::Minus => '-', + LocalTimeDirection::Plus => '+', + } + } +} + impl From for u8 { fn from(value: LocalTimeDirection) -> Self { match value { diff --git a/src/asn1/hex_view.rs b/src/asn1/hex_view.rs index 4218a9dc..e3554994 100644 --- a/src/asn1/hex_view.rs +++ b/src/asn1/hex_view.rs @@ -173,6 +173,7 @@ fn build_data_bytes( Asn1Type::PrintableString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::GeneralString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::UtcTime(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), + Asn1Type::GeneralizedTime(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BitString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::BmpString(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), Asn1Type::Bool(_) => default_bytes(asn1_node_id, cur_node, set_cur_node, asn1, bytes, select_all), diff --git a/src/asn1/scheme.rs b/src/asn1/scheme.rs index 3e618ae6..3e298715 100644 --- a/src/asn1/scheme.rs +++ b/src/asn1/scheme.rs @@ -19,7 +19,7 @@ use self::strings::{ Utf8StringNode, }; use self::tag::{ApplicationTagNode, ExplicitTagNode, ImplicitTagNode}; -use self::time::UtcTimeNode; +use self::time::{GeneralizedTimeNode, UtcTimeNode}; use crate::asn1::scheme::set::SetNode; use crate::asn1::HighlightAction; @@ -147,5 +147,10 @@ pub fn build_asn1_schema(asn1: &Asn1<'_>, cur_id: &Option, set_cur_node: &C }, + Asn1Type::GeneralizedTime(generalized_time) => html! { + + + + }, } } diff --git a/src/asn1/scheme/time.rs b/src/asn1/scheme/time.rs index 0d846c0d..bb9fe8de 100644 --- a/src/asn1/scheme/time.rs +++ b/src/asn1/scheme/time.rs @@ -1,4 +1,4 @@ -use asn1_parser::{OwnedRawAsn1EntityData, UtcTime}; +use asn1_parser::{GeneralizedTime, OwnedRawAsn1EntityData, UtcTime}; use yew::{function_component, html, Html, Properties}; use crate::asn1::node_options::NodeOptions; @@ -23,6 +23,26 @@ pub fn utc_time_string(props: &UtcTimeNodeProps) -> Html { } } +#[derive(PartialEq, Properties, Clone)] +pub struct GeneralizedTimeNodeProps { + pub node: GeneralizedTime, + pub meta: OwnedRawAsn1EntityData, +} + +#[function_component(GeneralizedTimeNode)] +pub fn general_time_string(props: &GeneralizedTimeNodeProps) -> Html { + let offset = props.meta.tag_position(); + let length_len = props.meta.length_range().len(); + let data_len = props.meta.data_range().len(); + + html! { +
+ + {format_generalized_time(&props.node)} +
+ } +} + fn format_utc_time(utc_time: &UtcTime) -> String { use time::OffsetDateTime; @@ -54,3 +74,33 @@ fn format_utc_time(utc_time: &UtcTime) -> String { formatted } + +fn format_generalized_time(generalized_time: &GeneralizedTime) -> String { + let mut formatted = String::new(); + + formatted.push_str(&format!("{:04}", generalized_time.year.as_ref())); + + formatted.push('-'); + formatted.push_str(&format!("{:02}", generalized_time.month.as_ref())); + formatted.push('-'); + formatted.push_str(&format!("{:02}", generalized_time.day.as_ref())); + + formatted.push(' '); + formatted.push_str(&format!("{:02}", generalized_time.hour.as_ref())); + formatted.push(':'); + formatted.push_str(&format!("{:02}", generalized_time.minute.as_ref())); + formatted.push(':'); + formatted.push_str(&format!("{:02}", generalized_time.second.as_ref())); + + if let Some(local_time) = generalized_time.local_time.as_ref() { + formatted.push(' '); + formatted.push(local_time.time_direction.into()); + formatted.push_str(&format!("{:02}", local_time.hour.as_ref())); + formatted.push(':'); + formatted.push_str(&format!("{:02}", local_time.minute.as_ref())); + } else { + formatted.push_str(" UTC"); + } + + formatted +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 98f8358b..891251b8 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -5,12 +5,11 @@ mod simple_output; mod switch; mod table; -pub use byte_input::{build_byte_input, ByteInput, ByteInputProps}; -pub use bytes_viewer::{BytesViewer, BytesViewerProps}; -pub use checkbox::{Checkbox, CheckboxProps}; +pub use byte_input::{build_byte_input, ByteInput}; +pub use checkbox::Checkbox; pub use simple_output::build_simple_output; -pub use switch::{Switch, SwitchProps}; -pub use table::{TableView, TableViewProps}; +pub use switch::Switch; +pub use table::TableView; use web_sys::MouseEvent; use yew::{classes, Callback, Classes, UseStateSetter}; diff --git a/src/crypto_helper.rs b/src/crypto_helper.rs index 4714ede6..c624cfdf 100644 --- a/src/crypto_helper.rs +++ b/src/crypto_helper.rs @@ -6,7 +6,7 @@ mod macros; mod output; mod serde; -pub use algorithm::{Algorithm, KrbInput, KrbInputData, KrbMode, RSA_HASH_ALGOS}; +pub use algorithm::Algorithm; use info::Info; use input::Input; use output::Output; diff --git a/src/jwt.rs b/src/jwt.rs index b709d10a..53d53fd1 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -8,7 +8,6 @@ mod macros; use std::str::FromStr; -pub use jwt::Jwt as JwtData; use web_sys::{HtmlInputElement, KeyboardEvent}; use yew::{function_component, html, use_effect_with_deps, use_state, Callback, Html, TargetCast}; use yew_hooks::use_location; From fa5320210b513e2b9e9cdc0a6fc450a5ecfb0d63 Mon Sep 17 00:00:00 2001 From: Pavlo Myroniuk Date: Thu, 25 Jan 2024 22:22:09 +0000 Subject: [PATCH 28/28] feat(asn1-parser): update readme.md --- crates/asn1-parser/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/asn1-parser/README.md b/crates/asn1-parser/README.md index bf30f913..3797940c 100644 --- a/crates/asn1-parser/README.md +++ b/crates/asn1-parser/README.md @@ -30,7 +30,7 @@ This `asn1` parser is aimed to parse input bytes and return an AST as the result --- -- [ ] [GeneralizedTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalizedtime.html) +- [X] [GeneralizedTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/generalizedtime.html) - [ ] [Time](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/time.html) - [X] [UtcTime](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/utctime.html) - [ ] [TimeOfDay](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/timeofday.html)