first shot at x.509 certificate parsing

This commit is contained in:
leitner
2011-05-12 00:22:49 +00:00
parent dae3ea8024
commit c7f7465116
8 changed files with 694 additions and 146 deletions

View File

@@ -12,7 +12,7 @@ scan_asn1STRING.o scan_asn1SEQUENCE.o scan_asn1ENUMERATED.o \
scan_asn1BOOLEAN.o scan_asn1rawint.o scan_asn1SET.o fmt_asn1sint.o \
fmt_asn1sintpayload.o scan_asn1oid.o scan_asn1BITSTRING.o \
scan_asn1tagint.o fmt_asn1tagint.o fmt_asn1OID.o scan_asn1generic.o \
fmt_asn1generic.o scan_asn1rawoid.o fmt_asn1bitstring.o
fmt_asn1generic.o scan_asn1rawoid.o fmt_asn1bitstring.o asn1oid.o
ldap.a: scan_ldapmessage.o fmt_ldapmessage.o fmt_ldapbindrequest.o \
scan_ldapbindrequest.o scan_ldapbindresponse.o scan_ldapresult.o \
@@ -159,4 +159,6 @@ scan_ldapsearchresultentry.o: scan_ldapsearchresultentry.c asn1.h ldap.h
scan_ldapstring.o: scan_ldapstring.c asn1.h ldap.h
scan_asn1generic.o: scan_asn1generic.c asn1.h
asn1oid.o: asn1oid.c asn1.h
ldap_match_sre.o: ldap_match_sre.c ldap.h

57
asn1.h
View File

@@ -23,10 +23,13 @@ enum asn1_tag {
INTEGER=2,
BIT_STRING=3,
OCTET_STRING=4,
_NULL=5,
OBJECT_IDENTIFIER=6,
ENUMERATED=10,
SEQUENCE_OF=16,
SET_OF=17,
PrintableString=19,
IA5String=22,
UTCTIME=23
};
@@ -160,4 +163,58 @@ struct oid {
size_t* a;
};
enum x509_oid {
X509_ATTR_COMMONNAME, X509_ATTR_SURNAME, X509_ATTR_SERIALNUMBER,
X509_ATTR_COUNTRY, X509_ATTR_LOCALITY, X509_ATTR_STATEPROVINCE,
X509_ATTR_STREET, X509_ATTR_ORG, X509_ATTR_ORGUNIT, X509_ATTR_TITLE,
X509_ATTR_DESC, X509_ATTR_GIVENNAME, X509_ATTR_INITIALS,
X509_ATTR_GENERATIONQUALIFIER, X509_ATTR_UNIQID, X509_ATTR_DNQUALIFIER,
X509_ATTR_EMAIL,
X509_EXT_SUBJKEYID, X509_EXT_KEYUSAGE, X509_EXT_PRIVKEYUSAGEPERIOD,
X509_EXT_SUBJALTNAME, X509_EXT_ISSUERALTNAME, X509_EXT_BASICCONSTRAINTS,
X509_EXT_CRL_NUMBER, X509_EXT_REASONCODE, X509_EXT_INSTRUCTIONCODE,
X509_EXT_INVALIDITYDATE, X509_EXT_DELTA_CRL_INDICATOR,
X509_EXT_ISSUING_DISTRIBUTION_POINT, X509_EXT_NAME_CONSTRAINTS,
X509_EXT_CRL_DISTRIBUTION_POINTS, X509_EXT_CERT_POLICIES,
X509_EXT_AUTH_KEY_ID, X509_EXT_KEY_USAGE,
X509_ALG_MD2RSA, X509_ALG_MD4RSA, X509_ALG_MD5RSA, X509_ALG_SHA1RSA,
X509_ALG_SHA256RSA, X509_ALG_SHA384RSA, X509_ALG_SHA512RSA,
X509_ALG_SHA224RSA, X509_ALG_RSA,
};
extern const struct oidlookup {
size_t l;
const char* oid,* name;
enum x509_oid id;
} oid2string[];
size_t lookupoid(const char* oid,size_t l);
/* Generic parser and formatter routines: */
size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...);
size_t fmt_asn1generic(char* dest,const char* fmt,...);
/* the format string works like this:
* 'i' next argument is a long* (scan) or unsigned long (fmt)
* '*' (fmt only) next argument is an unsigned long, tag type is set to APPLICATION and tag is set to that argument
* '*' (scan only) next argument is an unsigned long*; for next tag, expect tag type to be APPLICATION and write tag to this unsigned long*
* 'b' next argument is a struct string* but the length l in it is in bits, not bytes; if the length is not a multiple of 8, the unused bits are at the end of the last byte in the string
* 'S' (fmt only) next argument is struct string *, send as OCTET_STRING
* 's' (fmt only) next argument is const char*, use strlen and send as OCTET_STRING
* 's' (scan only) next argument is struct string*, parse OCTET_STRING into it
* 'o' (fmt only) next argument is struct oid*, send OBJECT_IDENTIFIER
* 'o' (scan only) next argument is struct string*, parse raw OBJECT_IDENTIFIER into it; you have to call scan_asn1rawoid on contents of string to process further
* '[' start set
* ']' end set
* '{' start sequence
* '}' end sequence
* '?' from here till end of input / set / sequence is optional and can be missing
* 'u' (scan only) next argument is time_t*, parse UTCTIME into it
* 'p' (scan only) like 's' but check that contents of string is printable
* 'a' (scan only) like 's' but check that contents of string is ascii
* '!' (scan only) next argument is struct string*, fill in region until end of current sequence / set (for optional data)
* 'c' context specific value (tag class PRIVATE, type CONSTRUCTED, tag taken from unsigned long arg / written to unsigned long* argument)
*/
#endif

68
asn1oid.c Normal file
View File

@@ -0,0 +1,68 @@
#include <string.h>
#include "asn1.h"
#define ENTRY(a,b,c) { sizeof(a)-1, a, b, c }
const struct oidlookup oid2string[] = {
/* naming attribute OIDs */
ENTRY("\x55\x04\x03", "commonName", X509_ATTR_COMMONNAME),
ENTRY("\x55\x04\x04", "surname", X509_ATTR_SURNAME),
ENTRY("\x55\x04\x05", "serialNumber", X509_ATTR_SERIALNUMBER),
ENTRY("\x55\x04\x06", "countryName", X509_ATTR_COUNTRY),
ENTRY("\x55\x04\x07", "localityName", X509_ATTR_LOCALITY),
ENTRY("\x55\x04\x08", "stateOrProvinceName", X509_ATTR_STATEPROVINCE),
ENTRY("\x55\x04\x09", "street", X509_ATTR_STREET),
ENTRY("\x55\x04\x0a", "organizationName", X509_ATTR_ORG),
ENTRY("\x55\x04\x0b", "organizationalUnitName", X509_ATTR_ORGUNIT),
ENTRY("\x55\x04\x0c", "title", X509_ATTR_TITLE),
ENTRY("\x55\x04\x0d", "description", X509_ATTR_DESC),
ENTRY("\x55\x04\x2a", "givenName", X509_ATTR_GIVENNAME),
ENTRY("\x55\x04\x2b", "initials", X509_ATTR_INITIALS),
ENTRY("\x55\x04\x2c", "generationQualifier", X509_ATTR_GENERATIONQUALIFIER),
ENTRY("\x55\x04\x2d", "uniqueIdentifier", X509_ATTR_UNIQID),
ENTRY("\x55\x04\x2e", "dnQualifier", X509_ATTR_DNQUALIFIER),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x09\x01", "emailAddress", X509_ATTR_EMAIL),
/* X.509v3 extension OIDs */
ENTRY("\x55\x1d\x0e", "subject_key_identifier", X509_EXT_SUBJKEYID),
ENTRY("\x55\x1d\x0f", "key_usage", X509_EXT_KEYUSAGE),
ENTRY("\x55\x1d\x10", "private_key_usage_period", X509_EXT_PRIVKEYUSAGEPERIOD),
ENTRY("\x55\x1d\x11", "subject_alt_name", X509_EXT_SUBJALTNAME),
ENTRY("\x55\x1d\x12", "issuer_alt_name", X509_EXT_ISSUERALTNAME),
ENTRY("\x55\x1d\x13", "basic_constraints", X509_EXT_BASICCONSTRAINTS),
ENTRY("\x55\x1d\x14", "crl_number", X509_EXT_CRL_NUMBER),
ENTRY("\x55\x1d\x15", "reasonCode", X509_EXT_REASONCODE),
ENTRY("\x55\x1d\x17", "instruction_code", X509_EXT_INSTRUCTIONCODE),
ENTRY("\x55\x1d\x18", "invalidity_date", X509_EXT_INVALIDITYDATE),
ENTRY("\x55\x1d\x1b", "delta_crl_indicator", X509_EXT_DELTA_CRL_INDICATOR),
ENTRY("\x55\x1d\x1c", "issuing_distribution_point", X509_EXT_ISSUING_DISTRIBUTION_POINT),
ENTRY("\x55\x1d\x1e", "name_constraints", X509_EXT_NAME_CONSTRAINTS),
ENTRY("\x55\x1d\x1f", "crl_distribution_points", X509_EXT_CRL_DISTRIBUTION_POINTS),
ENTRY("\x55\x1d\x20", "certificate_policies", X509_EXT_CERT_POLICIES),
ENTRY("\x55\x1d\x23", "authority_key_identifier", X509_EXT_AUTH_KEY_ID),
ENTRY("\x55\x1d\x25", "ext_key_usage", X509_EXT_KEY_USAGE),
/* X.509 algorithms */
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x02", "md2WithRSAEncryption", X509_ALG_MD2RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x03", "md4WithRSAEncryption", X509_ALG_MD4RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x04", "md5WithRSAEncryption", X509_ALG_MD5RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05", "SHA1WithRSAEncryption", X509_ALG_SHA1RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b", "SHA256WithRSAEncryption", X509_ALG_SHA256RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0c", "SHA384WithRSAEncryption", X509_ALG_SHA384RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0d", "SHA512WithRSAEncryption", X509_ALG_SHA512RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0e", "SHA224WithRSAEncryption", X509_ALG_SHA224RSA),
ENTRY("\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01", "rsaEncryption", X509_ALG_RSA),
};
#undef ENTRY
size_t lookupoid(const char* oid,size_t l) {
size_t i;
for (i=0; i<sizeof(oid2string)/sizeof(oid2string[0]); ++i)
if (oid2string[i].l==l && memcmp(oid,oid2string[i].oid,l)==0)
return i;
return -1;
}

View File

@@ -14,11 +14,12 @@ size_t fmt_asn1generic(char* dest,const char* fmt,...) {
size_t curlen=0;
size_t cursor=0;
size_t seqlen;
unsigned long desttag=0;
unsigned long appstore;
while (*fmt) {
char* realdest=dest?dest+cursor:NULL;
switch (*fmt) {
case 'a': // make next tag use APPLICATION with this tag
case '*': // make next tag use APPLICATION with this tag
appstore=va_arg(args,unsigned long);
application=&appstore;
break;
@@ -62,14 +63,21 @@ copystring:
curlen=fmt_asn1OID(realdest,UNIVERSAL,PRIMITIVE,OBJECT_IDENTIFIER,o->a,o->l);
application=NULL;
break;
case 'c': // start context specific section
desttag=va_arg(args,unsigned long);
// fall through
case '[': // start SET
case '{': // start SEQUENCE
if (application)
curlen=fmt_asn1tag(realdest,APPLICATION,CONSTRUCTED,*application);
else if (*fmt=='c')
curlen=fmt_asn1tag(realdest,PRIVATE,CONSTRUCTED,desttag);
else
curlen=fmt_asn1tag(realdest,UNIVERSAL,CONSTRUCTED,SEQUENCE_OF);
curlen=fmt_asn1tag(realdest,UNIVERSAL,CONSTRUCTED,*fmt=='{'?SEQUENCE_OF:SET_OF);
containerstack[curinstack++]=cursor+curlen;
application=NULL;
break;
case ']': // end of SET
case '}': // end of SEQUENCE
/* we just wrote the tag and the sequence. Now that we wrote the
* sequence, we know the length it took, and we need to move the

123
printasn1.c Normal file
View File

@@ -0,0 +1,123 @@
/* needs stdio.h and asn1.h included */
void printasn1(const char* buf,const char* max) {
const char* maxstack[100];
size_t sptr=0;
size_t indent=0;
unsigned long tag;
enum asn1_tagclass tc;
enum asn1_tagtype tt;
size_t cl,len;
maxstack[sptr]=max;
while (buf<max) {
size_t i;
printf("%*s",indent,"");
cl=scan_asn1tag(buf,maxstack[sptr],&tc,&tt,&tag);
if (cl==0) {
printf("[could not parse tag]\n");
return;
}
printf("tag ");
switch (tc) {
case UNIVERSAL: printf("UNIVERSAL"); break;
case APPLICATION: printf("APPLICATION"); break;
case PRIVATE: printf("PRIVATE"); break;
case CONTEXT_SPECIFIC: printf("CONTEXT_SPECIFIC"); break;
default: printf("[illegal tag class 0x%x]\n",tc); return;
}
printf(" ");
switch (tt) {
case PRIMITIVE: printf("PRIMITIVE"); break;
case CONSTRUCTED: printf("CONSTRUCTED"); break;
default: printf("[illegal tag type 0x%x]\n",tt); return;
}
printf(" ");
if (tc!=UNIVERSAL)
printf("%d (0x%x)",tag,tag);
else switch (tag) {
case 0: printf("EOI"); break;
case BOOLEAN: printf("BOOLEAN"); break;
case INTEGER: printf("INTEGER"); break;
case BIT_STRING: printf("BIT_STRING"); break;
case OCTET_STRING: printf("OCTET_STRING"); break;
case _NULL: printf("NULL"); break;
case OBJECT_IDENTIFIER: printf("OBJECT_IDENTIFIER"); break;
case ENUMERATED: printf("ENUMERATED"); break;
case SEQUENCE_OF: printf("SEQUENCE_OF"); break;
case SET_OF: printf("SET_OF"); break;
case PrintableString: printf("PrintableString"); break;
case IA5String: printf("IA5String"); break;
case UTCTIME: printf("UTCTime"); break;
default: printf("[unsupported tag 0x%x]",tag); break;
}
buf+=cl;
cl=scan_asn1length(buf,maxstack[sptr],&len);
if (cl==0) {
puts("[could not parse length]");
return;
}
printf(" length %zu\n",len);
buf+=cl;
if (tc==UNIVERSAL && tt==PRIMITIVE) {
if (tag==INTEGER) {
unsigned long l;
size_t mlen;
mlen=scan_asn1rawint(buf,maxstack[sptr],cl,&l);
if (mlen)
printf("%*s-> %ld\n",indent,"",l);
} else if (tag==OCTET_STRING || tag==PrintableString || tag==IA5String || tag==UTCTIME) {
printf("%*s-> \"",indent,"");
for (i=0; i<len; ++i) {
if (buf[i]<' ')
printf("\\x%02x",(unsigned char)(buf[i]));
else
putchar(buf[i]);
}
printf("\"\n");
} else if (tag==OBJECT_IDENTIFIER) {
struct oid o;
size_t mlen;
unsigned long fnord[100];
o.l=100;
o.a=fnord;
mlen=scan_asn1rawoid(buf,buf+len,o.a,&o.l);
if (mlen) {
printf("%*s-> ",indent,"");
for (i=0; i<o.l; ++i)
printf("%d%s",o.a[i],i+1==o.l?"\n":".");
}
i=lookupoid(buf,len);
if (i!=(size_t)-1)
printf("%*s(%s)\n",indent,"",oid2string[i].name);
else {
printf("%*s(\"",indent,"");
for (i=0; i<len; ++i)
printf("\\x%02x",(unsigned char)(buf[i]));
printf("\")\n");
}
}
}
if (tt==CONSTRUCTED) {
printf("%*s{\n",indent,"");
indent+=2;
if (sptr>=99) {
printf("too many nested constructed elements!\n");
return;
}
maxstack[++sptr]=buf+len;
} else
buf+=len;
while (sptr && maxstack[sptr]<=buf) {
--sptr;
indent-=2;
printf("%*s}\n",indent,"");
}
}
}

View File

@@ -1,5 +1,7 @@
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include "asn1.h"
size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...) {
@@ -12,6 +14,8 @@ size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...) {
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;
@@ -34,13 +38,58 @@ size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...) {
}
if (!curlen) { if (optional) break; else return 0; }
src+=curlen;
application=0;
application=NULL;
break;
}
case 'b': // s = BIT STRING
case 's': // s = STRING
case 'I': // I = INTEGER, but for bignum integers; writes to an array of size_t, first one contains number of digits after it
{
struct string* dest=va_arg(args,struct string*);
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);
@@ -49,23 +98,96 @@ size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...) {
if (tc!=APPLICATION) return 0;
*application=tag;
} else {
if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=(*fmt=='s'?OCTET_STRING:BIT_STRING))
if (tc!=UNIVERSAL || tt!=PRIMITIVE || tag!=wantedtag)
return 0;
}
if (*fmt=='b') { // additional checks for bit strings
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=0;
application=NULL;
break;
}
case 'o': // o == OID
{
struct oid* dest=va_arg(args,struct 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) {
@@ -79,32 +201,37 @@ size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...) {
curlen=scan_asn1length(src,maxstack[curmax],&seqlen);
if (!curlen) return 0;
src+=curlen;
curlen=scan_asn1rawoid(src,src+seqlen,dest->a,&dest->l);
if (!curlen) {
if (dest->l && !dest->a) {
dest->a=malloc(dest->l*sizeof(dest->a[0]));
curlen=scan_asn1rawoid(src,src+seqlen,dest->a,&dest->l);
}
if (!curlen) return 0;
}
src+=curlen;
application=0;
dest->s=src;
dest->l=seqlen;
src+=seqlen;
application=NULL;
break;
}
case 'a': // next tag class is APPLICATION instead of UNIVERSAL; write tag to unsigned long*
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) return 0;
if (tc!=APPLICATION || tt!=CONSTRUCTED) return 0;
*application=tag;
} else {
if (tc!=UNIVERSAL || tt!=CONSTRUCTED || tag!=SEQUENCE_OF)
return 0;
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);
@@ -112,24 +239,23 @@ size_t scan_asn1generic(const char* src,const char* max,const char* fmt,...) {
if (curmax>99) return 0;
maxstack[++curmax]=src+curlen+seqlen;
src+=curlen;
application=0;
application=NULL;
break;
}
case '!': // save current max-src into size_t
// useful for ldap, where you have an application sequence
// and the tag defines which encoding you have inside the
// sequence, so you can't put it in the format string.
// you still need to know the length so you can call this function
// again on the rest of the data.
case '!': // save current src and max-src into struct string*
// useful for optional parts or CHOICEs
{
size_t* dest=va_arg(args,size_t*);
*dest=maxstack[curmax]-src;
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;
}

254
scan_certificate.c Normal file
View File

@@ -0,0 +1,254 @@
#include <stdlib.h>
#include <stdio.h>
#include "asn1.h"
#include "str.h"
#include "textcode.h"
struct x509signature {
struct string oid; /* you are not expected to actually decode this */
size_t oididx; /* if this is (size_t)-1, then the parser did not know the OID.
Otherwise it's the index into oid2string. oid2string[oididx].id
should be something like X509_ALG_SHA1RSA (see asn1.h) */
struct string bitstring; /* In this string, the length is in bits, not bytes! */
/* If the length is not a multiple of 8, then the unused bits are missing in the last byte.
* The parser already validated that the last byte is padded with 0 bits */
};
struct x509cert {
enum { v1=0, v1988=0, v2=1, v3=2, v1996=2 } version;
size_t serial;
struct x509signature algid;
struct string issuer; /* this is the raw asn.1 structure, a SET of "[{op}]" in scan_asn1generic terms */
time_t notbefore, notafter;
struct string subject; /* this is the raw asn.1 structure, a SET of "[{op}]" in scan_asn1generic terms */
struct x509signature sig;
};
void printasn1(const char* buf,const char* max);
static int findindn(struct string* dn,enum x509_oid id,struct string* dest) {
size_t i;
const char* c=dn->s;
const char* max=dn->s+dn->l;
for (;;) {
struct string oid;
size_t l=scan_asn1generic(c,max,"[{op}]",&oid,dest);
if (l) {
i=lookupoid(oid.s,oid.l);
if (i!=(size_t)-1) { // recognized the oid!
if (oid2string[i].id==id)
return 1;
}
c+=l;
} else break;
}
return 0;
}
size_t scan_certificate(const char* cert,size_t l, struct x509cert* C) {
char* c=0,* x;
/* if it's base64 encoded, decode first */
if (l > 27+25+2 && str_start(cert,"-----BEGIN CERTIFICATE-----"))
certfound:
{
size_t cur,used;
/* "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----" and newlines */
c=malloc((l-27-25-2)/4*3);
if (!c) return 0;
x=c;
for (cur=27; cur+26<l;) {
size_t next;
if (cert[cur]=='\r') ++cur; /* skip line ending */
if (cert[cur]=='\n') ++cur;
next=scan_base64(cert+cur,x,&used);
if (next==0) break;
cur+=next;
x+=used;
}
if (!str_start(cert+cur,"-----END CERTIFICATE-----")) {
free(c);
return 0;
}
cert=c;
l=x-c;
} else {
/* Maybe it has text in front of the BEGIN CERTIFICATE line */
size_t i,a=1;
for (i=0; i+27+25+2<l; ++i) {
if (cert[i]!='\n' && cert[i]!='\r' && (cert[i]<' ' || cert[i]>'~')) {
a=0;
break;
}
if (str_start(cert+i,"-----BEGIN CERTIFICATE-----")) {
cert+=i;
l-=i;
goto certfound;
}
}
if (a) /* if we end up here, it was ascii but did not contain a certificate. fail. */
return 0;
}
/* if we end up here, we decoded some base64 data or we found some
* binary data. See if it looks like x.509 at all. If it does, it
* starts with a SEQUENCE_OF, which encodes as '0'. */
if (*cert!='0') {
parseerror:
free(c);
return 0;
}
/* now for the heavy lifting */
{
unsigned long tagforversion; // must be 0
unsigned long version;
struct string oidalg,algparams,pubkeyalg,extensions,oidsig,sigrest,sigdata;
size_t i;
if (scan_asn1generic(cert,cert+l,"{{ci]i{o!}{!}{uu}{!}{!}!}{o!}b}",
&tagforversion,
&version,
&C->serial,
&oidalg, &algparams,
&C->issuer,
&C->notbefore, &C->notafter,
&C->subject,
&pubkeyalg,
&extensions,
&oidsig, &sigrest, &sigdata)) {
if (version==0)
printf("X.509 certificate\n");
else if (version==1)
printf("X.509v2 certificate\n");
else if (version==2)
printf("X.509v3 certificate\n");
else
printf("unsupported version %ld (must be 0, 1 or 2)\n",version);
printf("serial number %lu\n",C->serial);
printf("issuer: ");
{
struct string s;
if (findindn(&C->issuer,X509_ATTR_COUNTRY,&s))
printf("C=%.*s ",(int)s.l,s.s);
if (findindn(&C->issuer,X509_ATTR_ORG,&s))
printf("O=%.*s ",(int)s.l,s.s);
if (findindn(&C->issuer,X509_ATTR_COMMONNAME,&s))
printf("CN=%.*s ",(int)s.l,s.s);
}
printf("\n");
{
char a[100],b[100];
a[fmt_httpdate(a,C->notbefore)]=0;
b[fmt_httpdate(b,C->notafter)]=0;
printf("valid not before %s and not after %s\n",a,b);
}
printf("subject: ");
{
struct string s;
if (findindn(&C->issuer,X509_ATTR_COUNTRY,&s))
printf("C=%.*s ",(int)s.l,s.s);
if (findindn(&C->issuer,X509_ATTR_ORG,&s))
printf("O=%.*s ",(int)s.l,s.s);
if (findindn(&C->issuer,X509_ATTR_COMMONNAME,&s))
printf("CN=%.*s ",(int)s.l,s.s);
}
printf("\n");
i=lookupoid(oidalg.s,oidalg.l);
if (i!=(size_t)-1)
printf("signature algorithm %s\n",oid2string[i].name);
else {
unsigned long temp[100];
size_t len=100;
if (scan_asn1rawoid(oidalg.s,oidalg.s+oidalg.l,temp,&len)) {
printf("Unknown signature algorithm (oid ");
for (i=0; i<len; ++i)
printf("%lu%s",temp[i],i+1<len?".":")\n");
} else
printf("I don't know the algorithm and I can't parse/print the OID\n");
}
/* pubkeyalg is a SubjectPublicKeyInfo:
SubjectPublicKeyInfo ::= SEQUENCE{
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING}
AlgorithmIdentifier ::= SEQUENCE{
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL}
*/
{
struct string pubkeyoid, pubkeyparams, bits;
if (scan_asn1generic(pubkeyalg.s,pubkeyalg.s+pubkeyalg.l,"{o!}b",&pubkeyoid,&pubkeyparams,&bits)) {
i=lookupoid(pubkeyoid.s,pubkeyoid.l);
if (i!=(size_t)-1) {
printf("public key algorithm %s\n",oid2string[i].name);
if (oid2string[i].id==X509_ALG_RSA) {
size_t* modulus,* publicExponent;
size_t allocsize=bits.l/(8*sizeof(modulus[0]))+2;
modulus=malloc(allocsize);
publicExponent=malloc(allocsize);
if (!modulus || !publicExponent)
printf("malloc for RSA bignums failed!\n");
else {
if (scan_asn1generic(bits.s,bits.s+bits.l/8,"{II}",modulus,publicExponent)) {
if (publicExponent[0]==1)
printf("public exponent %lu\n",publicExponent[1]);
else
printf("public exponent is larger than a word?!\n");
printf("modulus: ");
for (i=1; i<=modulus[0]; ++i) {
size_t j,k;
for (j=0, k=modulus[i]; j<sizeof(modulus[0]); ++j) {
printf("%02lx:",(k>>((sizeof(modulus[0])*8)-(j+1)*8))&0xff);
}
if ((i-1)%4==3 || i==modulus[0]) printf("\n");
}
} else
printf("bignum scanning failed!\n");
}
/* for RSA, bits is actually another sequence with two integers, modulus and publicExponent */
printf("pubkeyparams len %lu, bits len %lu\n",pubkeyparams.l,bits.l);
}
} else {
unsigned long temp[100];
size_t len=100;
if (scan_asn1rawoid(pubkeyoid.s,pubkeyoid.s+pubkeyoid.l,temp,&len)) {
printf("Unknown public key algorithm (oid ");
for (i=0; i<len; ++i)
printf("%lu%s",temp[i],i+1<len?".":")\n");
} else
printf("I don't know the public key algorithm and I can't parse/print the OID\n");
}
} else
printf("could not parse public key part!\n");
}
}
}
// printasn1(cert,cert+l);
}
#include "mmap.h"
#include <stdio.h>
#include "printasn1.c"
int main(int argc,char* argv[]) {
char* buf;
size_t l,n;
struct x509cert c;
buf=mmap_read(argc>1?argv[1]:"test.pem",&l);
if (!buf) { puts("test.pem not found"); return 1; }
n=scan_certificate(buf,l,&c);
}

134
t10.c
View File

@@ -1,115 +1,11 @@
#include <stdio.h>
#include <byte.h>
#include <stdlib.h>
#include "asn1.h"
void printasn1(const char* buf,const char* max) {
const char* maxstack[100];
size_t sptr=0;
size_t indent=0;
unsigned long tag;
enum asn1_tagclass tc;
enum asn1_tagtype tt;
size_t cl,len;
maxstack[sptr]=max;
while (buf<max) {
size_t i;
printf("%*s",indent,"");
cl=scan_asn1tag(buf,maxstack[sptr],&tc,&tt,&tag);
if (cl==0) {
printf("[could not parse tag]\n");
return;
}
printf("tag ");
switch (tc) {
case UNIVERSAL: printf("UNIVERSAL"); break;
case APPLICATION: printf("APPLICATION"); break;
case PRIVATE: printf("PRIVATE"); break;
case CONTEXT_SPECIFIC: printf("CONTEXT_SPECIFIC"); break;
default: printf("[illegal tag class 0x%x]\n",tc); return;
}
printf(" ");
switch (tt) {
case PRIMITIVE: printf("PRIMITIVE"); break;
case CONSTRUCTED: printf("CONSTRUCTED"); break;
default: printf("[illegal tag type 0x%x]\n",tt); return;
}
printf(" ");
if (tc!=UNIVERSAL)
printf("%d (0x%x)",tag,tag);
else switch (tag) {
case BOOLEAN: printf("BOOLEAN"); break;
case INTEGER: printf("INTEGER"); break;
case BIT_STRING: printf("BIT_STRING"); break;
case OCTET_STRING: printf("OCTET_STRING"); break;
case OBJECT_IDENTIFIER: printf("OBJECT_IDENTIFIER"); break;
case ENUMERATED: printf("ENUMERATED"); break;
case SEQUENCE_OF: printf("SEQUENCE_OF"); break;
case SET_OF: printf("SET_OF"); break;
case UTCTIME: printf("UTCTime"); break;
default: printf("[unsupported tag 0x%x]",tag); break;
}
#include "printasn1.c"
buf+=cl;
cl=scan_asn1length(buf,maxstack[sptr],&len);
if (cl==0) {
puts("[could not parse length]");
return;
}
printf(" length %zu\n",len);
buf+=cl;
if (tc==UNIVERSAL && tt==PRIMITIVE) {
if (tag==INTEGER) {
unsigned long l;
size_t mlen;
mlen=scan_asn1rawint(buf,maxstack[sptr],cl,&l);
if (mlen)
printf("%*s-> %ld\n",indent,"",l);
} else if (tag==OCTET_STRING) {
printf("%*s-> \"",indent,"");
for (i=0; i<len; ++i) {
if (buf[i]<' ')
printf("\\x%02x",(unsigned char)(buf[i]));
else
putchar(buf[i]);
}
printf("\"\n");
} else if (tag==OBJECT_IDENTIFIER) {
struct oid o;
size_t mlen;
unsigned long fnord[100];
o.l=100;
o.a=fnord;
mlen=scan_asn1rawoid(buf,maxstack[sptr],o.a,&o.l);
if (mlen) {
printf("%*s-> ",indent,"");
for (i=0; i<o.l; ++i)
printf("%d%s",o.a[i],i+1==o.l?"\n":".");
}
}
}
if (tt==CONSTRUCTED) {
printf("%*s{\n",indent,"");
indent+=2;
if (sptr>=99) {
printf("too many nested constructed elements!\n");
return;
}
maxstack[++sptr]=buf+len;
} else
buf+=len;
while (sptr && maxstack[sptr]<=buf) {
--sptr;
indent-=2;
printf("%*s}\n",indent,"");
}
}
}
const unsigned long oid[]={1,2,840,113549,1};
unsigned long oid[]={1,2,840,113549,1};
const unsigned long oidlen = sizeof(oid) / sizeof(oid[0]);
main() {
@@ -122,7 +18,7 @@ main() {
o.l=oidlen;
o.a=oid;
byte_zero(buf,1024);
l=fmt_asn1generic(buf,"a{isbo}",8,23,"fnord",&B,&o);
l=fmt_asn1generic(buf,"*{isbo}",8,23,"fnord",&B,&o);
printf("formatted into %d bytes\n",l);
{
printf("-> ");
@@ -139,9 +35,9 @@ main() {
unsigned long a2;
unsigned long b;
struct string c;
struct oid d;
struct string d;
struct string e;
l=scan_asn1generic(buf,buf+l,"a{!isbo}",&a,&a2,&b,&c,&e,&d);
l=scan_asn1generic(buf,buf+l,"*{!isbo}",&a,&a2,&b,&c,&e,&d);
printf("%lu\n",l);
if (l) {
printf("got application tag %d (should be 8)\n",a);
@@ -155,8 +51,22 @@ main() {
printf("\n");
printf("got oid ");
for (i=0; i<d.l; ++i)
printf("%d%s",d.a[i],i+1<d.l?".":" (should be 1.2.840.113549.1)\n");
{
struct oid o;
size_t mlen=scan_asn1rawoid(d.s,d.s+d.l,NULL,&o.l);
if (mlen==0 && o.l==0) {
puts("oid parse error!");
return 0;
}
o.a=malloc(o.l*sizeof(o.a[0]));
if (!o.a) {
puts("memory allocation error!");
return 0;
}
mlen=scan_asn1rawoid(d.s,d.s+d.l,o.a,&o.l);
for (i=0; i<o.l; ++i)
printf("%d%s",o.a[i],i+1<o.l?".":" (should be 1.2.840.113549.1)\n");
}
}
}
}