/* * certutil.c * this file contains source to extract info from an asn.1 encoded * certificate * * Author(s) * Scott A. Leerssen, leerssen@issl.atl.hp.com * (based on ASN.1 dump program by dpk) * * Protection Notice * Copyright (c) 1997, Scott Leerssen, All rights reserved. * * 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. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #if defined(WIN32) #include #include /* For u_char */ #else #include #include #include #endif #include "certutil.h" /* Tag classes */ #define CLASS_MASK 0xC0 /* Bits 8 and 7 */ #define UNIVERSAL 0x00 /* 0 = Universal (defined by ITU X.680) */ #define APPLICATION 0x40 /* 1 = Application */ #define CONTEXT 0x80 /* 2 = Context-specific */ #define PRIVATE 0xC0 /* 3 = Private */ /* Encoding type */ #define FORM_MASK 0x20 /* Bit 6 */ #define PRIMITIVE 0x00 /* 0 = primitive */ #define CONSTRUCTED 0x20 /* 1 = constructed */ /* Universal tags */ #define TAG_MASK 0x1F /* Bits 5 - 1 */ #define BOOLEAN 0x01 /* 1: TRUE or FALSE */ #define INTEGER 0x02 /* 2: Arbitrary precision integer */ #define BITSTRING 0x03 /* 2: Sequence of bits */ #define OCTETSTRING 0x04 /* 4: Sequence of bytes */ #define NULLTAG 0x05 /* 5: NULL */ #define OID 0x06 /* 6: Object Identifier (numeric sequence) */ #define OBJDESCRIPTOR 0x07 /* 7: Object Descriptor (human readable) */ #define EXTERNAL 0x08 /* 8: External / Instance Of */ #define REAL 0x09 /* 9: Real (Mantissa * Base^Exponent) */ #define ENUMERATED 0x0A /* 10: Enumerated */ #define EMBEDDED_PDV 0x0B /* 11: Embedded Presentation Data Value */ #define SEQUENCE 0x10 /* 16: Constructed Sequence / Sequence Of */ #define SET 0x11 /* 17: Constructed Set / Set Of */ #define NUMERICSTR 0x12 /* 18: Numeric String (digits only) */ #define PRINTABLESTR 0x13 /* 19: Printable String */ #define T61STR 0x14 /* 20: T61 String (Teletex) */ #define VIDEOTEXSTR 0x15 /* 21: Videotex String */ #define IA5STR 0x16 /* 22: IA5 String */ #define UTCTIME 0x17 /* 23: UTC Time */ #define GENERALIZEDTIME 0x18 /* 24: Generalized Time */ #define GRAPHICSTR 0x19 /* 25: Graphic String */ #define VISIBLESTR 0x1A /* 26: Visible String (ISO 646) */ #define GENERALSTR 0x1B /* 27: General String */ #define UNIVERSALSTR 0x1C /* 28: Universal String */ #define BMPSTR 0x1E /* 30: Basic Multilingual Plane String */ /* Length encoding */ #define LEN_XTND 0x80 /* Indefinite or long form */ #define LEN_MASK 0x7f /* Bits 7 - 1 */ /* Structure to hold info on an ASN.1 item */ struct Item { int id; /* Identifier/tag */ long len; /* Data length */ }; /* structure to hold one element of a sequencs in a decoded asn1 stream */ typedef struct { struct Item item; /* item info sucked from the stream */ char *texttag; /* text representation of the tag */ u_char *value; /* actual value (directly from stream) */ char *textval; /* text representation of value (if any) */ } tlv_t; typedef list_t seq_t; /* asn.1 tlv sequence */ /* Return descriptive strings for universal tags */ char *idstr(int tagID) { switch(tagID & TAG_MASK) { case BOOLEAN: return("BOOLEAN"); case INTEGER: return("INTEGER"); case BITSTRING: return("BIT STRING"); case OCTETSTRING: return("OCTET STRING"); case NULLTAG: return("NULL"); case OID: return("OBJECT IDENTIFIER"); case OBJDESCRIPTOR: return("ObjectDescriptor"); case EXTERNAL: return("EXTERNAL"); case REAL: return("REAL"); case ENUMERATED: return("ENUMERATED"); case EMBEDDED_PDV: return("EMBEDDED PDV (1993)"); case SEQUENCE: return("SEQUENCE"); case SET: return("SET"); case NUMERICSTR: return("NumericString"); case PRINTABLESTR: return("PrintableString"); case T61STR: return("TeletexString"); case VIDEOTEXSTR: return("VideotexString"); case IA5STR: return("IA5String"); case UTCTIME: return("UTCTime"); case GENERALIZEDTIME: return("GeneralizedTime"); case GRAPHICSTR: return("GraphicString"); case VISIBLESTR: return("VisibleString"); case GENERALSTR: return("GeneralString"); case UNIVERSALSTR: return("UniversalString (1993)"); case BMPSTR: return("BMPString (1993)"); default: return("Unknown (Reserved)"); } } /* * Return descriptive strings for objects and algorithms */ static char *objectStr(int objectID) { char stringBuffer[256]; static char *objectName[] = { "TagEncryptedKey", "TagPKCEncryptedKey", "TagSignature" }; if (objectID < 3) { sprintf(stringBuffer, "%s(%d)", objectName[objectID], objectID); return(strdup(stringBuffer)); } sprintf(stringBuffer, "Unknown(%d)", objectID); return(strdup(stringBuffer)); } static char *enumAlgo(int value) { char stringBuffer[256]; static struct { int value; char *name; } enumInfo[] = { { 0, "CRYPT_ALGO_NONE" }, { 1, "CRYPT_ALGO_DES" }, { 2, "CRYPT_ALGO_3DES" }, { 3, "CRYPT_ALGO_IDEA" }, { 4, "CRYPT_ALGO_MDCSHS" },{ 5, "CRYPT_ALGO_RC2" }, { 6, "CRYPT_ALGO_RC4" }, { 7, "CRYPT_ALGO_RC5" }, { 8, "CRYPT_ALGO_SAFER" }, { 9, "CRYPT_ALGO_BLOWFISH" }, { 10, "CRYPT_ALGO_GOST" }, { 11, "CRYPT_ALGO_SKIPJACK" }, { 100, "CRYPT_ALGO_DH" }, { 101, "CRYPT_ALGO_RSA" }, { 102, "CRYPT_ALGO_DSS" }, { 200, "CRYPT_ALGO_MD2" }, { 201, "CRYPT_ALGO_MD4" }, { 202, "CRYPT_ALGO_MD5" }, { 203, "CRYPT_ALGO_SHA" }, { 204, "CRYPT_ALGO_RIPEMD160" }, { -1, NULL } }; int i; for (i = 0; enumInfo[i].value != -1; i++) { if (enumInfo[i].value == value) { sprintf(stringBuffer, "%s (%d)", enumInfo[i].name, value); return(strdup(stringBuffer)); } } sprintf(stringBuffer, "CRYPT_ALGO_UNKNOWN (%d)", value); return(strdup(stringBuffer)); } static char *enumMode(int value) { char stringBuffer[256]; static struct { int value; char *name; } enumInfo[] = { { 0, "CRYPT_MODE_NONE" }, { 1, "CRYPT_MODE_STREAM" }, { 2, "CRYPT_MODE_ECB" }, { 3, "CRYPT_MODE_CBC" }, { 4, "CRYPT_MODE_CFB" }, { 5, "CRYPT_MODE_OFB" }, { 6, "CRYPT_MODE_PCBC" }, { 7, "CRYPT_MODE_COUNTER" }, { 100, "CRYPT_MODE_PKC" }, { -1, NULL } }; int i; for (i = 0; enumInfo[i].value != -1; i++) { if (enumInfo[i].value == value) { sprintf(stringBuffer, "%s (%d)", enumInfo[i].name, value); return(strdup(stringBuffer)); } } sprintf(stringBuffer, "CRYPT_MODE_UNKNOWN (%d)", value); return(strdup(stringBuffer)); } /* Return descriptive strings for an object identifier */ static void dump_data (void * block_p, int nbytes) { int i,j,k; /* loop counters */ unsigned char * p; /* temp pointer */ char dots[] = "................"; /* a row of dots for display */ char show_chars[17]; /* displayed dump row */ char s[80]; /* string to display */ char t[80]; /* temp holding string */ p = block_p; i = nbytes; while (i>0) { strcpy(show_chars,dots); sprintf(s, "%.8x: ", p); for (j=0; j<4; j++) { for (k=0; k<4 ; k++) { i--; if (i >= 0) { if ((*p >= 0x20) && (*p < 0x7f)) show_chars[(j*4)+k] = *p; sprintf(t, "%2.2x", *p); } else { show_chars[(j*4)+k] = ' '; sprintf(t, " "); } p++; strcat(s, t); } strcat(s, " "); } strcat(s, show_chars); printf("%s\n", s); } } static char *oid2str(char *oid, int oidLength) { static struct { char *oid; char *string; } oidInfo[] = { { "\x06\x08\x2A\x86\x48\x86\xF7\x0D\x01\x01", "pkcs-1 (1 2 840 113549 1 1)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01", "rsaEncryption (1 2 840 113549 1 1 1)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x02", "md2withRSAEncryption (1 2 840 113549 1 1 2)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x03", "md4withRSAEncryption (1 2 840 113549 1 1 3)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x04", "md5withRSAEncryption (1 2 840 113549 1 1 4)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05", "sha1withRSAEncryption (1 2 840 113549 1 1 5)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x06", "ripemd160WithRSAEncryption (1 2 840 113549 1 1 6)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x03\x01", "dhKeyAgreement (1 2 840 113549 1 3 1)" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01", "emailAddress (1 2 840 113549 1 9 1)" }, { "\x06\x08\x2A\x86\x48\x86\xF7\x0D\x03\x04", "rc4 (1 2 840 113549 3 4)" }, { "\x06\x05\x2B\x0E\x02\x1A\x05", "sha (1 3 14 2 26 5)" }, { "\x06\x05\x2B\x0E\x03\x02\x06", "desECB (1 3 14 3 2 6)" }, { "\x06\x05\x2B\x0E\x03\x02\x07", "desCBC (1 3 14 3 2 7)" }, { "\x06\x05\x2B\x0E\x03\x02\x08", "desOFB (1 3 14 3 2 8)" }, { "\x06\x05\x2B\x0E\x03\x02\x09", "desCFB (1 3 14 3 2 9)" }, { "\x06\x05\x2B\x0E\x03\x02\x0C", "dsa (1 3 14 3 2 12)" }, { "\x06\x05\x2B\x0E\x03\x02\x0D", "dsaWithSHA (1 3 14 3 2 13)" }, { "\x06\x05\x2B\x0E\x03\x02\x12", "sha (1 3 14 3 2 18)" }, { "\x06\x05\x2B\x0E\x03\x02\x1A", "sha1 (1 3 14 3 2 26)" }, { "\x06\x05\x2B\x0E\x03\x02\x1B", "ripemd160 (1 3 14 3 2 27)" }, { "\x06\x03\x55\x04\x03", "commonName (2 5 4 3)" }, { "\x06\x03\x55\x04\x06", "countryName (2 5 4 6)" }, { "\x06\x03\x55\x04\x07", "localityName (2 5 4 7)" }, { "\x06\x03\x55\x04\x08", "stateOrProvinceName (2 5 4 8)" }, { "\x06\x03\x55\x04\x0A", "organizationName (2 5 4 10)" }, { "\x06\x03\x55\x04\x0B", "organizationalUnitName (2 5 4 11)" }, { "\x06\x03\x55\x04\x0D", "description (2 5 4 13)" }, { "\x06\x03\x55\x04\x34", "supportedAlgorithms (2 5 4 52)" }, { "\x06\x04\x55\x08\x01\x01", "rsa (2 5 8 1 1)" }, { "\x06\x03\x55\x1D\x0E", "subjectKeyIdentifier (2 5 29 14)" }, { "\x06\x03\x55\x1D\x0F", "keyUsage (2 5 29 15)" }, { "\x06\x03\x55\x1D\x10", "privateKeyUsagePeriod (2 5 29 16)" }, { "\x06\x03\x55\x1D\x11", "subjectAltName (2 5 29 17)" }, { "\x06\x03\x55\x1D\x12", "issuerAltName (2 5 29 18)" }, { "\x06\x03\x55\x1D\x13", "basicConstraints (2 5 29 19)" }, { "\x06\x03\x55\x1D\x14", "cRLNumber (2 5 29 20)" }, { "\x06\x03\x55\x1D\x20", "certificatePolicies (2 5 29 32)" }, { "\x06\x03\x55\x1D\x23", "authorityKeyIdentifier (2 5 29 35)" }, { "\x06\x04\x86\x8D\x6F\x02", "hashedRootKey (2 54 1775 2)" }, { "\x06\x04\x86\x8D\x6F\x03", "certificateType (2 54 1775 3)" }, { "\x06\x04\x86\x8D\x6F\x04", "merchantData (2 54 1775 4)" }, { "\x06\x04\x86\x8D\x6F\x05", "cardCertRequired (2 54 1775 5)" }, { "\x06\x04\x86\x8D\x6F\x06", "tunneling (2 54 1775 6)" }, { "\x06\x04\x86\x8D\x6F\x07", "setQualifier (2 54 1775 7)" }, { "\x06\x04\x86\x8D\x6F\x63", "set-data (2 54 1775 99)" }, { "\x06\x0A\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x01", "uid (0 9 2342 19200300 100 1 1)" }, { NULL, NULL } }; int i = 0; while(oidInfo[i].oid != NULL) { if (!strncmp(oidInfo[i].oid + 2, oid, oidLength)) { return(oidInfo[i].string); } i++; } return(NULL); } /* returns short oid descriptions for constructing distinguished names */ static char *dnoid2str(char *oid, int oidLength) { static struct { char *oid; char *string; } oidInfo[] = { { "\x06\x03\x55\x04\x03", "CN" }, { "\x06\x03\x55\x04\x06", "C" }, { "\x06\x03\x55\x04\x07", "L" }, { "\x06\x03\x55\x04\x08", "ST" }, { "\x06\x03\x55\x04\x0A", "O" }, { "\x06\x03\x55\x04\x0B", "OU" }, { "\x06\x03\x55\x04\x0D", "Description" }, { "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01", "E" }, { "\x06\x0A\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x01", "UID" }, { NULL, NULL } }; int i = 0; while(oidInfo[i].oid != NULL) { if (!strncmp(oidInfo[i].oid + 2, oid, oidLength)) return(oidInfo[i].string); i++; } return("??"); } /* Indent a string by the appropriate amount */ static void doIndent(int level) { int i; for (i = 0; i < level; i++) fprintf(stderr, " "); } /* Dump data as a string of hex digits up to a maximum of 128 bytes */ static void dump_hex(u_char *buf_p, int length, int level) { int i; if (level > 8) { level = 8; /* Make sure we don't go off the edge of the screen */ } for (i = 0; (i < length) && (i < 128); i++) { if (!(i % 16)) { fprintf(stderr, "\n"); doIndent(level+1); } fprintf(stderr, "%s%02X", i % 16 ? " " : "", *buf_p); buf_p++; } if (length > 128) { length -= 128; fprintf(stderr, "\n"); doIndent(level+5); fprintf(stderr, "[Another %ld bytes skipped]", length); } if (!(i-1 % 16)) { fprintf(stderr, "\n"); } } /* Get an integer value */ static long getValue(u_char **asn_pp, long length) { long value = 0; int i; for (i = 0; i < length; (*asn_pp)++, i++) value = (value << 8) | **asn_pp; return(value); } /* Get an ASN.1 objects tag and length */ static struct Item *getItem(u_char **asn_pp) { char *myname = "getItem()"; struct Item *object; long length; if ((object = (struct Item *) malloc(sizeof(struct Item))) == NULL) { #ifdef DEBUG fprintf(stderr, "%s: malloc failed, %s", myname, strerror(errno)); #endif return(NULL); } object->id = **asn_pp; if (!object->id) { #ifdef DEBUG puts("End-of-contents octets found"); #endif free(object); return(NULL); } (*asn_pp)++; if (*asn_pp == NULL) { free(object); return(NULL); } if ((length = **asn_pp) & LEN_XTND) { int i; (*asn_pp)++; length &= LEN_MASK; if (length > 4) { #ifdef DEBUG fprintf(stderr, "%s: object length field %d too large.\n", myname, length); #endif free(object); return(NULL); } object->len = 0; for (i = 0; i < length; (*asn_pp)++, i++) { object->len = (object->len << 8) | **asn_pp; } } else { (*asn_pp)++; object->len = length; } return(object); } /* * asn1_seqlen * gets the length of the current asn1 sequence * * returns * length of sequence on success, 0 on failure * * arguments * asn_p - pointer to asn.1 stream */ static int asn1_seqlen(u_char *asn_p) { u_char *a_p = asn_p; /* make copy of pointer for update */ struct Item *item; int len; if ((item = getItem(&a_p)) != NULL) { len = item->len; free(item); return(len); } return(0); } /* * asn1_strlen * gets the length of the current asn1 sequence plus the sequence * tag and length fields. does not account for standard double-null * asn.1 terminator. * * returns * length of asn.1 stream on success, 0 on failure * * arguments * asn_p - pointer to asn.1 stream */ static int asn1_strlen(u_char *asn_p) { int seqlen; int l_length; /* length of length field [sic] */ if (seqlen = asn1_seqlen(asn_p)) { if ((l_length = *(asn_p+1)) & LEN_XTND) { l_length &= LEN_MASK; l_length += 1; } else { l_length = 1; } return(seqlen + l_length + 1); } return(0); } /* * asn1_parse * this function decodes an asn.1 sequence into a list that can be * traversed to pull out parts of a certificate. this should be * independent enough to take into account changes in X.509 standards, * but I'm not promising anything!!! * * The list structure is generic and lives in list.h. the list is * a circular double linked list with a list sentinel. each asn.1 * element is stored in one list node (excluding list sentinel, of * course. "plain" data types are stored directly in the list data * pointer as a tlv_t *. constructed types like sets and sequences * result in the creation of a new list which is stored in the list * data field for that node. look at the diagram below for a visual. * * |-----------------------------| * \/ | * sentinel-->element-->element--| * | | * \/ | |------------------------| * (integer) \/ \/ | * (sequence) | * (sentinel)-->element-->element--| * | | * \/ \/ * (octet) (octet) * (string) (string) * * NOTE: the above is not a representation of X.509, just a quicky * visual of the list structure in case you ever want to traverse it * yourself. * * returns * 0 on success, number of bytes of inconsistency on failure to * decode TLV * * arguments * asn_pp - address of asn.1 stream pointer, updated as the * the stream is traversed, so send in a copy! * length - length of asn.1 segment * d_p - decoded sequence list pointer */ static int asn1_parse(u_char **asn_pp, int length, seq_t *d_p) { char *myname = "asn1_parse()"; struct Item *item; u_char *lastPos = *asn_pp; int seenEnum = 0; while((item = getItem(asn_pp)) != NULL) { long value; int i; tlv_t *tlv_p; tlv_p = (tlv_t *) malloc(sizeof(tlv_t)); memset(tlv_p, 0, sizeof(tlv_t)); tlv_p->item = *item; free(item); /* * check length just in case... */ if (tlv_p->item.len > length) { #ifdef DEBUG fprintf(stderr, "%s: TLV reports length %d, only %d left\n", myname, tlv_p->item.len, length); #endif return(tlv_p->item.len - length); } /* Decrement the enum level */ if (seenEnum) seenEnum--; if ((tlv_p->item.id & CLASS_MASK) == UNIVERSAL) { /* Perform a sanity check */ if (((tlv_p->item.id & TAG_MASK) != NULLTAG) && (tlv_p->item.len < 0)) { #ifdef DEBUG fprintf(stderr, "%s: object has bad length field", myname); #endif return(-1); } list_insert(list_tail(d_p), tlv_p); tlv_p->texttag = strdup(idstr(tlv_p->item.id)); if ((tlv_p->item.id & FORM_MASK) == CONSTRUCTED) { int result; tlv_p->value = (u_char *) list_new(); result = asn1_parse(asn_pp, tlv_p->item.len, (seq_t *) tlv_p->value); if (result) { #ifdef DEBUG fprintf(stderr,"%s: inconsistent object length, %d byte%s " "difference.\n", myname, result, (result > 1) ? "s" : ""); #endif return(-1); } } else { char t[256], string[256], *oidName; int x, y; if (tlv_p->item.len) { tlv_p->value = (u_char *) malloc(tlv_p->item.len); memcpy(tlv_p->value, *asn_pp, tlv_p->item.len); } switch(tlv_p->item.id & TAG_MASK) { case BOOLEAN: tlv_p->textval = strdup(**asn_pp ? "TRUE" : "FALSE"); (*asn_pp)++; break; case INTEGER: if (tlv_p->item.len > 4) { *asn_pp += tlv_p->item.len; } else { sprintf(string, "%ld", getValue(asn_pp, tlv_p->item.len)); tlv_p->textval = strdup(string); } break; case ENUMERATED: if (!seenEnum) { tlv_p->textval = enumAlgo((int) getValue(asn_pp, tlv_p->item.len)); seenEnum = 2; } else { tlv_p->textval = enumMode((int) getValue(asn_pp, tlv_p->item.len)); } break; case BITSTRING: case OCTETSTRING: *asn_pp += tlv_p->item.len; break; case OID: /* Heirarchical Object Identifier: First two levels are encoded into one byte, since the root level has only 3 nodes (40*x + y). However if x = joint-iso-itu-t(2) then y may be > 39, so we have to add special-case handling for this */ memcpy(string, *asn_pp, tlv_p->item.len); *asn_pp += tlv_p->item.len; *t = '\0'; if ((oidName = oid2str(string, (int) tlv_p->item.len)) != NULL) { tlv_p->textval = strdup(oidName); break; } /* Pick apart the OID */ x = (unsigned char) string[0] / 40; y = (unsigned char) string[0] % 40; if (x > 2) { /* Handle special case for large y if x = 2 */ y += (x - 2) * 40; x = 2; } sprintf(t, "%d %d", x, y); value = 0; for (i = 1; i < tlv_p->item.len; i++) { value = (value << 7) | (string[i] & 0x7F); if (!(string[i] & 0x80)) { sprintf(t + strlen(t), " %ld", value); value = 0; } } tlv_p->textval = strdup(t); break; case NULLTAG: break; case OBJDESCRIPTOR: case PRINTABLESTR: case UTCTIME: case GENERALIZEDTIME: case GRAPHICSTR: case VISIBLESTR: case GENERALSTR: case UNIVERSALSTR: case NUMERICSTR: case T61STR: case VIDEOTEXSTR: case IA5STR: case BMPSTR: memcpy(string, *asn_pp, tlv_p->item.len); *asn_pp += tlv_p->item.len; string[(int) tlv_p->item.len] = '\0'; sprintf(t, "%s", string); tlv_p->textval = strdup(t); break; default: *asn_pp += tlv_p->item.len; break; } } } else { /* Print the object type */ char t[256]; char *s; if ((tlv_p->item.id & CLASS_MASK) == APPLICATION) { s = objectStr(tlv_p->item.id & TAG_MASK); sprintf(t, "[APPLICATION %s]", s); free(s); } else { switch(tlv_p->item.id & CLASS_MASK) { case CONTEXT: sprintf(t, "[CONTEXT-SPECIFIC"); break; case PRIVATE: sprintf(t, "[PRIVATE"); } sprintf(t+strlen(t), " %d]", tlv_p->item.id & TAG_MASK); } list_insert(list_tail(d_p), tlv_p); tlv_p->texttag = strdup(t); /* Perform a sanity check */ if (((tlv_p->item.id & TAG_MASK) != NULLTAG) && (tlv_p->item.len < 1)) { #ifdef DEBUG fprintf(stderr, "%s: object has bad length.", myname); #endif return(-1); } /* If it's constructed, print the various fields in it */ if ((tlv_p->item.id & FORM_MASK) == CONSTRUCTED) { int result; tlv_p->value = (u_char *) list_new(); result = asn1_parse(asn_pp, tlv_p->item.len, (seq_t *) tlv_p->value); if (result) { #ifdef DEBUG fprintf(stderr, "%s: inconsistent object length, %d byte%s " "difference.\n", myname, result, (result > 1) ? "s" : ""); #endif return(-1); } } else { /* This could be anything, just save the data */ if (tlv_p->item.len) { tlv_p->value = (u_char *) malloc(tlv_p->item.len); memcpy(tlv_p->value, *asn_pp, tlv_p->item.len); } *asn_pp += tlv_p->item.len; } } length -= (int)(*asn_pp - lastPos); lastPos = *asn_pp; if (length <= 0) { if (length < 0) return((int) -length); return(0); } } return(0); } /* * dump_sequence * dumps out the sequence list in ascii readable form, usefull for * debugging and dumping certificates. * * returns * nothing * * arguments * seq_p - sequence list to print out * level - indent level (starting call is set to 0) */ static void dump_sequence(seq_t *seq_p, int level) { tlv_t *tlv_p; FOREACH(seq_p, tlv_t, tlv_p) { doIndent(level); fprintf(stderr, "%s", tlv_p->texttag); if ((tlv_p->item.id & FORM_MASK) == CONSTRUCTED) { level++; fprintf(stderr, " {\n"); dump_sequence((seq_t *) tlv_p->value, level); level--; doIndent(level); fprintf(stderr, "}\n"); } else { if (tlv_p->textval) { fprintf(stderr, " (%s)", tlv_p->textval); } switch (tlv_p->item.id) { case BITSTRING: fprintf(stderr, " (%d unused bits)", (int) *tlv_p->value); dump_hex((u_char *) tlv_p->value + 1, tlv_p->item.len - 1, level); break; case OCTETSTRING: dump_hex((u_char *) tlv_p->value, tlv_p->item.len, level); break; } fprintf(stderr, "\n"); } } ENDEACH; } /* * free_sequence * frees up the decoded asn.1 sequence list * * returns * void * * arguments * seq_p - sequence list to free */ static void free_sequence(seq_t *seq_p) { tlv_t *tlv_p; if (!seq_p) { return; } FOREACH(seq_p, tlv_t, tlv_p) { if (tlv_p->textval) { free(tlv_p->textval); } if (tlv_p->texttag) { free(tlv_p->texttag); } if ((tlv_p->item.id & FORM_MASK) == CONSTRUCTED) { free_sequence((seq_t *) tlv_p->value); } else { if (tlv_p->value) { free(tlv_p->value); } } free(tlv_p); } ENDEACH; list_free(seq_p); } /* * cert_decode * decodes an asn.1 encoded certificate into a cert_t list * * returns * a cert_t* on representing the asn.1 structure on success, * NULL on failure. calling function needs to free the cert_t * * with cert_free(). * * arguments * asn_p - asn.1 encoded byte stream */ cert_t *cert_decode(u_char *asn_p) { u_char *t_asn_p; cert_t *cert_p = NULL; if (!asn_p) { return(NULL); } t_asn_p = asn_p; cert_p = list_new(); if (asn1_parse(&t_asn_p, asn1_strlen(asn_p), cert_p)) { free_sequence(cert_p); cert_p = NULL; } return(cert_p); } /* * cert_b64_decode * decodes a MIME base64-encoded asn.1 encoded certificate into * a cert_t list. calling function needs to free the cert_t * * with cert_free(). * * returns * a cert_t* on representing the asn.1 structure on success, * NULL on failure * * arguments * b64_p - NULL-TERMINATED base64-encoded string */ cert_t *cert_b64_decode(char *b64_p) { u_char *asn_p; cert_t *cert_p = NULL; if (!b64_p) { return(NULL); } if (b64decode(b64_p, (char **) &asn_p)) { cert_p = cert_decode(asn_p); } free(asn_p); return(cert_p); } /* * cert_free * frees a cert_t list created by cert_decode or cert_b64_decode * * returns * void * * arguments * cert_p - cert_t pointer to free up */ void cert_free(cert_t *cert_p) { free_sequence(cert_p); } /* * signed_seq_cert_head * traverses a certificate structure to get to the front of the * certificate part of a signed sequence certificate+signature * sequence * * returns * sequence pointer to first element in the certificate sequence * * arguments * cert_p - cert_t pointer */ static seq_t *signed_seq_cert_head(cert_t *cert_p) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p) { return(NULL); } /* * first element of main sequence */ tlv_p = (tlv_t *) head_element(cert_p); /* * skip the outside surrounding sequence */ seq_p = (seq_t *) tlv_p->value; tlv_p = (tlv_t *) head_element(seq_p); /* * first value in first sequence is the certificate portion * of the certificate+signature "signed sequence" */ seq_p = (seq_t *) tlv_p->value; return((seq_t *) list_head(seq_p)); } /* * skip_version * skips over the certificate version element if it exists * * returns * pointer to element directly after the version element if * it existed, or just the passed in location if no version * was found. * * arguments * seq_p - current position in certificate list */ static seq_t *skip_version(seq_t *seq_p) { tlv_t *tlv_p; tlv_p = (tlv_t *) data_element(seq_p); /* * data element could be the optional version, so see * if it exists and skip it if it does */ if ((tlv_p->item.id & CLASS_MASK) == CONTEXT) { seq_p = list_next(seq_p); } return(seq_p); } /* * skip_element * skips an element in a cert_t certificate structure * * returns * pointer to next element in the chain * * arguments * seq_p - current position in chain */ static seq_t *skip_element(seq_t *seq_p) { return(list_next(seq_p)); } /* * dn_seq2dn * parses a DN sequence into a string representation of the DN * * returns * pointer to static character array holding the parsed DN * * arguments * seq_p - DN sequence */ static char *dn_seq2str(seq_t *seq_p) { tlv_t *tlv_p; seq_t *set_p, *se_p; static char dn_tmp[1024]; char *oidstr; tlv_p = (tlv_t*) data_element(seq_p); /* * sequence of sets of sequences comprise the dn */ seq_p = (seq_t *) tlv_p->value; /* * traverse the set/sequence pairs */ *dn_tmp = 0; FOREACH(seq_p, tlv_t, tlv_p) { set_p = (seq_t *) tlv_p->value; FOREACH(set_p, tlv_t, tlv_p) { se_p = (seq_t *) list_head((seq_t *)tlv_p->value); tlv_p = (tlv_t *) data_element(se_p); oidstr = dnoid2str((char *) tlv_p->value, tlv_p->item.len); sprintf(dn_tmp+strlen(dn_tmp), "%s%s=",(*dn_tmp==0 ? "" : ", "), oidstr); se_p = list_next(se_p); tlv_p = (tlv_t *) data_element(se_p); /* this field need to be quoted if it contains a , */ if (strchr(tlv_p->textval, ',')) { sprintf(dn_tmp+strlen(dn_tmp), "\"%s\"", tlv_p->textval); } else { sprintf(dn_tmp+strlen(dn_tmp), "%s", tlv_p->textval); } } ENDEACH; } ENDEACH; tlv_p = (tlv_t *) data_element(set_p); return(dn_tmp); } /* * cert_version * gets the certificate version date of the certificate format * * returns * integer year YYYY of certificate version if exists, 1988 * by default, 0 on failure * * arguments * cert_p - cert_t pointer */ int cert_version(cert_t *cert_p) { seq_t *seq_p; tlv_t *tlv_p; int result = 0; if (!cert_p) { return(0); } seq_p = signed_seq_cert_head(cert_p); /* * first element of certificate sequence */ tlv_p = (tlv_t *) data_element(seq_p); /* * first data element could be the optional version, so see * if it exists and use it if it does. otherwise use 1988 default */ if ((tlv_p->item.id & CLASS_MASK) == CONTEXT) { tlv_p = (tlv_t *) head_element((seq_t *) tlv_p->value); memcpy(((char *)&result)+sizeof(int)-tlv_p->item.len, tlv_p->value, tlv_p->item.len); result = ntohl(result); } return(1988+result); } /* * cert_serial_number * gets the certificate serial number from the certificate structure * * returns * length of serial number in bytes * * arguments * cert_p - cert_t pointer */ int cert_serial_number(cert_t *cert_p, u_char **ser_pp) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p || !ser_pp) { return(0); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ tlv_p = (tlv_t *) data_element(seq_p); /* * now we should have the serial number element */ *ser_pp = tlv_p->value; return(tlv_p->item.len); } /* * cert_signature_alg_id * gets the subject certificate signature algorithm ID from the * certificate structure * * returns * pointer to string containing the subject signature alg ID * * arguments * cert_p - cert_t pointer */ char *cert_signature_alg_id(cert_t *cert_p) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p) { return(NULL); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ tlv_p = (tlv_t *) data_element(seq_p); /* * this is a sequence, so traverse it */ seq_p = (seq_t *) tlv_p->value; tlv_p = (tlv_t *) head_element(seq_p); /* * now we should have the signature algorithm ID */ return(tlv_p->textval); } /* * cert_issuer_dn * gets the issuer DN from the certificate structure * * returns * pointer to issuer DN string (in volitile memory!!!) * * arguments * cert_p - cert_t pointer */ char *cert_issuer_dn(cert_t *cert_p) { seq_t *seq_p; if (!cert_p) { return(NULL); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ seq_p = skip_element(seq_p); /* skip the signature alg id */ return(dn_seq2str(seq_p)); } /* * cert_validity_start * gets the starting validity date from the certificate structure * in UTCtime format * * returns * UTCtime format string * * arguments * cert_p - cert_t pointer */ char *cert_validity_start(cert_t *cert_p) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p) { return(NULL); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ seq_p = skip_element(seq_p); /* skip the signature alg id */ seq_p = skip_element(seq_p); /* skip the issuer dn */ tlv_p = (tlv_t *) data_element(seq_p); /* * this is a sequence, so traverse it */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); tlv_p = (tlv_t *) data_element(seq_p); /* * now we should have the starting validity date */ return(tlv_p->textval); } /* * cert_valididy_end * gets the ending valididy date of the certificate in UTCtime format * * returns * UTCtime format string * * arguments * cert_p - cert_t pointer */ char *cert_validity_end(cert_t *cert_p) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p) { return(NULL); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ seq_p = skip_element(seq_p); /* skip the signature alg id */ seq_p = skip_element(seq_p); /* skip the issuer dn */ tlv_p = (tlv_t *) data_element(seq_p); /* * this is a sequence, so traverse it */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); seq_p = list_next(seq_p); /* skip the start date */ tlv_p = (tlv_t *) data_element(seq_p); /* * now we should have the ending validity date */ return(tlv_p->textval); } /* * cert_subject_dn * gets the subject distinguished name from the certificate structure * * returns * pointer to the subject DN (volitile memory area!!!) * * arguments * cert_p - cert_t pointer */ char *cert_subject_dn(cert_t *cert_p) { seq_t *seq_p; if (!cert_p) { return(NULL); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ seq_p = skip_element(seq_p); /* skip the signature alg id */ seq_p = skip_element(seq_p); /* skip the issuer dn */ seq_p = skip_element(seq_p); /* skip the validity period */ return(dn_seq2str(seq_p)); } /* * cert_public_key_alg_id * gets the algorithm ID text string for the subject public key from * the certificate structure * * returns * pointer to string containing the algorithm ID * * arguments * cert_p - cert_t pointer */ char *cert_public_key_alg_id(cert_t *cert_p) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p) { return(NULL); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ seq_p = skip_element(seq_p); /* skip the signature alg id */ seq_p = skip_element(seq_p); /* skip the issuer dn */ seq_p = skip_element(seq_p); /* skip the validity period */ seq_p = skip_element(seq_p); /* skip the subject dn */ tlv_p = (tlv_t *) data_element(seq_p); /* * this is a subjectPublicKeyInfo sequence, so traverse it */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); tlv_p = (tlv_t *) data_element(seq_p); /* * ah, yet another sequence, so traverse it too */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); tlv_p = (tlv_t *) data_element(seq_p); /* * we got a BINGO, let's grab the OID string */ return(tlv_p->textval); } /* * cert_public_key * gets the subject public key out of the certificate structure * * returns * number of bytes in the public key plus one byte for the * unused-bits byte at the front of the data area * * arguments * cert_p - cert_t pointer * pub_key_pp - address of pointer to point to subject public key */ int cert_public_key(cert_t *cert_p, u_char **pub_key_pp) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p || !pub_key_pp) { return(0); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ seq_p = skip_element(seq_p); /* skip the signature alg id */ seq_p = skip_element(seq_p); /* skip the issuer dn */ seq_p = skip_element(seq_p); /* skip the validity period */ seq_p = skip_element(seq_p); /* skip the subject dn */ tlv_p = (tlv_t *) data_element(seq_p); /* * this is a subjectPublicKeyInfo sequence, so traverse it */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); seq_p = list_next(seq_p); /* skip the OID */ tlv_p = (tlv_t *) data_element(seq_p); *pub_key_pp = tlv_p->value; return(tlv_p->item.len); } /* * cert_extensions * gets the extension named by the passed OID * * contributed by * Enrico Badella * * returns * number of bytes in the extension on success, zero on failure * * arguments * cert_p - cert_t pointer * extOID - OID of extension to find * extData - updated pointer to buffer holding extension data */ int cert_extensions(cert_t *cert_p, u_char *extOID, u_char **extData) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p || !extData) { return(0); } seq_p = signed_seq_cert_head(cert_p); seq_p = skip_version(seq_p); /* skip the version number */ seq_p = skip_element(seq_p); /* skip the serial number */ seq_p = skip_element(seq_p); /* skip the signature alg id */ seq_p = skip_element(seq_p); /* skip the issuer dn */ seq_p = skip_element(seq_p); /* skip the validity period */ seq_p = skip_element(seq_p); /* skip the subject dn */ seq_p = skip_element(seq_p); /* skip the public key */ tlv_p = (tlv_t *) data_element(seq_p); if (((tlv_p->item.id & CLASS_MASK) == CONTEXT) && ((tlv_p->item.id & ~(CLASS_MASK | FORM_MASK)) == 3)) { seq_t *set_p, *se_p; tlv_p = (tlv_t *) data_element(seq_p); tlv_p = (tlv_t *) head_element((seq_t *) tlv_p->value); seq_p = (seq_t *) data_element((seq_t *) tlv_p->value); seq_p = (seq_t *) tlv_p->value; FOREACH(seq_p, tlv_t, tlv_p) { set_p = (seq_t *) tlv_p->value; tlv_p = (tlv_t *) head_element(set_p); if (!strncmp((char *)extOID, (char *)tlv_p->value, tlv_p->item.len)) { tlv_p = (tlv_t *) head_element(list_head(set_p)); *extData = tlv_p->value; return tlv_p->item.len; } } ENDEACH; } return 0; } /* * cert_issuer_signature_alg_id * gets the algorithm ID string from the certificate structure * * returns * pointer to string containing the algorithm ID string * * arguments * cert_p - cert_t pointer */ char *cert_issuer_signature_alg_id(cert_t *cert_p) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p) { return(NULL); } /* * first element of main sequence */ tlv_p = (tlv_t *) head_element(cert_p); /* * skip the outside surrounding sequence */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); /* * next sequence is the algorithm ID of the issuer's signature */ seq_p = list_next(seq_p); tlv_p = (tlv_t *) data_element(seq_p); /* * ah, yet another sequence, so traverse it too */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); tlv_p = (tlv_t *) data_element(seq_p); /* * we got a BINGO, let's grab the OID string */ return(tlv_p->textval); } /* * cert_issuer_signature * gets the issuers signature of the signed sequence certificate * * returns * number of bytes in the signature plus one more byte representing * the first bits-unused byte * * arguments * cert_p - cert_t pointer * issuer_sig_pp - address of pointer updated to point to memory * containing the signature bytes */ int cert_issuer_signature(cert_t *cert_p, u_char **issuer_sig_pp) { seq_t *seq_p; tlv_t *tlv_p; if (!cert_p || !issuer_sig_pp) { return(0); } /* * first element of main sequence */ tlv_p = (tlv_t *) head_element(cert_p); /* * skip the outside surrounding sequence */ seq_p = (seq_t *) tlv_p->value; seq_p = list_head(seq_p); /* * next sequence is the algorithm ID of the issuer's signature */ seq_p = list_next(seq_p); /* * next sequence is (finally) the issuer's signature */ seq_p = list_next(seq_p); tlv_p = (tlv_t *) data_element(seq_p); *issuer_sig_pp = tlv_p->value; return(tlv_p->item.len); } #if !defined(WIN32) /* * cert_UTC2ctime * formats an ugly UTCtime validity date in ctime() format * * returns * pointer to a formatted date string (VOLITILE) on success, * NULL on failure * * arguments * zulu - goofy zulu formatted UTCtime string */ char *cert_UTC2ctime(char *UTCtime) { char *t; int yy, mm, dd, HH, MM, SS; struct tm ts; time_t timer; static char gmt[128]; if (!UTCtime) { return(NULL); } if (sscanf(UTCtime, "%2d%2d%2d%2d%2d%2dZ", &yy, &mm, &dd, &HH, &MM, &SS) < 6) { return(NULL); } sprintf(gmt, "%d:%d:%d:%d:%d:%d", yy, mm, dd, HH, MM, SS); strptime(gmt, "%y:%m:%d:%H:%M:%S", &ts); strftime(gmt, sizeof(gmt),"%c",&ts); if (t = strrchr(gmt, '\n')) { *t = 0; } return(gmt); } #endif /* * cert_der_length * returns the length of the asn.1 encoded string, not including * null termination * * returns * length of octet string on success, zero on failure * * arguments * asn_p - asn.1 encoded string */ int cert_der_length(u_char *asn_p) { if (asn_p) { return(asn1_strlen(asn_p)); } else { return(0); } } #ifdef L1TEST /* * main * test harness to debug and display ussage of the cert_*() * functions. compile like: * * cc -DDEBUG -DL1TEST -Ae -g -c certutil.c * cc -Ae -g -c b64dec.c * cc -g -o certutil b64dec.o certutil.o */ char slurp[65536]; u_char *slurp_p; int slurpsize = 0; int main(int argc, char *argv[]) { FILE *inFile; u_char *asn_p, *kp; int len; cert_t *cert_p; u_char *ser_p; u_char extOID[512]; /* Check args and open the input file */ if (argc != 2) { fprintf(stderr, "Usage: certutil "); exit(EXIT_FAILURE); } if ((inFile = fopen(argv[1], "rb")) == NULL) { perror(argv[1]); exit(EXIT_FAILURE); } slurpsize = fread(slurp, 1, 65536, inFile); fclose(inFile); fprintf(stderr, "slurped %d bytes\n\n", slurpsize); cert_p = cert_b64_decode(slurp); dump_sequence(cert_p, 0); fprintf(stderr, "cert version : %d\n", cert_version(cert_p)); len = cert_serial_number(cert_p, &ser_p); fprintf(stderr, "serial number :"); dump_hex(ser_p, len, 2); fprintf(stderr, "\n"); fprintf(stderr, "signature alg : %s\n", cert_signature_alg_id(cert_p)); fprintf(stderr, "issuer dn : %s\n", cert_issuer_dn(cert_p)); fprintf(stderr, "validity start: %s\n", cert_validity_start(cert_p)); fprintf(stderr, "validity end : %s\n", cert_validity_end(cert_p)); fprintf(stderr, "subject dn : %s\n", cert_subject_dn(cert_p)); fprintf(stderr, "subj pub k alg: %s\n", cert_public_key_alg_id(cert_p)); len = cert_public_key(cert_p, &kp); fprintf(stderr, "subj pubkeylen: %d\n", len-1); fprintf(stderr, "subj pub key :"); dump_hex(kp+1, len-1, 2); len = cert_public_key(cert_p, &kp); fprintf(stderr, "extensions :"); dump_hex(kp+1, len-1, 2); fprintf(stderr, "\n"); fprintf(stderr, "issuer sig alg: %s\n", cert_issuer_signature_alg_id(cert_p)); len = cert_issuer_signature(cert_p, &kp); fprintf(stderr, "issuer sig len: %d\n", len-1); fprintf(stderr, "issuer sig :"); dump_hex(kp+1, len-1, 2); fprintf(stderr, "\n"); strcpy((char *)extOID, "\x55\x1D\x0E"); len = cert_extensions(cert_p, extOID, &kp); fprintf(stderr, "\n"); fprintf(stderr, "extension Netscape subjectKeyIdentifier len: %d\n", len); fprintf(stderr, "extension value :"); dump_hex(kp, len, 2); strcpy((char *)extOID, "\x60\x86\x48\x01\x86\xF8\x42\x01\x01"); len = cert_extensions(cert_p, extOID, &kp); fprintf(stderr, "\n"); fprintf(stderr, "extension Netscape cert-type len: %d\n", len); fprintf(stderr, "extension value :"); dump_hex(kp, len, 2); strcpy((char *)extOID, "\x55\x1D\x23"); len = cert_extensions(cert_p, extOID, &kp); fprintf(stderr, "\n"); fprintf(stderr, "extension Netscape authorityKeyIdentifier len: %d\n", len); fprintf(stderr, "extension value :"); dump_hex(kp, len, 2); fprintf(stderr, "\n"); cert_free(cert_p); return(EXIT_SUCCESS); } #endif