Skip to content

Commit 2fce639

Browse files
authored
fix(issue-88): Fix decoding asn1 code with empty bitstring (#93)
* fix(issue-88): Fix decoding asn1 code with empty bitstring * Fix formatting * Remove octets validation
1 parent 2af9de6 commit 2fce639

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

crates/asn1-parser/src/string/bit_string.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ impl BitString<'_> {
3232
}
3333

3434
pub fn bits_amount(&self) -> usize {
35-
(self.octets.as_ref().len() - 1) * 8 - usize::from(self.octets.as_ref()[0])
35+
let data_len = self.octets.len() - 1;
36+
let padding = usize::from(self.octets[0]);
37+
38+
(data_len * 8).saturating_sub(padding)
3639
}
3740

3841
/// Creates a new [BitString] from amount of bits and actual bits buffer
@@ -100,7 +103,12 @@ impl<'data> Asn1ValueDecoder<'data> for BitString<'data> {
100103
fn decode(_: Tag, reader: &mut Reader<'data>) -> Asn1Result<Self> {
101104
let data = reader.read_remaining();
102105

103-
let inner = if !data.is_empty() {
106+
if data.is_empty() {
107+
return Err(Error::from("BitString must have at least one byte (unused bits)"));
108+
}
109+
110+
let inner = if data.len() > 1 {
111+
// Check len > 1 since first byte is unused bits
104112
let mut inner_reader = Reader::new(&data[1..]);
105113
inner_reader.set_next_id(reader.next_id());
106114
inner_reader.set_offset(reader.full_offset() - data.len());

crates/asn1-parser/tests/decode_encode.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::sync::Once;
22

3-
use asn1_parser::{Asn1, Asn1Decoder, Asn1Encoder, Asn1Type, MetaInfo, ObjectIdentifier, Taggable};
3+
use asn1_parser::{Asn1, Asn1Decoder, Asn1Encoder, Asn1Type, BitString, MetaInfo, ObjectIdentifier, Tag, Taggable};
44
use prop_strategies::any_asn1_type;
55
use proptest::proptest;
66

@@ -305,3 +305,52 @@ fn test_2() {
305305
let asn1 = Asn1::decode_buff(raw).unwrap();
306306
println!("{:?}", asn1);
307307
}
308+
309+
#[test]
310+
fn empty_bitstring() {
311+
init_logging();
312+
313+
let raw = [0x03, 0x01, 0x00];
314+
let asn1 = Asn1::decode_buff(&raw).expect("Failed to decode empty BitString");
315+
316+
assert_eq!(asn1.inner_asn1().tag(), Tag::from(3), "Tag should be 0x03");
317+
318+
if let Asn1Type::BitString(bitstring) = asn1.inner_asn1() {
319+
assert_eq!(bitstring.raw_bits(), &[0], "Raw bits should be [0]");
320+
assert_eq!(bitstring.bits_amount(), 0, "Bits amount should be 0");
321+
assert!(bitstring.inner().is_none(), "Inner should be None");
322+
} else {
323+
panic!("Expected BitString type");
324+
}
325+
326+
// Test encoding back
327+
let mut encoded = vec![0; asn1.needed_buf_size()];
328+
asn1.encode_buff(&mut encoded)
329+
.expect("Failed to encode empty BitString");
330+
assert_eq!(encoded, raw, "Encoded bytes should match original");
331+
332+
// Test creating an empty BitString directly
333+
let empty_bits = BitString::from_raw_vec(0, vec![]).expect("Failed to create empty BitString");
334+
assert_eq!(empty_bits.raw_bits(), &[0], "Created raw bits should be [0]");
335+
assert_eq!(empty_bits.bits_amount(), 0, "Created bits amount should be 0");
336+
assert!(empty_bits.inner().is_none(), "Created inner should be None");
337+
338+
// Test invalid decoding: BitString with length 0 (invalid in DER)
339+
let invalid_raw = [0x03, 0x00];
340+
assert!(
341+
Asn1::decode_buff(&invalid_raw).is_err(),
342+
"Decoding length 0 should fail"
343+
);
344+
345+
// Test invalid creation: too many bits
346+
assert!(
347+
BitString::from_raw_vec(1, vec![]).is_err(),
348+
"Creating with too many bits should fail"
349+
);
350+
351+
// Test invalid creation: too many unused bits
352+
assert!(
353+
BitString::from_raw_vec(0, vec![0]).is_err(),
354+
"Creating with excess unused bits should fail"
355+
);
356+
}

src/asn1/scheme/strings.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::fmt::Write;
2+
13
use asn1_parser::{
24
OwnedBitString, OwnedBmpString, OwnedGeneralString, OwnedIA5String, OwnedNumericString, OwnedOctetString,
35
OwnedPrintableString, OwnedRawAsn1EntityData, OwnedUtf8String, OwnedVisibleString,
@@ -68,15 +70,20 @@ pub struct BitStringNodeProps {
6870

6971
#[function_component(BitStringNode)]
7072
pub fn bit_string(props: &BitStringNodeProps) -> Html {
71-
let bits = props.node.raw_bits()[1..]
72-
.iter()
73-
.map(|byte| format!("{:08b}", byte))
74-
.fold(String::new(), |mut ac, new| {
75-
ac.push_str(&new);
76-
ac
77-
});
73+
let raw_bits = props.node.raw_bits();
7874
let bits_amount = props.node.bits_amount();
79-
let bits = &bits[0..bits_amount];
75+
76+
let bits = if raw_bits.len() > 1 {
77+
let mut bits = String::with_capacity((raw_bits.len() - 1) * 8);
78+
for byte in &raw_bits[1..] {
79+
write!(bits, "{:08b}", byte).unwrap();
80+
}
81+
bits
82+
} else {
83+
String::new()
84+
};
85+
86+
let display_bits = &bits[0..bits_amount.min(bits.len())];
8087

8188
let offset = props.meta.tag_position();
8289
let length_len = props.meta.length_range().len();
@@ -99,7 +106,7 @@ pub fn bit_string(props: &BitStringNodeProps) -> Html {
99106
<div class="terminal-asn1-node">
100107
<NodeOptions node_bytes={RcSlice::from(props.meta.raw_bytes())} {offset} {length_len} {data_len} name={String::from("BitString")} />
101108
<span class="asn1-node-info-label">{format!("({} bits)", bits_amount)}</span>
102-
<span class="asn-simple-value">{bits}</span>
109+
<span class="asn-simple-value">{display_bits}</span>
103110
</div>
104111
}
105112
}

0 commit comments

Comments
 (0)