The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Convert::ASN1::asn1c - A perl module to convert ASN1 to XML and back, using the asn1c tools enber and unber.

SYNOPSIS

To use this module you need a xml template for the ASN1 PDU's you want to encode/decode. For now we assume we have a file named "test-pdu.xml" in the current working directory with the following content (read "DESCRIPTION" for information on how to create such a template):

        <C O="0" T="[1]" TL="2" V="12">
            <P O="2" T="[UNIVERSAL 2]" TL="2" V="1" A="INTEGER">$integer1</P>
            <P O="5" T="[UNIVERSAL 2]" TL="2" V="2" A="INTEGER">$integer2</P>
            <C O="9" T="[UNIVERSAL 16]" TL="2" V="3" A="SEQUENCE">
                <P O="11" T="[UNIVERSAL 10]" TL="2" V="1" A="ENUMERATED">$enumerated1</P>
            </C O="14" T="[UNIVERSAL 16]" A="SEQUENCE" L="5">
        </C O="14" T="[1]" L="14">

Now we can use this file together with Convert::ASN1::asn1c as shown:

        use Convert::ASN1::asn1c;
        
        my $pdu = "A1 0C 02 01 01 02 02 00 D3 30 03 0A 01 02";
        $pdu =~ s/ //g;
        $pdu = pack('H*', $pdu);

        # Now we have a binary ASN1 protocol data unit (PDU) in $pdu.
        # Typically you would read such data i.e., from a socket of course.

        my $conv = Convert::ASN1::asn1c->new();
        
        # Now let's decode this pdu, assuming it is a pdu which corresponds
        # to the test-pdu.xml file created earlier.

        my $values = $conv->decode("test-pdu.xml", $pdu);
        print $values->{'integer2'} . "\n";   # prints '211' for this example

        # Now let's change some values, use the same number of bytes to store this value as before
        $values->{'integer2'} = $conv->encode_integer(210, $values->{'integer2_length'});

        # and encode it into a binary ASN1 PDU again
        my $pdu_new = $conv->encode("test-pdu.xml", $values);

DESCRIPTION

Abstract Syntax Notation One (ASN1) is a protocol for data exchange by applications, defined by the ITU-T. It works as follows: All parties agree on a ASN1 specification for the Protocol Data Units (PDUs). Such a specification might look like:

        AARQ-apdu ::= [APPLICATION 0] IMPLICIT SEQUENCE {
            application-context-name        [1]     Application-context-name,
            sender-acse-requirements        [10]    IMPLICIT ACSE-requirements          OPTIONAL,
            calling-authentication-value    [12]    EXPLICIT Authentication-value       OPTIONAL,
            user-information                [30]    IMPLICIT Association-information    OPTIONAL
        }

        Application-context-name ::= SEQUENCE { foo OBJECT IDENTIFIER }
        ACSE-requirements ::= BIT STRING
        Authentication-value ::= CHOICE { external [2] IMPLICIT PrivatExtPassword }
        PrivatExtPassword ::= [UNIVERSAL 8] IMPLICIT SEQUENCE { encoding EncodingPassword } 
        ...

Now every party (that is aware of this specification) can take some data and encode it (using standardized encoding rules) - Every other party will be able to decode the information afterwards.

A module that does exactly this is Convert::ASN1. However, this approach has a slight problem if you just want to receive a ASN1 encoded data unit, modify a few values and send the modified PDU somewhere, for example during development, testing or fuzzing of ASN1 processing entities: Sometimes you don't have the ASN1 specification for that device.

In that case you can try to reverse engineer it, which is error prone and tiresome. One tool that can assist you with that is the open source ASN1 compiler asn1c. It comes with two tools, unber and enber. The unber program takes a binary pdu and tries to decode it to xml (without a matching ASN1 specification) just using the encoding information present in the binary ASN1 data. Due to the nature of BER-encoded (the most widely used encoding standard) data, this is almost always possible. The only information that might get lost is the description what kind of data we are dealing with, i.e., if we should interpret the data with a hex value of 0x31 as an 1-byte integer or a 1-char character string.

The enber tool can read the xml created by unber and convert it back into a binary ASN1 pdu. Of course it is possible to edit the xml in between this process to change some values. This is exactly what this module does.

Suppose you sniffed a data packet from somewhere (for example from a Siemens HiPath PBX, from which you know it uses the CSTA protocol, which itself uses ASN1 PDUs). You dumped the data in a file called pdu-siemens.bin for analysis.

    $ hexdump  pdu-siemens.bin
    0000000 0ca1 0102 0201 0002 30d3 0a03 0201     
    000000e

Now use the unber tool to decode this file:

    $ unber -p pdu-siemens.bin
    <C O="0" T="[1]" TL="2" V="12">
          <P O="2" T="[UNIVERSAL 2]" TL="2" V="1" A="INTEGER">&#x01;</P>
      <P O="5" T="[UNIVERSAL 2]" TL="2" V="2" A="INTEGER">&#x00;&#xd3;</P>
      <C O="9" T="[UNIVERSAL 16]" TL="2" V="3" A="SEQUENCE">
        <P O="11" T="[UNIVERSAL 10]" TL="2" V="1" A="ENUMERATED">&#x02;</P>
      </C O="14" T="[UNIVERSAL 16]" A="SEQUENCE" L="5">
   </C O="14" T="[1]" L="14">

The -p option instructs unber to generate xml that enber can understand. Now let's assume we want to take control over the two integer values, maybe because we want to change their values and see what happens or we want to examine their values in similar PDUs. We create a template with the following content:

    <C O="0" T="[1]" TL="2" V="12">
          <P O="2" T="[UNIVERSAL 2]" TL="2" V="1" A="INTEGER">$integer1</P>
      <P O="5" T="[UNIVERSAL 2]" TL="2" V="2" A="INTEGER">$integer2</P>
      <C O="9" T="[UNIVERSAL 16]" TL="2" V="3" A="SEQUENCE">
        <P O="11" T="[UNIVERSAL 10]" TL="2" V="1" A="ENUMERATED">&#x02;</P>
      </C O="14" T="[UNIVERSAL 16]" A="SEQUENCE" L="5">
   </C O="14" T="[1]" L="14">

And save it as "test-pdu.xml". Now we can use this module to read and create simillar PDUs.

        use Convert::ASN1::asn1c;
        
        my $pdu = "A1 0C 02 01 01 02 02 00 D3 30 03 0A 01 02";
        $pdu =~ s/ //g;
        $pdu = pack('H*', $pdu);

        my $conv = Convert::ASN1::asn1c->new();
        my $values = $conv->decode("test-pdu.xml", $pdu);
        print $values->{'integer2'} . "\n";   # prints '211' for this example

        # Now let's change some values, use the same number of bytes to store this value as before
        $values->{'integer2'} = $conv->encode_integer(210, $values->{'integer2_length'});

        # and encode it into a binary ASN1 PDU again
        my $pdu_new = $conv->encode("test-pdu.xml", $values);

Of course this is a quick hack and not a real protocol implementation. But quick hacks can be extremely usefull during protocol implementations. :-D

EXPORT

None by default.

METHODS

new()

Create a new ASN1 converter object

set_templatedir("./xmltemplates")

Set a directory where the xml templates for later encoding/decoding can be found

enable_sizecorr()

It is easily possible to produce invalid ASN1 packets with this module if you specify incorrect sizes for the values in your template. If you turn on automatic size correction with this function, such errors are automatically corrected for you. Note that automatic size correction is turned on by default.

disable_sizecorr()

It is easily possible to produce invalid ASN1 packets with this module if you specify incorrect sizes for the values in your template. If you turn off automatic size correction with this function, such errors are NOT automatically corrected for you. Note that automatic size correction is turned on by default.

$pdu = encode('pduname', { 'value1'=>encode_integer(42, 1), 'value2'=>encode_bitstring("10010") } );

The encode function takes the name of a template (the directory where to find those templates can be modified with set_templatedir($dir)) and a reference to a hash which's keys are names (the same that occur in the template) and values with which these variables in the template should be substituted.

Note that these values have to be in xml format. To encode perl scalars into the correct format you can use the encoding functions provided by this module.

The return value is the (binary) ASN1 PDU.

$pdu = sencode($xmltemplate, { 'value1'=>encode_integer(42, 1), 'value2'=>encode_bitstring("10010") } );

The sencode function takes a template and a reference to a hash which's keys are names (the same that occur in the template) and values with which these variables in the template should be substituted.

It works the same way as the encode() function but it directly takes the xml template as the first argument instead of a filename.

$values = decode('pduname', $pdu);

The decode function takes the name of a template (the directory where to find those templates can be modified with set_templatedir($dir)) and a binary pdu.

It will match the variables in the template against the decoded binary pdu and return a reference to a hash which contains these values.

For each variable $myvalue the hash will contain four keys:

$values->{'myvalue'}

The decoded value if we could "guess" myvalues type because it was specified as i.e. INTEGER or BIT STRING in the asn1 pdu.

$values->{'myvalue_orig'}

The original value as it was found in the unber -p output. Note that these values are still xml-encoded. To decode them you can use this modules decode_-functions or write your own decoders if the provided ones are not sufficient.

$values->{'myvalue_length'}

The length of $myvalue as it was encoded in the asn1 pdu. This value is needed for some _decode routines and can also be usefull if you write your own decoder functions.

$values->{'myvalue_type'}

If the type of $myvalue is specified in the pdu, for example as INTEGER, this key contains the value.

$values = sdecode($xml_template, $pdu);

The sdecode function takes a template and a binary pdu. It works the same way as the decode function, but it directly takes the template as it's first argument instead of a filename.

$tagpths = get_tagpaths_with_prefix($pdu, $prefix);

A ASN1 PDU is contains constructed and primitive datatypes. Constructed datatypes can contain other constructed or primitive datatypes. Each datatype (constructed or primitive) is identified by a tag.

This function decodes the pdu and constructs "tag paths": If a constructed datatype with tag "foo" contains a constructed datatype "bar" and a primitive datatype "moo". The constructed datatype "bar" contains a primitive datatype "frob", we have the following xml structure:

    <C ... T="foo">
        <C ... T="bar">
            <P ... T="frob"> ... </P>
        </C ... T="bar">
        <P ... T="moo"> ... </P>
    </C ... T="foo">

In that case we have the following "tag paths": foo, foo|bar, foo|bar|frob, foo|moo. This function returns all tag paths that match the given prefix. In the returned tag paths (as well as in the prefix) single tags have to be concatenated by the pipe character '|'.

Note that this function doesn't require a name or a xml template for a PDU. It's primary usage is to decide which template should be used to extract values from a PDU.

The result is returned as a reference to an array which contains the matching tag paths.

Encoding Functions

$xml = encode_bitstring("1010100")

Takes a string which contains 0's and 1's and encodes this binary string into xml understandable by enber(1).

$xml = encode_octet_string("foo")

Takes a perl string and encodes it as an ASN1 "OCTET STRING" in the xml format understandable by enber(1).

$xml = encode_hextxt2xml("DEADBEEF")

Takes a perl string which containts the characters [0-9] and [A-F] or [a-f], interprets this string as a hexadecimal value and encodes it in the xml format understandable by enber(1).

$xml = encode_integer(42, 4)

Takes a integer and a size and encodes the integer in the xml format understandable by enber(1). The size specifies how many bytes should be used to encode the integer in ASN1.

Decoding Functions

$bitstr = decode_bitstring($vals->{'myvalue_orig'})

Takes a ASN1 BIT STRING value in the format returned by unber(1) or this modules decode function and converts it into a perl string such as "101001".

$time = decode_timestamp($vals->{'myvalue_orig'})

Takes a ASN1 value of the type GeneralizedTimestamp in the format returned by unber(1) or this modules decode function and converts it into a perl string such as "2010-09-25 11:35:10" (year-month-day hour:minute:seconds).

$val = decode_octet_string($vals->{'myvalue_orig'})

Takes a ASN1 value of the type OCTET STRING in the format returned by unber(1) or this modules decode function and converts it into a perl scalar.

$int = decode_integer($vals->{'myvalue_orig'}, $vals->{'myvalue_length'})

Takes a ASN1 value of the type INTEGER in the format returned by unber(1) or this modules decode function and converts it into a perl scalar.

$hex = decode_xml2hextxt($vals->{'myvalue_orig'});

Takes any value in the format returned by unber(1) or this modules decode function and converts it into a string which consists of this values hex representation. This is usefull for opaque objects like identifiers, where you don't really know what they mean but still want to display and compare them.

SEE ALSO

ASN1 is specified in ITU-T publications X.680 - X.690, freely accessible at http://www.itu.int/rec/T-REC-X/e.

The open source ASN1 compiler asn1c can be downloaded from http://lionet.info/asn1c/, it includes the man pages for unber(1) and enber(1).

AUTHOR

Timo Schneider, <timos@informatik.tu-chemnitz.de>

COPYRIGHT AND LICENSE

Copyright (C) 2010 by Timo Schneider

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available.