Skip to content

Commit 8301598

Browse files
committed
Support for UtcDate and other string types.
1 parent c30a2ab commit 8301598

File tree

6 files changed

+198
-22
lines changed

6 files changed

+198
-22
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
authors = ["Tanveer Wahid <twahid@keeta.com>"]
33
edition = "2021"
44
name = "asn1-napi-rs"
5-
version = "1.1.3"
5+
version = "1.1.4"
66

77
[lib]
88
crate-type = ["cdylib"]

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@keetapay/asn1-napi-rs",
3-
"version": "1.1.3",
3+
"version": "1.1.4",
44
"homepage": "https://github.com/KeetaPay/asn1-napi-rs#readme",
55
"author": "Tanveer Wahid <twahid@keeta.com>",
66
"description": "KeetaPay ASN.1 TypeScript-Rust NAPI library",

src/asn1.rs

Lines changed: 160 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use napi::{
77
use num_bigint::BigInt;
88
use rasn::{
99
ber::{decode, encode},
10-
types::{Any, BitString, Class, OctetString, PrintableString},
10+
types::{
11+
Any, BitString, BmpString, Class, GeneralString, Ia5String, NumericString, OctetString,
12+
PrintableString, UniversalString, Utf8String, VisibleString,
13+
},
1114
Decode, Tag,
1215
};
1316

@@ -222,7 +225,17 @@ impl ASN1Decoder {
222225
/// Convert to a string.
223226
#[napi]
224227
pub fn into_string(&self) -> Result<String> {
225-
Ok(self.decode::<PrintableString>()?.as_str().into())
228+
Ok(match *self.get_tag() {
229+
Tag::PRINTABLE_STRING => self.decode::<PrintableString>()?.as_str().into(),
230+
Tag::BMP_STRING => self.decode::<BmpString>()?.as_str().into(),
231+
Tag::GENERAL_STRING => self.decode::<GeneralString>()?.as_str().into(),
232+
Tag::IA5_STRING => self.decode::<Ia5String>()?.as_str().into(),
233+
Tag::VISIBLE_STRING => self.decode::<VisibleString>()?.as_str().into(),
234+
Tag::NUMERIC_STRING => self.decode::<NumericString>()?.as_str().into(),
235+
Tag::UNIVERSAL_STRING => self.decode::<UniversalString>()?.as_str().into(),
236+
Tag::UTF8_STRING => self.decode::<Utf8String>()?.as_str().into(),
237+
_ => bail!(ASN1NAPIError::UnknownStringFormat),
238+
})
226239
}
227240

228241
/// Convert to a date.
@@ -351,21 +364,17 @@ impl TryFrom<Vec<u8>> for ASN1Decoder {
351364

352365
#[cfg(test)]
353366
mod test {
367+
use std::collections::VecDeque;
354368
use std::str::FromStr;
355369

356370
use chrono::{DateTime, FixedOffset, TimeZone, Utc};
357371
use num_bigint::BigInt;
358372
use rasn::types::BitString;
359373

360-
use crate::asn1::ASN1Decoder;
361-
use crate::asn1::ASN1Encoder;
362-
use crate::cast_data;
363-
use crate::objects::ASN1Context;
364-
use crate::objects::ASN1Object;
365-
use crate::objects::ASN1Set;
366-
use crate::objects::ASN1OID;
367-
use crate::types::ASN1Data;
368-
use crate::types::JsType;
374+
use crate::asn1::*;
375+
use crate::objects::*;
376+
use crate::types::*;
377+
use crate::*;
369378

370379
const TEST_VOTE: &str = "MFEGCWCGSAFlAwQCCDBEBCCb0PJlcOIUeBZH8vNeObY9pg\
371380
xw+6PUh6ku6n9k9VVYDgQge0hOYtjbsjyJqqx5m7D8iP+i\
@@ -377,6 +386,20 @@ mod test {
377386
v3MD2DK+so3K1BuRAgEKAkEA66ba0QK07zVrshYkOF3cO\
378387
aW61T1ckn9QymeSBE+yE7EJPDnrN6g54KxBaAjRVFlT3i\
379388
Ze4qTtQfXRoCkhoCgzqg==";
389+
const TEST_CERT: &str = "MIIB3jCCAYWgAwIBAgIBATAKBggqhkjOPQQDAjBEMQswCQ\
390+
YDVQQGEwJVUzELMAkGA1UECBMCQ0ExDjAMBgNVBAoTBUtl\
391+
ZXRhMRgwFgYDVQQDEw9ub2RlMS5rZWV0YS5jb20wHhcNMj\
392+
IxMTAzMDEyOTU4WhcNMjcwNTExMDEyOTU4WjBiMQswCQYD\
393+
VQQGEwJVUzELMAkGA1UECAwCQ0ExFDASBgNVBAcMC0xvcy\
394+
BBbmdlbGVzMQ4wDAYDVQQKDAVLZWV0YTEgMB4GA1UEAwwX\
395+
Y2xpZW50MS5ub2RlMS5rZWV0YS5jb20wVjAQBgcqhkjOPQ\
396+
IBBgUrgQQACgNCAAQ3605beUhS+2ZGuk4OkQ2utb239l2g\
397+
kAl4tgKp1JFyujP8aNZ5Zh7nnfB64eWCOHtaGIXHYeXlYf\
398+
+rZ9KfnULdo00wSzAdBgNVHQ4EFgQUGKqtzLuSNICC4hId\
399+
Fc3a7QdIkhMwHwYDVR0jBBgwFoAUeqmWlg9mdQnXDtFiV8\
400+
uXgiCC8yswCQYDVR0TBAIwADAKBggqhkjOPQQDAgNHADBE\
401+
AiB/sWgSvLZSddTHD64sWgPDgQSnWXxjfIzcoP1W48lZng\
402+
IgazAF+38D5aIrcmtnD2YEp5i1ydiYzxKCU1RFAZf540c=";
380403

381404
fn fixture_get_test_vote() -> Vec<ASN1Data> {
382405
vec![
@@ -445,6 +468,110 @@ mod test {
445468
]
446469
}
447470

471+
fn fixture_get_test_cert() -> Vec<ASN1Data> {
472+
vec![
473+
ASN1Data::Array(vec![
474+
ASN1Data::Object(ASN1Object::Context(ASN1Context {
475+
value: 0,
476+
contains: Box::new(ASN1Data::Integer(2)),
477+
})),
478+
ASN1Data::Integer(1),
479+
ASN1Data::Array(vec![ASN1Data::Object(ASN1Object::Oid(ASN1OID::new(
480+
"sha256WithEcDSA",
481+
)))]),
482+
ASN1Data::Array(vec![
483+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(ASN1OID::new("2.5.4.6"), "US"))),
484+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(ASN1OID::new("2.5.4.8"), "CA"))),
485+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(
486+
ASN1OID::new("2.5.4.10"),
487+
"Keeta",
488+
))),
489+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(
490+
ASN1OID::new("commonName"),
491+
"node1.keeta.com",
492+
))),
493+
]),
494+
ASN1Data::Array(vec![
495+
ASN1Data::Date(DateTime::<FixedOffset>::from(
496+
Utc.ymd(2022, 11, 03).and_hms_milli(1, 29, 58, 0),
497+
)),
498+
ASN1Data::Date(DateTime::<FixedOffset>::from(
499+
Utc.ymd(2027, 05, 11).and_hms_milli(1, 29, 58, 0),
500+
)),
501+
]),
502+
ASN1Data::Array(vec![
503+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(ASN1OID::new("2.5.4.6"), "US"))),
504+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(ASN1OID::new("2.5.4.8"), "CA"))),
505+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(
506+
ASN1OID::new("2.5.4.7"),
507+
"Los Angeles",
508+
))),
509+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(
510+
ASN1OID::new("2.5.4.10"),
511+
"Keeta",
512+
))),
513+
ASN1Data::Object(ASN1Object::Set(ASN1Set::new(
514+
ASN1OID::new("commonName"),
515+
"client1.node1.keeta.com",
516+
))),
517+
]),
518+
ASN1Data::Array(vec![
519+
ASN1Data::Array(vec![
520+
ASN1Data::Object(ASN1Object::Oid(ASN1OID::new("ecdsa"))),
521+
ASN1Data::Object(ASN1Object::Oid(ASN1OID::new("secp256k1"))),
522+
]),
523+
ASN1Data::Object(ASN1Object::BitString(ASN1RawBitString::new(
524+
BitString::from_vec(vec![
525+
0x04, 0x37, 0xEB, 0x4E, 0x5B, 0x79, 0x48, 0x52, 0xFB, 0x66, 0x46, 0xBA,
526+
0x4E, 0x0E, 0x91, 0x0D, 0xAE, 0xB5, 0xBD, 0xB7, 0xF6, 0x5D, 0xA0, 0x90,
527+
0x09, 0x78, 0xB6, 0x02, 0xA9, 0xD4, 0x91, 0x72, 0xBA, 0x33, 0xFC, 0x68,
528+
0xD6, 0x79, 0x66, 0x1E, 0xE7, 0x9D, 0xF0, 0x7A, 0xE1, 0xE5, 0x82, 0x38,
529+
0x7B, 0x5A, 0x18, 0x85, 0xC7, 0x61, 0xE5, 0xE5, 0x61, 0xFF, 0xAB, 0x67,
530+
0xD2, 0x9F, 0x9D, 0x42, 0xDD,
531+
]),
532+
))),
533+
]),
534+
ASN1Data::Object(ASN1Object::Context(ASN1Context::new(
535+
3,
536+
ASN1Data::Array(vec![
537+
ASN1Data::Array(vec![
538+
ASN1Data::Object(ASN1Object::Oid(ASN1OID::new("2.5.29.14"))),
539+
ASN1Data::Bytes(vec![
540+
0x04, 0x14, 0x18, 0xAA, 0xAD, 0xCC, 0xBB, 0x92, 0x34, 0x80, 0x82,
541+
0xE2, 0x12, 0x1D, 0x15, 0xCD, 0xDA, 0xED, 0x07, 0x48, 0x92, 0x13,
542+
]),
543+
]),
544+
ASN1Data::Array(vec![
545+
ASN1Data::Object(ASN1Object::Oid(ASN1OID::new("2.5.29.35"))),
546+
ASN1Data::Bytes(vec![
547+
0x30, 0x16, 0x80, 0x14, 0x7A, 0xA9, 0x96, 0x96, 0x0F, 0x66, 0x75,
548+
0x09, 0xD7, 0x0E, 0xD1, 0x62, 0x57, 0xCB, 0x97, 0x82, 0x20, 0x82,
549+
0xF3, 0x2B,
550+
]),
551+
]),
552+
ASN1Data::Array(vec![
553+
ASN1Data::Object(ASN1Object::Oid(ASN1OID::new("2.5.29.19"))),
554+
ASN1Data::Bytes(vec![0x30, 0]),
555+
]),
556+
]),
557+
))),
558+
]),
559+
ASN1Data::Array(vec![ASN1Data::Object(ASN1Object::Oid(ASN1OID::new(
560+
"sha256WithEcDSA",
561+
)))]),
562+
ASN1Data::Object(ASN1Object::BitString(ASN1RawBitString::new(
563+
BitString::from_vec(vec![
564+
0x30, 0x44, 0x02, 0x20, 0x7F, 0xB1, 0x68, 0x12, 0xBC, 0xB6, 0x52, 0x75, 0xD4,
565+
0xC7, 0x0F, 0xAE, 0x2C, 0x5A, 0x03, 0xC3, 0x81, 0x04, 0xA7, 0x59, 0x7C, 0x63,
566+
0x7C, 0x8C, 0xDC, 0xA0, 0xFD, 0x56, 0xE3, 0xC9, 0x59, 0x9E, 0x02, 0x20, 0x6B,
567+
0x30, 0x05, 0xFB, 0x7F, 0x03, 0xE5, 0xA2, 0x2B, 0x72, 0x6B, 0x67, 0x0F, 0x66,
568+
0x04, 0xA7, 0x98, 0xB5, 0xC9, 0xD8, 0x98, 0xCF, 0x12, 0x82, 0x53, 0x54, 0x45,
569+
0x01, 0x97, 0xF9, 0xE3, 0x47,
570+
]),
571+
))),
572+
]
573+
}
574+
448575
#[test]
449576
fn test_asn1_into_bool() {
450577
let encoded_true = "AQH/";
@@ -623,6 +750,28 @@ mod test {
623750
});
624751
}
625752

753+
#[test]
754+
fn test_asn1_cert_into_sequence() {
755+
let obj = ASN1Decoder::from_base64(TEST_CERT.into()).expect("base64");
756+
let js_type = *obj.get_js_type();
757+
758+
assert_eq!(js_type, JsType::Sequence);
759+
760+
let mut test = VecDeque::from(fixture_get_test_cert());
761+
let test_tbs = test.pop_front().unwrap();
762+
let test_algo = test.pop_front().unwrap();
763+
let test_sig = test.pop_front().unwrap();
764+
765+
let mut cert = obj.into_iter();
766+
let tbs = cert.next().unwrap().unwrap();
767+
let algo = cert.next().unwrap().unwrap();
768+
let sig = cert.next().unwrap().unwrap();
769+
770+
assert_eq!(tbs, test_tbs);
771+
assert_eq!(algo, test_algo);
772+
assert_eq!(sig, test_sig);
773+
}
774+
626775
#[test]
627776
fn test_asn1_encoder_to_base64() {
628777
let block = fixture_get_test_block();

src/constants.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ pub(crate) const ASN1_OBJECT_VALUE_KEY: &str = "value";
55
/// Key string for "name" attribute of objects.
66
pub(crate) const ASN1_OBJECT_NAME_KEY: &str = "name";
77
/// ASN1 Date format with milliseconds.
8-
pub(crate) const ASN1_DATE_TIME_UTC_FORMAT: &str = "%Y%m%d%H%M%S%.3fZ";
8+
pub(crate) const ASN1_DATE_TIME_GENERAL_FORMAT: &str = "%Y%m%d%H%M%S%.3fZ";
9+
/// ASN1 Date format with milliseconds.
10+
pub(crate) const ASN1_DATE_TIME_UTC_FORMAT: &str = "%y%m%d%H%M%SZ";
911
/// ASN1 null data.
1012
pub(crate) const ASN1_NULL: &[u8] = &[0x05, 0x00];

src/objects.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rasn::{
66
ber::de::DecoderOptions,
77
de::Error as rasnDeError,
88
enc::Error as rasnEncError,
9-
types::{BitString, Class, ObjectIdentifier, Oid, Open, PrintableString},
9+
types::{Any, BitString, Class, ObjectIdentifier, Oid, Open},
1010
AsnType, Decode, Decoder, Encode, Encoder, Tag,
1111
};
1212

@@ -312,10 +312,18 @@ impl Decode for ASN1Set {
312312
decoder.decode_sequence(Tag::SET, |decoder| {
313313
decoder.decode_sequence(Tag::SEQUENCE, |decoder| {
314314
let name = ObjectIdentifier::decode(decoder)?;
315-
let value = PrintableString::decode(decoder)?;
315+
let value = Any::decode(decoder)?;
316316

317317
if let Ok(oid) = ASN1OID::try_from(name.to_vec()) {
318-
Ok(Self::new(oid, value.as_str()))
318+
let asn1 = ASN1Decoder::new(value.as_bytes().to_owned());
319+
320+
if let Ok(value) = asn1.into_string() {
321+
Ok(Self::new(oid, value))
322+
} else {
323+
Err(<D as Decoder>::Error::custom(
324+
ASN1NAPIError::UnknownStringFormat,
325+
))
326+
}
319327
} else {
320328
Err(<D as Decoder>::Error::custom(ASN1NAPIError::UnknownOid))
321329
}
@@ -365,7 +373,7 @@ impl Encode for ASN1Data {
365373
date.encode(encoder)
366374
} else {
367375
date.naive_utc()
368-
.format(ASN1_DATE_TIME_UTC_FORMAT)
376+
.format(ASN1_DATE_TIME_GENERAL_FORMAT)
369377
.to_string()
370378
.encode_with_tag(encoder, Tag::GENERALIZED_TIME)
371379
}

src/utils.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ use napi::{
99
use num_bigint::{BigInt, Sign};
1010
use rasn::{ber::de::DecoderOptions, types::Utf8String, Decode, Tag};
1111

12-
use crate::{constants::ASN1_DATE_TIME_UTC_FORMAT, types::ASN1Data, ASN1NAPIError};
12+
use crate::{
13+
constants::{ASN1_DATE_TIME_GENERAL_FORMAT, ASN1_DATE_TIME_UTC_FORMAT},
14+
types::ASN1Data,
15+
ASN1NAPIError,
16+
};
1317

1418
/// Get utf16 bytes from a string.
1519
pub(crate) fn get_utf16_from_string<T: AsRef<str>>(value: T) -> Vec<u16> {
@@ -26,6 +30,7 @@ pub(crate) fn get_oid_elements_from_string<T: AsRef<str>>(value: T) -> Result<Ve
2630
.collect()
2731
}
2832

33+
/// Get a string representation of the OID.
2934
pub(crate) fn get_string_from_oid_elements<T: AsRef<[u32]>>(value: T) -> Result<String> {
3035
Ok(value
3136
.as_ref()
@@ -42,12 +47,24 @@ pub(crate) fn get_words_from_big_int(data: BigInt) -> (bool, Vec<u64>) {
4247
}
4348

4449
/// Helper for handling date/times with milliseconds
50+
/// TODO rasn library does not properly handle dates with milliseconds.
4551
pub(crate) fn get_utc_date_time_from_asn1_milli<T: AsRef<[u8]>>(data: T) -> Result<DateTime<Utc>> {
4652
let mut decoder = rasn::ber::de::Decoder::new(data.as_ref(), DecoderOptions::ber());
47-
48-
if let Ok(decoded) = Utf8String::decode_with_tag(&mut decoder, Tag::GENERALIZED_TIME) {
53+
let (decoded, format) = match data.as_ref().first().unwrap_or(&0) {
54+
0x17 => (
55+
Utf8String::decode_with_tag(&mut decoder, Tag::UTC_TIME),
56+
ASN1_DATE_TIME_UTC_FORMAT,
57+
),
58+
0x18 => (
59+
Utf8String::decode_with_tag(&mut decoder, Tag::GENERALIZED_TIME),
60+
ASN1_DATE_TIME_GENERAL_FORMAT,
61+
),
62+
_ => bail!(ASN1NAPIError::MalformedData),
63+
};
64+
65+
if let Ok(decoded) = decoded {
4966
Ok(DateTime::<FixedOffset>::from_utc(
50-
NaiveDateTime::parse_from_str(&decoded, ASN1_DATE_TIME_UTC_FORMAT)?,
67+
NaiveDateTime::parse_from_str(&decoded, format)?,
5168
FixedOffset::east(0),
5269
)
5370
.with_timezone(&Utc))

0 commit comments

Comments
 (0)