summaryrefslogblamecommitdiffstats
path: root/scan_asn1generic.c
blob: c246efd4cd0715e6435519c35a2a6f19eba20346 (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 'i':		// i = INTEGER
      {
	long* dest=va_arg(args,long*);
	*dest=0;
	curlen=scan_asn1int(src,maxstack[curmax],&tc,&tt,&tag,dest);
	if (application) {
	  if (tc!=APPLICATION) return 0;
	  *application=tag;
	} else {
	  if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=INTEGER)
	    return 0;
	}
	if (!curlen) { if (optional) break; else return 0; }
	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))) return 0;
	if (!(tmp=scan_asn1length(src+len,maxstack[curmax],&tlen))) return 0;
	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 return 0; }
	if (application) {
	  if (tc!=APPLICATION) return 0;
	  *application=tag;
	} else {
	  if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=wantedtag)
	    return 0;
	}
	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])) return 0;	// 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]!=' ') return 0;
	} 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) return 0;
	} 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) return 0;
	  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])) return 0;
	  j=(dest->s[2]-'0')*10+dest->s[3]-'0';		// is the month plausible?
	  if (j<1 || j>12) return 0;
	  t.tm_mon=j-1;
	  j=(dest->s[4]-'0')*10+dest->s[5]-'0';		// is the day plausible?
	  if (j<1 || j>31) return 0;
	  t.tm_mday=j;
	  j=(dest->s[6]-'0')*10+dest->s[7]-'0';		// is the hour plausible?
	  if (j>23) return 0;
	  t.tm_hour=j;
	  j=(dest->s[8]-'0')*10+dest->s[9]-'0';		// is the minutes plausible?
	  if (j>59) return 0;
	  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) return 0;
	    t.tm_sec=j;
	  }
	  *desttime=mktime(&t);
	  if (dest->s[i]=='+' || dest->s[i]=='-') {
	    size_t j;
	    if (dest->l!=15) return 0;
	    for (j=i; j<i+4; ++j)
	      if (!isdigit(dest->s[j])) return 0;
	    j=(dest->s[i]-'0')*10+dest->s[i+1]-'0';		// is the offset minutes plausible?
	    if (j>59) return 0;
	    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) return 0;
	    if (dest->s[i]=='+')
	      *desttime+=j;
	    else
	      *desttime-=j;
	  } else if (dest->s[i]!='Z') return 0;
	}
	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 return 0; }
	if (application) {
	  if (tc!=APPLICATION) return 0;
	  *application=tag;
	} else {
	  if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=OBJECT_IDENTIFIER)
	    return 0;
	}
	src+=curlen;
	curlen=scan_asn1length(src,maxstack[curmax],&seqlen);
	if (!curlen) return 0;
	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; PRIVATE 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 return 0; }
	if (application) {
	  if (tc!=APPLICATION || tt!=CONSTRUCTED) return 0;
	  *application=tag;
	} else {
	  if (*fmt=='c') {
	    if (tc!=PRIVATE || tt!=CONSTRUCTED)
	      return 0;
	    *desttag=tag;
	  } else {
	    if (tc!=UNIVERSAL || tt!=CONSTRUCTED || tag!=(*fmt=='{'?SEQUENCE_OF:SET_OF))
	      return 0;
	  }
	}
	src+=curlen;
	curlen=scan_asn1length(src,maxstack[curmax],&seqlen);
	if (!curlen) return 0;
	if (curmax>99) return 0;
	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) return 0;
	src=maxstack[curmax];
	--curmax;
	break;
      }
    default:
      return 0;
    }
    ++fmt;
  }
  va_end(args);
  return src-orig;
}