Devolutions / picky-rs

Picky portable PKI implementation and microservice
Apache License 2.0
52 stars 26 forks source link

DER Serialization Problem #249

Open tabajara-scontain opened 10 months ago

tabajara-scontain commented 10 months ago

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.

CBenoit commented 9 months ago

Hi!

The output is:

DER RDN sequence name:     300f310d300b06035504030c0474657374
DER     general names: 3011840f310d300b06035504030c0474657374

where 300f disappeared 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 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.

About this, on the other hand, we do have a long-standing issue:

But this has been mitigated:

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.

tabajara-scontain commented 6 months ago

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