├── .appveyor.yml ├── .gitignore ├── .rustfmt.toml ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── Cargo.toml ├── LICENSE BSD 2-CLAUSE.md ├── LICENSE MIT.md ├── README.md ├── src ├── data.rs ├── der.rs ├── error.rs ├── lib.rs └── typed │ ├── boolean.rs │ ├── integer.rs │ ├── mod.rs │ ├── null.rs │ ├── octet_string.rs │ ├── sequence.rs │ └── utf8_string.rs ├── test_unix.sh └── tests ├── err.json ├── err.rs ├── err_typed.rs ├── helpers └── mod.rs ├── ok.json ├── ok.rs └── ok_typed.rs /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Since we use custom test script, disable the default build action 2 | build: false 3 | 4 | 5 | # The Rust target platforms 6 | platform: 7 | - x86_64-pc-windows-msvc 8 | - i686-pc-windows-msvc 9 | - x86_64-pc-windows-gnu 10 | - i686-pc-windows-gnu 11 | - x86_64-unknown-linux-gnu 12 | - x86_64-apple-darwin 13 | 14 | 15 | # The Rust feature matrix 16 | configuration: 17 | - --features= 18 | - --features=native_types 19 | - --features=std 20 | #- --features=no_panic 21 | - --features=native_types,std 22 | #- --features=std,no_panic 23 | #- --features=no_panic,native_types 24 | #- --features=native_types,std,no_panic 25 | 26 | 27 | # General environment vars 28 | environment: 29 | # Promote warnings to errors 30 | RUSTFLAGS: -D warnings 31 | 32 | 33 | # Fail fast if one job in the matrix fails 34 | matrix: 35 | fast_finish: true 36 | 37 | 38 | for: 39 | # Windows specific build settings 40 | - matrix: 41 | only: 42 | - platform: x86_64-pc-windows-msvc 43 | - platform: i686-pc-windows-msvc 44 | - platform: x86_64-pc-windows-gnu 45 | - platform: i686-pc-windows-gnu 46 | environment: 47 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 48 | install: 49 | # Early-abort no_panic builds on windows 50 | - ps: | 51 | if ($env:CONFIGURATION -match ".*no_panic.*" ) { 52 | Exit-AppveyorBuild 53 | } 54 | - cmd: appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 55 | - cmd: rustup-init.exe -y --default-host "%PLATFORM%" 56 | - cmd: set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 57 | test_script: 58 | - cmd: cargo test --verbose --no-default-features %CONFIGURATION% 59 | - cmd: cargo test --verbose --release --no-default-features %CONFIGURATION% 60 | 61 | # Linux specific build settings 62 | - matrix: 63 | only: 64 | - platform: x86_64-unknown-linux-gnu 65 | environment: 66 | APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 67 | install: 68 | - sh: curl https://sh.rustup.rs -sSf -o rustup-init.sh 69 | - sh: sh rustup-init.sh -y --default-host "$PLATFORM" 70 | - sh: source $HOME/.cargo/env 71 | test_script: 72 | # Skip no_panic tests for debug builds 73 | - sh: | 74 | if ! echo "$CONFIGURATION" | grep -q ".*no_panic.*"; then 75 | cargo test --verbose --no-default-features $CONFIGURATION 76 | fi 77 | - sh: cargo test --verbose --release --no-default-features $CONFIGURATION 78 | 79 | # macOS specific build settings 80 | - matrix: 81 | only: 82 | - platform: x86_64-apple-darwin 83 | environment: 84 | APPVEYOR_BUILD_WORKER_IMAGE: macOS 85 | install: 86 | - sh: curl https://sh.rustup.rs -sSf -o rustup-init.sh 87 | - sh: sh rustup-init.sh -y --default-host "$PLATFORM" 88 | - sh: source $HOME/.cargo/env 89 | test_script: 90 | # Skip no_panic tests for debug builds 91 | - sh: | 92 | if ! echo "$CONFIGURATION" | grep -q ".*no_panic.*"; then 93 | cargo test --verbose --no-default-features $CONFIGURATION 94 | fi 95 | - sh: cargo test --verbose --release --no-default-features $CONFIGURATION -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore .DS_Store 2 | .DS_Store 3 | 4 | # Ignore project files 5 | .idea/ 6 | 7 | # Ignore build results 8 | Cargo.lock 9 | target/ -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | newline_style = "Unix" 3 | fn_args_layout = "Tall" 4 | use_small_heuristics = "Max" 5 | use_field_init_shorthand = true 6 | use_try_shorthand = true 7 | 8 | # Unstable args 9 | unstable_features = true 10 | group_imports = "One" 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vadimcn.vscode-lldb", 4 | "rust-lang.rust-analyzer" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "cargo test", 8 | "cargo": { 9 | "args": [ 10 | "test", 11 | "--no-run", 12 | "--lib", 13 | "--package=http_tiny" 14 | ], 15 | "filter": { 16 | "name": "http_tiny", 17 | "kind": "lib" 18 | } 19 | }, 20 | "args": [], 21 | "cwd": "${workspaceFolder}" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.checkOnSave.allTargets": true, 3 | "[rust]": { 4 | "editor.formatOnSave": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "build", 7 | "problemMatcher": [ 8 | "$rustc" 9 | ], 10 | "group": "build", 11 | "label": "rust: cargo build" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asn1_der" 3 | version = "0.7.6" 4 | edition = "2021" 5 | authors = ["KizzyCode Software Labs./Keziah Biermann "] 6 | keywords = ["asn1", "asn1-der", "serialize", "deserialize", "no_panic"] 7 | categories = ["encoding"] 8 | description = "This crate provides an ASN.1-DER en-/decoder" 9 | license = "BSD-2-Clause OR MIT" 10 | repository = "https://github.com/KizzyCode/asn1_der-rust" 11 | readme = "README.md" 12 | exclude = [".*", "test_unix.sh"] 13 | 14 | 15 | [badges] 16 | appveyor = { repository = "KizzyCode/asn1_der-rust" } 17 | 18 | 19 | [features] 20 | default = ["std", "native_types"] 21 | std = [] 22 | native_types = [] 23 | no_panic = ["no-panic"] 24 | 25 | 26 | [dependencies] 27 | no-panic = { version = "0.1", optional = true } 28 | 29 | 30 | [dev-dependencies] 31 | serde = { version = "1.0", features = ["serde_derive"] } 32 | serde_json = "1.0" 33 | 34 | 35 | [profile.release] 36 | overflow-checks = true 37 | 38 | [profile.bench] 39 | overflow-checks = true 40 | -------------------------------------------------------------------------------- /LICENSE BSD 2-CLAUSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Keziah Biermann 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | __THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.__ -------------------------------------------------------------------------------- /LICENSE MIT.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Keziah Biermann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | __THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![docs.rs](https://docs.rs/asn1_der/badge.svg)](https://docs.rs/asn1_der) 2 | [![License BSD-2-Clause](https://img.shields.io/badge/License-BSD--2--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) 3 | [![License MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 4 | [![crates.io](https://img.shields.io/crates/v/asn1_der.svg)](https://crates.io/crates/asn1_der) 5 | [![Download numbers](https://img.shields.io/crates/d/asn1_der.svg)](https://crates.io/crates/asn1_der) 6 | [![AppVeyor CI](https://ci.appveyor.com/api/projects/status/github/KizzyCode/asn1_der-rust?svg=true)](https://ci.appveyor.com/project/KizzyCode/asn1-der-rust) 7 | [![dependency status](https://deps.rs/crate/asn1_der/latest/status.svg)](https://deps.rs/crate/asn1_der) 8 | 9 | # asn1_der 10 | Welcome to `asn1_der` 🎉 11 | 12 | This crate provides a basic `no_std`-compatible, [no-panic](#no-panic) and [zero-copy](#zero-copy) 13 | DER implementation. It is designed to be reliable and reasonable fast without getting too large or 14 | sacrificing too much comfort. To achieve this, `asn1_der` makes extensive use of the 15 | [`no-panic`](https://crates.io/crates/no-panic) crate and offers slice-based object views to avoid 16 | allocations and unnecessary copies. 17 | 18 | 19 | ## Example 20 | ```ignore 21 | use asn1_der::{ 22 | DerObject, 23 | typed::{ DerEncodable, DerDecodable } 24 | }; 25 | 26 | fn main() { 27 | /// An ASN.1-DER encoded integer `7` 28 | const INT7: &'static[u8] = b"\x02\x01\x07"; 29 | 30 | // Decode an arbitrary DER object 31 | let object = DerObject::decode(INT7).expect("Failed to decode object"); 32 | 33 | // Encode an arbitrary DER object 34 | let mut encoded_object = Vec::new(); 35 | object.encode(&mut encoded_object).expect("Failed to encode object"); 36 | 37 | // Decode a `u8` 38 | let number = u8::decode(INT7).expect("Failed to decode number"); 39 | assert_eq!(number, 7); 40 | 41 | // Encode a new `u8` 42 | let mut encoded_number = Vec::new(); 43 | 7u8.encode(&mut encoded_number).expect("Failed to encode number"); 44 | } 45 | ``` 46 | 47 | For the (de-)serialization of structs and similar via `derive`, see 48 | [`serde_asn1_der`](https://crates.io/crates/serde_asn1_der). 49 | 50 | 51 | ## Typed Implementations 52 | There are also some direct `DerDecodable`/`DerDecodable` implementations for native Rust type 53 | equivalents: 54 | - The ASN.1-`BOOLEAN` type as Rust-`bool` 55 | - The ASN.1-`INTEGER` type as Rust-[`u8`, `u16`, `u32`, `u64`, `u128`, `usize`] 56 | - The ASN.1-`NULL` type as either `()` or `Option::None` (which allows the encoding of 57 | optionals) 58 | - The ASN.1-`OctetString` type as `Vec` 59 | - The ASN.1-`SEQUENCE` type as `SequenceVec(Vec)` 60 | - The ASN.1-`UTF8String` type as `String` 61 | 62 | 63 | ## No-Panic 64 | `asn1_der` is designed to be as panic-free as possible. To ensure that, nearly every function is 65 | attributed with `#[no_panic]`, which forces the compiler to prove that a function cannot panic in 66 | the given circumstances. However since `no_panic` can cause a lot of false-positives, it is 67 | currently only used by the CI-tests and disabled by default in normal builds. If you want to use 68 | this crate with `no_panic` enabled, you can do so by specifying the `no_panic` feature. 69 | 70 | ### What No-Panic Does Not Cover 71 | It is important to know that `no_panic` is no silver bullet and does not help against certain kinds 72 | of errors that can also happen in this crate. This especially includes: 73 | - Dynamic memory allocation errors: Since it is not possible to predict memory allocation errors, 74 | everything that requires dynamic memory allocation is mutually exclusive to `no_panic` and will 75 | be omitted if `no_panic` is enabled. 76 | 77 | This crate might allocate memory in the following circumstances: 78 | - When writing to a dynamically allocating sink (e.g. `Vec`, `VecBacking(Vec)`) 79 | - When decoding a native owned type such as `Vec`, `SequenceVec(Vec)` or `String` 80 | - During error propagation 81 | 82 | If the crate is compiled without `std` enabled, it does performy any dynamic memory allocation 83 | directly by itself – however for foreign implementations passed to this crate may still allocate 84 | memory and fail (e.g. a custom `Sink` implementation). 85 | 86 | - Stack overflows: Since the stack size is not necessarily known during compile time, it is not 87 | possible to predict stack overflow errors e.g. caused by recursion. 88 | - Calls to `abort` or similar: Since calls to `abort` or similar do not trigger stack unwinding, 89 | they can also no be detected by `no_panic`. __This also means that `no_panic` does not work for 90 | builds that use `panic = "abort"` in their config.__ 91 | 92 | This crate by itself does never call `abort` directly. 93 | 94 | Due to the limitations described above, the following functions are mutually exclusive to 95 | `no_panic` and disabled if `no_panic` is set: 96 | - Error stacking/propagation (`propagate` is a no-op if compiled with `no_panic`) 97 | - The sink implementation for a byte vector (`impl Sink for Vec`) 98 | - The `VecBacking(Vec)` type 99 | - The native OctetString type which uses `Vec` (`impl<'a> DerDecodable<'a> for Vec` and 100 | `impl DerEncodable for Vec`) 101 | - The native Sequence type wrapper `SequenceVec` since it is based upon `Vec` 102 | - The native Utf8String type based upon `String` (`impl<'a> DerDecodable<'a> for String` and 103 | `impl DerEncodable for String`) 104 | 105 | 106 | ## Zero-Copy 107 | The crate is designed to be as much zero-copy as possible. In fact this means that the `DerObject` 108 | type and all typed views are zero-copy views over the underlying slice. Of course, zero-copy is not 109 | always reasonable: The `new`-constructors are not zero-copy because they construct a new object into 110 | a sink and the native type implementations are not zero-copy because they are either `Copy`-types 111 | (e.g. `u128`) or owned (e.g. `String`). 112 | 113 | 114 | ## What happened to `asn1_der_derive`? 115 | Since version 0.7.0, the `asn1_der_derive`-crates has been deprecated in favor of 116 | [`serde_asn1_der`](https://crates.io/crates/serde_asn1_der). If you have a specific use-case why you 117 | cannot use `serde`, let me know; it's probably not that hard to revive `asn1_der_derive` 😊 -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::ErrorChain, Asn1DerError}; 2 | use core::{iter, slice}; 3 | 4 | /// A trait defining a byte source 5 | pub trait Source: Sized { 6 | /// Reads the next element 7 | fn read(&mut self) -> Result; 8 | 9 | /// Creates a counting source 10 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 11 | fn counting_source(self, ctr: &mut usize) -> CountingSource { 12 | CountingSource { source: self, ctr } 13 | } 14 | /// Creates a copying source 15 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 16 | fn copying_source(self, sink: U) -> CopyingSource { 17 | CopyingSource { source: self, sink } 18 | } 19 | } 20 | impl Source for &mut S { 21 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 22 | fn read(&mut self) -> Result { 23 | (*self).read() 24 | } 25 | } 26 | impl<'a> Source for slice::Iter<'a, u8> { 27 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 28 | fn read(&mut self) -> Result { 29 | match self.next() { 30 | Some(e) => Ok(*e), 31 | None => Err(eio!("Cannot read beyond end of slice-iterator")), 32 | } 33 | } 34 | } 35 | impl<'a, A: Iterator, B: Iterator> Source for iter::Chain { 36 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 37 | fn read(&mut self) -> Result { 38 | match self.next() { 39 | Some(e) => Ok(*e), 40 | None => Err(eio!("Cannot read beyond end of chain-iterator")), 41 | } 42 | } 43 | } 44 | impl<'a, I: Iterator + 'a> Source for iter::Skip { 45 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 46 | fn read(&mut self) -> Result { 47 | match self.next() { 48 | Some(e) => Ok(*e), 49 | None => Err(eio!("Cannot read beyond end of chain-iterator")), 50 | } 51 | } 52 | } 53 | 54 | /// A source that counts the amount of elements read 55 | /// 56 | /// __Warning: if a call to `read` would cause `ctr` to exceed `usize::max_value()`, this source 57 | /// will return an error and the element that has been read from the underlying source will be 58 | /// lost__ 59 | pub struct CountingSource<'a, S: Source> { 60 | source: S, 61 | ctr: &'a mut usize, 62 | } 63 | impl<'a, S: Source> Source for CountingSource<'a, S> { 64 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 65 | fn read(&mut self) -> Result { 66 | let e = self.source.read().propagate(e!("Failed to read element from underlying source"))?; 67 | match self.ctr.checked_add(1) { 68 | Some(ctr_next) => { 69 | *self.ctr = ctr_next; 70 | Ok(e) 71 | } 72 | _ => Err(eio!("Cannot read more because the position counter would overflow")), 73 | } 74 | } 75 | } 76 | 77 | /// A source that also copies each read element to the `sink` 78 | /// 79 | /// __Warning: if a call to `write` fails, this source will return an error and the element that has 80 | /// been read from the underlying source will be lost__ 81 | pub struct CopyingSource { 82 | source: S, 83 | sink: U, 84 | } 85 | impl CopyingSource { 86 | /// Copies the next element 87 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 88 | pub fn copy_next(&mut self) -> Result<(), Asn1DerError> { 89 | self.read()?; 90 | Ok(()) 91 | } 92 | /// Copies the next `n` elements 93 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 94 | pub fn copy_n(&mut self, n: usize) -> Result<(), Asn1DerError> { 95 | (0..n).try_for_each(|_| self.copy_next()) 96 | } 97 | } 98 | impl Source for CopyingSource { 99 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 100 | fn read(&mut self) -> Result { 101 | let e = self.source.read().propagate(e!("Failed to read element from underlying source"))?; 102 | self.sink.write(e).propagate(e!("Failed to copy element to sink"))?; 103 | Ok(e) 104 | } 105 | } 106 | 107 | /// A trait defining a byte sink 108 | pub trait Sink: Sized { 109 | /// Writes `e` to `self` 110 | fn write(&mut self, e: u8) -> Result<(), Asn1DerError>; 111 | /// Creates a counting sink 112 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 113 | fn counting_sink(self, ctr: &mut usize) -> CountingSink { 114 | CountingSink { sink: self, ctr } 115 | } 116 | } 117 | impl Sink for &mut S { 118 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 119 | fn write(&mut self, e: u8) -> Result<(), Asn1DerError> { 120 | (*self).write(e) 121 | } 122 | } 123 | impl<'a> Sink for slice::IterMut<'a, u8> { 124 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 125 | fn write(&mut self, e: u8) -> Result<(), Asn1DerError> { 126 | match self.next() { 127 | Some(t) => { 128 | *t = e; 129 | Ok(()) 130 | } 131 | None => Err(eio!("Cannot write beyond end of slice-iterator")), 132 | } 133 | } 134 | } 135 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 136 | impl Sink for Vec { 137 | fn write(&mut self, e: u8) -> Result<(), Asn1DerError> { 138 | self.push(e); 139 | Ok(()) 140 | } 141 | } 142 | 143 | /// A sink that counts the amount of elements written 144 | /// 145 | /// __Warning: if a call to `write` would cause `ctr` to exceed `usize::max_value()`, this sink 146 | /// will return an error but the element is written nonetheless__ 147 | pub struct CountingSink<'a, S: Sink> { 148 | sink: S, 149 | ctr: &'a mut usize, 150 | } 151 | impl<'a, S: Sink> Sink for CountingSink<'a, S> { 152 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 153 | fn write(&mut self, e: u8) -> Result<(), Asn1DerError> { 154 | self.sink.write(e).propagate(e!("Failed to write element to underlying source"))?; 155 | *self.ctr = match self.ctr.checked_add(1) { 156 | Some(ctr_next) => ctr_next, 157 | None => Err(eio!("Cannot write more because the position counter would overflow"))?, 158 | }; 159 | Ok(()) 160 | } 161 | } 162 | 163 | /// A slice-backed sink 164 | pub struct SliceSink<'a> { 165 | slice: &'a mut [u8], 166 | pos: &'a mut usize, 167 | } 168 | impl<'a> SliceSink<'a> { 169 | /// Creates a new `SliceSink` with `slice` as backing and `pos` as position counter 170 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 171 | pub fn new(slice: &'a mut [u8], pos: &'a mut usize) -> Self { 172 | Self { slice, pos } 173 | } 174 | } 175 | impl<'a> Sink for SliceSink<'a> { 176 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 177 | fn write(&mut self, e: u8) -> Result<(), Asn1DerError> { 178 | match self.slice.get_mut(*self.pos) { 179 | Some(t) => match self.pos.checked_add(1) { 180 | Some(pos_next) => { 181 | *self.pos = pos_next; 182 | *t = e; 183 | Ok(()) 184 | } 185 | None => Err(eio!("Cannot write more because the position counter would overflow"))?, 186 | }, 187 | None => Err(eio!("Cannot write beyond the end-of-slice"))?, 188 | } 189 | } 190 | } 191 | impl<'a> From> for &'a [u8] { 192 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 193 | fn from(sink: SliceSink<'a>) -> Self { 194 | match sink.slice.len() { 195 | len if *sink.pos < len => &sink.slice[..*sink.pos], 196 | _ => sink.slice, 197 | } 198 | } 199 | } 200 | 201 | /// A newtype wrapper around a `&'a mut Vec` that implements `Sink` and `Into<&'a [u8]>` 202 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 203 | pub struct VecBacking<'a>(pub &'a mut Vec); 204 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 205 | impl<'a> Sink for VecBacking<'a> { 206 | fn write(&mut self, e: u8) -> Result<(), Asn1DerError> { 207 | self.0.push(e); 208 | Ok(()) 209 | } 210 | } 211 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 212 | impl<'a> From> for &'a [u8] { 213 | fn from(backing: VecBacking<'a>) -> &'a [u8] { 214 | backing.0.as_slice() 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/der.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::ErrorChain, Asn1DerError, Sink, Source}; 2 | 3 | /// A mod for ASN.1-length-coding 4 | pub mod length { 5 | use crate::{error::ErrorChain, Asn1DerError, Sink, Source}; 6 | use core::mem; 7 | 8 | /// The byte length of an `usize` 9 | const SIZE: usize = mem::size_of::(); 10 | 11 | /// Tries to read the length or returns `None` if there are not enough bytes 12 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 13 | #[cfg_attr(feature = "no_panic", inline(always))] 14 | pub fn decode(source: &mut S) -> Result, Asn1DerError> { 15 | // Read first byte 16 | let first = match source.read() { 17 | Ok(first) => first, 18 | Err(_) => return Ok(None), 19 | }; 20 | 21 | // Check if we have a simple or a complex length 22 | match first as usize { 23 | len if len < 0b1000_0000 => Ok(Some(len)), 24 | size if size & 0b0111_1111 > SIZE => { 25 | Err(eunsupported!("The object length is greater than `usize::max_value()`")) 26 | } 27 | size => { 28 | // Prepare buffer 29 | let skip = SIZE - (size & 0b0111_1111); 30 | let mut buf = [0u8; SIZE]; 31 | 32 | // Read the complex length bytes or return `None` in case the length is truncated 33 | for target in buf.iter_mut().skip(skip) { 34 | *target = match source.read() { 35 | Ok(next) => next, 36 | Err(_) => return Ok(None), 37 | }; 38 | } 39 | 40 | // Validate the length 41 | match usize::from_be_bytes(buf) { 42 | len if len < 0b1000_0000 => Err(einval!("Encountered complex length < 128"))?, 43 | len => Ok(Some(len)), 44 | } 45 | } 46 | } 47 | } 48 | 49 | /// Encodes `len` to `sink` and returns the amount of 50 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 51 | #[cfg_attr(feature = "no_panic", inline(always))] 52 | pub fn encode(len: usize, sink: &mut S) -> Result<(), Asn1DerError> { 53 | match len { 54 | // Write simple length 55 | len if len < 128 => sink.write(len as u8).propagate(e!("Failed to write length byte")), 56 | // Encode complex length 57 | len => { 58 | // Compute the size of the length (without the complex length header byte) 59 | // #implicit_validation: Since the amount of leading zero bytes cannot be larger 60 | // than the amount of bytes in an `usize` (aka `SIZE`), both subtractions cannot 61 | // overflow so a `saturating_sub` is safe 62 | let size = SIZE.saturating_sub(len.leading_zeros() as usize / 8); 63 | let skip = SIZE.saturating_sub(size); 64 | 65 | // Write the length 66 | sink.write(0x80 | size as u8).propagate(e!("Failed to write length byte"))?; 67 | len.to_be_bytes() 68 | .iter() 69 | .skip(skip) 70 | .try_for_each(|b| sink.write(*b).propagate(e!("Failed to write length byte"))) 71 | } 72 | } 73 | } 74 | } 75 | 76 | /// An untyped DER object 77 | #[derive(Copy, Clone)] 78 | pub struct DerObject<'a> { 79 | raw: &'a [u8], 80 | header: &'a [u8], 81 | tag: u8, 82 | value: &'a [u8], 83 | } 84 | impl<'a> DerObject<'a> { 85 | /// Writes a new DER object with `tag` and `value` into `sink` and returns a view over it 86 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 87 | pub fn new>(tag: u8, value: &[u8], sink: S) -> Result { 88 | Self::new_from_source(tag, value.len(), &mut value.iter(), sink) 89 | } 90 | /// Writes a new DER object with `tag`, `len` and `value` into `sink` and returns a view over it 91 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 92 | pub fn new_from_source>( 93 | tag: u8, 94 | len: usize, 95 | value: &mut A, 96 | mut sink: B, 97 | ) -> Result { 98 | Self::write(tag, len, value, &mut sink).propagate(e!("Failed to construct boolean"))?; 99 | DerObject::decode(sink.into()).propagate(e!("Failed to load constructed object")) 100 | } 101 | 102 | /// Decodes an object from `raw` 103 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 104 | pub fn decode(raw: &'a [u8]) -> Result { 105 | Self::decode_at(raw, 0) 106 | } 107 | /// Decodes an object from `&raw[header_start..]` 108 | #[doc(hidden)] 109 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 110 | pub fn decode_at(raw: &'a [u8], header_start: usize) -> Result { 111 | // Create iterator 112 | let mut value_start = header_start; 113 | let mut iter = raw.iter().skip(header_start).counting_source(&mut value_start); 114 | 115 | // Skip tag and read length 116 | let tag = iter.read().propagate(e!("Failed to read tag"))?; 117 | let len = 118 | length::decode(&mut iter).propagate(e!("Failed to decode length"))?.ok_or(eio!("Truncated length"))?; 119 | let value_end = match value_start.checked_add(len) { 120 | Some(value_end) => value_end, 121 | None => Err(eunsupported!("The object bounds would exceed `usize::max_value()`"))?, 122 | }; 123 | 124 | // Get the header slice 125 | let header = match raw.len() { 126 | len if len >= value_start => &raw[..value_start], 127 | _ => Err(eio!("The object is truncated"))?, 128 | }; 129 | let header = match header.len() { 130 | len if len >= header_start => &header[header_start..], 131 | _ => Err(eio!("The object is truncated"))?, 132 | }; 133 | 134 | // Get the value slice 135 | let value = match raw.len() { 136 | len if len >= value_end => &raw[..value_end], 137 | _ => Err(eio!("The object is truncated"))?, 138 | }; 139 | let value = match value.len() { 140 | len if len >= value_start => &value[value_start..], 141 | _ => Err(eio!("The object is truncated"))?, 142 | }; 143 | 144 | Ok(Self { raw, header, tag, value }) 145 | } 146 | /// Reads a DER-TLV structure from `source` by parsing the length field and copying the 147 | /// necessary bytes into `sink` and returns a view over it 148 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 149 | pub fn decode_from_source>( 150 | source: &mut A, 151 | mut sink: B, 152 | ) -> Result { 153 | // Create a copying iterator and copy the tag 154 | let mut source = source.copying_source(&mut sink); 155 | source.copy_next().propagate(e!("Failed to read tag"))?; 156 | 157 | // Read the length and copy the value 158 | let len = 159 | length::decode(&mut source).propagate(e!("Failed to decode length"))?.ok_or(eio!("Truncated length"))?; 160 | source.copy_n(len).propagate(e!("Failed to copy object value"))?; 161 | 162 | // Load the object 163 | Self::decode(sink.into()).propagate(e!("Failed to decode object")) 164 | } 165 | 166 | /// The underlying raw slice 167 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 168 | pub fn raw(self) -> &'a [u8] { 169 | self.raw 170 | } 171 | /// The object header 172 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 173 | pub fn header(self) -> &'a [u8] { 174 | self.header 175 | } 176 | /// The object tag 177 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 178 | pub fn tag(self) -> u8 { 179 | self.tag 180 | } 181 | /// The object value 182 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 183 | pub fn value(self) -> &'a [u8] { 184 | self.value 185 | } 186 | 187 | /// Encodes `self` to `sink` 188 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 189 | pub fn encode(&self, sink: &mut U) -> Result<(), Asn1DerError> { 190 | Self::write(self.tag, self.value.len(), &mut self.value.iter(), sink) 191 | .propagate(e!("Failed to write DER object")) 192 | } 193 | 194 | /// Writes a `tag`-`len`-`value` combination as DER-TLV structure into `sink` 195 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 196 | pub fn write(tag: u8, len: usize, value: &mut A, sink: &mut B) -> Result<(), Asn1DerError> { 197 | sink.write(tag).propagate(e!("Failed to write tag"))?; 198 | length::encode(len, sink).propagate(e!("Failed to write length"))?; 199 | value.copying_source(sink).copy_n(len).propagate(e!("Failed to write value")) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | 3 | #[cfg(feature = "std")] 4 | use std::error::Error; 5 | 6 | /// Creates a static error description with file and line information 7 | #[doc(hidden)] 8 | #[macro_export] 9 | macro_rules! e { 10 | () => { 11 | concat!("@", file!(), ":", line!()) 12 | }; 13 | ($str:expr) => { 14 | concat!($str, " @", file!(), ":", line!()) 15 | }; 16 | } 17 | 18 | /// Creates an `InOutError` variant 19 | #[doc(hidden)] 20 | #[macro_export] 21 | macro_rules! eio { 22 | ($str:expr) => { 23 | $crate::error::Asn1DerError::new($crate::error::Asn1DerErrorVariant::InOutError(e!($str))) 24 | }; 25 | } 26 | /// Creates an `InvalidData` variant 27 | #[doc(hidden)] 28 | #[macro_export] 29 | macro_rules! einval { 30 | ($str:expr) => { 31 | $crate::error::Asn1DerError::new($crate::error::Asn1DerErrorVariant::InvalidData(e!($str))) 32 | }; 33 | } 34 | /// Creates an `Unsupported` variant 35 | #[doc(hidden)] 36 | #[macro_export] 37 | macro_rules! eunsupported { 38 | ($str:expr) => { 39 | $crate::error::Asn1DerError::new($crate::error::Asn1DerErrorVariant::Unsupported(e!($str))) 40 | }; 41 | } 42 | /// Creates an `Other` variant 43 | #[doc(hidden)] 44 | #[macro_export] 45 | macro_rules! eother { 46 | ($str:expr) => { 47 | $crate::error::Asn1DerError::new($crate::error::Asn1DerErrorVariant::Other(e!($str))) 48 | }; 49 | } 50 | 51 | /// A trait to chain errors 52 | pub trait ErrorChain { 53 | /// Chains another error to `self` 54 | /// 55 | /// _Info: does nothing if not build with `std`_ 56 | fn propagate(self, desc: &'static str) -> Self; 57 | } 58 | impl ErrorChain for Result { 59 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 60 | fn propagate(self, _desc: &'static str) -> Self { 61 | #[cfg(any(not(feature = "std"), feature = "no_panic"))] 62 | return self; 63 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 64 | { 65 | self.map_err(|e| { 66 | let new_error = match e.error { 67 | Asn1DerErrorVariant::InOutError(_) => Asn1DerErrorVariant::InOutError(_desc), 68 | Asn1DerErrorVariant::InvalidData(_) => Asn1DerErrorVariant::InvalidData(_desc), 69 | Asn1DerErrorVariant::Unsupported(_) => Asn1DerErrorVariant::Unsupported(_desc), 70 | Asn1DerErrorVariant::Other(_) => Asn1DerErrorVariant::Other(_desc), 71 | }; 72 | Asn1DerError { error: new_error, source: Some(ErrorSource::new(e)) } 73 | }) 74 | } 75 | } 76 | } 77 | 78 | /// An `Asn1DerError` variant 79 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 80 | pub enum Asn1DerErrorVariant { 81 | /// An in-out error occurred (e.g. failed to read/write some bytes) 82 | InOutError(&'static str), 83 | /// The data has an invalid encoding 84 | InvalidData(&'static str), 85 | /// The object type or length is not supported by this implementation 86 | Unsupported(&'static str), 87 | /// An unspecified error 88 | Other(&'static str), 89 | } 90 | impl Display for Asn1DerErrorVariant { 91 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 92 | match self { 93 | Asn1DerErrorVariant::InOutError(desc) => write!(f, "I/O error {}", desc), 94 | Asn1DerErrorVariant::InvalidData(desc) => write!(f, "Invalid encoding {}", desc), 95 | Asn1DerErrorVariant::Unsupported(desc) => write!(f, "Unsupported {}", desc), 96 | Asn1DerErrorVariant::Other(desc) => write!(f, "Other {}", desc), 97 | } 98 | } 99 | } 100 | 101 | /// An error source 102 | #[doc(hidden)] 103 | #[derive(Debug, Clone, Eq, PartialEq)] 104 | pub struct ErrorSource { 105 | #[cfg(any(not(feature = "std"), feature = "no_panic"))] 106 | inner: &'static str, 107 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 108 | inner: Box, 109 | } 110 | impl ErrorSource { 111 | /// Creates a new error source 112 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 113 | pub fn new(e: Asn1DerError) -> Self { 114 | Self { inner: Box::new(e) } 115 | } 116 | } 117 | 118 | /// An `asn1_der` error 119 | #[derive(Debug, Clone, Eq, PartialEq)] 120 | pub struct Asn1DerError { 121 | #[doc(hidden)] 122 | pub error: Asn1DerErrorVariant, 123 | #[doc(hidden)] 124 | pub source: Option, 125 | } 126 | impl Asn1DerError { 127 | /// Creates a new error with `variant` 128 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 129 | pub fn new(variant: Asn1DerErrorVariant) -> Self { 130 | Self { error: variant, source: None } 131 | } 132 | } 133 | impl Display for Asn1DerError { 134 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 135 | match self.source.as_ref() { 136 | Some(source) => write!(f, "{}\n caused by: {}", self.error, source.inner), 137 | None => write!(f, "{}", self.error), 138 | } 139 | } 140 | } 141 | #[cfg(feature = "std")] 142 | impl Error for Asn1DerError { 143 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 144 | fn source(&self) -> Option<&(dyn Error + 'static)> { 145 | #[cfg(any(not(feature = "std"), feature = "no_panic"))] 146 | return None; 147 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 148 | return self.source.as_ref().map(|s| s.inner.as_ref() as _); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | // Handle no_std if set 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | 5 | #[macro_use] 6 | #[doc(hidden)] 7 | pub mod error; 8 | mod data; 9 | #[doc(hidden)] 10 | pub mod der; 11 | #[cfg(feature = "native_types")] 12 | pub mod typed; 13 | 14 | // Reexport common types 15 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 16 | pub use crate::data::VecBacking; 17 | pub use crate::{ 18 | data::{CopyingSource, CountingSource, Sink, SliceSink, Source}, 19 | der::DerObject, 20 | error::{Asn1DerError, Asn1DerErrorVariant, ErrorChain}, 21 | }; 22 | -------------------------------------------------------------------------------- /src/typed/boolean.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ErrorChain, 3 | typed::{DerDecodable, DerEncodable, DerTypeView}, 4 | Asn1DerError, DerObject, Sink, 5 | }; 6 | 7 | /// An ASN.1-DER boolean type view 8 | #[derive(Copy, Clone)] 9 | pub struct Boolean<'a> { 10 | object: DerObject<'a>, 11 | } 12 | impl<'a> Boolean<'a> { 13 | /// Writes a new boolean object with `value` into `sink` and returns a type view over it 14 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 15 | pub fn new>(value: bool, mut sink: S) -> Result { 16 | Self::write(value, &mut sink).propagate(e!("Failed to construct boolean"))?; 17 | let object = DerObject::decode(sink.into()).propagate(e!("Failed to load constructed boolean"))?; 18 | Ok(Self { object }) 19 | } 20 | /// Gets the boolean value 21 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 22 | pub fn get(&self) -> bool { 23 | match self.object.value() { 24 | b"\x00" => false, 25 | // #implicit_validation: Since we validate this value at `load`, the only possible value 26 | // here is `b"\xff"` unless the underlying object has been modified in an invalid way 27 | _ => true, 28 | } 29 | } 30 | 31 | /// Writes a boolean `value` as DER-object to `sink` 32 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 33 | pub fn write(value: bool, sink: &mut S) -> Result<(), Asn1DerError> { 34 | let value = match value { 35 | true => b"\xff".as_ref(), 36 | false => b"\x00".as_ref(), 37 | }; 38 | DerObject::write(Self::TAG, value.len(), &mut value.iter(), sink).propagate(e!("Failed to write boolean")) 39 | } 40 | } 41 | impl<'a> DerTypeView<'a> for Boolean<'a> { 42 | const TAG: u8 = b'\x01'; 43 | 44 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 45 | fn object(&self) -> DerObject<'a> { 46 | self.object 47 | } 48 | } 49 | impl<'a> DerDecodable<'a> for Boolean<'a> { 50 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 51 | fn load(object: DerObject<'a>) -> Result { 52 | match object.value() { 53 | _ if object.tag() != Self::TAG => Err(einval!("DER object is not a boolean"))?, 54 | b"\x00" | b"\xff" => Ok(Self { object }), 55 | _ => Err(einval!("DER object is not a valid boolean")), 56 | } 57 | } 58 | } 59 | impl<'a> DerEncodable for Boolean<'a> { 60 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 61 | fn encode(&self, sink: &mut U) -> Result<(), Asn1DerError> { 62 | self.object().encode(sink).propagate(e!("Failed to encode boolean")) 63 | } 64 | } 65 | 66 | impl<'a> DerDecodable<'a> for bool { 67 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 68 | fn load(object: DerObject<'a>) -> Result { 69 | let boolean = Boolean::load(object).propagate(e!("Failed to load boolean"))?; 70 | Ok(boolean.get()) 71 | } 72 | } 73 | impl DerEncodable for bool { 74 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 75 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 76 | Boolean::write(*self, sink).propagate(e!("Failed to encode boolean")) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/typed/integer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ErrorChain, 3 | typed::{DerDecodable, DerEncodable, DerTypeView}, 4 | Asn1DerError, DerObject, Sink, 5 | }; 6 | use core::mem; 7 | 8 | /// An ASN.1-DER integer view 9 | #[derive(Copy, Clone)] 10 | pub struct Integer<'a> { 11 | object: DerObject<'a>, 12 | } 13 | impl<'a> Integer<'a> { 14 | /// Writes a new integer object with the big-endian encoded `be_value` into `sink` and returns a 15 | /// type view over it 16 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 17 | pub fn new>( 18 | be_value: &[u8], 19 | is_negative: bool, 20 | mut sink: S, 21 | ) -> Result { 22 | Self::write(be_value, is_negative, &mut sink).propagate(e!("Failed to construct integer"))?; 23 | let object = DerObject::decode(sink.into()).propagate(e!("Failed to load constructed integer"))?; 24 | Ok(Self { object }) 25 | } 26 | 27 | /// Returns if the number is negative or not 28 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 29 | pub fn is_negative(&self) -> bool { 30 | match self.object.value().first() { 31 | Some(first) => first & 0b1000_0000 != 0, 32 | // #implicit_validation: Since we validate the length at `load`, there is always a first 33 | // element which in turn does not begin with a leading `1`-bit unless the underlying 34 | // object has been modified in an invalid way 35 | None => false, 36 | } 37 | } 38 | /// Get the number bytes 39 | /// 40 | /// __Important: Any leading zero-byte that might indicate a positive number is stripped off. 41 | /// This means that a return value of `0b1111_1111` can be either `255` or `-1` depending on 42 | /// whether the number is negative or not. Use `is_negative` to determine the correct sign.__ 43 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 44 | pub fn get_numbytes(&self) -> &[u8] { 45 | let slice = self.object.value(); 46 | match slice.len() { 47 | len if len >= 1 && slice[0] == 0 => &slice[1..], 48 | _ => slice, 49 | } 50 | } 51 | 52 | /// Copies the num bytes into `buf` if they can fit 53 | /// 54 | /// __Important: Any leading zero-byte that might indicate a positive number is stripped off. 55 | /// This means that a return value of `0b1111_1111` can be either `255` or `-1` depending on 56 | /// whether the number is negative or not. Use `is_negative` to determine the correct sign.__ 57 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 58 | pub fn copy_numbytes>(&self, mut buf: T) -> Result { 59 | // Ensure that the number fits 60 | let (slice, buf_slice) = (self.get_numbytes(), buf.as_mut()); 61 | let to_skip = match slice.len() { 62 | len if len > buf_slice.len() => Err(eunsupported!("The numeric value is too large"))?, 63 | len => buf_slice.len() - len, 64 | }; 65 | 66 | // Copy the number and ensure that it can be represented 67 | buf_slice.iter_mut().skip(to_skip).zip(slice.iter()).for_each(|(t, b)| *t = *b); 68 | Ok(buf) 69 | } 70 | /// Writes an integer `value` as DER-object to `sink` 71 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 72 | #[inline(always)] 73 | pub fn write(value: &[u8], is_negative: bool, sink: &mut S) -> Result<(), Asn1DerError> { 74 | // Determine the amount leading zero bytes to skip 75 | let to_skip = value.iter().take_while(|b| **b == 0).count(); 76 | let len = value.iter().skip(to_skip).count(); 77 | 78 | // Construct a leading zero byte prefix if necessary 79 | let value_prefix = match value.get(to_skip) { 80 | None => b"\x00".as_ref(), 81 | Some(first) if first & 0b1000_0000 != 0 && !is_negative => b"\x00".as_ref(), 82 | _ => b"".as_ref(), 83 | }; 84 | let len = match len.checked_add(value_prefix.len()) { 85 | Some(len) => len, 86 | None => Err(eunsupported!("The number length would exceed `usize::max_value()`"))?, 87 | }; 88 | 89 | // Encode integer 90 | let mut value = value_prefix.iter().chain(value.iter().skip(to_skip)); 91 | DerObject::write(Self::TAG, len, &mut value, sink).propagate(e!("Failed to write integer")) 92 | } 93 | } 94 | impl<'a> DerTypeView<'a> for Integer<'a> { 95 | const TAG: u8 = b'\x02'; 96 | 97 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 98 | fn object(&self) -> DerObject<'a> { 99 | self.object 100 | } 101 | } 102 | impl<'a> DerDecodable<'a> for Integer<'a> { 103 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 104 | fn load(object: DerObject<'a>) -> Result { 105 | match object.value() { 106 | _ if object.tag() != Self::TAG => Err(einval!("DER object is not an integer"))?, 107 | value if value.is_empty() => Err(einval!("DER object is not a valid integer"))?, 108 | value if value.len() >= 2 && value[0] == b'\x00' && value[1] & 0b1000_0000 == 0 => { 109 | Err(einval!("DER object is not a valid integer")) 110 | } 111 | value if value.len() >= 2 && value[0] == b'\xff' && value[1] & 0b1000_0000 != 0 => { 112 | Err(einval!("DER object is not a valid integer")) 113 | } 114 | _ => Ok(Self { object }), 115 | } 116 | } 117 | } 118 | impl<'a> DerEncodable for Integer<'a> { 119 | /// Encodes `self` to `sink` 120 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 121 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 122 | self.object().encode(sink).propagate(e!("Failed to encode integer")) 123 | } 124 | } 125 | 126 | /// Implements `DerCodable` 127 | macro_rules! impl_dercodable { 128 | (unsigned: $num:ty) => { 129 | impl<'a> DerDecodable<'a> for $num { 130 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 131 | fn load(object: DerObject<'a>) -> Result { 132 | // Load integer 133 | let integer = Integer::load(object).propagate(e!("Failed to load integer"))?; 134 | let buf = integer.copy_numbytes([0; mem::size_of::()]) 135 | .propagate(e!("The numeric value is too large"))?; 136 | 137 | // Validate the integer 138 | match Self::from_be_bytes(buf) { 139 | _ if integer.is_negative() => 140 | Err(eunsupported!("The numeric value is negative")), 141 | num => Ok(num) 142 | } 143 | } 144 | } 145 | impl DerEncodable for $num { 146 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 147 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 148 | Integer::write(&self.to_be_bytes(), false, sink) 149 | } 150 | } 151 | }; 152 | (unsigned: $($num:ty),+) => ($( impl_dercodable!(unsigned: $num); )+); 153 | } 154 | impl_dercodable!(unsigned: u8, u16, u32, u64, u128, usize); 155 | -------------------------------------------------------------------------------- /src/typed/mod.rs: -------------------------------------------------------------------------------- 1 | //! Some traits to de-/encode DER objects via type-specific zero-copy views as well as direct 2 | //! de-/encode implementations for some native Rust types 3 | 4 | mod boolean; 5 | mod integer; 6 | mod null; 7 | mod octet_string; 8 | mod sequence; 9 | mod utf8_string; 10 | 11 | pub use crate::typed::{ 12 | boolean::Boolean, integer::Integer, null::Null, octet_string::OctetString, sequence::Sequence, 13 | utf8_string::Utf8String, 14 | }; 15 | use crate::{error::ErrorChain, Asn1DerError, DerObject, Sink, Source}; 16 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 17 | pub use sequence::SequenceVec; 18 | 19 | /// A trait for DER type views 20 | pub trait DerTypeView<'a>: Sized { 21 | /// The tag for this type 22 | const TAG: u8; 23 | /// Provides raw access to the underlying `DerObject` 24 | fn object(&self) -> DerObject<'a>; 25 | } 26 | 27 | /// A trait for DER decodable types 28 | pub trait DerDecodable<'a>: Sized { 29 | /// Loads `object` as `Self` 30 | fn load(object: DerObject<'a>) -> Result; 31 | /// Decodes an object as `Self` 32 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 33 | fn decode(raw: &'a [u8]) -> Result { 34 | Self::decode_at(raw, 0) 35 | } 36 | /// Decodes an object as `Self` 37 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 38 | fn decode_at(raw: &'a [u8], header_start: usize) -> Result { 39 | let object = DerObject::decode_at(raw, header_start).propagate(e!("Failed to decode object"))?; 40 | Self::load(object).propagate(e!("Failed to load object")) 41 | } 42 | /// Reads an object from `source` by parsing the length field and copying the necessary bytes 43 | /// into `sink` and decoding it from `sink` 44 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 45 | fn decode_from_source>(source: &mut A, sink: B) -> Result { 46 | let object = DerObject::decode_from_source(source, sink).propagate(e!("Failed to decode object"))?; 47 | Self::load(object).propagate(e!("Failed to load object")) 48 | } 49 | } 50 | impl<'a> DerDecodable<'a> for DerObject<'a> { 51 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 52 | fn load(object: DerObject<'a>) -> Result { 53 | Ok(object) 54 | } 55 | } 56 | 57 | /// A trait for DER encodable types 58 | pub trait DerEncodable: Sized { 59 | /// Encodes `self` into `sink` 60 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError>; 61 | 62 | /// Creates an DER object from an encodable type 63 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 64 | fn der_object<'a, S: Sink + Into<&'a [u8]>>(&self, mut sink: S) -> Result, Asn1DerError> { 65 | self.encode(&mut sink).propagate(e!("Failed to encode object"))?; 66 | DerObject::decode(sink.into()).propagate("Failed to load constructed object") 67 | } 68 | } 69 | impl<'a> DerEncodable for DerObject<'a> { 70 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 71 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 72 | self.encode(sink).propagate(e!("Failed to encode object")) 73 | } 74 | } 75 | impl DerEncodable for &T { 76 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 77 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 78 | (*self).encode(sink) 79 | } 80 | } 81 | impl DerEncodable for &mut T { 82 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 83 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 84 | (*self as &T).encode(sink) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/typed/null.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ErrorChain, 3 | typed::{DerDecodable, DerEncodable, DerTypeView}, 4 | Asn1DerError, DerObject, Sink, 5 | }; 6 | 7 | /// An ASN.1-DER null object view 8 | #[derive(Copy, Clone)] 9 | pub struct Null<'a> { 10 | object: DerObject<'a>, 11 | } 12 | impl<'a> Null<'a> { 13 | /// Writes a new null object into `sink` and returns a type view over it 14 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 15 | pub fn new>(mut sink: S) -> Result { 16 | Self::write(&mut sink).propagate(e!("Failed to construct null object"))?; 17 | let object = DerObject::decode(sink.into()).propagate(e!("Failed to load constructed null object"))?; 18 | Ok(Self { object }) 19 | } 20 | 21 | /// Writes a boolean `value` as DER-object to `sink` 22 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 23 | pub fn write(sink: &mut S) -> Result<(), Asn1DerError> { 24 | DerObject::write(Self::TAG, 0, &mut b"".iter(), sink).propagate(e!("Failed to write null object")) 25 | } 26 | } 27 | impl<'a> DerTypeView<'a> for Null<'a> { 28 | const TAG: u8 = b'\x05'; 29 | 30 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 31 | fn object(&self) -> DerObject<'a> { 32 | self.object 33 | } 34 | } 35 | impl<'a> DerDecodable<'a> for Null<'a> { 36 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 37 | fn load(object: DerObject<'a>) -> Result { 38 | match object.value() { 39 | _ if object.tag() != Self::TAG => Err(einval!("DER object is not a null object"))?, 40 | b"" => Ok(Self { object }), 41 | _ => Err(einval!("DER object is not a valid null object")), 42 | } 43 | } 44 | } 45 | impl<'a> DerEncodable for Null<'a> { 46 | /// Encodes `self` to `sink` 47 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 48 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 49 | self.object().encode(sink).propagate(e!("Failed to encode null object")) 50 | } 51 | } 52 | 53 | impl<'a> DerDecodable<'a> for () { 54 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 55 | fn load(object: DerObject<'a>) -> Result { 56 | Null::load(object).propagate(e!("Failed to load null object"))?; 57 | Ok(()) 58 | } 59 | } 60 | impl DerEncodable for () { 61 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 62 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 63 | Null::write(sink).propagate(e!("Failed to encode null object")) 64 | } 65 | } 66 | 67 | impl<'a, T: DerDecodable<'a>> DerDecodable<'a> for Option { 68 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 69 | fn load(object: DerObject<'a>) -> Result { 70 | match object.tag() { 71 | Null::TAG => { 72 | Null::load(object).propagate(e!("Failed to load null object"))?; 73 | Ok(None) 74 | } 75 | _ => { 76 | let object = T::load(object).propagate(e!("Failed to load object"))?; 77 | Ok(Some(object)) 78 | } 79 | } 80 | } 81 | } 82 | impl DerEncodable for Option { 83 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 84 | #[cfg_attr(feature = "no_panic", inline(always))] 85 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 86 | match self { 87 | Some(object) => object.encode(sink).propagate(e!("Failed to encode object")), 88 | None => Null::write(sink).propagate(e!("Failed to encode null object")), 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/typed/octet_string.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ErrorChain, 3 | typed::{DerDecodable, DerEncodable, DerTypeView}, 4 | Asn1DerError, DerObject, Sink, 5 | }; 6 | 7 | /// An ASN.1-DER octet string view 8 | #[derive(Copy, Clone)] 9 | pub struct OctetString<'a> { 10 | object: DerObject<'a>, 11 | } 12 | impl<'a> OctetString<'a> { 13 | /// Writes a new octet string object with `value` into `sink` and returns a type view over it 14 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 15 | pub fn new>(value: &[u8], mut sink: S) -> Result { 16 | Self::write(value, &mut sink).propagate(e!("Failed to construct octet string"))?; 17 | let object = DerObject::decode(sink.into()).propagate(e!("Failed to load constructed octet string"))?; 18 | Ok(Self { object }) 19 | } 20 | /// Gets the octet string value 21 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 22 | pub fn get(&self) -> &[u8] { 23 | self.object.value() 24 | } 25 | 26 | /// Writes an octet string `value` as DER-object to `sink` 27 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 28 | pub fn write(value: &[u8], sink: &mut S) -> Result<(), Asn1DerError> { 29 | DerObject::write(Self::TAG, value.len(), &mut value.iter(), sink).propagate(e!("Failed to write octet string")) 30 | } 31 | } 32 | impl<'a> DerTypeView<'a> for OctetString<'a> { 33 | const TAG: u8 = b'\x04'; 34 | 35 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 36 | fn object(&self) -> DerObject<'a> { 37 | self.object 38 | } 39 | } 40 | impl<'a> DerDecodable<'a> for OctetString<'a> { 41 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 42 | fn load(object: DerObject<'a>) -> Result { 43 | match object.value() { 44 | _ if object.tag() != Self::TAG => Err(einval!("DER object is not an octet string"))?, 45 | _ => Ok(Self { object }), 46 | } 47 | } 48 | } 49 | impl<'a> DerEncodable for OctetString<'a> { 50 | /// Encodes `self` to `sink` 51 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 52 | fn encode(&self, sink: &mut U) -> Result<(), Asn1DerError> { 53 | self.object().encode(sink).propagate(e!("Failed to encode octet string")) 54 | } 55 | } 56 | 57 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 58 | impl<'a> DerDecodable<'a> for Vec { 59 | fn load(object: DerObject<'a>) -> Result { 60 | let octet_string = OctetString::load(object).propagate(e!("Failed to load octet string"))?; 61 | Ok(octet_string.get().to_vec()) 62 | } 63 | } 64 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 65 | impl DerEncodable for Vec { 66 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 67 | OctetString::write(self, sink).propagate(e!("Failed to encode octet string")) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/typed/sequence.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | der, 3 | error::ErrorChain, 4 | typed::{DerDecodable, DerEncodable, DerTypeView}, 5 | Asn1DerError, DerObject, Sink, 6 | }; 7 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 8 | use core::ops::{Deref, DerefMut}; 9 | 10 | /// A counting sink that swallows each element and increments a counter 11 | struct CountingSink(pub usize); 12 | impl Sink for CountingSink { 13 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 14 | fn write(&mut self, _e: u8) -> Result<(), Asn1DerError> { 15 | match self.0.checked_add(1) { 16 | Some(next) => { 17 | self.0 = next; 18 | Ok(()) 19 | } 20 | None => Err(eunsupported!("Cannot write more than `usize::max_value()` bytes")), 21 | } 22 | } 23 | } 24 | 25 | /// An ASN.1-DER sequence view 26 | #[derive(Copy, Clone)] 27 | pub struct Sequence<'a> { 28 | object: DerObject<'a>, 29 | } 30 | impl<'a> Sequence<'a> { 31 | /// Writes a new sequence object with `objs` as subobjects into `sink` and returns a type view 32 | /// over it 33 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 34 | pub fn new, T: DerEncodable>(objs: &[T], mut sink: S) -> Result { 35 | Self::write(objs, &mut sink).propagate(e!("Failed to construct sequence"))?; 36 | let object = DerObject::decode(sink.into()).propagate(e!("Failed to load constructed sequence"))?; 37 | Ok(Self { object }) 38 | } 39 | 40 | /// The amount of subelements in the sequence 41 | /// 42 | /// _Note: since there is no underlying index, the amount of subelements has to be recomputed 43 | /// every time. If you need the length more than once, consider caching it._ 44 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 45 | #[allow(clippy::len_without_is_empty)] 46 | pub fn len(&self) -> usize { 47 | let (mut pos, mut ctr) = (0usize, 0usize); 48 | // #implicit_validation: Since we validate the subelements at `load`, end-of-elements is 49 | // the only possible error here unless the underlying object has been modified in an 50 | // invalid way 51 | while self.subobject_at(&mut pos).is_ok() { 52 | // #implicit_validation: The counter can never overflow an usize because this would 53 | // imply object lengths < 1 54 | ctr = ctr.saturating_add(1); 55 | } 56 | ctr 57 | } 58 | /// Gets the `n`th subobject 59 | /// 60 | /// _Note: since there is no underlying index, the position of each subelement has to be 61 | /// recomputed every time. If you need the subobjects more than once, consider caching them._ 62 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 63 | pub fn get(&self, n: usize) -> Result, Asn1DerError> { 64 | let mut pos = 0; 65 | for _ in 0..n { 66 | // #implicit_validation: Since we validate the subelements at `load`, end-of-elements is 67 | // the only possible error here unless the underlying object has been modified in an 68 | // invalid way 69 | self.subobject_at(&mut pos).propagate(e!("No subobject for given index"))?; 70 | } 71 | self.subobject_at(&mut pos).propagate(e!("No subobject for given index")) 72 | } 73 | /// Gets the `n`th subobject as `T` 74 | /// 75 | /// _Note: since there is no underlying index, the position of each subelement has to be 76 | /// recomputed every time. If you need the subobjects more than once, consider caching them._ 77 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 78 | pub fn get_as>(&self, n: usize) -> Result { 79 | let object = self.get(n).propagate(e!("No subobject for given index"))?; 80 | T::load(object).propagate(e!("Failed to load subobject")) 81 | } 82 | 83 | /// Gets the subobject at `pos` 84 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 85 | fn subobject_at(&self, pos: &mut usize) -> Result, Asn1DerError> { 86 | // Load object 87 | let sequence_value = self.object.value(); 88 | let object = DerObject::decode_at(sequence_value, *pos).propagate(e!("Failed to decode subobject"))?; 89 | let (object_header, object_value) = (object.header(), object.value()); 90 | 91 | // #implicit_validation: since both slices are subslices of the same slice, their lengths 92 | // can never exceed `usize::max_value()` 93 | let len = object_header.len().saturating_add(object_value.len()); 94 | match pos.checked_add(len) { 95 | Some(next_pos) => *pos = next_pos, 96 | None => Err(einval!("The new object cannot be as long as announced"))?, 97 | } 98 | Ok(object) 99 | } 100 | 101 | /// Writes a sequence consisting of `objs` as DER-object to `sink` 102 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 103 | pub fn write(objs: &[T], sink: &mut S) -> Result<(), Asn1DerError> { 104 | // Compute the total length 105 | let mut ctr = CountingSink(0); 106 | objs.iter().try_for_each(|o| o.encode(&mut ctr).propagate(e!("Failed to size subobject")))?; 107 | 108 | // Encode the object by hand 109 | sink.write(Self::TAG).propagate(e!("Failed to write tag"))?; 110 | der::length::encode(ctr.0, sink).propagate(e!("Failed to encode length"))?; 111 | objs.iter().try_for_each(|o| o.encode(sink).propagate(e!("Failed to encode subobject"))) 112 | } 113 | } 114 | impl<'a> DerTypeView<'a> for Sequence<'a> { 115 | const TAG: u8 = b'\x30'; 116 | 117 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 118 | fn object(&self) -> DerObject<'a> { 119 | self.object 120 | } 121 | } 122 | impl<'a> DerDecodable<'a> for Sequence<'a> { 123 | /// Loads the `Sequence` and performs a shallow validation that each underlying object is a 124 | /// valid DER object 125 | /// 126 | /// _Note: This function does not look "into" the underlying elements nor does it perform any 127 | /// type-specific validation – only the tag-length constructions are validated._ 128 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 129 | fn load(object: DerObject<'a>) -> Result { 130 | // Validate the tag 131 | let this = match object.tag() { 132 | Self::TAG => Self { object }, 133 | _ => Err(einval!("DER object is not a valid sequence"))?, 134 | }; 135 | 136 | // Validate the subobjects 137 | let (mut pos, total_len) = (0, this.object.value().len()); 138 | while pos < total_len { 139 | this.subobject_at(&mut pos).propagate(e!("Invalid subobject in sequence"))?; 140 | } 141 | Ok(this) 142 | } 143 | } 144 | impl<'a> DerEncodable for Sequence<'a> { 145 | /// Encodes `self` to `sink` 146 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 147 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 148 | self.object().encode(sink).propagate(e!("Failed to encode sequence")) 149 | } 150 | } 151 | 152 | /// A newtype wrapper around `Vec` to work with sequences in a `Vec`-like way 153 | /// 154 | /// _Note: We use a newtype wrapper here because Rust's generic type system does not allow 155 | /// specializations, so a direct implementation for `Vec` would conflict with other 156 | /// implementations; e.g. the octet string implementation for `Vec`_ 157 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 158 | pub struct SequenceVec(pub Vec); 159 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 160 | impl Deref for SequenceVec { 161 | type Target = [T]; 162 | fn deref(&self) -> &Self::Target { 163 | &self.0 164 | } 165 | } 166 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 167 | impl DerefMut for SequenceVec { 168 | fn deref_mut(&mut self) -> &mut Self::Target { 169 | &mut self.0 170 | } 171 | } 172 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 173 | impl<'a, T: DerDecodable<'a>> DerDecodable<'a> for SequenceVec { 174 | fn load(object: DerObject<'a>) -> Result { 175 | let sequence = Sequence::load(object).propagate(e!("Failed to load sequence"))?; 176 | let objects = (0..sequence.len()).try_fold(Vec::new(), |mut vec, i| { 177 | let subobject: T = sequence.get_as(i).propagate(e!("Failed to load subelement"))?; 178 | vec.push(subobject); 179 | Ok(vec) 180 | })?; 181 | Ok(Self(objects)) 182 | } 183 | } 184 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 185 | impl DerEncodable for SequenceVec { 186 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 187 | Sequence::write(self, sink).propagate(e!("Failed to write sequence")) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/typed/utf8_string.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::ErrorChain, 3 | typed::{DerDecodable, DerEncodable, DerTypeView}, 4 | Asn1DerError, DerObject, Sink, 5 | }; 6 | use core::str; 7 | 8 | /// An ASN.1-DER UTF-8 string view 9 | #[derive(Copy, Clone)] 10 | pub struct Utf8String<'a> { 11 | object: DerObject<'a>, 12 | } 13 | impl<'a> Utf8String<'a> { 14 | /// Writes a new UTF8String object with `value` into `sink` and returns a type view over it 15 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 16 | pub fn new>(value: &str, mut sink: S) -> Result { 17 | Self::write(value, &mut sink).propagate(e!("Failed to construct UTF-8 string"))?; 18 | let object = DerObject::decode(sink.into()).propagate(e!("Failed to load constructed UTF-8 string"))?; 19 | Ok(Self { object }) 20 | } 21 | /// Gets the UTF-8 string value 22 | // _#implicit validation_: no_panic does not work due to `str::from_utf8`; however we just have to assume that the 23 | // stdlib works correctly in this case 24 | pub fn get(&self) -> &str { 25 | let slice = self.object.value(); 26 | match str::from_utf8(slice) { 27 | Ok(string) => string, 28 | // #implicit_validation: Since we validate the string on `load`, this codepath is only 29 | // possible if the underlying object has been modified in an invalid way 30 | _ => "", 31 | } 32 | } 33 | 34 | /// Writes an UTF-8 string `value` as DER-object to `sink` 35 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 36 | pub fn write(value: &str, sink: &mut S) -> Result<(), Asn1DerError> { 37 | DerObject::write(Self::TAG, value.len(), &mut value.as_bytes().iter(), sink) 38 | .propagate(e!("Failed to write UTF-8 string")) 39 | } 40 | } 41 | impl<'a> DerTypeView<'a> for Utf8String<'a> { 42 | const TAG: u8 = b'\x0c'; 43 | 44 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 45 | fn object(&self) -> DerObject<'a> { 46 | self.object 47 | } 48 | } 49 | impl<'a> DerDecodable<'a> for Utf8String<'a> { 50 | // _#implicit validation_: no_panic does not work due to `str::from_utf8`; however we just have to assume that the 51 | // stdlib works correctly in this case 52 | fn load(object: DerObject<'a>) -> Result { 53 | match object.value() { 54 | _ if object.tag() != Self::TAG => Err(einval!("DER object is not an UTF-8 string"))?, 55 | s => match str::from_utf8(s).is_ok() { 56 | true => Ok(Self { object }), 57 | false => Err(einval!("DER object is not a valid UTF-8 string")), 58 | }, 59 | } 60 | } 61 | } 62 | impl<'a> DerEncodable for Utf8String<'a> { 63 | /// Encodes `self` to `sink` 64 | #[cfg_attr(feature = "no_panic", no_panic::no_panic)] 65 | fn encode(&self, sink: &mut U) -> Result<(), Asn1DerError> { 66 | self.object().encode(sink).propagate(e!("Failed to encode UTF-8 string")) 67 | } 68 | } 69 | 70 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 71 | impl<'a> DerDecodable<'a> for String { 72 | fn load(object: DerObject<'a>) -> Result { 73 | let string = Utf8String::load(object).propagate(e!("Failed to load UTF-8 string"))?; 74 | Ok(string.get().to_string()) 75 | } 76 | } 77 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 78 | impl DerEncodable for String { 79 | fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { 80 | Utf8String::write(self, sink).propagate(e!("Failed to encode UTF-8 string")) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test_unix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cargo clean && cargo test --verbose --release --no-default-features 5 | 6 | cargo test --verbose --release --no-default-features --features="native_types" 7 | cargo test --verbose --release --no-default-features --features="std" 8 | #cargo clean && cargo test --verbose --release --no-default-features --features="no_panic" 9 | 10 | cargo test --verbose --release --no-default-features --features="native_types,std" 11 | #cargo clean && cargo test --verbose --release --no-default-features --features="std,no_panic" 12 | #cargo clean && cargo test --verbose --release --no-default-features --features="no_panic,native_types" 13 | 14 | #cargo clean && cargo test --verbose --release --no-default-features --features="native_types,std,no_panic" 15 | -------------------------------------------------------------------------------- /tests/err.json: -------------------------------------------------------------------------------- 1 | { 2 | "length": [ 3 | { 4 | "name": "Zero-sized complex length", 5 | "bytes": [128], 6 | "err": "InvalidData" 7 | }, 8 | { 9 | "name": "Simple length encoded as complex length", 10 | "bytes": [129,127], 11 | "err": "InvalidData" 12 | }, 13 | { 14 | "name": "Unsupported length > 2^64 - 1", 15 | "bytes": [137,1,0,0,0,0,0,0,0,0], 16 | "err": "Unsupported" 17 | } 18 | ], 19 | "object": [ 20 | { 21 | "name": "Object with invalid length (zero-sized complex length)", 22 | "bytes": [0,128], 23 | "err": "InvalidData" 24 | }, 25 | { 26 | "name": "Object with invalid length (simple length encoded as complex length)", 27 | "bytes": [175,129,127], 28 | "err": "InvalidData" 29 | }, 30 | { 31 | "name": "Truncated object (expected 1, got 0)", 32 | "bytes": [190,129], 33 | "err": "InOutError" 34 | }, 35 | { 36 | "name": "Truncated object (expected 4, got 3)", 37 | "bytes": [215,132,1,0,0], 38 | "err": "InOutError" 39 | }, 40 | { 41 | "name": "Truncated object (expected 9, got 8)", 42 | "bytes": [12,9,84,101,115,116,111,108,111,112], 43 | "err": "InOutError" 44 | }, 45 | { 46 | "name": "Truncated object (unsupported length > 2^64 - 1)", 47 | "bytes": [119,137,1,0,0,0,0,0,0,0,0], 48 | "err": "Unsupported" 49 | }, 50 | { 51 | "name": "Truncated object (unsupported length > 2^64 - 1)", 52 | "bytes": [157,247,157,157,157,157,157,157,157,157,157,157,157,157,157,67,157,1,0,0,0,157,157,157,157,157,157,157,157], 53 | "err": "Unsupported" 54 | }, 55 | { 56 | "name": "Truncated object with excessive length announcement", 57 | "bytes": [5,136,112,0,0,0,0,0,0,0,7,12,5,4], 58 | "err": "InOutError", 59 | "err_32bit": "Unsupported" 60 | } 61 | ], 62 | "typed": { 63 | "bool": [ 64 | { 65 | "name": "Invalid boolean (invalid tag)", 66 | "bytes": [2,1,0], 67 | "err": "InvalidData" 68 | }, 69 | { 70 | "name": "Invalid boolean (invalid value byte)", 71 | "bytes": [1,1,1], 72 | "err": "InvalidData" 73 | }, 74 | { 75 | "name": "Invalid boolean (invalid value length)", 76 | "bytes": [1,2,0,0], 77 | "err": "InvalidData" 78 | }, 79 | { 80 | "name": "Truncated boolean (expected 2, got 1)", 81 | "bytes": [1,2,0], 82 | "err": "InOutError" 83 | } 84 | ], 85 | "integer": [ 86 | { 87 | "name": "Invalid integer (invalid tag)", 88 | "bytes": [3,1,7], 89 | "err": "InvalidData" 90 | }, 91 | { 92 | "name": "Invalid integer (empty value)", 93 | "bytes": [2,0], 94 | "err": "InvalidData" 95 | }, 96 | { 97 | "name": "Invalid integer (two leading zeroes)", 98 | "bytes": [2,2,0,0], 99 | "err": "InvalidData" 100 | }, 101 | { 102 | "name": "Invalid integer (excessive representation of 127)", 103 | "bytes": [2,2,0,127], 104 | "err": "InvalidData" 105 | }, 106 | { 107 | "name": "Invalid integer (excessive representation of -1)", 108 | "bytes": [2,2,255,255], 109 | "err": "InvalidData" 110 | }, 111 | { 112 | "name": "Truncated integer (expected 2, got 1)", 113 | "bytes": [2,2,128], 114 | "err": "InOutError" 115 | } 116 | ], 117 | "null": [ 118 | { 119 | "name": "Invalid null object (invalid tag)", 120 | "bytes": [6,0], 121 | "err": "InvalidData" 122 | }, 123 | { 124 | "name": "Invalid null object (not empty)", 125 | "bytes": [5,1,0], 126 | "err": "InvalidData" 127 | }, 128 | { 129 | "name": "Truncated null object (expected 2, got 1)", 130 | "bytes": [5,2,0], 131 | "err": "InOutError" 132 | } 133 | ], 134 | "octet_string": [ 135 | { 136 | "name": "Invalid octet string (invalid tag)", 137 | "bytes": [3,1,0], 138 | "err": "InvalidData" 139 | }, 140 | { 141 | "name": "Truncated octet string (expected 1, got 0)", 142 | "bytes": [4,1], 143 | "err": "InOutError" 144 | } 145 | ], 146 | "sequence": [ 147 | { 148 | "name": "Invalid sequence (invalid tag)", 149 | "bytes": [49,0], 150 | "err": "InvalidData" 151 | }, 152 | { 153 | "name": "Truncated sequence (truncated subobject; expected 2, got 1)", 154 | "bytes": [48,3,2,2,128], 155 | "err": "InOutError" 156 | }, 157 | { 158 | "name": "Truncated sequence (expected 5, got 4)", 159 | "bytes": [48,5,4,2,55,228], 160 | "err": "InOutError" 161 | } 162 | ], 163 | "utf8_string": [ 164 | { 165 | "name": "Invalid UTF-8 string (invalid tag)", 166 | "bytes": [13,0], 167 | "err": "InvalidData" 168 | }, 169 | { 170 | "name": "Invalid UTF-8 string (non-UTF-8 literal)", 171 | "bytes": [12,4,240,40,140,40], 172 | "err": "InvalidData" 173 | }, 174 | { 175 | "name": "Truncated UTF-8 string (expected 2, got 1)", 176 | "bytes": [12,2,84], 177 | "err": "InOutError" 178 | } 179 | ] 180 | } 181 | } 182 | 183 | 184 | -------------------------------------------------------------------------------- /tests/err.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, deny(warnings))] 2 | 3 | pub mod helpers; 4 | 5 | use crate::helpers::{ ResultExt, test_err }; 6 | use asn1_der::{ DerObject, der }; 7 | 8 | 9 | #[test] 10 | fn length() { 11 | for test in test_err::load().length { 12 | der::length::decode(&mut test.bytes.iter()).assert_err(&test.err, &test.name); 13 | } 14 | } 15 | 16 | 17 | #[test] 18 | fn object() { 19 | for test in test_err::load().object { 20 | DerObject::decode(test.bytes.as_slice()).assert_err(test.err(), &test.name); 21 | } 22 | } -------------------------------------------------------------------------------- /tests/err_typed.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, deny(warnings))] 2 | #![cfg(feature = "native_types")] 3 | 4 | pub mod helpers; 5 | 6 | use crate::helpers::{test_err, ResultExt}; 7 | use asn1_der::typed::{Boolean, DerDecodable, Integer, Null, OctetString, Sequence, Utf8String}; 8 | 9 | #[test] 10 | fn boolean() { 11 | for test in test_err::load().typed.bool { 12 | Boolean::decode(&test.bytes).assert_err(&test.err, &test.name); 13 | bool::decode(&test.bytes).assert_err(&test.err, &test.name); 14 | } 15 | } 16 | 17 | #[test] 18 | fn integer() { 19 | for test in test_err::load().typed.integer { 20 | Integer::decode(&test.bytes).assert_err(&test.err, &test.name); 21 | macro_rules! native { 22 | ($num:ty) => (<$num>::decode(&test.bytes).assert_err(&test.err, &test.name)); 23 | ($( $num:ty ),+) => ($( native!($num); )+); 24 | } 25 | native!(u8, u16, u32, u64, u128, usize); 26 | } 27 | } 28 | 29 | #[test] 30 | fn null() { 31 | for test in test_err::load().typed.null { 32 | type OptBool = Option; 33 | Null::decode(&test.bytes).assert_err(&test.err, &test.name); 34 | OptBool::decode(&test.bytes).assert_err(&test.err, &test.name); 35 | } 36 | } 37 | 38 | #[test] 39 | fn octet_string() { 40 | for test in test_err::load().typed.octet_string { 41 | OctetString::decode(&test.bytes).assert_err(&test.err, &test.name); 42 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 43 | Vec::::decode(&test.bytes).assert_err(&test.err, &test.name); 44 | } 45 | } 46 | 47 | #[test] 48 | fn sequence() { 49 | for test in test_err::load().typed.sequence { 50 | Sequence::decode(&test.bytes).assert_err(&test.err, &test.name); 51 | } 52 | } 53 | 54 | #[test] 55 | fn utf8_string() { 56 | for test in test_err::load().typed.utf8_string { 57 | Utf8String::decode(&test.bytes).assert_err(&test.err, &test.name); 58 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 59 | String::decode(&test.bytes).assert_err(&test.err, &test.name); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | use asn1_der::{ 2 | Asn1DerError, 3 | Asn1DerErrorVariant::{InOutError, InvalidData, Other, Unsupported}, 4 | }; 5 | 6 | pub trait OptionExt { 7 | /// Returns the `Some` variant or pretty prints the error and panics 8 | fn assert(self, name: &str) -> T; 9 | /// Returns the `Some` variant or pretty prints the error and panics 10 | fn assert_index(self, name: &str, i: usize) -> T; 11 | } 12 | impl OptionExt for Option { 13 | fn assert(self, name: &str) -> T { 14 | self.unwrap_or_else(|| { 15 | eprintln!("Unexpectet `None` result @\"{}\"", name); 16 | panic!("Panicked due to fatal error"); 17 | }) 18 | } 19 | fn assert_index(self, name: &str, i: usize) -> T { 20 | self.unwrap_or_else(|| { 21 | eprintln!("Unexpected `None` result @\"{}\":{}", name, i); 22 | panic!("Panicked due to fatal error"); 23 | }) 24 | } 25 | } 26 | 27 | pub trait ResultExt { 28 | /// Returns the `Ok` variant or pretty prints the error and panics 29 | fn assert(self, name: &str) -> T; 30 | /// Returns the `Ok` variant or pretty prints the error and panics 31 | fn assert_index(self, name: &str, i: usize) -> T; 32 | /// Ensures that the result is an `Err` of type `variant` 33 | fn assert_err(self, variant: &str, name: &str); 34 | } 35 | impl ResultExt for Result { 36 | fn assert(self, name: &str) -> T { 37 | self.unwrap_or_else(|e| { 38 | eprintln!("Fatal error @\"{}\": {}", name, e); 39 | panic!("Panicked due to fatal error"); 40 | }) 41 | } 42 | fn assert_index(self, name: &str, i: usize) -> T { 43 | self.unwrap_or_else(|e| { 44 | eprintln!("Fatal error @\"{}\":{}: {}", name, i, e); 45 | panic!("Panicked due to fatal error"); 46 | }) 47 | } 48 | fn assert_err(self, variant: &str, name: &str) { 49 | match self { 50 | Err(Asn1DerError { error: InOutError(_), .. }) if variant == "InOutError" => (), 51 | Err(Asn1DerError { error: InvalidData(_), .. }) if variant == "InvalidData" => (), 52 | Err(Asn1DerError { error: Unsupported(_), .. }) if variant == "Unsupported" => (), 53 | Err(Asn1DerError { error: Other(_), .. }) if variant == "Other" => (), 54 | Ok(_) => { 55 | eprintln!("Unexpected success @\"{}\"; expected {}", name, variant); 56 | panic!("Panicked due to unexpected success") 57 | } 58 | Err(e) => { 59 | eprintln!("Unexpected error kind @\"{}\"; got {}\ninstead of {}", name, e, variant); 60 | panic!("Panicked due to invalid error kind"); 61 | } 62 | } 63 | } 64 | } 65 | 66 | pub mod test_ok { 67 | #[derive(serde::Serialize, serde::Deserialize)] 68 | pub struct Length { 69 | pub name: String, 70 | pub bytes: Vec, 71 | pub value: Option, 72 | } 73 | 74 | #[derive(serde::Serialize, serde::Deserialize)] 75 | pub struct Object { 76 | pub name: String, 77 | pub bytes: Vec, 78 | pub tag: u8, 79 | pub value: Vec, 80 | } 81 | 82 | #[derive(serde::Serialize, serde::Deserialize)] 83 | pub struct TypedBool { 84 | pub name: String, 85 | pub bytes: Vec, 86 | pub value: Vec, 87 | pub bool: bool, 88 | } 89 | #[derive(serde::Serialize, serde::Deserialize)] 90 | pub struct TypedInteger { 91 | pub name: String, 92 | pub bytes: Vec, 93 | pub value: Vec, 94 | pub uint: Option, 95 | pub int: Option, 96 | } 97 | #[derive(serde::Serialize, serde::Deserialize)] 98 | pub struct TypedNull { 99 | pub name: String, 100 | pub bytes: Vec, 101 | } 102 | #[derive(serde::Serialize, serde::Deserialize)] 103 | pub struct TypedOctetString { 104 | pub name: String, 105 | pub bytes: Vec, 106 | pub value: Vec, 107 | } 108 | #[derive(serde::Serialize, serde::Deserialize)] 109 | pub struct TypedSequence { 110 | pub name: String, 111 | pub bytes: Vec, 112 | pub value: Vec, 113 | pub sequence: Vec, 114 | } 115 | #[derive(serde::Serialize, serde::Deserialize)] 116 | pub struct TypedUtf8String { 117 | pub name: String, 118 | pub bytes: Vec, 119 | pub value: Vec, 120 | pub utf8str: String, 121 | } 122 | #[derive(serde::Serialize, serde::Deserialize)] 123 | pub struct Typed { 124 | pub bool: Vec, 125 | pub integer: Vec, 126 | pub null: Vec, 127 | pub octet_string: Vec, 128 | pub sequence: Vec, 129 | pub utf8_string: Vec, 130 | } 131 | 132 | /// A test vector for valid constructions 133 | #[derive(serde::Serialize, serde::Deserialize)] 134 | pub struct Test { 135 | pub length: Vec, 136 | pub object: Vec, 137 | pub typed: Typed, 138 | } 139 | /// Loads the test vectors for valid constructions 140 | pub fn load() -> Test { 141 | serde_json::from_str(include_str!("../ok.json")).expect("Failed to load test vectors") 142 | } 143 | } 144 | 145 | pub mod test_err { 146 | #[derive(serde::Serialize, serde::Deserialize)] 147 | pub struct Length { 148 | pub name: String, 149 | pub bytes: Vec, 150 | pub err: String, 151 | } 152 | 153 | #[derive(serde::Serialize, serde::Deserialize)] 154 | pub struct Object { 155 | pub name: String, 156 | pub bytes: Vec, 157 | err: String, 158 | err_32bit: Option, 159 | } 160 | impl Object { 161 | /// Gets the platform dependent error 162 | pub fn err(&self) -> &str { 163 | match self.err_32bit.as_ref() { 164 | #[cfg(target_pointer_width = "32")] 165 | Some(err_32bit) => err_32bit, 166 | _ => &self.err, 167 | } 168 | } 169 | } 170 | 171 | #[derive(serde::Serialize, serde::Deserialize)] 172 | pub struct TypedAny { 173 | pub name: String, 174 | pub bytes: Vec, 175 | pub err: String, 176 | } 177 | #[derive(serde::Serialize, serde::Deserialize)] 178 | pub struct Typed { 179 | pub bool: Vec, 180 | pub integer: Vec, 181 | pub null: Vec, 182 | pub octet_string: Vec, 183 | pub sequence: Vec, 184 | pub utf8_string: Vec, 185 | } 186 | 187 | /// A test vector for invalid constructions 188 | #[derive(serde::Serialize, serde::Deserialize)] 189 | pub struct Test { 190 | pub length: Vec, 191 | pub object: Vec, 192 | pub typed: Typed, 193 | } 194 | /// Loads the test vectors for invalid constructions 195 | pub fn load() -> Test { 196 | serde_json::from_str(include_str!("../err.json")).expect("Failed to load test vectors") 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/ok.json: -------------------------------------------------------------------------------- 1 | { 2 | "length": [ 3 | { 4 | "name": "Simple length (0)", 5 | "bytes": [0], 6 | "value": 0 7 | }, 8 | { 9 | "name": "Simple length (71)", 10 | "bytes": [71], 11 | "value": 71 12 | }, 13 | { 14 | "name": "Simple length (2^7 - 1)", 15 | "bytes": [127], 16 | "value": 127 17 | }, 18 | 19 | { 20 | "name": "Complex length (2^7)", 21 | "bytes": [129,128], 22 | "value": 128 23 | }, 24 | { 25 | "name": "Complex length (247)", 26 | "bytes": [129,247], 27 | "value": 247 28 | }, 29 | { 30 | "name": "Complex length (63479)", 31 | "bytes": [130,247,247], 32 | "value": 63479 33 | }, 34 | { 35 | "name": "Complex length (2^16 - 1)", 36 | "bytes": [130,255,255], 37 | "value": 65535 38 | }, 39 | 40 | { 41 | "name": "Complex length (2^16)", 42 | "bytes": [131,1,0,0], 43 | "value": 65536 44 | }, 45 | { 46 | "name": "Complex length (16219972)", 47 | "bytes": [131,247,127,68], 48 | "value": 16219972 49 | }, 50 | { 51 | "name": "Complex length (4152312833)", 52 | "bytes": [132,247,127,68,1], 53 | "value": 4152312833 54 | }, 55 | { 56 | "name": "Complex length (2^32 - 1)", 57 | "bytes": [132,255,255,255,255], 58 | "value": 4294967295 59 | }, 60 | 61 | { 62 | "name": "Complex length (2^32)", 63 | "bytes": [133,1,0,0,0,0], 64 | "value": 4294967296 65 | }, 66 | { 67 | "name": "Complex length (1062992085431)", 68 | "bytes": [133,247,127,68,1,183], 69 | "value": 1062992085431 70 | }, 71 | { 72 | "name": "Complex length (272125973870533)", 73 | "bytes": [134,247,127,68,1,183,197], 74 | "value": 272125973870533 75 | }, 76 | { 77 | "name": "Complex length (69664249310856483)", 78 | "bytes": [135,247,127,68,1,183,197,35], 79 | "value": 69664249310856483 80 | }, 81 | { 82 | "name": "Complex length (17834047823579259648)", 83 | "bytes": [136,247,127,68,1,183,197,35,0], 84 | "value": 17834047823579259648 85 | }, 86 | { 87 | "name": "Complex length (2^64 - 1)", 88 | "bytes": [136,255,255,255,255,255,255,255,255], 89 | "value": 18446744073709551615 90 | }, 91 | 92 | { 93 | "name": "Truncated length (expected 1, got 0)", 94 | "bytes": [], 95 | "value": null 96 | }, 97 | { 98 | "name": "Truncated length (expected 4, got 3)", 99 | "bytes": [], 100 | "value": null 101 | } 102 | ], 103 | "object": [ 104 | { 105 | "name": "Null object", 106 | "bytes": [5,0], 107 | "tag": 5, 108 | "value": [] 109 | }, 110 | { 111 | "name": "Octet string", 112 | "bytes": [4,2,55,228], 113 | "tag": 4, 114 | "value": [55,228] 115 | } 116 | ], 117 | "typed": { 118 | "bool": [ 119 | { 120 | "name": "Boolean (false)", 121 | "bytes": [1,1,0], 122 | "tag": 1, 123 | "value": [0], 124 | "bool": false 125 | }, 126 | { 127 | "name": "Boolean (true)", 128 | "bytes": [1,1,255], 129 | "tag": 1, 130 | "value": [255], 131 | "bool": true 132 | } 133 | ], 134 | "integer": [ 135 | { 136 | "name": "Integer (0)", 137 | "bytes": [2,1,0], 138 | "tag": 2, 139 | "value": [0], 140 | "uint": 0, 141 | "int": 0 142 | }, 143 | { 144 | "name": "Integer (7)", 145 | "bytes": [2,1,7], 146 | "tag": 2, 147 | "value": [7], 148 | "uint": 7, 149 | "int": 7 150 | }, 151 | { 152 | "name": "Integer (128)", 153 | "bytes": [2,2,0,128], 154 | "tag": 2, 155 | "value": [0,128], 156 | "uint": 128, 157 | "int": 128 158 | }, 159 | { 160 | "name": "Integer (255)", 161 | "bytes": [2,2,0,255], 162 | "tag": 2, 163 | "value": [0,255], 164 | "uint": 255, 165 | "int": 255 166 | }, 167 | 168 | { 169 | "name": "Integer (32759)", 170 | "bytes": [2,2,127,247], 171 | "tag": 2, 172 | "value": [127,247], 173 | "uint": 32759, 174 | "int": 32759 175 | }, 176 | { 177 | "name": "Integer (32933)", 178 | "bytes": [2,3,0,128,165], 179 | "tag": 2, 180 | "value": [0,128,165], 181 | "uint": 32933, 182 | "int": 32933 183 | }, 184 | { 185 | "name": "Integer (65535)", 186 | "bytes": [2,3,0,255,255], 187 | "tag": 2, 188 | "value": [0,255,255], 189 | "uint": 65535, 190 | "int": 65535 191 | }, 192 | 193 | { 194 | "name": "Integer (2146947863)", 195 | "bytes": [2,4,127,247,211,23], 196 | "tag": 2, 197 | "value": [127,247,211,23], 198 | "uint": 2146947863, 199 | "int": 2146947863 200 | }, 201 | { 202 | "name": "Integer (2158316671)", 203 | "bytes": [2,5,0,128,165,76,127], 204 | "tag": 2, 205 | "value": [0,128,165,76,127], 206 | "uint": 2158316671, 207 | "int": 2158316671 208 | }, 209 | { 210 | "name": "Integer (4294967295)", 211 | "bytes": [2,5,0,255,255,255,255], 212 | "tag": 2, 213 | "value": [0,255,255,255,255], 214 | "uint": 4294967295, 215 | "int": 4294967295 216 | }, 217 | 218 | { 219 | "name": "Integer (9221070861274031910)", 220 | "bytes": [2,8,127,247,211,23,206,241,167,38], 221 | "tag": 2, 222 | "value": [127,247,211,23,206,241,167,38], 223 | "uint": 9221070861274031910, 224 | "int": 9221070861274031910 225 | }, 226 | { 227 | "name": "Integer (9269899520199460000)", 228 | "bytes": [2,9,0,128,165,76,127,229,13,132,160], 229 | "tag": 2, 230 | "value": [0,128,165,76,127,229,13,132,160], 231 | "uint": 9269899520199460000, 232 | "int": 9269899520199460000 233 | }, 234 | { 235 | "name": "Integer (18446744073709551615)", 236 | "bytes": [2,9,0,255,255,255,255,255,255,255,255], 237 | "tag": 2, 238 | "value": [0,255,255,255,255,255,255,255,255], 239 | "uint": 18446744073709551615, 240 | "int": 18446744073709551615 241 | }, 242 | 243 | { 244 | "name": "Integer (169853733957366961371495358725388383073)", 245 | "bytes": [2,16,127,200,163,165,50,73,204,242,115,179,233,77,225,182,51,97], 246 | "tag": 2, 247 | "value": [127,200,163,165,50,73,204,242,115,179,233,77,225,182,51,97], 248 | "uint": 169853733957366961371495358725388383073, 249 | "int": 169853733957366961371495358725388383073 250 | }, 251 | { 252 | "name": "Integer (171182961953151877244399165785668727649)", 253 | "bytes": [2,17,0,128,200,163,165,50,73,204,242,115,179,233,77,225,182,51,97], 254 | "tag": 2, 255 | "value": [0,128,200,163,165,50,73,204,242,115,179,233,77,225,182,51,97], 256 | "uint": 171182961953151877244399165785668727649 257 | }, 258 | { 259 | "name": "Integer (340282366920938463463374607431768211455)", 260 | "bytes": [2,17,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255], 261 | "tag": 2, 262 | "value": [0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255], 263 | "uint": 340282366920938463463374607431768211455 264 | } 265 | ], 266 | "null": [ 267 | { 268 | "name": "Null object", 269 | "bytes": [5,0], 270 | "tag": 5 271 | } 272 | ], 273 | "octet_string": [ 274 | { 275 | "name": "Octet string (empty)", 276 | "bytes": [4,0], 277 | "tag": 4, 278 | "value": [] 279 | }, 280 | { 281 | "name": "Null object (\\x37\\xe4)", 282 | "bytes": [4,2,55,228], 283 | "tag": 4, 284 | "value": [55,228] 285 | } 286 | ], 287 | "sequence": [ 288 | { 289 | "name": "Sequence (empty)", 290 | "bytes": [48,0], 291 | "tag": 48, 292 | "value": [], 293 | "sequence": [] 294 | }, 295 | { 296 | "name": "Sequence (one octet string)", 297 | "bytes": [48,4,4,2,55,228], 298 | "tag": 48, 299 | "value": [4,2,55,228], 300 | "sequence": [ 301 | { 302 | "name": "Sequence subobject 0 (octet string)", 303 | "bytes": [4,2,55,228], 304 | "tag": 4, 305 | "value": [55,228] 306 | } 307 | ] 308 | }, 309 | { 310 | "name": "Sequence (two octet strings)", 311 | "bytes": [48,129,135,4,2,55,228,4,129,128,114,51,14,141,185,27,51,33,92,14,83,63,210,142,52,204,139,9,168,8,135,125,199,216,39,65,147,4,49,189,9,208,214,243,26,104,125,64,96,18,111,12,224,54,10,207,149,222,129,47,164,47,98,246,113,151,224,73,96,59,101,116,143,210,87,227,193,97,29,180,84,164,150,166,179,244,59,39,170,90,235,201,35,88,146,27,39,84,121,230,124,177,121,131,0,91,8,91,133,47,12,47,141,52,71,44,164,112,223,176,163,155,97,51,109,211,145,132,129,151,104,103,84,178,238,87,253,132], 312 | "tag": 48, 313 | "value": [4,2,55,228,4,129,128,114,51,14,141,185,27,51,33,92,14,83,63,210,142,52,204,139,9,168,8,135,125,199,216,39,65,147,4,49,189,9,208,214,243,26,104,125,64,96,18,111,12,224,54,10,207,149,222,129,47,164,47,98,246,113,151,224,73,96,59,101,116,143,210,87,227,193,97,29,180,84,164,150,166,179,244,59,39,170,90,235,201,35,88,146,27,39,84,121,230,124,177,121,131,0,91,8,91,133,47,12,47,141,52,71,44,164,112,223,176,163,155,97,51,109,211,145,132,129,151,104,103,84,178,238,87,253,132], 314 | "sequence": [ 315 | { 316 | "name": "Sequence subobject 0 (octet string)", 317 | "bytes": [4,2,55,228], 318 | "tag": 4, 319 | "value": [55,228] 320 | }, 321 | { 322 | "name": "Sequence subobject 1 (octet string)", 323 | "bytes": [4,129,128,114,51,14,141,185,27,51,33,92,14,83,63,210,142,52,204,139,9,168,8,135,125,199,216,39,65,147,4,49,189,9,208,214,243,26,104,125,64,96,18,111,12,224,54,10,207,149,222,129,47,164,47,98,246,113,151,224,73,96,59,101,116,143,210,87,227,193,97,29,180,84,164,150,166,179,244,59,39,170,90,235,201,35,88,146,27,39,84,121,230,124,177,121,131,0,91,8,91,133,47,12,47,141,52,71,44,164,112,223,176,163,155,97,51,109,211,145,132,129,151,104,103,84,178,238,87,253,132], 324 | "tag": 4, 325 | "value": [114,51,14,141,185,27,51,33,92,14,83,63,210,142,52,204,139,9,168,8,135,125,199,216,39,65,147,4,49,189,9,208,214,243,26,104,125,64,96,18,111,12,224,54,10,207,149,222,129,47,164,47,98,246,113,151,224,73,96,59,101,116,143,210,87,227,193,97,29,180,84,164,150,166,179,244,59,39,170,90,235,201,35,88,146,27,39,84,121,230,124,177,121,131,0,91,8,91,133,47,12,47,141,52,71,44,164,112,223,176,163,155,97,51,109,211,145,132,129,151,104,103,84,178,238,87,253,132] 326 | } 327 | ] 328 | } 329 | ], 330 | "utf8_string": [ 331 | { 332 | "name": "UTF-8 string (\"\")", 333 | "bytes": [12,0], 334 | "tag": 12, 335 | "value": [], 336 | "utf8str": "" 337 | }, 338 | { 339 | "name": "UTF-8 string (\"Testolope\")", 340 | "bytes": [12,9,84,101,115,116,111,108,111,112,101], 341 | "tag": 12, 342 | "value": [84,101,115,116,111,108,111,112,101], 343 | "utf8str": "Testolope" 344 | }, 345 | { 346 | "name": "UTF-8 string (\"Some UTF-8 Emoji \uD83D\uDD96\uD83C\uDFFD\")", 347 | "bytes": [12,25,83,111,109,101,32,85,84,70,45,56,32,69,109,111,106,105,32,240,159,150,150,240,159,143,189], 348 | "tag": 12, 349 | "value": [83,111,109,101,32,85,84,70,45,56,32,69,109,111,106,105,32,240,159,150,150,240,159,143,189], 350 | "utf8str": "Some UTF-8 Emoji \uD83D\uDD96\uD83C\uDFFD" 351 | } 352 | ] 353 | } 354 | } -------------------------------------------------------------------------------- /tests/ok.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, deny(warnings))] 2 | 3 | pub mod helpers; 4 | 5 | use crate::helpers::{test_ok, OptionExt, ResultExt}; 6 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 7 | use asn1_der::VecBacking; 8 | use asn1_der::{der, DerObject, Sink}; 9 | 10 | #[test] 11 | fn length() { 12 | for test in test_ok::load().length { 13 | if let Some(value) = test.value { 14 | // Test valid lengths 15 | if value <= usize::max_value() as u64 { 16 | // Decode length 17 | let len = der::length::decode(&mut test.bytes.iter()).assert(&test.name).assert(&test.name); 18 | assert_eq!(len, value as usize, "@\"{}\"", &test.name); 19 | 20 | // Encode length 21 | let (mut buf, mut buf_len) = ([0; 9], 0); 22 | let mut sink = buf.iter_mut().counting_sink(&mut buf_len); 23 | der::length::encode(len, &mut sink).assert(&test.name); 24 | assert_eq!(&buf[..buf_len], test.bytes.as_slice(), "@\"{}\"", &test.name); 25 | } 26 | } else { 27 | // Test truncated lengths 28 | let len = der::length::decode(&mut test.bytes.iter()).assert(&test.name); 29 | assert!(len.is_none(), "@\"{}\"", &test.name); 30 | } 31 | } 32 | } 33 | 34 | #[test] 35 | fn object() { 36 | for test in test_ok::load().object { 37 | // Test-copy the object 38 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 39 | { 40 | let mut bytes = Vec::new(); 41 | DerObject::decode_from_source(&mut test.bytes.iter(), VecBacking(&mut bytes)).assert(&test.name); 42 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 43 | } 44 | 45 | // Decode the object 46 | let object = DerObject::decode(test.bytes.as_slice()).assert(&test.name); 47 | assert_eq!(object.tag(), test.tag, "@\"{}\"", &test.name); 48 | assert_eq!(object.value(), test.value.as_slice(), "@\"{}\"", &test.name); 49 | 50 | // Encode the object 51 | let mut bytes = vec![0; test.bytes.len()]; 52 | object.encode(&mut bytes.iter_mut()).assert(&test.name); 53 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/ok_typed.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, deny(warnings))] 2 | #![cfg(feature = "native_types")] 3 | 4 | pub mod helpers; 5 | 6 | use crate::helpers::{test_ok, ResultExt}; 7 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 8 | use asn1_der::typed::SequenceVec; 9 | use asn1_der::{ 10 | typed::{Boolean, DerDecodable, DerEncodable, DerTypeView, Integer, Null, OctetString, Sequence, Utf8String}, 11 | DerObject, SliceSink, 12 | }; 13 | use core::convert::TryFrom; 14 | 15 | #[test] 16 | fn boolean() { 17 | for test in test_ok::load().typed.bool { 18 | // Decode the object 19 | let boolean = Boolean::decode(&test.bytes).assert(&test.name); 20 | assert_eq!(boolean.get(), test.bool, "@\"{}\"", &test.name); 21 | 22 | let native = bool::decode(&test.bytes).assert(&test.name); 23 | assert_eq!(native, test.bool, "@\"{}\"", &test.name); 24 | 25 | // Encode the object 26 | let mut bytes = vec![0; test.bytes.len()]; 27 | boolean.encode(&mut bytes.iter_mut()).assert(&test.name); 28 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 29 | 30 | let mut bytes = vec![0; test.bytes.len()]; 31 | test.bool.encode(&mut bytes.iter_mut()).assert(&test.name); 32 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 33 | 34 | let (mut bytes, mut pos) = ([0; 1024], 0); 35 | let sink = SliceSink::new(&mut bytes, &mut pos); 36 | Boolean::new(test.bool, sink).assert(&test.name); 37 | assert_eq!(&bytes[..pos], test.bytes.as_slice(), "@\"{}\"", &test.name); 38 | } 39 | } 40 | 41 | #[test] 42 | fn integer() { 43 | for test in test_ok::load().typed.integer { 44 | // Decode the object 45 | let object = Integer::decode(test.bytes.as_slice()).assert(&test.name); 46 | assert_eq!(object.object().value(), test.value.as_slice(), "@\"{}\"", &test.name); 47 | 48 | // Encode the object 49 | let mut bytes = vec![0; test.bytes.len()]; 50 | object.encode(&mut bytes.iter_mut()).assert(&test.name); 51 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 52 | 53 | // Test native types 54 | macro_rules! native { 55 | ($num:ty, $field:ident, $is_signed:expr) => { 56 | if let Some(value) = test.$field.and_then(|n| <$num>::try_from(n).ok()) { 57 | // Decode native 58 | let native = <$num>::decode(test.bytes.as_slice()).assert(&test.name); 59 | assert_eq!(native, value, "@\"{}\"", &test.name); 60 | 61 | // Encode native 62 | let mut bytes = vec![0; test.bytes.len()]; 63 | value.encode(&mut bytes.iter_mut()).assert(&test.name); 64 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 65 | 66 | let (mut bytes, mut pos) = ([0; 1024], 0); 67 | let sink = SliceSink::new(&mut bytes, &mut pos); 68 | Integer::new(&value.to_be_bytes(), $is_signed(value), sink).assert(&test.name); 69 | assert_eq!(&bytes[..pos], test.bytes.as_slice(), "@\"{}\"", &test.name); 70 | } 71 | }; 72 | (unsigned: $( $num:ty ),+) => ($( native!($num, uint, |_| false); )+); 73 | } 74 | native!(unsigned: u8, u16, u32, u64, u128, usize); 75 | } 76 | } 77 | 78 | #[test] 79 | fn null() { 80 | for test in test_ok::load().typed.null { 81 | const TRUE: &[u8] = b"\x01\x01\xff"; 82 | type OptBool = Option; 83 | 84 | // Decode the object 85 | let object = Null::decode(test.bytes.as_slice()).assert(&test.name); 86 | 87 | let native = OptBool::decode(test.bytes.as_slice()).assert(&test.name); 88 | assert!(native.is_none(), "@\"{}\"", &test.name); 89 | 90 | let native = OptBool::decode(TRUE).assert(&test.name); 91 | assert_eq!(native, Some(true), "@\"{}\"", &test.name); 92 | 93 | // Encode the object 94 | let mut bytes = vec![0; test.bytes.len()]; 95 | object.encode(&mut bytes.iter_mut()).assert(&test.name); 96 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 97 | 98 | let (mut bytes, mut pos) = ([0; 1024], 0); 99 | let sink = SliceSink::new(&mut bytes, &mut pos); 100 | Null::new(sink).assert(&test.name); 101 | assert_eq!(&bytes[..pos], test.bytes.as_slice(), "@\"{}\"", &test.name); 102 | 103 | let mut bytes = [0; 2]; 104 | OptBool::None.encode(&mut bytes.iter_mut()).assert(&test.name); 105 | assert_eq!(bytes.as_ref(), test.bytes.as_slice(), "@\"{}\"", &test.name); 106 | } 107 | } 108 | 109 | #[test] 110 | fn octet_string() { 111 | for test in test_ok::load().typed.octet_string { 112 | // Decode the object 113 | let object = OctetString::decode(test.bytes.as_slice()).assert(&test.name); 114 | assert_eq!(object.get(), test.value.as_slice(), "@\"{}\"", &test.name); 115 | 116 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 117 | { 118 | let native = Vec::::decode(test.bytes.as_slice()).assert(&test.name); 119 | assert_eq!(native, test.value, "@\"{}\"", &test.name); 120 | } 121 | 122 | // Encode the object 123 | let mut bytes = vec![0; test.bytes.len()]; 124 | object.encode(&mut bytes.iter_mut()).assert(&test.name); 125 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 126 | 127 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 128 | { 129 | let mut bytes = vec![0; test.bytes.len()]; 130 | test.value.encode(&mut bytes.iter_mut()).assert(&test.name); 131 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 132 | } 133 | 134 | let (mut bytes, mut pos) = ([0; 1024], 0); 135 | let sink = SliceSink::new(&mut bytes, &mut pos); 136 | OctetString::new(&test.value, sink).assert(&test.name); 137 | assert_eq!(&bytes[..pos], test.bytes.as_slice(), "@\"{}\"", &test.name); 138 | } 139 | } 140 | 141 | #[test] 142 | fn sequence() { 143 | for test in test_ok::load().typed.sequence { 144 | // Decode the object 145 | let object = Sequence::decode(test.bytes.as_slice()).assert(&test.name); 146 | assert_eq!(object.object().value(), test.value.as_slice(), "@\"{}\"", &test.name); 147 | 148 | for (i, obj) in test.sequence.iter().enumerate() { 149 | let object = object.get(i).assert_index(&test.name, i); 150 | assert_eq!(object.tag(), obj.tag, "@\"{}\"", &test.name); 151 | assert_eq!(object.value(), obj.value.as_slice(), "@\"{}\":{}", &test.name, i); 152 | } 153 | 154 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 155 | { 156 | let native = SequenceVec::>::decode(test.bytes.as_slice()).assert(&test.name); 157 | for (i, obj) in test.sequence.iter().enumerate() { 158 | assert_eq!(native[i], obj.value.as_slice(), "@\"{}\":{}", &test.name, i); 159 | } 160 | } 161 | 162 | // Encode the object 163 | let mut bytes = vec![0; test.bytes.len()]; 164 | object.encode(&mut bytes.iter_mut()).assert(&test.name); 165 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 166 | 167 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 168 | { 169 | let values: Vec<_> = 170 | test.sequence.iter().map(|o| DerObject::decode(o.bytes.as_slice()).assert(&test.name)).collect(); 171 | let mut bytes = vec![0; test.bytes.len()]; 172 | SequenceVec(values).encode(&mut bytes.iter_mut()).assert(&test.name); 173 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 174 | } 175 | 176 | { 177 | let values: Vec<_> = 178 | test.sequence.iter().map(|o| DerObject::decode(o.bytes.as_slice()).assert(&test.name)).collect(); 179 | 180 | let (mut bytes, mut pos) = ([0; 4096], 0); 181 | let sink = SliceSink::new(&mut bytes, &mut pos); 182 | Sequence::new(&values, sink).assert(&test.name); 183 | assert_eq!(&bytes[..pos], test.bytes.as_slice(), "@\"{}\"", &test.name); 184 | } 185 | } 186 | } 187 | 188 | #[test] 189 | fn utf8_string() { 190 | for test in test_ok::load().typed.utf8_string { 191 | // Decode the object 192 | let object = Utf8String::decode(test.bytes.as_slice()).assert(&test.name); 193 | assert_eq!(object.get(), test.utf8str.as_str(), "@\"{}\"", &test.name); 194 | 195 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 196 | { 197 | let native = String::decode(test.bytes.as_slice()).assert(&test.name); 198 | assert_eq!(native, test.utf8str, "@\"{}\"", &test.name); 199 | } 200 | 201 | // Encode the object 202 | let mut bytes = vec![0; test.bytes.len()]; 203 | object.encode(&mut bytes.iter_mut()).assert(&test.name); 204 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 205 | 206 | #[cfg(all(feature = "std", not(feature = "no_panic")))] 207 | { 208 | let mut bytes = vec![0; test.bytes.len()]; 209 | test.utf8str.encode(&mut bytes.iter_mut()).assert(&test.name); 210 | assert_eq!(bytes, test.bytes, "@\"{}\"", &test.name); 211 | } 212 | 213 | let (mut bytes, mut pos) = ([0; 1024], 0); 214 | let sink = SliceSink::new(&mut bytes, &mut pos); 215 | Utf8String::new(&test.utf8str, sink).assert(&test.name); 216 | assert_eq!(&bytes[..pos], test.bytes.as_slice(), "@\"{}\"", &test.name); 217 | } 218 | } 219 | --------------------------------------------------------------------------------