SEQUENCE EncodingsThe SEQUENCE type along with the SET and SET OF types are by far the most encom-passing types to develop.They require all of the ASN.1 functions including them as well!
Trang 1belonged to (e.g., IA5 or PRINTABLE), a single encode/decoder could be written with an
additional input argument specifying which target code we are trying to use
For the sake of simplicity, the routines demonstrated were implemented individually
They are fairly small and in the grand scheme of things are not significant code size
contributors
UTCTIME Encodings
UTCTIME encoding has been simplified in the 2002 specifications to only include one
format.The time is encoded as a string in the format “YYMMDDHHMMSSZ” using two
digits per component
The year is encoded by examining the last two digits Nothing beyond the year 2069can be encoded with this format as it will be interpreted as 1970 (of course, by that time we
can just reprogram the software to treat it as 2070) Despite the Y2K debacle, they still used
two digits for the date
The actual byte encoding of the string is using the ASCII code, and fortunately, we have
to look no further than our PRINTABLE STRING routines for help
To efficiently handle dates we require some structure to hold the parameters We couldjust pass all six of them as arguments to the function However, a more useful way (as we
shall see when dealing with SEQUENCE types) is to have a structure.This structure is
stored in our ASN.1 header file
Trang 2This function stores an integer in a two-digit ASCII representation.The number must
be in the range 0–99 for this function to succeed It updates the output pointer, which as weshall see in the next function is immediately useful
We are re-using the der_printable_char_encode() to convert our integers to the ASCIIcode required
025 int der_utctime_encode( UTCTIME *in,
034 /* store header and length */
035 der_put_header_length(&out, ASN1_DER_UTCTIME, 13, outlen);
036
037 /* store data */
Trang 3double-OR bars (||) Specifically, that is always implemented from left to right and will
abort on the first nonzero return value without proceeding to the next case
This means, for example, that if after encoding the month the routine fails, it will notencode the rest and will proceed inside to the braced statements directly
Trang 4This function decodes two bytes into the numerical value they represent It borrows thePRINTABLE STRING routines again to decode the bytes to characters It returns thenumber in the “dest” field and signals errors by the return code We will use the same trick
in the decoder to cascade a series of reads, which is why storing the result to a pointer vided by the caller is important
pro-033 int der_utctime_decode(unsigned char *in,
Trang 5SEQUENCE Encodings
The SEQUENCE type along with the SET and SET OF types are by far the most
encom-passing types to develop.They require all of the ASN.1 functions including them as well!
We will demonstrate relatively simple SEQUENCE encodings and omit SET types forbrevity.The complete listing includes routines to handle SET types and the reader is encour-
aged to seek them out
The first thing we need before we can encode a SEQUENCE is some way of senting it in the C programming language In this case, we are going to use an array of a
repre-structure to represent the elements of the SEQUENCE
This is the structure for a SEQUENCE element type (it will also be used for SET
types).The type indicates the ASN.1 type of the element, length the length or size of the
value, and data is a pointer to the native representation of the given data type An array of
this type describes the elements of a SEQUENCE
The length and data fields have different meanings depending on what the ASN.1 typeis.Table 2.8 describes their use
Table 2.8Definitions for the ASN.1_List Type
ASN.1 Type Meaning of “Data” Meaning of “Length”
BIT STRING Pointer to array of unsigned char Number of bits
OCTET STRING Pointer to array of unsigned char Number of octets
OBJECT IDENTIFIER Pointer to array of unsigned long Number of words in the OID
IA5 STRING Pointer to array of unsigned char Number of characters
PRINTABLE STRING Pointer to array of unsigned char Number of characters
UTCTIME Pointer to a UTCTIME structure Ignored
SEQUENCE Pointer to an array of asn1_list Number of elements in the
listThe use of the length field takes on a dual role depending on whether we are encodingfor decoding For all but the SEQUENCE type where the length is not ignored, the length
Trang 6specifies the maximum output size when decoding It will be updated by the decoder to theactual length of the object decoded (in terms of what you would pass to the encoder).For example, if you specify a length of 16 for a BIT STRING element and the decoderplaces a 7 in its place, that means that the decoder read a BIT STRING of seven bits.
We will also define a macro that is handy for creating lists at runtime, but first let usproceed through the SEQUENCE functions
der_sequence_length.c:
001 #include “asn1.h"
002 unsigned long der_sequence_paylen(asn1_list *list,
to be encoded, whereas, for example, in the case of OID, length means the number of words
in the OID encoding
Trang 7058 unsigned long der_sequence_length(asn1_list *list,
002 int der_sequence_encode(asn1_list *list,
006 {
007 unsigned long i, x;
009
010 /* check output size */
011 if (der_sequence_length(list, length) > *outlen) {
Trang 8017 der_sequence_paylen(list, length),
019
020 /* now encode each element */
021 for (i = 0; i < length; i++) {
055 err = der_null_encode(out, &x);
Trang 9071 if (err < 0) return err;
084 err = der_utctime_encode(list[i].data, out, &x);
002 int der_sequence_decode(unsigned char *in,
Trang 10022 for (i = 0; i < length; i++) {
051 ret = der_null_decode(in, payload_length);
Trang 11Second, the decoding structure must match exactly or the decoding routine will abort.
When we are encoding a structure, the calling application has to understand what it is
encoding.The lists being encoded are meant to be generated at runtime instead of at
com-pile time.This allows us a great level of flexibility as to how we interact with the modifiers
One of the key aspects of the implementation is that it allows new ASN.1 types to be
sup-ported by adding a minimal amount of code to these three routines
Despite the ability to generate encoding SEQUENCEs at runtime, decoding is stillproblematic.The only way to use these functions to decode speculative SEQUENCEs is to
continuously update the list using decoding failures as feedback For example, if an element
is listed as DEFAULT, then the de facto decoding list will have the element If the decoding
fails, the next logical step is to attempt decoding without the default item present
For simple SEQUENCEs this can work, but as they grow in size (such as an X.509 tificate), this approach is completely inappropriate One solution to this would be to have a
cer-more complete decoder that understands the modifiers and can perform the required
ele-ment lookahead (much like a parser in any programming language who lookahead for
tokens) to speculatively decode However, this is actually much more work than the ideal
solution requires
Trang 12ASN.1 Flexi Decoder
Our approach to this problem is to employ a Flexible Decoder (A.K.A.The Flexi Decoder),which decodes the ASN.1 data on the fly and generates its own linked list of ASN.1 objects.This allows us to decode essentially any ASN.1 data as long as we recognize the ASN.1 typesencoded.The linked list grows in two directions—left to right and parent to child.The left
to right links are for elements that are at the same depth level, and the parent to child linksare for depth changes (e.g., SEQUENCE)
This is our Flexi list, which resembles the asn1_list except that it has the doubly linked
list pointers inside it From a caller’s point of view, they simply pass in an array of bytes andget as output a linked list structure for all the elements decoded
While this solution allows us to easily decode arbitrary ASN.1 data, it still does notimmediately yield a method of parsing it First, we present the decoding algorithm, and inthe next section present how to parse with it
der_flexi_decode.c:
001 #include “asn1.h"
002 #include <stdlib.h>
003 int der_flexi_decode(unsigned char *in,
006 {
007 asn1_flexi *list, *tlist;
008 unsigned long len, x, y;
We start with an empty list (line 11) and handle its construction later We then proceed
to parse data as long as there are at least two bytes left.This allows us to soft error out when
we hit the end of the perceived ASN.1 data
014 /* make sure list points to a valid node */
015 if (list == NULL) {
016 list = calloc(1, sizeof(asn1_flexi));
017 if (list == NULL) return -3;
019 list->next = calloc(1, sizeof(asn1_flexi));
Trang 13walk the list in any direction.
028 /* decode the payload length */
The encoded payload length immediately reveals this For other types such as BIT STRING
and OID, their maximum size can be calculated from the payload length, which is good
enough for our purposes
048 case ASN1_DER_BOOLEAN: /* BOOLEAN */
049 list->type = ASN1_DER_BOOLEAN;
051 list->data = calloc(1, sizeof(int));
052 if (list->data == NULL) { goto MEM_ERR; }
053
054 err = der_boolean_decode(in, inlen, list->data);
055 if (err < 0) { goto DEC_ERR; }
Trang 14063 list->data = calloc(1, sizeof(long));
064 if (list->data == NULL) { goto MEM_ERR; }
065
066 err = der_integer_decode(in, inlen, list->data);
067 if (err < 0) { goto DEC_ERR; }
068
069 len = der_integer_length(*((long *)list->data));
076 calloc(list->length, sizeof(unsigned char));
077 if (list->data == NULL) { goto MEM_ERR; }
087 case ASN1_DER_OCTETSTRING: /* OCTET STRING */
Trang 15120 len = der_oid_length(list->data, list->length);
Similar as for the BIT STRING, we have to estimate the length here In this case, weuse the fact that there cannot be more OID words than there are bytes of payload (the
smallest word encoding is one byte).The wasted memory here can be several words, which
for most circumstances will require to a trivial amount of storage
The interested reader may want to throw a realloc() call in there to free up the unusedwords
123 case ASN1_DER_PRINTABLESTRING: /* PRINTABLE STRING */
Trang 16157 list->length = 1;
158 list->data = calloc(1, sizeof(UTCTIME));
159 if (list->data == NULL) { goto MEM_ERR; }
160
161 err = der_utctime_decode(in, inlen, list->data);
162 if (err < 0) { goto DEC_ERR; }
171 /* we know the length of the objects in
173 err = der_flexi_decode(in+2, len, &list->child);
174 if (err < 0) { goto DEC_ERR; }
175 list->child->parent = list;
176
177 /* len is the payload length, we have
When we encounter a type we do not recognize (line 181), we do not give a fatal error;
we simply reset the lengths, terminate the decoding, and exit We have to set the payloadlength to zero (line 184) to avoid having the subtraction of payload (line 187) going belowzero
We use the der_*_length() function to compute the length of the decoded type and
store the value back into len By time we get to the end (line 187), we know how much to
move the input pointer up by and decrease the remaining input length by
Trang 17At this point, we may have added one too many nodes to the list.This is determined byhaving a type of zero, which is not a valid ASN.1 type If this is the case, we get the previous
link, free the leaf node, and update the pointer (lines 193 through 196)
Putting It All Together
At this point, we have all the code we require to implement public key (PK) standards such
as PKCS or ANSI X9.62.The first thing we must master is working with SEQUENCEs No
common PK standard will use the ASN.1 types (other than SEQUENCE) directly
Building Lists
Essentially container ASN.1 type is an array of the asn1_list type So let us examine how we
convert a simple container to code these routines can use
Trang 18asn1_set(mylist, i++, type, length, data);
If we did not first make a copy of the index, it would be different in every instance itwas used during the macro Similarly, we could have
asn1_set(mylist++, 0, type, length, data);
The use of the do-while loop, allows us to use the macro as a single C statement Forexample:
if (x > 0)
asn1_set(mylist, x, type, length, data);
Now we can reconsider the RSAKey definition
asn1_set(RSAKey, 0, ASN1_DER_INTEGER, 1, &N);
asn1_set(RSAKey, 1, ASN1_DER_INTEGER, 1, &E);
This is much more sensible and pleasant to the eyes
Now suppose we have the RSA key with a modulus N=17*13=221 and E=7 and want
to encode this First, we need a place to store the key
unsigned char output[100];
unsigned char output_length;
Now we can encode the key Let us put the entire example together
asn1_list RSAKey[2];
unsigned char output[100];
unsigned char output_length;
Trang 19asn1_set(RSAKey, 0, ASN1_DER_INTEGER, 1, &N);
asn1_set(RSAKey, 1, ASN1_DER_INTEGER, 1, &E);
printf("We encoded a SEQUENCE into %lu bytes\n", output_length);
At this point the array output[0 output_length – 1] contains the DER encoding of the
RSAKey SEQUENCE
Nested Lists
Handling SEQUENCEs within a SEQUENCE is essentially an extension of what we
already know Let us consider the following ASN.1 construction
User := SEQUENCE {
Name PRINTABLE STRING,
Credentials SEQUENCE { passwdHash OCTET STRING }
}
As before, we build two lists Here is the complete example
asn1_list User[3], Credentials;
unsigned char output[100];
unsigned char output_length;
unsigned char Name[MAXLEN+1], passwdHash[HASHLEN];
/* build the first list */
asn1_set(User, 0, ASN1_DER_PRINTABLESTRING, strlen(Name), Name);
asn1_set(User, 1, ASN1_DER_INTEGER, 1, &Age);
asn1_set(User, 2, ASN1_DER_SEQUENCE, 1, &Credentials);
/* build second list */
asn1_set(Credentials, 0, ASN1_DER_OCTETSTRING, HASHLEN, passwdHash);
/* encode it */
Trang 20output_length = sizeof(output);
err = der_sequence_encode(User, 3, output, &output_length);
if (err < 0) { printf("Error encoding %d\n", err); exit(EXIT_FAILURE); }
When building the first list we pass a pointer to the second list (the third entry in theUser array).The corresponding “1” marked in the length field for the third entry is actuallythe length of the second list.That is, if the Credentials list had two items we would see a two
in place of the one
Note that we encode the entire construction by invoking the encoder once with theUser list.The encoder will follow the pointer to the second list and encode it in turn aswell
Now we have to setup the list
asn1_set(User, 0, ASN1_DER_PRINTABLESTRING, sizeof(Name)-1, Name);
asn1_set(User, 1, ASN1_DER_INTEGER, 1, &Age);
asn1_set(User, 2, ASN1_DER_BITSTRING, sizeof(Flags), Flags);
Note that we are using sizeof(Name)-1 and not just the size of the object.This allows us
to have a trailing NUL byte so that the C string functions can work on the data upondecoding it
Let us put this entire example together:
unsigned char Name[MAXNAMELEN+1], Flags[MAXFLAGSLEN];
asn1_list User[3];
asn1_set(User, 0, ASN1_DER_PRINTABLESTRING, sizeof(Name)-1, Name);
asn1_set(User, 1, ASN1_DER_INTEGER, 1, &Age);
asn1_set(User, 2, ASN1_DER_BITSTRING, sizeof(Flags), Flags);
memset(Name, 0, sizeof(Name));
err = der_sequence_decode(input, input_len, &User, 3);
if (err < 0) {
Trang 21printf("Error decoding the sequence: %d\n", err);
exit(EXIT_FAILURE);
}
printf("Decoded the sequence\n");
printf("User Name[%lu] == [%s]\n", User[0].length, Name);
printf("Age == %ld\n", Age);
This example assumes that some array of unsigned char called input has been providedand its length is input_len Upon successful decoding, the program output will display the
user name and the age For instance, the output may resemble the following
Decoded the sequence
User Name[3] == [Tom]
Age == 24
As see the User array is updated for specific elements such as the STRING types.
User[0].length will hold the length of the decoded value Note that in our example we first
memset the array to zero.This allows us to use the decoded array as a valid C string
regard-less of the length
FlexiLists
As we discussed in the previous section the SEQUENCE decoder is not very amenable to
speculative encodings.The flexi decoder allows us to decode ASN.1 data without knowing
the order or even the types of the elements that were encoded
While the flexi decoder allows for speculative decoding, it does not allow for speculativeparsing What we are given by the decoder is simply a multi-way doubly linked list Each
node of the list contains the ASN.1 type, its respective length parameter and a pointer (if
appropriate) to the decoding of the data (in a respective format)
The encodings do not tell us which element of the constructed type we are looking at
For example, consider the following SEQUENCE
pres-we will get two depths to the list.The first depth will contain just the SEQUENCE and the
child of that node will be a list of up to three items (see Figure 2.4)
Trang 22Figure 2.4Organization of the Flexi Decoding of RSAKey
Suppose we store the outcome in MyKey such as the following:
asn1_flexi *MyKey;
der_flexi_decode(keyPacket, keyPacketLen, &MyKey);
In this instance, MyKey would point to the SEQUENCE node of the list Its next and
prev pointers will be NULL and it will have a pointer to a child node.The child node will be
the “D” INTEGER, if it is present, and it will have a pointer to the “E” INTEGER through
the next pointer.
Given the MyKey pointer, we can move to the child node with the following code:
MyKey = MyKey->next; /* now we point to "E" */
MyKey = MyKey->parent; /* this is invalid */
Now how do we know if we have a private or public key? The simplest method in thisparticular case is to count the number of children:
If the x variable is two, it is a public key; otherwise, it is a private key This approach
works well for simple OPTIONAL types but only if there is one OPTIONAL element.Similarly, for CHOICE modifiers we cannot simply look at the length but need also theSequence