summaryrefslogblamecommitdiffstats
path: root/scan_certificate.c
blob: 4cffcf78ee073cd46e95abcaca4b44bc147e8fb4 (plain) (tree)


































                                                                                                                  
  











































































































































                                                                                                            

                                                                          








                                                     
                                                         






















































































                                                                                                           
                                         


                                                                        
                                                                                                                                   
                     




                                        




                                                      













                                                                                                














































                                                                                        

       




                             


     








                                 
                  







                                                     

                                            

                     
                                                    


                                                           

                                                


                       
#include <stdlib.h>
#include <stdio.h>
#include "asn1.h"
#include <str.h>
#include "textcode.h"
#include "fmt.h"
#include "byte.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;
};

struct rsaprivatekey {
  size_t* modulus,* publicExponent,* privateExponent,* prime1,* prime2,* exponent1,* exponent2,* coefficient;
  struct string otherPrimeInfos;
  size_t* freewhendone;
};

struct dsaprivatekey {
};

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;
}

static size_t base64_decode(const char* cert, size_t l, const char* name, char** dest) {
  /* cert should be something like "-----BEGIN CERTIFICATE-----\n[base64 gunk]\n-----END CERTIFICATE-----\n"
   * l should be strlen(cert), but cert does not need to be 0-terminated
   * name should be something like "CERTIFICATE" or "RSA PRIVATE KEY", what you are trying to decode
   * dest will end up pointing to the decoded data.
   * return value can be 0 if we can't decode the data and it does not
   * look like it's a binary certificate.  Or it can be some length
   * value, in which case dest points to a malloced area of that length
   * with the decoded data in it. */
  size_t taglen=strlen(name)+sizeof("-----BEGIN -----")-1;
  char tag[taglen+1];
  char* c=0,* x;
  tag[fmt_strm(tag,"-----BEGIN ",name,"-----")]=0;
  if (l > 2*taglen && byte_equal(cert,taglen,tag))
certfound:
  {
    size_t cur,used;
    /* "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----" and newlines */
    c=malloc((l-2*taglen)/4*3);
    if (!c) return 0;
    x=c;
    for (cur=taglen; cur+taglen-1<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 ") ||
	byte_diff(cert+cur+sizeof("-----END"),taglen-sizeof("----BEGIN")-1,tag+sizeof("-----BEGIN"))) {
      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 (byte_equal(cert+i,taglen,tag)) {
	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;
  }
  *dest=c;
  return l;
}

size_t scan_rsaprivatekey(const char* cert, size_t l, struct rsaprivatekey* C, char** freewhendone) {
  char* c;
  size_t maxdigits,ret;
  unsigned long version;
  *freewhendone=NULL;
  l=base64_decode(cert,l,"RSA PRIVATE KEY",&c);
  if (!l) return 0;
  if (c!=cert) *freewhendone=c;
  maxdigits=l/sizeof(size_t)+2;
  C->freewhendone=malloc(maxdigits*sizeof(size_t)*8);
  if (!C->freewhendone) {
fail:
    free(*freewhendone);
    freewhendone=NULL;
    return 0;
  }
  C->modulus=C->freewhendone;
  C->publicExponent=C->modulus+maxdigits;
  C->privateExponent=C->publicExponent+maxdigits;
  C->prime1=C->privateExponent+maxdigits;
  C->prime2=C->prime1+maxdigits;
  C->exponent1=C->prime2+maxdigits;
  C->exponent2=C->exponent1+maxdigits;
  C->coefficient=C->exponent2+maxdigits;
  C->otherPrimeInfos.l=0;
  C->otherPrimeInfos.s=NULL;
  if ((ret=scan_asn1generic(c,c+l,"{iIIIIIIII!}",&version,
		       C->modulus,C->publicExponent,C->privateExponent,
		       C->prime1,C->prime2,C->exponent1,C->exponent2,
		       C->coefficient,&C->otherPrimeInfos))) {
    if (version!=0 && version!=1) goto fail;
    if (version==0 && C->otherPrimeInfos.l) goto fail;
    if (version==1 && !C->otherPrimeInfos.l) goto fail;
    if (version==0) C->otherPrimeInfos.s=NULL;
    return ret;
  } else
    goto fail;
}

size_t scan_certificate(const char* cert, size_t l, struct x509cert* C, char** freewhendone) {
  char* c=0,* x;
  *freewhendone=NULL;
  l=base64_decode(cert,l,"CERTIFICATE",&c);
  if (!l) return 0;
  if (c!=cert) *freewhendone=c;
  cert=c;

  /* 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 n,i;
    if ((n=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+2*sizeof(modulus[0]);
	      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:\n  ");
		  for (i=1; i<=modulus[0]; ++i) {
		    size_t j,k;
		    for (j=0, k=modulus[i]; j<sizeof(modulus[0]); ++j) {
		      printf("%02lx%s",(k>>((sizeof(modulus[0])*8)-(j+1)*8))&0xff,i==modulus[0] && j==sizeof(modulus[0])-1?"":":");
		    }
		    if ((i-1)%4==3)
		      if (i==modulus[0])
			printf("\n");
		      else
			printf("\n  ");
		  }
		} else
		  printf("bignum scanning failed!\n");
	      }
	      free(modulus); free(publicExponent);
	    }
	  } 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");

	// parse x.509v3 extensions
	if (version!=2 && extensions.l) {
	  printf("Not X.509v3 but extensions present!?\n");
	} else if (extensions.l) {
	  const char* c=extensions.s;
	  const char* max=extensions.s+extensions.l;
	  struct string extoid,extval;
	  unsigned long noextensions;
	  if (c!=max) {
	    size_t n=scan_asn1generic(c,max,"c{!}}!",&noextensions,&extensions,&extval);
	    if (n==0 || extval.l>0) {
	      printf("failed to parse X.509v3 extensions!\n");
	      c=max;
	    } else {
	      c=extensions.s;
	      max=extensions.s+extensions.l;
	    }
	  }
	  while (c<max) {
	    size_t n=scan_asn1generic(c,max,"{os}",&extoid,&extval);
	    if (n) {
	      size_t i=lookupoid(extoid.s,extoid.l);
	      if (i!=(size_t)-1) {
		printf("X.509 extension %s\n",oid2string[i].name);
	      } else {
		unsigned long temp[100];
		size_t len=100;
		if (scan_asn1rawoid(extoid.s,extoid.s+extoid.l,temp,&len)) {
		  printf("Unknown X.509v3 extension (oid ");
		  for (i=0; i<len; ++i)
		    printf("%lu%s",temp[i],i+1<len?".":")\n");
		} else
		  printf("Failed to parse X.509v3 extension OID\n");
	      }
	      c+=n;
	    } else {
	      printf("X.509v3 extension parse error!\n");
	      printasn1(c,max);
	      break;
	    }
	  }
	}
	/*
			 &extensions,
			 &oidsig, &sigrest, &sigdata))) {
	 */
      }

      return n;

    } else {
      printasn1(cert,cert+l);
      return 0;
    }
  }

}

#include "mmap.h"
#include <stdio.h>

#include "printasn1.c"

int main(int argc,char* argv[]) {
  char* freewhendone;
  const char* buf;
  size_t l,n;
  struct x509cert c;
  struct rsaprivatekey k;

  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,&freewhendone);
  if (!n)
    printf("failed to parse certificate\n");
  free(freewhendone);

  buf=mmap_read(argc>2?argv[2]:"privatekey.pem",&l);
  if (!buf) { puts("privatekey.pem not found"); return 1; }

  n=scan_rsaprivatekey(buf,l,&k,&freewhendone);
  if (!n)
    printf("failed to parse rsa private key\n");
  free(freewhendone);
  free(k.freewhendone);
}