Skip to main content

wildboar_asn1/
error.rs

1//! The `ASN1Error` error type
2use crate::{ASN1Value, ComponentSpec};
3use crate::tag::Tag;
4use std::fmt;
5use std::io::{Error, ErrorKind};
6use std::str::Utf8Error;
7
8const VALUE_PREVIEW_SIZE: usize = 32;
9
10/// The overall ASN.1 error type
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum ASN1ErrorCode {
13    // I went with an enum rather than integer error codes, because of debug printing.
14
15    /// Some kind of I/O error
16    io,
17
18    /// The encoding was truncated. This is NOT to be used when there are enough
19    /// bytes of content octets for the length, but the content contained
20    /// therein is invalid. This is for when the whole Tag-Length-Value (TLV) is
21    /// truncated.
22    tlv_truncated,
23
24    /// The tag was too big
25    tag_too_big,
26
27    /// The length of an encoded value was too big
28    length_too_big,
29
30    /// The value encoded was too big, such as by overflowing an integer, or
31    /// exceeding some other bounds imposed by the library.
32    value_too_big,
33
34    /// A field within an encoded value was too large
35    field_too_big,
36
37    /// An encoded value was too short to be valid
38    value_too_short,
39
40    /// An encoded value was malformed in some general way
41    malformed_value,
42
43    /// Unnecessary padding bytes (or bits) in an encoded tag number
44    padding_in_tag_number,
45
46    /// Tag number was encoded using the long form when it could have fit in
47    /// short form. (I believe only applies to X.690 encodings, such as BER,
48    /// CER, or DER.)
49    tag_number_could_have_used_short_form,
50
51    /// A constructed string was composed of substituent elements whose tag or
52    /// tag class did not match.
53    string_constructed_with_invalid_tagging,
54
55    /// An element was expected to be encoded primitively, but it was
56    /// constructed or vice versa.
57    invalid_construction,
58
59    /// An unrecognized component was found in an inextensible `SET` or
60    /// `SEQUENCE`.
61    unrecognized_components_in_inextensible_type,
62
63    /// An unrecognized alternative was found in an inextensible `CHOICE`
64    /// encoding.
65    unrecognized_alternative_in_inextensible_choice,
66
67    /// A prohibited character was found in a string. The first member is the
68    /// character code point itself, and the second is its index.
69    prohibited_character(u32, usize),
70
71    /// An encoded constructed value was composed of way too many components and
72    /// was not decoded as a protection against Denial of Service (DoS).
73    construction_too_complex,
74
75    /// Duplicate tags were found in a `SET` type.
76    duplicate_tags_in_set,
77
78    /// Duplicate components were found in a `SET` or `SEQUENCE`. This is a
79    /// little different from [ASN1ErrorCode::duplicate_tags_in_set] in that
80    /// this can happen when a component is identified by multiple tags, such as
81    /// when it is a `CHOICE` type without an explicit tag.
82    duplicate_components,
83
84    /// Components that were required in a `SET` or `SEQUENCE` (not `OPTIONAL`
85    /// or `DEFAULT`ing) were not found.
86    missing_required_components,
87
88    /// Padding bits or bytes were found in an integer encoding. In X.690
89    /// encodings, the byte `0x00` may not appear at the start of a positive
90    /// `INTEGER`, other than `0`, and `0xFF` may not appear at the start of a
91    /// negative `INTEGER`.
92    int_padding,
93
94    /// A Distinguished Encoding Rules (DER)-encoded `BOOLEAN` was encoded using
95    /// something other than a `0x00` or `0xFF`.
96    der_boolean_not_0x00_or_0xFF,
97
98    /// Unnecessary padding bytes or bits in an encoded `OBJECT IDENTIFIER`. In
99    /// X.690 encodings, an `OBJECT IDENTIFIER` arc may not start with the byte
100    /// `0x80`, which is meaningless padding.
101    oid_padding,
102
103    /// Unrecognized special `REAL` value not encountered.
104    unrecognized_special_real,
105
106    /// A binary real number used a base value (as in part of the mantissa,
107    /// base, and exponent that comprise a real number) that was unrecognized.
108    bin_real_unrecognized_base,
109
110    /// A binary real number used an exponent format that was unrecognized.
111    bin_real_unrecognized_exp_fmt,
112
113    /// A base-10 `REAL` string was malformed. The variant value is the bytes
114    /// of the string.
115    base_10_real_string_malformed(Vec<u8>), // bytes of the string
116
117    /// Unrecognized `REAL` string format. The variant value is the identifier
118    /// for the format.
119    base_10_real_unrecognized_format(u8),
120
121    /// A base-10 `REAL` number used a base value (as in part of the mantissa,
122    /// base, and exponent that comprise a real number) that was unrecognized.
123    base_10_real_unrecognized_base(u8),
124
125    /// Long-form length was used when it was not necessary.
126    x690_long_form_unnecessary,
127
128    /// Indefinite length was indicated, but constructed encoding was not used.
129    x690_indefinite_length_but_not_constructed,
130
131    /// X.690 `BOOLEAN` value not encoded on one byte
132    x690_boolean_not_one_byte,
133
134    /// X.690 `BIT STRING` had trailing bits greater than 7.
135    x690_bit_string_remainder_gt_7,
136
137    /// X.690 `BIT STRING` had trailing bits indicated, yet no bits
138    x690_bit_string_remainder_but_no_bits,
139
140    /// X.690 `BIT STRING` encoded on zero bytes
141    x690_bit_string_zero_bytes,
142
143    /// X.690 constructed `BIT STRING` had a non-terminal substituent
144    /// tag-length-value that had trailing bits.
145    x690_bit_string_non_terminal_segment_with_trailing_bits,
146
147    /// Invalid year
148    invalid_year,
149
150    /// Invalid month
151    invalid_month,
152
153    /// Invalid day
154    invalid_day,
155
156    /// Invalid hour
157    invalid_hour,
158
159    /// Invalid minute
160    invalid_minute,
161
162    /// Invalid second
163    invalid_second,
164
165    /// Invalid fraction of seconds
166    invalid_fraction_of_seconds,
167
168    /// Invalid time offset
169    invalid_time_offset,
170
171    /// Invalid UTF-8 encoding
172    invalid_utf8(Option<Utf8Error>),
173
174    /// An impossible error that should never happen.
175    nonsense,
176
177    /// Constraint violation, such as an `INTEGER` value of `5` being used where
178    /// the type `INTEGER (1..3)` is expected.
179    constraint_violation,
180
181    /// Trailing content octets
182    trailing_content_octets,
183
184    /// Invalid `DURATION` component
185    invalid_duration_component(char),
186
187    /// An OID arc was negative or the first OID arc was not 0, 1, or 2, or the
188    /// second OID arc was greater than 39 whent he first was 0 or 1.
189    invalid_oid_arc,
190
191    /// Indefinite length was used where not allowed, such as in DER.
192    indefinite_length_prohibited,
193
194    /// A `BIT STRING` had trailing bits set, which is not allowed.
195    bit_string_trailing_bits_set,
196
197    /// A `REAL` was encoded using a format that is not allowed.
198    real_format_prohibited,
199
200    /// A `REAL` was encoded using a base that is not allowed.
201    real_base_prohibited,
202
203    /// An unrecognized universal type was encountered.
204    unrecognized_universal_type,
205}
206
207/// An ASN.1-related error
208#[derive(Debug)]
209pub struct ASN1Error {
210
211    /// The "kind" of error
212    pub error_code: ASN1ErrorCode,
213
214    /// The name of the offending component in a `SET` or `SEQUENCE`
215    pub component_name: Option<String>,
216
217    /// The tag of the offending component
218    pub tag: Option<Tag>,
219
220    /// The length of the offending component
221    pub length: Option<usize>,
222
223    /// Whether the offending component was constructed
224    pub constructed: Option<bool>,
225
226    /// A human-readable preview of the value. Users MUST NOT rely on this
227    /// having any particular format.
228    pub value_preview: Option<String>,
229
230    /// The number of bytes into the IO read stream where this error appeared.
231    pub bytes_read: Option<usize>,
232
233    /// The number of ASN.1 values into the IO read stream where this error appeared.
234    pub values_read: Option<usize>,
235
236    /// The underlying error
237    pub err_source: Option<Box<dyn std::error::Error + 'static>>,
238}
239
240impl std::error::Error for ASN1Error {
241    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
242        self.err_source.as_deref()
243    }
244}
245
246impl fmt::Display for ASN1ErrorCode {
247    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
248        write!(f, "{:?}", self)
249    }
250}
251
252impl ASN1Error {
253
254    /// Construct a new `ASN1Error` from just an error code
255    #[inline]
256    pub const fn new(error_code: ASN1ErrorCode) -> Self {
257        ASN1Error {
258            error_code,
259            component_name: None,
260            tag: None,
261            length: None,
262            constructed: None,
263            value_preview: None,
264            bytes_read: None,
265            values_read: None,
266            err_source: None,
267        }
268    }
269
270    /// Relate an ASN.1 value to this error code.
271    #[inline]
272    pub fn relate_value (&mut self, value: &ASN1Value) {
273        // We truncate the value string to prevent Denial-of-Service by flooding
274        // logs with gigantic strings
275        self.value_preview = Some(value
276            .to_string() // TODO: to_preview() instead?
277            .chars()
278            .take(VALUE_PREVIEW_SIZE)
279            .collect());
280    }
281
282    /// Relate a Tag to this error code
283    #[inline]
284    pub fn relate_tag (&mut self, tag: &Tag) {
285        self.tag = Some(*tag);
286    }
287
288    /// Relate a component spec to this error code
289    #[inline]
290    pub fn relate_spec (&mut self, spec: &ComponentSpec<'_>) {
291        self.component_name = Some(String::from(spec.name));
292    }
293
294    #[inline]
295    pub fn with_tag (mut self, tag: Tag) -> Self {
296        self.relate_tag(&tag);
297        self
298    }
299
300    #[inline]
301    pub fn with_component_name (mut self, name: &str) -> Self {
302        self.component_name = Some(name.to_owned());
303        self
304    }
305
306    #[inline]
307    pub fn with_length (mut self, len: usize) -> Self {
308        self.length = Some(len);
309        self
310    }
311
312    #[inline]
313    pub fn with_construction (mut self, constructed: bool) -> Self {
314        self.constructed = Some(constructed);
315        self
316    }
317
318    #[inline]
319    pub fn with_preview (mut self, preview: &str) -> Self {
320        self.value_preview = Some(preview.to_owned());
321        self
322    }
323
324    #[inline]
325    pub fn with_bytes_read (mut self, bytes_read: usize) -> Self {
326        self.bytes_read = Some(bytes_read);
327        self
328    }
329
330    #[inline]
331    pub fn with_values_read (mut self, values_read: usize) -> Self {
332        self.values_read = Some(values_read);
333        self
334    }
335
336    #[inline]
337    pub fn with_source<E: std::error::Error + 'static> (mut self, source: E) -> Self {
338        self.err_source = Some(Box::new(source));
339        self
340    }
341
342}
343
344impl From<Error> for ASN1Error {
345    #[inline]
346    fn from(other: Error) -> Self {
347        ASN1Error {
348            error_code: ASN1ErrorCode::io,
349            component_name: None,
350            tag: None,
351            length: None,
352            constructed: None,
353            value_preview: None,
354            bytes_read: None,
355            values_read: None,
356            err_source: Some(Box::new(other)),
357        }
358    }
359}
360
361impl From<ASN1Error> for std::io::Error {
362
363    #[inline]
364    fn from(mut value: ASN1Error) -> Self {
365        if let Some(e) = value.err_source.take() {
366            if let Ok(io_err) = e.downcast::<std::io::Error>() {
367                return *io_err
368            }
369        }
370        std::io::Error::from(ErrorKind::InvalidData)
371    }
372
373}
374
375impl From<Utf8Error> for ASN1Error {
376
377    #[inline]
378    fn from(value: Utf8Error) -> Self {
379        ASN1Error {
380            error_code: ASN1ErrorCode::invalid_utf8(Some(value)),
381            component_name: None,
382            tag: None,
383            length: None,
384            constructed: None,
385            value_preview: None,
386            bytes_read: Some(value.valid_up_to()),
387            values_read: None,
388            err_source: None,
389        }
390    }
391
392}
393
394impl fmt::Display for ASN1Error {
395    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
396        self.error_code.fmt(f)?;
397        if let Some(component_name) = &self.component_name {
398            write!(f, " component='{}'", component_name)?;
399        }
400        if let Some(tag) = self.tag {
401            write!(f, " tag={}", tag)?;
402        }
403        if let Some(len) = self.length {
404            write!(f, " len={}", len)?;
405        }
406        if let Some(c) = self.constructed {
407            write!(f, " cons={}", c)?;
408        }
409        if let Some(index) = self.bytes_read {
410            write!(f, " bytes_read={}", index)?;
411        }
412        if let Some(index) = self.values_read {
413            write!(f, " values_read={}", index)?;
414        }
415        if let Some(preview) = &self.value_preview {
416            write!(f, " peek='{}'", preview)?;
417        }
418        Ok(())
419    }
420}
421
422/// An ASN.1-related result
423pub type ASN1Result<T> = Result<T, ASN1Error>;
424
425#[cfg(test)]
426mod test {
427    use crate::{ASN1Error, TagClass, Tag};
428
429
430    #[test]
431    fn error_fluent_api() {
432        let src = ASN1Error::new(super::ASN1ErrorCode::malformed_value);
433        let e = ASN1Error::new(super::ASN1ErrorCode::malformed_value)
434            .with_tag(Tag::new(TagClass::UNIVERSAL, 10))
435            .with_bytes_read(10)
436            .with_values_read(5)
437            .with_component_name("chunky")
438            .with_construction(true)
439            .with_preview("fogqwirg")
440            .with_length(7)
441            .with_source(src)
442            ;
443
444        assert!(e.component_name.is_some());
445        assert!(e.tag.is_some());
446        assert!(e.length.is_some());
447        assert!(e.constructed.is_some());
448        assert!(e.value_preview.is_some());
449        assert!(e.bytes_read.is_some());
450        assert!(e.values_read.is_some());
451        assert!(e.err_source.is_some());
452    }
453
454}