Logo Search packages:      
Sourcecode: strongswan version File versions  Download package

asn1.c

/* Simple ASN.1 parser
 * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * RCSID $Id: asn1.c,v 1.16 2006/01/04 21:00:43 as Exp $
 */

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

#include <freeswan.h>

#include "constants.h"
#include "defs.h"
#include "mp_defs.h"
#include "asn1.h"
#include "oid.h"
#include "log.h"

/* some common prefabricated ASN.1 constants */

static u_char ASN1_INTEGER_0_str[] = { 0x02, 0x00 };
static u_char ASN1_INTEGER_1_str[] = { 0x02, 0x01, 0x01 };
static u_char ASN1_INTEGER_2_str[] = { 0x02, 0x01, 0x02 };

const chunk_t ASN1_INTEGER_0 = strchunk(ASN1_INTEGER_0_str);
const chunk_t ASN1_INTEGER_1 = strchunk(ASN1_INTEGER_1_str);
const chunk_t ASN1_INTEGER_2 = strchunk(ASN1_INTEGER_2_str);

/* some popular algorithmIdentifiers */

static u_char ASN1_md5_id_str[] = {
    0x30, 0x0C,
          0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05,
        0x05, 0x00
};

static u_char ASN1_sha1_id_str[] = {
    0x30, 0x09,
        0x06, 0x05, 0x2B, 0x0E,0x03, 0x02, 0x1A,
        0x05, 0x00
};

static u_char ASN1_md5WithRSA_id_str[] = {
    0x30, 0x0D,
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04,
        0x05, 0x00
};

static u_char ASN1_sha1WithRSA_id_str[] = {
    0x30, 0x0D,
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05,
        0x05, 0x00
};

static u_char ASN1_rsaEncryption_id_str[] = {
    0x30, 0x0D,
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
        0x05, 0x00
};

const chunk_t ASN1_md5_id = strchunk(ASN1_md5_id_str);
const chunk_t ASN1_sha1_id = strchunk(ASN1_sha1_id_str);
const chunk_t ASN1_rsaEncryption_id = strchunk(ASN1_rsaEncryption_id_str);
const chunk_t ASN1_md5WithRSA_id = strchunk(ASN1_md5WithRSA_id_str);
const chunk_t ASN1_sha1WithRSA_id = strchunk(ASN1_sha1WithRSA_id_str);

/* ASN.1 definiton of an algorithmIdentifier */

static const asn1Object_t algorithmIdentifierObjects[] = {
  { 0, "algorithmIdentifier", ASN1_SEQUENCE,       ASN1_NONE }, /* 0 */
  { 1,   "algorithm",         ASN1_OID,      ASN1_BODY }, /* 1 */
  { 1,   "parameters",        ASN1_EOC,      ASN1_RAW  }  /* 2 */
};

#define ALGORITHM_ID_ALG            1
#define ALGORITHM_ID_PARAMETERS           2
#define ALGORITHM_ID_ROOF           3

/*
 * return the ASN.1 encoded algorithm identifier
 */
chunk_t
asn1_algorithmIdentifier(int oid)
{
    switch (oid)
    {
    case OID_RSA_ENCRYPTION:
      return ASN1_rsaEncryption_id;
    case OID_MD5_WITH_RSA:
      return ASN1_md5WithRSA_id;
    case OID_SHA1_WITH_RSA:
      return ASN1_sha1WithRSA_id;
    case OID_MD5:
      return ASN1_md5_id;
   case OID_SHA1:
      return ASN1_sha1_id;
   default:
      return empty_chunk;
    }
}

/*  If the oid is listed in the oid_names table then the corresponding
 *  position in the oid_names table is returned otherwise -1 is returned
 */
int
known_oid(chunk_t object)
{
    int oid = 0;

    while (object.len)
    {
      if (oid_names[oid].octet == *object.ptr)
      {
          if (--object.len == 0 || oid_names[oid].down == 0)
          {
            return oid;          /* found terminal symbol */
          }
          else
          {
            object.ptr++; oid++; /* advance to next hex octet */
          }
      }
      else
      {
          if (oid_names[oid].next)
            oid = oid_names[oid].next;
          else
            return OID_UNKNOWN;
      }
    }
    return -1;
}

/*
 *  Decodes the length in bytes of an ASN.1 object
 */
u_int
asn1_length(chunk_t *blob)
{
    u_char n;
    size_t len;

    /* advance from tag field on to length field */
    blob->ptr++;
    blob->len--;

    /* read first octet of length field */
    n = *blob->ptr++;
    blob->len--;

    if ((n & 0x80) == 0) /* single length octet */
      return n;

    /* composite length, determine number of length octets */
    n &= 0x7f;

    if (n > blob->len)
    {
      DBG(DBG_PARSING,
          DBG_log("number of length octets is larger than ASN.1 object")
      )
      return ASN1_INVALID_LENGTH;
    }

    if (n > sizeof(len))
    {
      DBG(DBG_PARSING,
          DBG_log("number of length octets is larger than limit of %d octets"
            , (int)sizeof(len))
      )
      return ASN1_INVALID_LENGTH;
    }

    len = 0;
    
    while (n-- > 0)
    {
      len = 256*len + *blob->ptr++;
      blob->len--;
    }
    return len;
}

/*
 * codes ASN.1 lengths up to a size of 16'777'215 bytes
 */
void
code_asn1_length(size_t length, chunk_t *code)
{
    if (length < 128)
    {
      code->ptr[0] = length;
      code->len = 1;
    }
    else if (length < 256)
    {
      code->ptr[0] = 0x81;
      code->ptr[1] = (u_char) length;
      code->len = 2;
    }
    else if (length < 65536)
    {
      code->ptr[0] = 0x82;
      code->ptr[1] = length >> 8;
      code->ptr[2] = length & 0x00ff;
      code->len = 3;
    }
    else
    {
      code->ptr[0] = 0x83;
      code->ptr[1] = length >> 16;
      code->ptr[2] = (length >> 8) & 0x00ff;
      code->ptr[3] = length & 0x0000ff;
      code->len = 4;
    }
}

/*
 * build an empty asn.1 object with tag and length fields already filled in
 */
u_char*
build_asn1_object(chunk_t *object, asn1_t type, size_t datalen)
{
    u_char length_buf[4];
    chunk_t length = { length_buf, 0 };
    u_char *pos;

    /* code the asn.1 length field */
    code_asn1_length(datalen, &length);

    /* allocate memory for the asn.1 TLV object */
    object->len = 1 + length.len + datalen;
    object->ptr = alloc_bytes(object->len, "asn1 object");

    /* set position pointer at the start of the object */
    pos = object->ptr;

    /* copy the asn.1 tag field and advance the pointer */
   *pos++ = type;
   
   /* copy the asn.1 length field and advance the pointer */
   chunkcpy(pos, length);

   return pos;
}

/*
 * build a simple ASN.1 object
 */
chunk_t
asn1_simple_object(asn1_t tag, chunk_t content)
{
    chunk_t object;

    u_char *pos = build_asn1_object(&object, tag, content.len);
    chunkcpy(pos, content);

    return object;
}

/* Build an ASN.1 object from a variable number of individual chunks.
 * Depending on the mode, chunks either are moved ('m') or copied ('c').
 */
chunk_t
asn1_wrap(asn1_t type, const char *mode, ...)
{
    chunk_t construct;
    va_list chunks;
    u_char *pos;
    int i;
    int count = strlen(mode);
      
    /* sum up lengths of individual chunks */ 
    va_start(chunks, mode);
    construct.len = 0;
    for (i = 0; i < count; i++)
    {
        chunk_t ch = va_arg(chunks, chunk_t);
      construct.len += ch.len;
    }
    va_end(chunks);

    /* allocate needed memory for construct */
    pos = build_asn1_object(&construct, type, construct.len);

    /* copy or move the chunks */
    va_start(chunks, mode);
    for (i = 0; i < count; i++)
    {
      chunk_t ch = va_arg(chunks, chunk_t);

      switch (*mode++)
      {
      case 'm':
          mv_chunk(&pos, ch);
          break;
      case 'c':
      default:
          chunkcpy(pos, ch);
      }
    }
    va_end(chunks);

    return construct;
}

/*
 * convert a MP integer into a DER coded ASN.1 object
 */
chunk_t
asn1_integer_from_mpz(const mpz_t value)
{
    size_t bits = mpz_sizeinbase(value, 2);  /* size in bits */
    size_t size = 1 + bits / BITS_PER_BYTE;  /* size in bytes */
    chunk_t n = mpz_to_n(value, size);

    return asn1_wrap(ASN1_INTEGER, "m", n);
}

/*
 *  determines if a character string is of type ASN.1 printableString
 */
bool
is_printablestring(chunk_t str)
{
    const char printablestring_charset[] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 '()+,-./:=?";
    u_int i;

    for (i = 0; i < str.len; i++)
    {
      if (strchr(printablestring_charset, str.ptr[i]) == NULL)
          return FALSE;
    }
    return TRUE;
}

/*
 *  Converts ASN.1 UTCTIME or GENERALIZEDTIME into calender time
 */
time_t
asn1totime(const chunk_t *utctime, asn1_t type)
{
    struct tm t;
    time_t tz_offset;
    u_char *eot = NULL;

    if ((eot = memchr(utctime->ptr, 'Z', utctime->len)) != NULL)
    {
      tz_offset = 0; /* Zulu time with a zero time zone offset */
    }
    else if ((eot = memchr(utctime->ptr, '+', utctime->len)) != NULL)
    {
      int tz_hour, tz_min;

      sscanf(eot+1, "%2d%2d", &tz_hour, &tz_min);
      tz_offset = 3600*tz_hour + 60*tz_min;  /* positive time zone offset */
    }
    else if ((eot = memchr(utctime->ptr, '-', utctime->len)) != NULL)
    {
      int tz_hour, tz_min;

      sscanf(eot+1, "%2d%2d", &tz_hour, &tz_min);
      tz_offset = -3600*tz_hour - 60*tz_min;  /* negative time zone offset */
    }
    else
    {
      return 0; /* error in time format */
    }

    {
      const char* format = (type == ASN1_UTCTIME)? "%2d%2d%2d%2d%2d":
                                         "%4d%2d%2d%2d%2d";

      sscanf(utctime->ptr, format, &t.tm_year, &t.tm_mon, &t.tm_mday,
                             &t.tm_hour, &t.tm_min);
    }

    /* is there a seconds field? */
    if ((eot - utctime->ptr) == ((type == ASN1_UTCTIME)?12:14))
    {
      sscanf(eot-2, "%2d", &t.tm_sec);
    }
    else
    {
      t.tm_sec = 0;
    }

    /* representation of year */
    if (t.tm_year >= 1900)
    {
      t.tm_year -= 1900;
    }
    else if (t.tm_year >= 100)
    {
      return 0;
    }
    else if (t.tm_year < 50)
    {
      t.tm_year += 100;
    }

    /* representation of month 0..11*/
    t.tm_mon--;

    /* set daylight saving time to off */
    t.tm_isdst = 0;

    /* compensate timezone */

    return mktime(&t) - timezone - tz_offset;
}

/*
 *  convert a date into ASN.1 UTCTIME or GENERALIZEDTIME format
 */
chunk_t
timetoasn1(const time_t *time, asn1_t type)
{
    int offset;
    const char *format;
    char buf[TIMETOA_BUF];
    chunk_t formatted_time;
    struct tm *t = gmtime(time);

    if (type == ASN1_GENERALIZEDTIME)
    {
      format = "%04d%02d%02d%02d%02d%02dZ";
      offset = 1900;
    }
    else /* ASN1_UTCTIME */
    {
      format = "%02d%02d%02d%02d%02d%02dZ";
      offset = (t->tm_year < 100)? 0 : -100;
    }
    sprintf(buf, format, t->tm_year + offset, t->tm_mon + 1, t->tm_mday
                   , t->tm_hour, t->tm_min, t->tm_sec);
    formatted_time.ptr = buf;
    formatted_time.len = strlen(buf);
    return asn1_simple_object(type, formatted_time);
}


/*
 * Initializes the internal context of the ASN.1 parser
 */
void
asn1_init(asn1_ctx_t *ctx, chunk_t blob, u_int level0,
      bool implicit, u_int cond)
{
    ctx->blobs[0] = blob;
    ctx->level0   = level0;
    ctx->implicit = implicit;
    ctx->cond     = cond;
    memset(ctx->loopAddr, '\0', sizeof(ctx->loopAddr));
}

/*
 * print the value of an ASN.1 simple object
 */
static void
debug_asn1_simple_object(chunk_t object, asn1_t type, u_int cond)
{
    int oid;

    switch (type)
    {
    case ASN1_OID:
      oid = known_oid(object);
      if (oid != OID_UNKNOWN)
      {
          DBG(DBG_PARSING,
            DBG_log("  '%s'",oid_names[oid].name);
          )
          return;
      }
      break;
    case ASN1_UTF8STRING:
    case ASN1_IA5STRING:
    case ASN1_PRINTABLESTRING:
    case ASN1_T61STRING:
    case ASN1_VISIBLESTRING:
      DBG(DBG_PARSING,
          DBG_log("  '%.*s'", (int)object.len, object.ptr);
      )
      return;
    case ASN1_UTCTIME:
    case ASN1_GENERALIZEDTIME:
      DBG(DBG_PARSING,
          time_t time = asn1totime(&object, type);
          DBG_log("  '%s'", timetoa(&time, TRUE));
      )
      return;
    default:
       break;
    }
    DBG(cond,
      DBG_dump_chunk("", object);
    )
}

/*
 * Parses and extracts the next ASN.1 object
 */
bool
extract_object(asn1Object_t const *objects,
      u_int *objectID, chunk_t *object, u_int *level, asn1_ctx_t *ctx)
{
    asn1Object_t obj = objects[*objectID];
    chunk_t *blob;
    chunk_t *blob1;
    u_char *start_ptr;

    *object = empty_chunk;

    if (obj.flags & ASN1_END)  /* end of loop or option found */
    {
      if (ctx->loopAddr[obj.level] && ctx->blobs[obj.level+1].len > 0)
      {
          *objectID = ctx->loopAddr[obj.level]; /* another iteration */
          obj = objects[*objectID];
      }
      else
      {
          ctx->loopAddr[obj.level] = 0;         /* exit loop or option*/
          return TRUE;
      }
    }

    *level = ctx->level0 + obj.level;
    blob = ctx->blobs + obj.level;
    blob1 = blob + 1;
    start_ptr = blob->ptr;

   /* handle ASN.1 defaults values */

    if ((obj.flags & ASN1_DEF)
    && (blob->len == 0 || *start_ptr != obj.type) )
    {
      /* field is missing */
      DBG(DBG_PARSING,
          DBG_log("L%d - %s:", *level, obj.name);
      )
      if (obj.type & ASN1_CONSTRUCTED)
      {
          (*objectID)++ ;  /* skip context-specific tag */
      }
      return TRUE;
    }

    /* handle ASN.1 options */

    if ((obj.flags & ASN1_OPT)
    && (blob->len == 0 || *start_ptr != obj.type))
    {
        /* advance to end of missing option field */
      do
          (*objectID)++;
      while (!((objects[*objectID].flags & ASN1_END)
            && (objects[*objectID].level == obj.level)));
      return TRUE;
    }

    /* an ASN.1 object must possess at least a tag and length field */

    if (blob->len < 2)
    {
      DBG(DBG_PARSING,
          DBG_log("L%d - %s:  ASN.1 object smaller than 2 octets",
                *level, obj.name);
      )
      return FALSE;
    }

    blob1->len = asn1_length(blob);

    if (blob1->len == ASN1_INVALID_LENGTH || blob->len < blob1->len)
    {
      DBG(DBG_PARSING,
          DBG_log("L%d - %s:  length of ASN.1 object invalid or too large",
                *level, obj.name);
      )
      return FALSE;
    }

    blob1->ptr = blob->ptr;
    blob->ptr += blob1->len;
    blob->len -= blob1->len;

    /* return raw ASN.1 object without prior type checking */

    if (obj.flags & ASN1_RAW)
    {
      DBG(DBG_PARSING,
          DBG_log("L%d - %s:", *level, obj.name);
        )
      object->ptr = start_ptr;
      object->len = (size_t)(blob->ptr - start_ptr);
      return TRUE;
    }

    if (*start_ptr != obj.type && !(ctx->implicit && *objectID == 0))
    {
      DBG(DBG_PARSING,
          DBG_log("L%d - %s: ASN1 tag 0x%02x expected, but is 0x%02x",
            *level, obj.name, obj.type, *start_ptr);
          DBG_dump("", start_ptr, (u_int)(blob->ptr - start_ptr));
      )
      return FALSE;
    }

    DBG(DBG_PARSING,
      DBG_log("L%d - %s:", ctx->level0+obj.level, obj.name);
    )

    /* In case of "SEQUENCE OF" or "SET OF" start a loop */

    if (obj.flags & ASN1_LOOP)
    {
      if (blob1->len > 0)
      {
          /* at least one item, start the loop */
          ctx->loopAddr[obj.level] = *objectID + 1;
      }
      else
      {
          /* no items, advance directly to end of loop */
          do
            (*objectID)++;
          while (!((objects[*objectID].flags & ASN1_END)
              && (objects[*objectID].level == obj.level)));
          return TRUE;
      }
    }

    if (obj.flags & ASN1_OBJ)
    {
      object->ptr = start_ptr;
      object->len = (size_t)(blob->ptr - start_ptr);
      DBG(ctx->cond,
          DBG_dump_chunk("", *object);
      )
    }
    else if (obj.flags & ASN1_BODY)
    {
      *object = *blob1;
      debug_asn1_simple_object(*object, obj.type, ctx->cond);
    }
    return TRUE;
}

/*
 * parse an ASN.1 simple type
 */
bool
parse_asn1_simple_object(chunk_t *object, asn1_t type, u_int level
, const char* name)
{
    size_t len;

    /* an ASN.1 object must possess at least a tag and length field */
    if (object->len < 2)
    {
      DBG(DBG_PARSING,
          DBG_log("L%d - %s:  ASN.1 object smaller than 2 octets",
                level, name);
      )
      return FALSE;
    }

    if (*object->ptr != type)
    {
      DBG(DBG_PARSING,
          DBG_log("L%d - %s: ASN1 tag 0x%02x expected, but is 0x%02x",
            level, name, type, *object->ptr);
      )
      return FALSE;
    }

    len = asn1_length(object);

    if (len == ASN1_INVALID_LENGTH || object->len < len)
    {
      DBG(DBG_PARSING,
          DBG_log("L%d - %s:  length of ASN.1 object invalid or too large",
                level, name);
      )
      return FALSE;
    }

    DBG(DBG_PARSING,
      DBG_log("L%d - %s:", level, name);
    )
    debug_asn1_simple_object(*object, type, DBG_RAW);
    return TRUE;
}

/*
 * extracts an algorithmIdentifier
 */
int
parse_algorithmIdentifier(chunk_t blob, int level0, chunk_t *parameters)
{
    asn1_ctx_t ctx;
    chunk_t object;
    u_int level;
    int alg = OID_UNKNOWN;
    int objectID = 0;

    asn1_init(&ctx, blob, level0, FALSE, DBG_RAW);

    while (objectID < ALGORITHM_ID_ROOF)
    {
      if (!extract_object(algorithmIdentifierObjects, &objectID, &object, &level, &ctx))
           return OID_UNKNOWN;

      switch (objectID)
      {
      case ALGORITHM_ID_ALG:
          alg = known_oid(object);
          break;
      case ALGORITHM_ID_PARAMETERS:
          if (parameters != NULL)
            *parameters = object;
          break;
      default:
          break;
      }
      objectID++;
    }
    return alg;
 }

/*
 *  tests if a blob contains a valid ASN.1 set or sequence
 */
bool
is_asn1(chunk_t blob)
{
    u_int len;
    u_char tag = *blob.ptr;

    if (tag != ASN1_SEQUENCE && tag != ASN1_SET)
    {
      DBG(DBG_PARSING,
          DBG_log("  file content is not binary ASN.1");
      )
      return FALSE;
    }
    len = asn1_length(&blob);
    if (len != blob.len)
    {
      DBG(DBG_PARSING,
          DBG_log("  file size does not match ASN.1 coded length");
      )
      return FALSE;
    }
    return TRUE;
}

Generated by  Doxygen 1.6.0   Back to index