DER Serialization Problem
Hi all,
I noticed two potential problems when serializing a custom SAN containing a directory name using the picky_asn1_der crate.
Please, read them below.
Potential Problem 1
According to RFC 5280, the SAN should follow:
id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
SubjectAltName ::= GeneralNames
GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
GeneralName ::= CHOICE {
otherName [0] OtherName,
rfc822Name [1] IA5String,
dNSName [2] IA5String,
x400Address [3] ORAddress,
directoryName [4] Name,
ediPartyName [5] EDIPartyName,
uniformResourceIdentifier [6] IA5String,
iPAddress [7] OCTET STRING,
registeredID [8] OBJECT IDENTIFIER }
OtherName ::= SEQUENCE {
type-id OBJECT IDENTIFIER,
value [0] EXPLICIT ANY DEFINED BY type-id }
EDIPartyName ::= SEQUENCE {
nameAssigner [0] DirectoryString OPTIONAL,
partyName [1] DirectoryString }
and:
Name ::= CHOICE { -- only one possibility for now --
rdnSequence RDNSequence }
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName ::=
SET SIZE (1..MAX) OF AttributeTypeAndValue
AttributeTypeAndValue ::= SEQUENCE {
type AttributeType,
value AttributeValue }
AttributeType ::= OBJECT IDENTIFIER
AttributeValue ::= ANY -- DEFINED BY AttributeType
DirectoryString ::= CHOICE {
teletexString TeletexString (SIZE (1..MAX)),
printableString PrintableString (SIZE (1..MAX)),
universalString UniversalString (SIZE (1..MAX)),
utf8String UTF8String (SIZE (1..MAX)),
bmpString BMPString (SIZE (1..MAX)) }
But when implementing it in Rust, the sequence tag for RDNSequence disappears. Please, see the code snippet below.
let rdn_sequence = RdnSequence::from(vec![RelativeDistinguishedName::from(vec![
AttributeTypeAndValue::new_common_name("test"),
])]);
let der_rdn_sequence = picky_asn1_der::to_vec(&rdn_sequence).unwrap();
println!("DER RDN sequence name: {}", hex::encode(der_rdn_sequence));
let der_general_names =
picky_asn1_der::to_vec(&GeneralNames::from(vec![GeneralName::DirectoryName(Name(
rdn_sequence,
))]))
.unwrap();
println!("DER general names: {}", hex::encode(der_general_names));
The output is:
DER RDN sequence name: 300f310d300b06035504030c0474657374
DER general names: 3011840f310d300b06035504030c0474657374
where 300f disappeared from the serialized value. Did I miss anything?
Potential Problem 2
Another question, as I'm constructing the SAN, shouldn't the tag for Name be 0xa4 instead of 0x84 in the output above?
If I try to visualize the SAN content with tag 0x84 using the openssl CLI, it dumps binary, but with tag 0xa4 it parses the DER content as expected.
Hi!
The output is:
DER RDN sequence name: 300f310d300b06035504030c0474657374 DER general names: 3011840f310d300b06035504030c0474657374where
300fdisappeared from the serialized value. Did I miss anything?
I think this is normal, because the directory name variant is defined as implicitly tagged.
This means that the original tag (said universal tag) for the SEQUENCE type (0x30) is not specified and only the context-specific tag [4] is encoded (0x84). When using explicit tagging, both the context-specific tag and the universal tag are encoded.
We know that it’s implicitly tagged by looking at the title of the module:
A.2. Implicitly Tagged Module, 1988 Syntax
And this line:
DEFINITIONS IMPLICIT TAGS ::=
More on this: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/#explicit-vs-implicit
Another question, as I'm constructing the SAN, shouldn't the tag for
Namebe0xa4instead of0x84in the output above? If I try to visualize the SAN content with tag0x84using the openssl CLI, it dumps binary, but with tag0xa4it parses the DER content as expected.
About this, on the other hand, we do have a long-standing issue:
- https://github.com/Devolutions/picky-rs/pull/78#issuecomment-789904165
But this has been mitigated:
- https://github.com/Devolutions/picky-rs/pull/92
In this case, I believe this should be a context-specific tag (0x8X) and not an application tag (0xAX).
When an application tag must be used, it’s written with the APPLICATION prefix, like in this definition:
CountryName ::= [APPLICATION 1] CHOICE {
x121-dcc-code NumericString
(SIZE (ub-country-name-numeric-length)),
iso-3166-alpha2-code PrintableString
(SIZE (ub-country-name-alpha-length)) }
The certificate for github.com is using a context-specific tag as expected:
82 0A 67 69 74 68 75 62 2E 63 6F 6D
For [2] github.com
All that said, I’ve sometimes seen application tags used instead of context tags and vice versa.
Hi @CBenoit!
Thanks for the reply and sorry for the delay! I'm still trying to figure out this puzzle.
I think this is normal, because the directory name variant is defined as implicitly tagged.
Is this the case because, even though we see GeneralNames in the implicit module as you mentioned, I thought the CHOICE type would imply an explicit tag encoding. This is based on the below [1, 2]:
30.6 The tagging construction specifies explicit tagging if any of the following holds: a) the "Tag EXPLICIT Type" alternative is used; b) the "Tag Type" alternative is used and the value of "TagDefault" for the module is either "EXPLICIT TAGS" or is empty; c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is "IMPLICIT TAGS" or "AUTOMATIC TAGS", but the type defined by "Type" is a choice type, open type, or a "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3).
How do you understand that?
In this case, I believe this should be a context-specific tag (0x8X) and not an application tag (0xAX).
0xAX should still be a context-specific tag, is that correct? Because we only set bit 6 to constructed instead of primitive.
That would also make sense if we assume that CHOICE is explicitly encoded. That is, in that case, we see the bit constructed set.
I'm still trying to understand why openssl does not recognize the custom SAN I wrote.
The Rust code I wrote is mainly this:
let der = picky_asn1_der::to_vec(&GeneralNames::from(vec![GeneralName::DirectoryName(Name(
RdnSequence::from(vec![RelativeDistinguishedName::from(vec![
AttributeTypeAndValue::new_common_name("TEST"),
])]),
))])).unwrap();
resulting in the DER bytes [30, 11, 84, 0F, 31, 0D, 30, 0B, 06, 03, 55, 04, 03, 0C, 04, 54, 45, 53, 54], and the openssl output:
Requested Extensions:
X509v3 Subject Alternative Name:
0...U....TEST 0...1
However, if I do a hack adding a SEQUENCE tag as a placeholder to account for the implicit encoding (to keep track of the length) and then replace it with the intended tag, I get the desired output:
let mut der =
picky_asn1_der::to_vec(&vec![vec![vec![RelativeDistinguishedName::from(vec![
AttributeTypeAndValue::new_common_name("TEST"),
])]]]).unwrap();
der[2] = 0xa4;
resulting in the DER bytes [30, 13, A4, 11, 30, 0F, 31, 0D, 30, 0B, 06, 03, 55, 04, 03, 0C, 04, 54, 45, 53, 54], and the openssl output:
Requested Extensions:
X509v3 Subject Alternative Name:
DirName:/CN=TEST
[1] https://www.itu.int/ITU-T/studygroups/com10/languages/X.680_1297.pdf [2] https://stackoverflow.com/questions/63997789/encoding-of-implicit-and-explicit-tags-in-asn-1