ASN.1 macros -- SSLeay 0.9.0b -- January 1999

NAME

ASN.1 macros for building your own i2d/d2i/new functions

SYNOPSIS

#include "asn1_mac.h"

M_ASN1_D2I_vars(a,type,func)

M_ASN1_D2I_Init()

M_ASN1_D2I_Finish_2(a)

M_ASN1_D2I_Finish(a,func,e)

M_ASN1_D2I_start_sequence()

M_ASN1_D2I_end_sequence()

M_ASN1_D2I_get(b,func)

M_ASN1_D2I_get_opt(b,func,type)

M_ASN1_D2I_get_IMP_opt(b,func,tag,type)

M_ASN1_D2I_get_set(r,func)

M_ASN1_D2I_get_IMP_set_opt(b,func,tag)

M_ASN1_D2I_get_seq(r,func)

M_ASN1_D2I_get_seq_opt(r,func)

M_ASN1_D2I_get_IMP_set(r,func,x)

M_ASN1_D2I_get_imp_set(r,func,a,b)

M_ASN1_D2I_get_set_strings(r,func,a,b)

M_ASN1_D2I_get_EXP_opt(r,func,tag)

M_ASN1_D2I_get_set_EXP_opt(r,func,tag,b)

M_ASN1_New_Malloc(ret,type)

M_ASN1_New(arg,func)

M_ASN1_New_Error(a)

M_ASN1_next

M_ASN1_next_prev

M_ASN1_I2D_vars(a)

M_ASN1_I2D_len(a,f)

M_ASN1_I2D_len_IMP_opt(a,f)

M_ASN1_I2D_len_SET(a,f)

M_ASN1_I2D_len_SEQ(a,f)

M_ASN1_I2D_len_SEQ_opt(a,f)

M_ASN1_I2D_len_IMP_set(a,f,x)

M_ASN1_I2D_len_IMP_set_opt(a,f,x)

M_ASN1_I2D_len_EXP_opt(a,f,mtag,v)

M_ASN1_I2D_len_EXP_set_opt(a,f,mtag,tag,v)

M_ASN1_I2D_put(a,f)

M_ASN1_I2D_put_IMP_opt(a,f,t)

M_ASN1_I2D_put_SET(a,f)

M_ASN1_I2D_put_IMP_set(a,f,x)

M_ASN1_I2D_put_SEQ(a,f)

M_ASN1_I2D_put_SEQ_opt(a,f)

M_ASN1_I2D_put_IMP_set_opt(a,f,x)

M_ASN1_I2D_put_EXP_opt(a,f,tag,v)

M_ASN1_I2D_put_EXP_set_opt(a,f,mtag,tag,v)

M_ASN1_I2D_seq_total()

M_ASN1_I2D_INF_seq_start(tag,ctx)

M_ASN1_I2D_INF_seq_end()

M_ASN1_I2D_finish()

DESCRIPTION

The best way to explain these functions is to show them in use. You should have read ASN.1 introduction and overview and i2d, d2i -- ASN.1 conversion to and from DER-encoded form before looking at this documentation.

These macros are meant to be used as building blocks to create your own ASN1_*_new, i2d_* and d2i_* functions.

We'll look at x_req.c, ignoring the code that deals with attributes, since it is not essential to our discussion.

Leaving out the attributes, the rest of the X509_REQ structure is defined as:

typedef struct X509_req_info_st
        {
        ASN1_INTEGER *version;
        X509_NAME *subject;
        X509_PUBKEY *pubkey;
        } X509_REQ_INFO;

Writing *_new functions

Let's start with X509_REQ_new(). Why write code for this when we have macros?

M_ASN1_New_Malloc can be used to do the initial malloc for the main structure.

M_ASN1_New can be used to do the mallocs for subfields (version, subject, pubkey).

M_ASN1_New_Error does standardized error handling in case the initial malloc fails.

Here's the code for each of these macros:

#define M_ASN1_New_Malloc(ret,type) 
        if ((ret=(type *)Malloc(sizeof(type))) == NULL) goto err2;
#define M_ASN1_New(arg,func) 
        if (((arg)=func()) == NULL) return(NULL)
#define M_ASN1_New_Error(a) 
        err2:   ASN1err((a),ERR_R_MALLOC_FAILURE); \
                return(NULL)

M_ASN1_New expects arg to be the subfield you want malloced anf func is the function to call to do it.

As you can see by the error handling, it is assumed that these secondary funcs func all hasve their own error handling (standard or otherwise) built in.

Now you can look at the relevant code from X509_REQ_new (skipping over the attributes stuff, as usual):

X509_REQ_INFO *X509_REQ_INFO_new()
        {
        X509_REQ_INFO *ret=NULL;

        M_ASN1_New_Malloc(ret,X509_REQ_INFO);
        M_ASN1_New(ret->version,ASN1_INTEGER_new);
        M_ASN1_New(ret->subject,X509_NAME_new);
        M_ASN1_New(ret->pubkey,X509_PUBKEY_new);
        return(ret);
        M_ASN1_New_Error(ASN1_F_X509_REQ_INFO_NEW);
        }

How easy was that??

Writing i2d_* functions

What do we need to do? First, set up some initial variables internal to the function; we do this with M_ASN1_I2D_vars.

Then, get the length of the der-encoded object each subfield will be written into; we can use some version of M_ASN1_I2D_len for that.

If the whole structure is going to be a SEQUENCE, then we need to figure out how many bytes the wrapper for SEQUENCE is going to be; we call M_ASN1_I2D_seq_total for this. That will alsowrite the wrapper itself, so that we are in a position to start writing the subfields in DER-encoded form.

Now, to write each of the subfields, we call some version of M_ASN1_I2D_put.

And finally, we finish up by calling M_ASN1_I2D_finish.

Let's look at the macro code before we look at the actual i2d_X509_REQ.

Here's M_ASN1_I2D_vars:

#define M_ASN1_I2D_vars(a)
   int r=0,ret=0; \
   unsigned char *p; \
   if (a == NULL) return(0)

This sets up a pointer which will move along the DER-encoded string we create. It also sets up initial return values (recall that an id function returns the size of the DER-encoded object it wrote or would write).

Now, on to the various M_ASN1_I2D_len macros:

#define M_ASN1_I2D_len(a,f)     ret+=f(a,NULL)
#define M_ASN1_I2D_len_IMP_opt(a,f)     if (a != NULL) M_ASN1_I2D_len(a,f)

#define M_ASN1_I2D_len_SET(a,f) 
   ret+=i2d_ASN1_SET(a,NULL,f,V_ASN1_SET,V_ASN1_UNIVERSAL);

#define M_ASN1_I2D_len_SEQ(a,f) 
   ret+=i2d_ASN1_SET(a,NULL,f,V_ASN1_SEQUENCE,V_ASN1_UNIVERSAL);

#define M_ASN1_I2D_len_SEQ_opt(a,f) 
   if ((a != NULL) && (sk_num(a) != 0)) \
      M_ASN1_I2D_len_SEQ(a,f);

#define M_ASN1_I2D_len_IMP_set(a,f,x) 
   ret+=i2d_ASN1_SET(a,NULL,f,x,V_ASN1_CONTEXT_SPECIFIC);

#define M_ASN1_I2D_len_IMP_set_opt(a,f,x) 
   if ((a != NULL) && (sk_num(a) != 0)) \
      ret+=i2d_ASN1_SET(a,NULL,f,x,V_ASN1_CONTEXT_SPECIFIC);

#define M_ASN1_I2D_len_EXP_opt(a,f,mtag,v) 
   if (a != NULL)\
   { \
      v=f(a,NULL); \
      ret+=ASN1_object_size(1,v,mtag); \
   }

#define M_ASN1_I2D_len_EXP_set_opt(a,f,mtag,tag,v) 
   if ((a != NULL) && (sk_num(a) != 0))\
   { \
      v=i2d_ASN1_SET(a,NULL,f,tag,V_ASN1_UNIVERSAL); \
      ret+=ASN1_object_size(1,v,mtag); \
   }

The first of these, M_I2D_len(), is what you call when your subfield is not a SEQUENCE or SET or some other funny thing. See asn1/p7_evp.c for a function that calls something else (look at how it handles a->recipientinfo).

These basically all do the same thing, however; they call func as the i2d conversion function on the subfield of your structure which you have passed as a; they don't pass any string to write into, so they collect the size that the DER-encoded string *would* be and add it to the length (ret) to be returned to you.

Thus you can call several of these in a row and get the total length of the object that would be written.

Now, if all these subfields are supposed to be written into a SEQUENCE you need to call M_ASN1_I2D_seq_total:

#define M_ASN1_I2D_seq_total() 
    r=ASN1_object_size(1,ret,V_ASN1_SEQUENCE); \
    if (pp == NULL) return(r); \
    p= *pp; \
    ASN1_put_object(&p,1,ret,V_ASN1_SEQUENCE,V_ASN1_UNIVERSAL)

This figures out how much room the identifier octet and length octets for SEQUENCE are going to take and adds that to the length. It then either returns it, because we were given a NULL to scribble data into, indicating that the caller really just wanted to know the size in advance, or it writes those SEQUENCE bytes and advances the pointer to the next byte to write into.

ASN1_object_size gets the number of bytes the identifier octet and length octets will be, and ASN1_put_object actually writes the identifier and length octets.

Note that p and pp are hardcoded into these macros; if you have to fool with them then you had better use those same names.

Now we are ready to write out all the subfields with one of the M_I2D_put macros:

#define M_ASN1_I2D_put(a,f)     f(a,&p)

#define M_ASN1_I2D_put_IMP_opt(a,f,t)   \
   if (a != NULL) \
   { \
     unsigned char *q=p; \
     f(a,&p); \
     *q=(V_ASN1_CONTEXT_SPECIFIC|t|(*q&V_ASN1_CONSTRUCTED));\
   }

#define M_ASN1_I2D_put_SET(a,f) 
   i2d_ASN1_SET(a,&p,f,V_ASN1_SET,V_ASN1_UNIVERSAL)

#define M_ASN1_I2D_put_IMP_set(a,f,x) 
   i2d_ASN1_SET(a,&p,f,x,V_ASN1_CONTEXT_SPECIFIC)

#define M_ASN1_I2D_put_SEQ(a,f) 
   i2d_ASN1_SET(a,&p,f,V_ASN1_SEQUENCE,V_ASN1_UNIVERSAL)

#define M_ASN1_I2D_put_SEQ_opt(a,f)
   if ((a != NULL) && (sk_num(a) != 0)) \
      M_ASN1_I2D_put_SEQ(a,f);

#define M_ASN1_I2D_put_IMP_set_opt(a,f,x) 
   if ((a != NULL) && (sk_num(a) != 0)) \
       { i2d_ASN1_SET(a,&p,f,x,V_ASN1_CONTEXT_SPECIFIC); }

#define M_ASN1_I2D_put_EXP_opt(a,f,tag,v) 
   if (a != NULL) \
   { \
      ASN1_put_object(&p,1,v,tag,V_ASN1_CONTEXT_SPECIFIC); \
      f(a,&p); \
   }

#define M_ASN1_I2D_put_EXP_set_opt(a,f,mtag,tag,v) 
   if ((a != NULL) && (sk_num(a) != 0)) \
   { \
      ASN1_put_object(&p,1,v,mtag,V_ASN1_CONTEXT_SPECIFIC); \
      i2d_ASN1_SET(a,&p,f,tag,V_ASN1_UNIVERSAL); \
   }

The first of these, M_I2D_put(), is what you call when your subfield is not a SEQUENCE or SET or some other funny thing. See asn1/p7_evp.c for a function that calls something else (look at how it handles a->recipientinfo).

These basically all do the same thing, however; they call f as the i2d conversion function on the subfield of your structure which you have passed as a; and they pass

p to write into, which should get incremented by your function f to point to the next byte to write into after this field has been written out.

Thus you can call several of these in a row and get a nice concatenation of objects in your string.

Now we are ready to finish up:

#define M_ASN1_I2D_finish()     
   *pp=p; \
   return(r);

We pass p back to the user (pointing to the end of the string we just wrote), and return the size of the thing we just wrote.

Now, let's see what i2d_X509_REQ looks like (again, leaving out stuff to do with the attributes):

int i2d_X509_REQ_INFO(a,pp)
X509_REQ_INFO *a;
unsigned char **pp;
        {
        M_ASN1_I2D_vars(a);

        M_ASN1_I2D_len(a->version,              i2d_ASN1_INTEGER);
        M_ASN1_I2D_len(a->subject,              i2d_X509_NAME);
        M_ASN1_I2D_len(a->pubkey,               i2d_X509_PUBKEY);
        M_ASN1_I2D_seq_total();
        M_ASN1_I2D_put(a->version,              i2d_ASN1_INTEGER);
        M_ASN1_I2D_put(a->subject,              i2d_X509_NAME);
        M_ASN1_I2D_put(a->pubkey,               i2d_X509_PUBKEY);
        M_ASN1_I2D_finish();
        }

Here's the reprisal in a nutshell: set up initial vars; get the length of each subfield; get the length of the SEQUENCE id and length octets and write them out; write out each subfield; update the pointer and return the size of the DER-encoding.

Writing d2i_* functions

What do we need to do? First, set up some initial variables internal to the function; we do this with M_ASN1_D2I_vars and then M_ASN1_D2I_Init.

If the whole structure is a SEQUENCE, then we need to read past the SEQUENCE identifier octets and length octets, using M_ASN1_D2I_start_sequence.

Now, to read each of the subfields, we call some version of M_ASN1_D2I_get.

And finally, we finish up by calling M_ASN1_D2I_Finish.

Let's look at the macro code before we look at the actual i2d_X509_REQ. Here's M_ASN1_D2I_vars:

#define M_ASN1_D2I_vars(a,type,func) 
   ASN1_CTX c; \
   type ret=NULL; \
   \
   c.pp=pp; \
   c.error=ASN1_R_ERROR_STACK; \
   if ((a == NULL) || ((*a) == NULL)) \
      { if ((ret=(type)func()) == NULL) goto err; } \
   else    ret=(*a);

This sets up a pointer to keep track of our starting place in the DER-encoded string. It also mallocs a for us if needed, using the function func provided (this should be some sort of *_new() function.)

Here's M_ASN1_D2I_Init:

#define M_ASN1_D2I_Init() 
   c.p= *pp; \
   c.max=(length == 0)?0:(c.p+length);

This sets up a pointer which will move along the DER-encoded string we read. It also sets up the maximum number of bytes we will try to parse.

Now, if this object is a SEQUENCE, you need to call M_ASN1_D2I_start_sequence:

#define M_ASN1_D2I_start_sequence() \
        if (!asn1_GetSequence(&c,&length)) goto err;

This uses the internal function asn1_GetSequence to process the id and length octets for SEQUENCE and put the total length expected into an internal counter; it also updates length to reflect that those bytes were already read.

Now we are ready to read in all the subfields with one of the M_D2I_get macros:

#define M_ASN1_D2I_get(b,func) 
   c.q=c.p; \
   if (func(&(b),&c.p,c.slen) == NULL) goto err; \
   c.slen-=(c.p-c.q);

#define M_ASN1_D2I_get_opt(b,func,type) 
   if ((c.slen != 0) && ((M_ASN1_next & (~V_ASN1_CONSTRUCTED)) \
      == (V_ASN1_UNIVERSAL|(type)))) \
   { \
      M_ASN1_D2I_get(b,func); \
   }

#define M_ASN1_D2I_get_IMP_opt(b,func,tag,type) 
   if ((c.slen != 0) && ((M_ASN1_next & (~V_ASN1_CONSTRUCTED)) == \
      (V_ASN1_CONTEXT_SPECIFIC|(tag)))) \
   { \
      unsigned char tmp; \
      tmp=M_ASN1_next; \
      M_ASN1_next=(tmp& ~V_ASN1_PRIMATIVE_TAG)|type; \
      M_ASN1_D2I_get(b,func); \
      M_ASN1_next_prev=tmp; \
   }

#define M_ASN1_D2I_get_set(r,func) 
   M_ASN1_D2I_get_imp_set(r,func,V_ASN1_SET,V_ASN1_UNIVERSAL);

#define M_ASN1_D2I_get_IMP_set_opt(b,func,tag) 
   if ((c.slen != 0) && \
       (M_ASN1_next == \
         (V_ASN1_CONTEXT_SPECIFIC|V_ASN1_CONSTRUCTED|(tag))))\
   { \
       M_ASN1_D2I_get_imp_set(b,func,tag,V_ASN1_CONTEXT_SPECIFIC); \
   }

#define M_ASN1_D2I_get_seq(r,func) 
   M_ASN1_D2I_get_imp_set(r,func,V_ASN1_SEQUENCE,V_ASN1_UNIVERSAL);

#define M_ASN1_D2I_get_seq_opt(r,func) 
   if ((c.slen != 0) && (M_ASN1_next == (V_ASN1_UNIVERSAL| \
      V_ASN1_CONSTRUCTED|V_ASN1_SEQUENCE)))\
   { M_ASN1_D2I_get_seq(r,func); }

#define M_ASN1_D2I_get_IMP_set(r,func,x) 
   M_ASN1_D2I_get_imp_set(r,func,x,V_ASN1_CONTEXT_SPECIFIC);

#define M_ASN1_D2I_get_imp_set(r,func,a,b) 
   c.q=c.p; \
   if (d2i_ASN1_SET(&(r),&c.p,c.slen,(char *(*)())func,a,b) == NULL) \
      goto err; \
   c.slen-=(c.p-c.q);

#define M_ASN1_D2I_get_set_strings(r,func,a,b) 
   c.q=c.p; \
   if (d2i_ASN1_STRING_SET(&(r),&c.p,c.slen,a,b) == NULL) \
      goto err; \
   c.slen-=(c.p-c.q);

#define M_ASN1_D2I_get_EXP_opt(r,func,tag) 
   if ((c.slen != 0L) && (M_ASN1_next == \
      (V_ASN1_CONSTRUCTED|V_ASN1_CONTEXT_SPECIFIC|tag))) \
   { \
      int Tinf,Ttag,Tclass; \
      long Tlen; \
      \
      c.q=c.p; \
      Tinf=ASN1_get_object(&c.p,&Tlen,&Ttag,&Tclass,c.slen); \
      if (Tinf & 0x80) \
         { c.error=ASN1_R_BAD_OBJECT_HEADER; goto err; } \
      if (func(&(r),&c.p,Tlen) == NULL) \
         goto err; \
      c.slen-=(c.p-c.q); \
   }

#define M_ASN1_D2I_get_EXP_set_opt(r,func,tag,b) 
   if ((c.slen != 0) && (M_ASN1_next == \
      (V_ASN1_CONSTRUCTED|V_ASN1_CONTEXT_SPECIFIC|tag))) \
   { \
      int Tinf,Ttag,Tclass; \
      long Tlen; \
      \
      c.q=c.p; \
      Tinf=ASN1_get_object(&c.p,&Tlen,&Ttag,&Tclass,c.slen); \
      if (Tinf & 0x80) \
         { c.error=ASN1_R_BAD_OBJECT_HEADER; goto err; } \
      if (d2i_ASN1_SET(&(r),&c.p,Tlen,(char *(*)())func, \
          b,V_ASN1_UNIVERSAL) == NULL) \
         goto err; \
      c.slen-=(c.p-c.q); \
   }

The first of these, M_D2I_get(), is what you call when your subfield is not a SEQUENCE or SET or some other funny thing. See asn1/p7_evp.c for a function that calls something else (look at how it handles a->recipientinfo).

These basically all do the same thing, however; they call func as the d2i conversion function to write into your subfield structure which you have passed as b; the internal length counter is updated; and the pointer itself should have been updated by func to point to the next piece to process.

Thus you can call several of these in a row and read in consecutive pieces of your DER-encdoded string.

Some of the above macros rely on M_ASN1_next:

#define M_ASN1_next          (*c.p)

This just returns 'pointer to next piece to process'.

Some of them rely on M_ASN1_next_prev:

#define M_ASN1_next_prev     (*c.q)

This just returns 'pointer to the last piece we just processed'.

Note that it is important to use the same variable names pp and length in your code that calls these macros, as these names are hard-coded into some of the macros that don't take arguments.

Now we are ready to finish up:

#define M_ASN1_D2I_Finish(a,func,e) \
        M_ASN1_D2I_Finish_2(a); \
err:\
        ASN1err((e),c.error); \
        asn1_add_error(*pp,(int)(c.q- *pp)); \
        if ((ret != NULL) && ((a == NULL) || (*a != ret))) func(ret); \
        return(NULL)

#define M_ASN1_D2I_Finish_2(a) \
        if (!asn1_Finish(&c)) goto err; \
        *pp=c.p; \
        if (a != NULL) (*a)=ret; \
        return(ret);

Ok, this looks a little funny. Some functions however call only M_ASN1_D2I_Finish_2 because they need their own error handling section instead of the standard one.

M_ASN1_D2I_Finish (or actually M_ASN1_D2I_Finish_2) uses the internal function asn1_Finish to check lengths and look for EOS (end of sequence) octets and other details that might be necessary.

Then the pointer pp gets set past the end of the whole thing we processed, a gets the new structure all filled in, and we return to the user.

Now, let's see what d2i_X509_REQ looks like (again, leaving out stuff to do with the attributes):

X509_REQ_INFO *d2i_X509_REQ_INFO(a,pp,length)
X509_REQ_INFO **a;
unsigned char **pp;
long length;
        {
        M_ASN1_D2I_vars(a,X509_REQ_INFO *,X509_REQ_INFO_new);

        M_ASN1_D2I_Init();
        M_ASN1_D2I_start_sequence();
        M_ASN1_D2I_get(ret->version,d2i_ASN1_INTEGER);
        M_ASN1_D2I_get(ret->subject,d2i_X509_NAME);
        M_ASN1_D2I_get(ret->pubkey,d2i_X509_PUBKEY);
        M_ASN1_D2I_Finish(a,X509_REQ_INFO_free,ASN1_F_D2I_X509_REQ_INFO);
        }

Here's the reprisal in a nutshell: set up initial vars and do malloc; read the SEQUENCE id and length octets; read in each substructure; update the pointer to the DER-encoded string, and return pointer to the new structure.

The macros

M_ASN1_D2I_end_sequence
M_ASN1_I2D_INF_seq_start
M_ASN1_I2D_INF_seq_end
are all used for constructed SEQUENCEs that don't have an explicit length at the beginning but have an END OF SEQUENCE octet at the end; you should see the DER/BER tutorial for more information about this.