summaryrefslogblamecommitdiffstats
path: root/scan_asn1generic.c
blob: 09c84a4dac254a78a41426200d960b97baa3d8e7 (plain) (tree)


























                                                                              
                                      


                                      



                                                                  
                          
                                          

                           

                                                                                 
         




                                                              







                                                                                                                                           

                                                                               













































                                                                                    
                                                              
                          
                                          


                                                               
                       



                                                                                                                                                     
                                                                                                                             














                                                                            
                                               

                                                                                                     
                                                              











                                        
                                                   



                                               
                                                 
                                                                                  
                                      

                                                                                
                                      

                                                                                 
                               

                                                                                    
                               




                                                                                            
                                 




                                                   
                                        
                                 
                                                   
                                                                                                   
                                 




                                                                                                   
                                 



                                
                                                 








                                                               
                                                              
                          
                                          


                                                                       
                       


                                                             
                                











                                                                                                          
                                                                                               





                                                               
                                                              
                          
                                                             


                           
                                                        
                         


                                                                                        
                         



                                                             
                                             
















                                                                           
                                  




                             
                 




                  


               
 
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include "asn1.h"
#include <string.h>

size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...) {
  size_t curlen,seqlen;
  const char* maxstack[100];
  size_t curmax=0;
  va_list args;
  int optional=0;
  unsigned long* application=NULL;
  unsigned long tag;
  enum asn1_tagclass tc;
  enum asn1_tagtype tt;
  unsigned int wantedtag;
  unsigned long* desttag=NULL;
  const char* orig=src;
  va_start(args,fmt);
  maxstack[0]=max;
  while (*fmt) {
    switch (*fmt) {
    case '?':		// ? = rest is optional (until end of sequence)
      optional=1;
      break;
    case 'B':		// B = BOOLEAN
    case 'i':		// i = INTEGER
      {
	long* dest=va_arg(args,long*);
	int* bdest=(int*)dest;
	long l;
	if (*fmt=='B') *bdest=0; else *dest=0;
	curlen=scan_asn1int(src,maxstack[curmax],&tc,&tt,&tag,&l);
	if (application) {
	  if (tc!=APPLICATION) goto error;
	  *application=tag;
	} else {
	  if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=(*fmt=='B'?BOOLEAN:INTEGER))
	    goto error;
	}
	if (!curlen) { if (optional) break; else goto error; }
	if (*fmt=='B')
	  *bdest=l;
	else
	  *dest=l;
	src+=curlen;
	application=NULL;
	break;
      }
    case 'I':		// I = INTEGER, but for bignum integers; writes to an array of size_t, first one contains number of digits after it
      {
	size_t* dest=va_arg(args,size_t*);
	size_t len,tmp,tlen,j,t;
	if (!(len=scan_asn1tag(src,maxstack[curmax],&tc,&tt,&tag))) goto error;
	if (!(tmp=scan_asn1length(src+len,maxstack[curmax],&tlen))) goto error;
	len+=tmp;
	j=0; t=1;
	src+=len;
	/* asn.1 sends n bytes, most significant first.
	 * we want m digits, most significant first.
	 * if n is not a multiple of sizeof(digit) then we need to
	 * insert a few 0 bytes in the first word
	 */
	while (tlen) {
	  j=(j<<8)+(unsigned char)(*src);
	  ++src;
	  --tlen;
	  if ((tlen%sizeof(j))==0 && (j || t>1)) {
	    dest[t]=j;
	    j=0;
	    ++t;
	  }
	}
	if (j) dest[t++]=j;
	dest[0]=t-1;
	break;
      }
    case 'b':
      wantedtag=BIT_STRING; goto stringmain;
    case 'u':
      wantedtag=UTCTIME; goto stringmain;
    case 'p':
      wantedtag=PrintableString; goto stringmain;
    case 'a':
      wantedtag=IA5String; goto stringmain;
    case 's':
      wantedtag=OCTET_STRING; goto stringmain;
stringmain:
      {
	struct string* dest;
	struct string temp;
	time_t* desttime=NULL;
	size_t i;
	if (wantedtag==UTCTIME) {
	  dest=&temp;
	  desttime=va_arg(args,time_t*);
	} else
	  dest=va_arg(args,struct string*);
	dest->l=0;
	dest->s=0;
	curlen=scan_asn1string(src,maxstack[curmax],&tc,&tt,&tag,&dest->s,&dest->l);
	if (!curlen) { if (optional) break; else goto error; }
	if (application) {
	  if (tc!=APPLICATION) goto error;
	  *application=tag;
	} else {
	  if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=wantedtag)
	    goto error;
	}
	if (wantedtag==BIT_STRING) {	// additional checks for bit strings
	  if (dest->l==0 ||	// length can't be 0 because the format starts with 1 octet that contains the number of unused bits in the last octet
	      ((unsigned char)(dest->s[0])>7) ||	// it's the number of unused bits in an octet, must be [0..7]
	      (dest->l==1 && dest->s[0])) goto error;	// if there is no last octet, there can't be any unused bits in there
	  dest->l=(dest->l-1)*8-dest->s[0];
	  dest->s+=1;
	} else if (wantedtag==PrintableString) {
	  for (i=0; i<dest->l; ++i)	// RFC 2252 section 4.1 production p
	    if (!isalnum(dest->s[i])
		&& dest->s[i]!='"'
		&& dest->s[i]!='('
		&& dest->s[i]!=')'
		&& dest->s[i]!='+'
		&& dest->s[i]!=','
		&& dest->s[i]!='-'
		&& dest->s[i]!='.'
		&& dest->s[i]!='/'
		&& dest->s[i]!=':'
		&& dest->s[i]!='?'
		&& dest->s[i]!=' ') goto error;
	} else if (wantedtag==IA5String) {
	  for (i=0; i<dest->l; ++i)	// IA5String is an ASCII string, which means 0 <= s[i] <= 127
	    if ((unsigned char)(dest->s[i]) > 127) goto error;
	} else if (wantedtag==UTCTIME) {
	  size_t j;
	  struct tm t;
	  memset(&t,0,sizeof(t));
	  /*
		YYMMDDhhmmZ
		YYMMDDhhmm+hh'mm'
		YYMMDDhhmm-hh'mm'
		YYMMDDhhmmssZ
		YYMMDDhhmmss+hh'mm'
		YYMMDDhhmmss-hh'mm'
	   */
	  if (dest->l<11 || dest->l>17) goto error;
	  j=(dest->s[0]-'0')*10+dest->s[1]-'0';
	  t.tm_year=j+(j<70)*100;

	  for (i=0; i<10; ++i)
	    if (!isdigit(dest->s[i])) goto error;
	  j=(dest->s[2]-'0')*10+dest->s[3]-'0';		// is the month plausible?
	  if (j<1 || j>12) goto error;
	  t.tm_mon=j-1;
	  j=(dest->s[4]-'0')*10+dest->s[5]-'0';		// is the day plausible?
	  if (j<1 || j>31) goto error;
	  t.tm_mday=j;
	  j=(dest->s[6]-'0')*10+dest->s[7]-'0';		// is the hour plausible?
	  if (j>23) goto error;
	  t.tm_hour=j;
	  j=(dest->s[8]-'0')*10+dest->s[9]-'0';		// is the minutes plausible?
	  if (j>59) goto error;
	  t.tm_min=j;
	  i=10;
	  if (isdigit(dest->s[10])) {
	    i+=2;
	    j=(dest->s[10]-'0')*10+dest->s[11]-'0';		// is the seconds plausible?
	    if (j>59) goto error;
	    t.tm_sec=j;
	  }
	  *desttime=mktime(&t);
	  if (dest->s[i]=='+' || dest->s[i]=='-') {
	    size_t j;
	    if (dest->l!=15) goto error;
	    for (j=i; j<i+4; ++j)
	      if (!isdigit(dest->s[j])) goto error;
	    j=(dest->s[i]-'0')*10+dest->s[i+1]-'0';		// is the offset minutes plausible?
	    if (j>59) goto error;
	    if (dest->s[i]=='+')
	      *desttime+=j*60;
	    else
	      *desttime-=j*60;
	    j=(dest->s[i+2]-'0')*10+dest->s[i+3]-'0';		// is the offset seconds plausible?
	    if (j>59) goto error;
	    if (dest->s[i]=='+')
	      *desttime+=j;
	    else
	      *desttime-=j;
	  } else if (dest->s[i]!='Z') goto error;
	}
	src+=curlen;
	application=NULL;
	break;
      }
    case 'o':		// o == OID
      {
	struct string* dest=va_arg(args,struct string*);
	curlen=scan_asn1tag(src,maxstack[curmax],&tc,&tt,&tag);
	if (!curlen) { if (optional) break; else goto error; }
	if (application) {
	  if (tc!=APPLICATION) goto error;
	  *application=tag;
	} else {
	  if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=OBJECT_IDENTIFIER)
	    goto error;
	}
	src+=curlen;
	curlen=scan_asn1length(src,maxstack[curmax],&seqlen);
	if (!curlen) goto error;
	src+=curlen;
	dest->s=src;
	dest->l=seqlen;
	src+=seqlen;
	application=NULL;
	break;
      }
    case '*':		// next tag class is APPLICATION instead of UNIVERSAL; write tag to unsigned long*
      {
	application=va_arg(args,unsigned long*);
	break;
      }
    case 'c':		// c = context specific; CONTEXT_SPECIFIC CONSTRUCTED 0, close with '}'
      desttag=va_arg(args,unsigned long*);
      // fall through
    case '[':		// [ = SET
    case '{':		// { = SEQUENCE
      {
	curlen=scan_asn1tag(src,maxstack[curmax],&tc,&tt,&tag);
	if (!curlen) { if (optional) break; else goto error; }
	if (application) {
	  if (tc!=APPLICATION || tt!=CONSTRUCTED) goto error;
	  *application=tag;
	} else {
	  if (*fmt=='c') {
	    if (tc!=CONTEXT_SPECIFIC || tt!=CONSTRUCTED)
	      goto error;
	    *desttag=tag;
	  } else {
	    if (tc!=UNIVERSAL || tt!=CONSTRUCTED || tag!=(*fmt=='{'?SEQUENCE_OF:SET_OF))
	      goto error;
	  }
	}
	src+=curlen;
	curlen=scan_asn1length(src,maxstack[curmax],&seqlen);
	if (!curlen || curmax>99) goto error;
	maxstack[++curmax]=src+curlen+seqlen;
	src+=curlen;
	application=NULL;
	break;
      }
    case '!':		// save current src and max-src into struct string*
      // useful for optional parts or CHOICEs
      {
	struct string* dest=va_arg(args,struct string*);
	dest->s=src;
	dest->l=maxstack[curmax]-src;
	break;
      }
    case ']':		// ] = end of SET
    case '}':		// } = end of SEQUENCE
      {
	optional=0;
	if (curmax==0) goto error;
	src=maxstack[curmax];
	--curmax;
	break;
      }
    default:
      goto error;
    }
    ++fmt;
  }
  va_end(args);
  return src-orig;
error:
  va_end(args);
  return 0;
}