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}