Skip to content

Commit

Permalink
Added an asn1parse command for local testing
Browse files Browse the repository at this point in the history
  • Loading branch information
alex committed Dec 3, 2024
1 parent e23fbf1 commit d7a9904
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 13 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
matrix:
RUST:
# MSRV
- VERSION: "1.59.0"
- VERSION: "1.65.0"
FLAGS: ""

- VERSION: stable
Expand Down Expand Up @@ -88,8 +88,13 @@ jobs:

- run: |
cargo generate-lockfile
cargo update -p libc --precise 0.2.163
if: matrix.RUST.VERSION == '1.59.0'
cargo update -p anstyle --precise 1.0.8
cargo update -p anstyle-parse --precise 0.2.5
cargo update -p anstyle-query --precise 1.1.1
cargo update -p clap --precise 4.3.24
cargo update -p clap_builder --precise 4.3.24
cargo update -p clap_lex --precise 0.5.0
if: matrix.RUST.VERSION == '1.65.0'
- run: cargo check --workspace --tests ${{ matrix.RUST.FLAGS }}
- run: cargo test --workspace ${{ matrix.RUST.FLAGS }}
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license = "BSD-3-Clause"
description = "ASN.1 (DER) parser and writer for Rust."
edition = "2021"
# This specifies the MSRV
rust-version = "1.59.0"
rust-version = "1.65.0"

[features]
default = ["std"]
Expand All @@ -22,4 +22,4 @@ itoa = "1.0.11"
libc = "0.2.11"

[workspace]
members = ["asn1_derive"]
members = ["asn1_derive", "asn1parse"]
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Add `asn1` to the `[dependencies]` section of your `Cargo.toml`:
asn1 = "0.20"
```

Builds on Rust 1.59.0 and newer.
Builds on Rust 1.65.0 and newer.

`rust-asn1` is compatible with `#![no_std]` environments:

Expand All @@ -24,7 +24,11 @@ asn1 = { version = "0.20", default-features = false }

## Changelog

### [0.20.1]
### [0.21.0]

#### Changes

- Updated MSRV to 1.65.0.

#### Fixes

Expand Down
2 changes: 1 addition & 1 deletion asn1_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "BSD-3-Clause"
description = "#[derive] support for asn1"
edition = "2021"
# This specifies the MSRV
rust-version = "1.59.0"
rust-version = "1.65.0"

[lib]
proc-macro = true
Expand Down
11 changes: 11 additions & 0 deletions asn1parse/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "asn1parse"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
asn1 = { version = "0.20", path = ".." }
clap = { version = "4.3", features = ["derive"] }
hex = "0.4.3"
pem = { version = "3.0.4", default-features = false, features = ["std"] }
156 changes: 156 additions & 0 deletions asn1parse/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#[derive(asn1::Asn1Read)]
pub enum Value<'a> {
SequenceOf(asn1::SequenceOf<'a, Value<'a>>),
SetOf(asn1::SetOf<'a, Value<'a>>),
Integer(asn1::BigInt<'a>),
Boolean(bool),
ObjectIdentifier(asn1::ObjectIdentifier),
Null(asn1::Null),
PrintableString(asn1::PrintableString<'a>),
IA5String(asn1::IA5String<'a>),
Utf8String(asn1::Utf8String<'a>),
OctetString(&'a [u8]),
BitString(asn1::BitString<'a>),
UtcTime(asn1::UtcTime),

Fallback(asn1::Tlv<'a>),
}

impl Value<'_> {
pub fn render(
&self,
out: &mut dyn std::io::Write,
indent: usize,
) -> Result<(), Box<dyn std::error::Error>> {
match self {
Value::SequenceOf(v) => {
writeln!(out, "{}SEQUENCE", " ".repeat(indent))?;
for el in v.clone() {
el.render(out, indent + 2)?;
}
}
Value::SetOf(v) => {
writeln!(out, "{}SET", " ".repeat(indent))?;
for el in v.clone() {
el.render(out, indent + 2)?;
}
}
Value::Integer(v) => {
let data = hex::encode(v.as_bytes());
let mut output = data.trim_start_matches('0');
if output.is_empty() {
output = "0";
}

writeln!(
out,
"{}INTEGER: {}0x{}",
" ".repeat(indent),
if v.is_negative() { "-" } else { "" },
output,
)?;
}
Value::Boolean(v) => {
writeln!(out, "{}BOOLEAN: {:?}", " ".repeat(indent), v)?;
}
Value::ObjectIdentifier(v) => {
writeln!(out, "{}OBJECT IDENTIFIER: {}", " ".repeat(indent), v)?;
}
Value::PrintableString(v) => {
writeln!(
out,
"{}PRINTABLE STRING: {:?}",
" ".repeat(indent),
v.as_str()
)?;
}
Value::IA5String(v) => {
writeln!(out, "{}IA5 STRING: {:?}", " ".repeat(indent), v.as_str())?;
}
Value::Utf8String(v) => {
writeln!(out, "{}UTF8 STRING: {:?}", " ".repeat(indent), v.as_str())?;
}
Value::OctetString(v) => {
if let Ok(v) = asn1::parse_single::<Value<'_>>(v) {
writeln!(out, "{}OCTET STRING", " ".repeat(indent))?;
v.render(out, indent + 2)?;
} else {
writeln!(out, "{}OCTET STRING: {:?}", " ".repeat(indent), v)?;
}
}
Value::BitString(v) => {
writeln!(
out,
"{}BIT STRING: {:?}",
" ".repeat(indent),
hex::encode(v.as_bytes())
)?;
}
Value::UtcTime(v) => {
let dt = v.as_datetime();
writeln!(
out,
"{}UTC TIME: {}-{:02}-{:02} {:02}:{:02}:{:02}",
" ".repeat(indent),
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second()
)?;
}
Value::Null(_) => {
writeln!(out, "{}NULL", " ".repeat(indent))?;
}
Value::Fallback(tlv) => {
let tag = tlv.tag();
if tag.is_constructed() {
writeln!(
out,
"{}[{:?} {}]",
" ".repeat(indent),
tag.class(),
tag.value()
)?;
asn1::parse_single::<Value<'_>>(tlv.data())?.render(out, indent + 2)?;
} else {
writeln!(
out,
"{}[{:?} {} PRIMITIVE]: {:?}",
" ".repeat(indent),
tag.class(),
tag.value(),
tlv.data()
)?;
}
}
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::Value;

#[test]
fn test_render() {
for (der, expected) in [
(b"\x01\x01\xff" as &[u8], "BOOLEAN: true\n"),
(b"\x17\x0d910506234540Z", "UTC TIME: 1991-05-06 23:45:40\n"),
(b"\x13\x03abc", "PRINTABLE STRING: \"abc\"\n"),
(b"\x16\x03abc", "IA5 STRING: \"abc\"\n"),
(b"\x03\x03\x04\x81\xf0", "BIT STRING: \"81f0\"\n"),
(b"\x02\x01\x00", "INTEGER: 0x0\n"),
(b"\x02\x01\x02", "INTEGER: 0x2\n"),
(b"\x02\x01\x80", "INTEGER: -0x80\n"),
] {
let v = asn1::parse_single::<Value<'_>>(der).unwrap();
let mut output = vec![];
v.render(&mut output, 0).unwrap();
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
}
}
}
33 changes: 33 additions & 0 deletions asn1parse/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use clap::Parser;
use std::io::Read;
use std::path::PathBuf;

#[derive(clap::Parser)]
struct Args {
#[clap(long)]
pem: bool,

#[clap()]
path: Option<PathBuf>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();

let mut data = if let Some(path) = args.path {
std::fs::read(path)?
} else {
let mut buf = vec![];
std::io::stdin().read_to_end(&mut buf)?;
buf
};

if args.pem {
data = pem::parse(data)?.into_contents();
}

let v = asn1::parse_single::<asn1parse::Value<'_>>(&data)?;
v.render(&mut std::io::stdout().lock(), 0)?;

Ok(())
}
8 changes: 3 additions & 5 deletions src/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,17 @@ impl Tag {

// Long form tag
if value == 0x1f {
let result = base128::read_base128_int(data).map_err(|e| {
let large_value;
(large_value, data) = base128::read_base128_int(data).map_err(|e| {
if matches!(e.kind(), ParseErrorKind::ShortData { .. }) {
e
} else {
ParseError::new(ParseErrorKind::InvalidTag)
}
})?;
// MSRV of 1.59 required for `(value, data) = ...;`
value = result
.0
value = large_value
.try_into()
.map_err(|_| ParseError::new(ParseErrorKind::InvalidTag))?;
data = result.1;
// Tags must be encoded in minimal form.
if value < 0x1f {
return Err(ParseError::new(ParseErrorKind::InvalidTag));
Expand Down

0 comments on commit d7a9904

Please sign in to comment.