How to decode DER-encoded X.509 certificate subject in perl?

86 views Asked by At

300f310d300b0603550403130446616B65

The above is hex bytes of a DER-encoded X.509 certificate subject. when it is decoded, it should return string "Fake" which is certificate subject. How to do it in perl.

1

There are 1 answers

4
pts On BEST ANSWER

This is easy to decode manually:

  • 30: data type: SEQUENCE (OF)
  • 0f: 15 bytes of data follows
    • 31: data type: SET (OF)
    • 0d: 13 bytes of data follows
      • 30: data type: SEQUENCE (OF)
      • 0b: 11 bytes of data follows
        • 06: data type: OBJECT IDENTIFIER
        • 03: 3 bytes of data follows
          • 550403: data: 2.5.4.3 == commonName
        • 13: data type: PrintableString
        • 04: 4 bytes of data follows
          • 46616b65: data: "Fake"

Just to double-check, the output of perl -e "print pack qw/H* 300f310d300b0603550403130446616b65/" | openssl asn1parse -inform DER is:

    0:d=0  hl=2 l=  15 cons: SEQUENCE          
    2:d=1  hl=2 l=  13 cons: SET               
    4:d=2  hl=2 l=  11 cons: SEQUENCE          
    6:d=3  hl=2 l=   3 prim: OBJECT            :commonName
   11:d=3  hl=2 l=   4 prim: PRINTABLESTRING   :Fake

Here is an ad hoc parser:

$_ = pack('H*', '300f310d300b0603550403130446616b65');
s@^\x30.\x31.\x30.\x06(.)@@s or die;
length >= ord($1) or die;
substr($_, 0, ord($1)) = "";
s@^\x13.@@s or die;
print "$_\n";  #: Fake

Let's try to parse it with the Perl DER parser library Convert::ASN1. For that we need type definitions for the ->prepare(...) method. Let's try to guess the types.

I copy-pasted 300f310d300b0603550403130446616b65 to an online ASN.1 decoder: https://lapo.it/asn1js/#MA8xDTALBgNVBAMTBEZha2U , with the following result:

Certificate SEQUENCE (1 elem)
  tbsCertificate TBSCertificate [?] SET (1 elem)
    serialNumber CertificateSerialNumber [?] SEQUENCE (2 elem)
      OBJECT IDENTIFIER 2.5.4.3 commonName (X.520 DN component)
      PrintableString Fake

The keywords like tbsCertificate and CertificateSerialNumber pointed me to https://www.rfc-editor.org/rfc/rfc5280, but I noticed that the types above were autodetected incorrectly, because CertificateSerialNumber is an INTEGER there. By looking at the RFC document, I guessed these types:

RDNSequence = SEQUENCE (1 elem)
  RelativeDistinguishedName = SET (1 elem)
    AttributeTypeAndValue = SEQUENCE (2 elem)
      type OBJECT IDENTIFIER = 2.5.4.3 commonName (X.520 DN component)
      value PrintableString = Fake

The corresponding ASN.1 type definitions extracted from the RFC document https://www.rfc-editor.org/rfc/rfc5280:

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   

Let's try to feed these types to the ->prepare(...) method of the Perl module Convert::ASN1. With a Google search for Convert::ASN1 RDNSequence, I've found this example code: https://github.com/gbarr/perl-Convert-ASN1/blob/master/examples/x509decode , based on this and the Convert::ASN1 documentation I wrote the following Perl script:

use warnings;
use strict;
use Convert::ASN1;  # sudo apt-get install libconvert-asn1-perl
use Data::Dumper qw();

my $asn = Convert::ASN1->new;
die "fatal: ASN.1 prepare: " . $asn->error unless $asn->prepare(q<
-- ASN.1 from RFC2459 and X.509(2001)
-- Adapted for use with Convert::ASN1

-- attribute data types --

Attribute ::= SEQUENCE {
        type                    AttributeType,
        values                  SET OF AttributeValue
                -- at least one value is required -- 
        }

AttributeType ::= OBJECT IDENTIFIER

AttributeValue ::= DirectoryString  --ANY 

AttributeTypeAndValue ::= SEQUENCE {
        type                    AttributeType,
        value                   AttributeValue
        }


-- naming data types --

Name ::= CHOICE { -- only one possibility for now 
        rdnSequence             RDNSequence                     
        }

RDNSequence ::= SEQUENCE OF RelativeDistinguishedName

DistinguishedName ::= RDNSequence

RelativeDistinguishedName ::= 
        SET OF AttributeTypeAndValue  --SET SIZE (1 .. MAX) OF


-- Directory string type --

DirectoryString ::= CHOICE {
        teletexString           TeletexString,  --(SIZE (1..MAX)),
        printableString         PrintableString,  --(SIZE (1..MAX)),
        bmpString               BMPString,  --(SIZE (1..MAX)),
        universalString         UniversalString,  --(SIZE (1..MAX)),
        utf8String              UTF8String,  --(SIZE (1..MAX)),
        ia5String               IA5String  --added for EmailAddress
        }
>);

my $asn_rdns = $asn->find('RDNSequence');
my $der = $_ = pack('H*', '300f310d300b0603550403130446616b65');
my $value = $asn_rdns->decode($der);
die "fatal: ASN.1 decode: " . $asn_rdns->error unless $value;
print Data::Dumper::Dumper($value);

Output:

$VAR1 = [
          [
            {
              'value' => {
                           'printableString' => 'Fake'
                         },
              'type' => '2.5.4.3'
            }
          ]
        ];

Here you go.