
/* sxnet.c	- Thawte StrongExtraNet interface code
 *
 * Requires SSLeay-0.8.0 or above
 *
 * Copyright 1997, 1998 Thawte Consulting Pty Ltd
 *
 */

/* define LOCAL_TEST to build the command line test harness */
/* #define LOCAL_TEST */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "crypto.h"
#include "err.h"
#include "asn1_mac.h"
#include "bio.h"
#include "x509.h"

#include "sxnet.h"

#if SSLEAY_VERSION_NUMBER >= 0x0800
#define SSLEAY8
#endif

#if SSLEAY_VERSION_NUMBER <= 0x0810
#define OBJ_create OBJ_create_and_add_object
#endif

#ifdef SSLEAY8
#define ONELINE_NAME(X) X509_NAME_oneline(X,NULL,0)
#else
#define ONELINE_NAME(X) X509_NAME_oneline(X)
#endif

#if defined(LOCAL_DEBUG) || defined(LOCAL_TEST)
static BIO  *bio_err=NULL;
#endif /* LOCAL_DEBUG || LOCAL_TEST */

/* we need to have a NID for being able to find things */
static int sxnetNID=0;

static void THAWTE_SXNET_init()
{
  if (sxnetNID==0)
    sxnetNID=OBJ_create("1.3.101.1.4.1","SXNET","Thawte StrongExtranet");
}

/* THAWTE_SXNET_INTERNAL is simply an SSLeay-ASN1-friendly interface
 * so we can quickly extract the sequence from the SXNET extension
 * into a nice STACK which we then make even easier to work with
 */
typedef struct {
  ASN1_INTEGER *i;
  ASN1_OCTET_STRING *os;
} THAWTE_SXNET_INTERNAL;

static THAWTE_SXNET_INTERNAL *THAWTE_SXNET_INTERNAL_new()
{
  THAWTE_SXNET_INTERNAL *ret;

  ret=Malloc(sizeof(THAWTE_SXNET_INTERNAL));
  if (ret!=NULL)
    memset(ret,0,sizeof(THAWTE_SXNET_INTERNAL));
  return(ret);
}

static void THAWTE_SXNET_INTERNAL_free(a)
THAWTE_SXNET_INTERNAL *a;
{
  if (a!=NULL) {
    if (a->i!=NULL) 
      ASN1_INTEGER_free(a->i);
    if (a->os != NULL) 
      ASN1_OCTET_STRING_free(a->os);
    Free(a);
  }
}

static THAWTE_SXNET_INTERNAL *d2i_THAWTE_SXNET_INTERNAL(a,pp,length)
THAWTE_SXNET_INTERNAL **a;
unsigned char **pp;
long length;
{
  M_ASN1_D2I_vars(a,THAWTE_SXNET_INTERNAL *,THAWTE_SXNET_INTERNAL_new);

  M_ASN1_D2I_Init();
  M_ASN1_D2I_start_sequence();
  M_ASN1_D2I_get(ret->i,d2i_ASN1_INTEGER);
  M_ASN1_D2I_get(ret->os,d2i_ASN1_OCTET_STRING);
  M_ASN1_D2I_Finish(a,THAWTE_SXNET_INTERNAL_free,0);
}

static int decode(pp,length,ids)
unsigned char **pp;
long length;
THAWTE_SXNET_IDLIST *ids;
{
  ASN1_CTX c;
  char *p;
  int i;
  ASN1_INTEGER *version=NULL;
  STACK *sk;
  THAWTE_SXNET_INTERNAL *t;
  int ret=1;
  int count=0;

  sk=sk_new_null();
  c.pp=pp;
  M_ASN1_D2I_Init();
  M_ASN1_D2I_start_sequence();
  M_ASN1_D2I_get(version,d2i_ASN1_INTEGER);
  M_ASN1_D2I_get_seq(sk,d2i_THAWTE_SXNET_INTERNAL);
  for (i=0; i<sk_num(sk); i++) {
    t=(THAWTE_SXNET_INTERNAL *)sk_value(sk,i);

    if (ids!=NULL) {
      ids->idList[count].zone=ASN1_INTEGER_get(t->i);
      if (t->os->length<=THAWTE_SXNET_MAX_ID_LENGTH) {
	ids->idList[count].idLength=t->os->length;
	memcpy(ids->idList[count].id,t->os->data,t->os->length);
      } else {
	ids->idList[count].idLength=t->os->length;
      }
      ids->idListCount++;
    }
    count++;

#ifdef LOCAL_DEBUG
    /* debug logging of things */
    if (bio_err!=NULL) {
      BIO_printf(bio_err,"<");
      i2a_ASN1_INTEGER(bio_err,t->i);
      BIO_printf(bio_err,"><");
      BIO_dump(bio_err,t->os->data,t->os->length);
      BIO_printf(bio_err,">\n");
    }
#endif /* LOCAL_DEBUG */

  }
  if (!asn1_Finish(&c)) {
err:;
    ret=(-1);
  } else {
    (*pp)=c.p;
  }

  sk_pop_free(sk,THAWTE_SXNET_INTERNAL_free);

  /* as long as there was no error then we return the count */
  if (ret!=-1)
    ret=count;

  return(ret);
}

int THAWTE_SXNET_extract_list(X509 *cert,THAWTE_SXNET_IDLIST **ids)
{
  int pos,count,add;

  /* sanity check */
  if (cert==NULL)
    return (-1);

  /* make sure "library" init done */
  THAWTE_SXNET_init();

  /* we must start with a zero counter */
again: ;
  if (ids!=NULL) 
    if ((*ids)!=NULL)
      (*ids)->idListCount=0;

  /* figure out the number of zone ids in the cert */
  count=0;
  pos=(-1);
  while ((pos=X509_get_ext_by_NID(cert,sxnetNID,pos))>=0) {
    ASN1_OCTET_STRING *data;
    unsigned char *bytes;
    int len;

    data=X509_EXTENSION_get_data(X509_get_ext(cert,pos));
    bytes=data->data;
    len=data->length;

    add=decode(&bytes,len,(ids==NULL)?NULL:(*ids));
    if (add>0) 
      count+=add;
  }

  /* if we only wanted the count then we have the answer */
  if (ids==NULL)
    return(count);

  /* if the user wanted us to allocate the space then we do 
   * so now and then copy out the data 
   */
  if ((*ids)==NULL) {
    (*ids)=(THAWTE_SXNET_IDLIST *)Malloc(sizeof(int)+
    					count*sizeof(THAWTE_SXNET_ID));
    /* malloc error ... return error */
    if ((*ids)==NULL) 
      return(-1);

    /* now repeat the processing only this time we will be 
     * copying out the data
     */
    goto again;
  }

  return(count);

}

THAWTE_SXNET_ID *THAWTE_extract_zone(THAWTE_SXNET_IDLIST *ids, int zone)
{
  int i;

  /* sanity check */
  if (ids==NULL)
    return(NULL);

  /* find the entry for the particular zone */
  for(i=0;i<ids->idListCount;i++) {
    if (ids->idList[i].zone==zone)
      return (ids->idList+i);
  }
  return (NULL);
}

char *binaryToBase64(char *data,int len)
{
  BIO *bmem, *b64, *out;
  int dlen;
  char *p;

  bmem=b64=NULL;

  if ((bmem=BIO_new(BIO_s_mem()))==NULL) goto err;
  if ((b64=BIO_new(BIO_f_base64()))==NULL) goto err;

  out=BIO_push(b64,bmem);
  BIO_write(out,data,len);
  BIO_flush(out);

  dlen=BIO_pending(bmem);
  if (dlen<0) goto err;

  p=(char *)Malloc(dlen+1);
  if (p!=NULL) {
    /* get the encoded data and then nul terminate */
    if (dlen>0) {
      BIO_read(bmem,p,dlen);
      /* drop a newline if there is one ... as we want raw data */
      if (p[dlen-1]=='\n') dlen--;
    }
    p[dlen]='\0';
  }

  BIO_free(bmem);
  BIO_free(b64);
  return(p);

err:;
  if (bmem!=NULL) BIO_free(bmem);
  if (b64!=NULL) BIO_free(b64);
  return (NULL);
}


#ifdef LOCAL_TEST

#define FORMAT_ASN1 1
#define FORMAT_PEM  2

int main(argc,argv)
int argc;
char *argv[];
{
  BIO *in;
  char *filename;
  int format;
  X509 *x;
  int i,j,n,pos;
  X509_EXTENSION *ex;
  ASN1_OBJECT *obj;
  int sxnetNID;
  THAWTE_SXNET_IDLIST *idlist;
  THAWTE_SXNET_ID *id;

  CRYPTO_malloc_init();
  SSLeay_add_all_algorithms();

  /* error stream */
  bio_err=BIO_new_fp(stderr,BIO_NOCLOSE);

  BIO_printf(bio_err,"sxnetNID=%d\n",sxnetNID);

  /* simply process all the file args */
  for(i=1;i<argc;i++) {
    filename=argv[i];
    /* a quick hack for determining format dependant on the file
     * extension ... which works okay for a test harness
     */
    if ((strstr(filename,".pem")!=NULL)||(strstr(filename,".PEM")!=NULL))
      format=FORMAT_PEM;
    else
      format=FORMAT_ASN1;
    in=BIO_new(BIO_s_file());
    if (in==NULL) {
      BIO_printf(bio_err,"unable to create file bio\n");
      goto fatal_error;
    }
    if (BIO_read_filename(in,filename)<=0) {
      BIO_printf(bio_err,"unable to open %s\n",filename);
      goto fatal_error;
    }

    /* pull in the certificate */
    if (format==FORMAT_ASN1) {
      x=d2i_X509_bio(in,NULL);
    } else if (format==FORMAT_PEM) {
      x=(X509 *)PEM_read_bio_X509(in,NULL,NULL);
    }

    /* ignore this file if we didn't get a cert */
    if (x==NULL) {
      BIO_printf(bio_err,"unable to load cert in %s\n",filename);
      continue;
    }

    /* now process the cert */
    BIO_printf(bio_err,"subject= %s\n",ONELINE_NAME(X509_get_subject_name(x)));
    BIO_printf(bio_err,"issuer= %s\n",ONELINE_NAME(X509_get_issuer_name(x)));

    /* dump the extension list */
    n=X509_get_ext_count(x);
    BIO_printf(bio_err,"extensions= %d\n",n);

    /* now get the extensions */
    idlist=NULL;
    n=THAWTE_SXNET_extract_list(x,&idlist);
    BIO_printf(bio_err,"zones: %d\n",n);
    for(j=0;j<n;j++) {
      BIO_printf(bio_err,"zone %ld\nid ",idlist->idList[j].zone);
      BIO_dump(bio_err,idlist->idList[j].id,idlist->idList[j].idLength);
    }

    /* simple interface for getting value for a particular zone 
     * which is the sort of interface a specific zone user would 
     * see rather than a list of zones
     */
    id=THAWTE_extract_zone(idlist,3);
    if (id!=NULL) {
      BIO_printf(bio_err,"MAGIC ZONE 3 data\n");
      BIO_dump(bio_err,id->id,id->idLength);
    }

  }

  exit(0);
fatal_error: ;
  exit(1);
}

#endif /* LOCAL_TEST */



