Files
mars-matrixssl/crypto/keyformat/asn1fmt.c
Janne Johansson d0a51a7e43 MatrixSSL 4.0.0
2018-09-13 12:17:26 +03:00

744 lines
22 KiB
C

/**
* @file x509dbg.c
* @version $Format:%h%d$
*
* ASN.1 Parsing: convenience functions for formatting ASN.1.
*/
/*
* Copyright (c) 2013-2017 INSIDE Secure Corporation
* Copyright (c) PeerSec Networks, 2002-2011
* All Rights Reserved
*
* The latest version of this code is available at http://www.matrixssl.org
*
* This software is open source; 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 General Public License does NOT permit incorporating this software
* into proprietary programs. If you are unable to comply with the GPL, a
* commercial license for this software may be purchased from INSIDE at
* http://www.insidesecure.com/
*
* This program is distributed in 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* http://www.gnu.org/copyleft/gpl.html
*/
/******************************************************************************/
#if !defined USE_X509 && !defined USE_OCSP_RESPONSE
# include "../cryptoImpl.h" /* MatrixSSL API interface and configuration. */
#endif
#if (defined USE_X509 && defined USE_FULL_CERT_PARSE) || defined USE_OCSP_RESPONSE
# include "osdep_stdio.h" /* for Snprintf() */
# include "osdep_string.h" /* for Strlen() */
#include "psPrnf.h"
/* Constants used in OID formatting code. */
# define OID_STR_BUF_LEN (129 * 4) /* Temporary string length. */
# define OID_STR_MAX_SEQ_LEN 64 /* Maximum octets in sequence. */
/* Access bitarray containing 7 bits of data per octet. */
static unsigned char oid_get_bit7(const unsigned char *bitarray,
size_t n, int i)
{
unsigned char byte;
size_t a = (size_t) (i / 7);
int bitidx = i % 7;
if (n <= a)
{
return 0;
}
byte = bitarray[n - a - 1];
byte >>= bitidx;
return byte & 1;
}
/* Perform conversion between OID encoded data (i.e. BER compressed
integer like perl pack("w"), and a long sequence of octets. */
static
unsigned int oid_double_dabble_workhorse(const unsigned char *b,
size_t n,
unsigned char t[],
int v_bits, size_t t_bytes)
{
int i;
size_t j;
unsigned int x;
unsigned int overflow = 0;
size_t t_bcdbytes = (t_bytes + 1) / 2;
for (j = t_bcdbytes; j-- > 0; )
{
t[j] = 0;
}
/* Compute BCD corresponding with Buc_p.
(double-dabble algorithm). */
for (i = v_bits; i-- > 0; )
{
unsigned char c = oid_get_bit7(b, n, i);
x = c;
for (j = t_bcdbytes; j-- > 0; )
{
x += (2 * (unsigned int) t[j]);
t[j] = x & 255;
x >>= 8;
}
overflow |= x;
if (i == 0)
{
break;
}
for (j = t_bcdbytes; j-- > 0; )
{
unsigned char a, add51, m;
a = t[j];
add51 = a + 51;
m = add51 & 0x88;
m |= m >> 2;
m |= m >> 1;
t[j] = (a & ~m) | (add51 & m);
}
}
/* Convert BCD to decimal. */
if ((t_bytes & 1) == 1)
{
/* The result is shifted 4 bits; fix it. */
overflow |= t[0] >> 4;
for (j = t_bytes; j-- > 0; )
{
if (j & 1)
{
t[j] = '0' + (t[j / 2 + 1] >> 4);
}
else
{
t[j] = '0' + (t[j / 2] & 15);
}
}
}
else
{
for (j = t_bytes; j-- > 0; )
{
if (j & 1)
{
t[j] = '0' + (t[j / 2] & 15);
}
else
{
t[j] = '0' + (t[j / 2] >> 4);
}
}
}
return overflow;
}
/* Append to string s (assumed sufficiently long) a contiguous segment of
BER compressed integer like perl pack("w") unpacked. This function
processes at most 64 bytes at once (i.e. up-to 72683872429560689054932380
7888004534353641360687318060281490199180639288113397923326191050713763565
560762521606266177933534601628614655).
This range is sufficient for typical OIDs as well as UUID-based OIDs.
*/
static size_t oid_part_append(char *s, const unsigned char *oid, size_t oidlen)
{
size_t pos;
unsigned long long ll;
const unsigned char *oid_orig = oid;
/* The most common case: single byte oid segment. */
if (*oid < 128)
{
Sprintf(s, ".%d", *oid);
return 1;
}
else if (*oid == 128)
{
/* Illegal: One of the highest bits shall be set. */
return 0;
}
/* Handle oid parts smaller than 2**64-1. */
ll = *oid & 127;
pos = 1;
while (pos < oidlen)
{
oid++;
ll *= 128;
ll += *oid & 127;
if (*oid < 128)
{
if (pos < 8)
{
Sprintf(s, ".%llu", ll);
return pos + 1;
}
else if (pos < OID_STR_MAX_SEQ_LEN)
{
size_t plen;
size_t ilen;
/* Precision may exceed capacity of unsigned long long.
Use variant of double-dabble that can do arbitrary
precision. */
pos += 1;
*s = '.';
Memset(s + 1, 0, pos * 3 + 1);
oid_double_dabble_workhorse(oid_orig, pos,
(unsigned char *) (s + 1),
pos * 8, pos * 3);
/* The string formatting generates extra zeroes. Remove them. */
s += 1; /* Skip '.' */
ilen = Strlen(s);
plen = 0;
while (plen < ilen && plen < ilen - 1 && s[plen] == '0')
{
plen++;
}
/* Remove initial zeroes. */
Memmove(s, s + plen, ilen + 1 - plen);
return pos;
}
else
{
/* Single OID component exceeds sizes required for any
known uses. These are not handled. */
return 0;
}
}
pos++;
}
return 0; /* Unable to process. */
}
/* Decrement 1 from number expressed in ascii. */
static void oid_asciidec(char *s, size_t l)
{
size_t i;
int dec = 1;
for (i = l; i-- > 0; )
{
s[i] -= dec;
if (s[i] < '0')
{
s[i] = '9';
}
else
{
dec = 0;
}
}
}
/* Format OID to string buffer. Returns position within the buffer
on successful execution or NULL on failure. */
static char *oid_to_string(const unsigned char *oid, size_t oidlen,
char str[OID_STR_BUF_LEN])
{
char *s = str;
int prefix = 0; /* Ignored bytes in beginning. */
str[0] = 0;
/* Only process OID identifiers, and up-to 129 bytes long, with
correct length identifier. */
if (oidlen < 3 || oidlen > 129 || oid[0] != 0x06 || oid[1] != oidlen - 2)
{
return NULL;
}
if (oid[2] < 120)
{
/* Simple case, [012].x where x < 40. */
Sprintf(s, "%d.%d", oid[2] / 40, oid[2] % 40);
s += Strlen(s);
oid += 3;
oidlen -= 3;
}
else
{
/* Process 2.xxx, where xxx is arbitrary length number >= 40. */
size_t bytes = oid_part_append(s + 1, oid + 2, oidlen - 2);
int i;
if (bytes < 2)
{
return NULL;
}
/* Decrement tens eight time. */
for (i = 0; i < 8; i++)
{
oid_asciidec(s + 2, Strlen(s + 2) - 1);
}
/* Check if there were extra zeroes in s[2]. */
while (Strlen(s + 2) && s[2] == '0')
{
s++;
prefix++;
}
s[0] = '2';
s[1] = '.';
s += Strlen(s);
oid += 2 + bytes;
oidlen -= 2 + bytes;
}
while (oidlen > 0)
{
size_t bytes = oid_part_append(s, oid, oidlen);
if (bytes == 0)
{
return NULL;
}
oidlen -= bytes;
oid += bytes;
s += Strlen(s);
}
return str + prefix;
}
# ifndef NO_ASN_FORMAT_OID
char *asnFormatOid(psPool_t *pool,
const unsigned char *oid, size_t oidlen)
{
/* Perform formatting for oid. */
char *out;
char str_tmp[OID_STR_BUF_LEN];
char *str = oid_to_string(oid, oidlen, str_tmp);
if (str == NULL)
{
return NULL;
}
/* Allocate dynamically new memory for the result. */
out = psMalloc(pool, Strlen(str) + 1);
if (out)
{
Memcpy(out, str, Strlen(str) + 1);
}
return out;
}
# endif /* NO_ASN_FORMAT_OID */
struct asnFormatTagInfo
{
const char *str_short;
const char *str_long;
unsigned char constructed;
unsigned char omit;
unsigned char string8bit;
};
static struct asnFormatTagInfo asnFormatTagInfo[128] =
{
{ "END", "End-Of-Contents", 0, 0, 0 },
{ "BOOL", "BOOLEAN", 0, 1, 0 },
{ "INT", "INTEGER", 0, 1, 0 },
{ "BITS", "BIT STRING", 0, 0, 0 },
{ "OCTETS", "OCTET STRING", 0, 0, 0 },
{ "NULL", "NULL", 0, 0, 0 },
{ "OID", "OBJECT IDENTIFIER", 0, 1, 0 },
{ "Obj", "ObjectDescriptor", 0, 0, 0 },
{ "INST", "INSTANCE OF", 0, 0, 0 },
{ "REAL", "REAL", 0, 1, 0 },
{ "ENUM", "ENUMERATED", 0, 0, 0 },
{ "EMB", "EMBEDDED PDV", 0, 0, 0 },
{ "UTF8", "UTF8String", 0, 0, 1 },
{ "REL-OID", "RELATIVE-OID", 0, 0, 0 },
{ "Uni[14]", "Universal[14]", 0, 0, 0 },
{ "Uni[15]", "Universal[15]", 0, 0, 0 },
{ "SEQ", "SEQUENCE", 1, 0, 0 },
{ "SET", "SET", 1, 0, 0 },
{ "Num", "NumericString", 0, 0, 1 },
{ "Print", "PrintableString", 0, 1, 1 },
{ "T.61", "TeletexString", 0, 1, 1 },
{ "VID", "VideotexString", 0, 1, 1 },
{ "ASCII", "IA5String", 0, 0, 1 },
{ "UTCTime", "UTCTime", 0, 1, 1 },
{ "GenTime", "GeneralizedTime", 0, 1, 1 },
{ "Graphic", "GraphicString", 0, 0, 1 },
{ "Visible", "VisibleString", 0, 0, 1 },
{ "Gen", "GeneralString", 0, 0, 1 },
{ "UTF-32", "UniversalString", 0, 0, 0 },
{ "STR", "CHARACTER STRING", 0, 0, 1 },
{ "BMP", "BMP STRING", 0, 0, 1 },
{ "Uni[31]", "Universal[31]", 0, 0, 1 },
{ "App[0]", "Application[0]", 1, 0, 0 },
{ "App[1]", "Application[1]", 1, 0, 0 },
{ "App[2]", "Application[2]", 1, 0, 0 },
{ "App[3]", "Application[3]", 1, 0, 0 },
{ "App[4]", "Application[4]", 1, 0, 0 },
{ "App[5]", "Application[5]", 1, 0, 0 },
{ "App[6]", "Application[6]", 1, 0, 0 },
{ "App[7]", "Application[7]", 1, 0, 0 },
{ "App[8]", "Application[8]", 1, 0, 0 },
{ "App[9]", "Application[9]", 1, 0, 0 },
{ "App[10]", "Application[10]", 1, 0, 0 },
{ "App[11]", "Application[11]", 1, 0, 0 },
{ "App[12]", "Application[12]", 1, 0, 0 },
{ "App[13]", "Application[13]", 1, 0, 0 },
{ "App[14]", "Application[14]", 1, 0, 0 },
{ "App[15]", "Application[15]", 1, 0, 0 },
{ "App[16]", "Application[16]", 1, 0, 0 },
{ "App[17]", "Application[17]", 1, 0, 0 },
{ "App[18]", "Application[18]", 1, 0, 0 },
{ "App[19]", "Application[19]", 1, 0, 0 },
{ "App[20]", "Application[20]", 1, 0, 0 },
{ "App[21]", "Application[21]", 1, 0, 0 },
{ "App[22]", "Application[22]", 1, 0, 0 },
{ "App[23]", "Application[23]", 1, 0, 0 },
{ "App[24]", "Application[24]", 1, 0, 0 },
{ "App[25]", "Application[25]", 1, 0, 0 },
{ "App[26]", "Application[26]", 1, 0, 0 },
{ "App[27]", "Application[27]", 1, 0, 0 },
{ "App[28]", "Application[28]", 1, 0, 0 },
{ "App[29]", "Application[29]", 1, 0, 0 },
{ "App[30]", "Application[30]", 1, 0, 0 },
{ "App[31]", "Application[31]", 1, 0, 0 },
{ "Cont[0]", "Context-Specific[0]", 1, 0, 0 },
{ "Cont[1]", "Context-Specific[1]", 1, 0, 0 },
{ "Cont[2]", "Context-Specific[2]", 1, 0, 0 },
{ "Cont[3]", "Context-Specific[3]", 1, 0, 0 },
{ "Cont[4]", "Context-Specific[4]", 1, 0, 0 },
{ "Cont[5]", "Context-Specific[5]", 1, 0, 0 },
{ "Cont[6]", "Context-Specific[6]", 1, 0, 0 },
{ "Cont[7]", "Context-Specific[7]", 1, 0, 0 },
{ "Cont[8]", "Context-Specific[8]", 1, 0, 0 },
{ "Cont[9]", "Context-Specific[9]", 1, 0, 0 },
{ "Cont[10]", "Context-Specific[10]", 1, 0, 0 },
{ "Cont[11]", "Context-Specific[11]", 1, 0, 0 },
{ "Cont[12]", "Context-Specific[12]", 1, 0, 0 },
{ "Cont[13]", "Context-Specific[13]", 1, 0, 0 },
{ "Cont[14]", "Context-Specific[14]", 1, 0, 0 },
{ "Cont[15]", "Context-Specific[15]", 1, 0, 0 },
{ "Cont[16]", "Context-Specific[16]", 1, 0, 0 },
{ "Cont[17]", "Context-Specific[17]", 1, 0, 0 },
{ "Cont[18]", "Context-Specific[18]", 1, 0, 0 },
{ "Cont[19]", "Context-Specific[19]", 1, 0, 0 },
{ "Cont[20]", "Context-Specific[20]", 1, 0, 0 },
{ "Cont[21]", "Context-Specific[21]", 1, 0, 0 },
{ "Cont[22]", "Context-Specific[22]", 1, 0, 0 },
{ "Cont[23]", "Context-Specific[23]", 1, 0, 0 },
{ "Cont[24]", "Context-Specific[24]", 1, 0, 0 },
{ "Cont[25]", "Context-Specific[25]", 1, 0, 0 },
{ "Cont[26]", "Context-Specific[26]", 1, 0, 0 },
{ "Cont[27]", "Context-Specific[27]", 1, 0, 0 },
{ "Cont[28]", "Context-Specific[28]", 1, 0, 0 },
{ "Cont[29]", "Context-Specific[29]", 1, 0, 0 },
{ "Cont[30]", "Context-Specific[30]", 1, 0, 0 },
{ "Cont[31]", "Context-Specific[31]", 1, 0, 0 },
{ "Priv[0]", "Private[0]", 1, 0, 0 },
{ "Priv[1]", "Private[1]", 1, 0, 0 },
{ "Priv[2]", "Private[2]", 1, 0, 0 },
{ "Priv[3]", "Private[3]", 1, 0, 0 },
{ "Priv[4]", "Private[4]", 1, 0, 0 },
{ "Priv[5]", "Private[5]", 1, 0, 0 },
{ "Priv[6]", "Private[6]", 1, 0, 0 },
{ "Priv[7]", "Private[7]", 1, 0, 0 },
{ "Priv[8]", "Private[8]", 1, 0, 0 },
{ "Priv[9]", "Private[9]", 1, 0, 0 },
{ "Priv[10]", "Private[10]", 1, 0, 0 },
{ "Priv[11]", "Private[11]", 1, 0, 0 },
{ "Priv[12]", "Private[12]", 1, 0, 0 },
{ "Priv[13]", "Private[13]", 1, 0, 0 },
{ "Priv[14]", "Private[14]", 1, 0, 0 },
{ "Priv[15]", "Private[15]", 1, 0, 0 },
{ "Priv[16]", "Private[16]", 1, 0, 0 },
{ "Priv[17]", "Private[17]", 1, 0, 0 },
{ "Priv[18]", "Private[18]", 1, 0, 0 },
{ "Priv[19]", "Private[19]", 1, 0, 0 },
{ "Priv[20]", "Private[20]", 1, 0, 0 },
{ "Priv[21]", "Private[21]", 1, 0, 0 },
{ "Priv[22]", "Private[22]", 1, 0, 0 },
{ "Priv[23]", "Private[23]", 1, 0, 0 },
{ "Priv[24]", "Private[24]", 1, 0, 0 },
{ "Priv[25]", "Private[25]", 1, 0, 0 },
{ "Priv[26]", "Private[26]", 1, 0, 0 },
{ "Priv[27]", "Private[27]", 1, 0, 0 },
{ "Priv[28]", "Private[28]", 1, 0, 0 },
{ "Priv[29]", "Private[29]", 1, 0, 0 },
{ "Priv[30]", "Private[30]", 1, 0, 0 },
{ "Priv[31]", "Private[31]", 1, 0, 0 }
};
static unsigned char idx(unsigned char tag)
{
unsigned char idx = (tag & 31) | ((tag & 192) >> 1);
return idx;
}
const char *asnFormatTagId(unsigned char tag)
{
return asnFormatTagInfo[idx(tag)].str_short;
}
const char *asnFormatTagIdLong(unsigned char tag)
{
return asnFormatTagInfo[idx(tag)].str_long;
}
static char *internalStrdup(psPool_t *pool, const char *string)
{
size_t len;
char *new_str;
if (string == NULL)
{
return NULL;
}
len = Strlen(string) + 1;
new_str = psMalloc(pool, len);
if (new_str)
{
Memcpy(new_str, string, len);
}
return new_str;
}
char *asnFormatDer(psPool_t *pool,
const unsigned char *Der_p,
size_t DerLen,
size_t MaxElements,
size_t MaxDepth,
size_t MaxElementOutput,
unsigned char Flags)
{
size_t i;
psParseBuf_t pb;
psParseBuf_t sub;
psDynBuf_t str;
size_t endlen;
size_t sz;
size_t MaxElementOutputD2;
if (MaxElements == 0)
{
MaxElements = (size_t) -1;
}
if (MaxDepth == 0)
{
MaxDepth = (size_t) -1;
}
if (MaxElementOutput == 0)
{
MaxElementOutput = (size_t) -1;
}
MaxElementOutputD2 = MaxElementOutput / 2;
if (DerLen < 1)
{
return internalStrdup(pool, "[length = 0]");
}
if (psParseBufFromStaticData(&pb, Der_p, DerLen) != PS_SUCCESS)
{
return internalStrdup(pool, "[ASN.1 Parser Failure]");
}
psDynBufInit(pool, &str, 80);
for(i = 0; i < MaxElements; i++)
{
unsigned char tagid[1];
const char *tagstr;
int has_content;
size_t content_len;
int printed;
int negative = 0;
const unsigned char *content;
const unsigned char *tagdata;
if (!psParseCanRead(&pb, 1))
{
break; /* All elements read. */
}
sz = 1;
(void) psParseBufCopyN(&pb, 1, tagid, &sz);
tagdata = psBufGetData(&pb.buf);
if (sz != 1 || !psParseBufTryReadTagSub(&pb, &sub, tagid[0]))
{
psDynBufAppendStr(&str, "[unparseable data]");
break; /* All elements read up-to error. */
}
if (i > 0)
{
psDynBufAppendStr(&str, ", ");
}
tagstr = asnFormatTagId(tagid[0]);
has_content = psParseCanRead(&sub, 1);
content = psBufGetData(&sub.buf);
content_len = psBufGetDataSize(&sub.buf);
printed = 0;
/* Formatting: tag specific. */
switch (tagid[0])
{
case 1:
if (content_len == 1 && content[0] == 0xff)
{
psDynBufAppendStrf(&str, "%s(true)", tagstr);
printed = 1;
}
if (content_len == 1 && content[0] == 0x00)
{
psDynBufAppendStrf(&str, "%s(false)", tagstr);
printed = 1;
}
break;
case 2:
if (content_len > 0 && (content[0] & 0x80) != 0)
{
negative = 1;
}
/* FALL-THROUGH */
case 4:
if (content_len > 0 && content_len <= MaxElementOutputD2)
{
/* This is a number or other sequence of hex. */
PS_PRNF_CTX;
char *tmp_str = psAsprnf(pool, PSF,
PSA_HEX(content, content_len));
if (tmp_str)
{
psDynBufAppendStrf(&str, "%s(%s0x%s)",
tagstr, negative ? "-" : "", tmp_str);
psFree(tmp_str, pool);
printed = 1;
}
}
break;
case 3:
if (content_len > 1 && content_len <= MaxElementOutputD2)
{
/* Print bit string as hex sequence. */
PS_PRNF_CTX;
char *tmp_str = psAsprnf(pool, PSF,
PSA_HEX(content + 1, content_len - 1));
if (tmp_str)
{
if (content[0] != 0)
{
psDynBufAppendStrf(&str, "%s(0x%s[padding=%u])",
tagstr, tmp_str, content[0]);
}
else
{
psDynBufAppendStrf(&str, "%s(0x%s)",
tagstr, tmp_str);
}
psFree(tmp_str, pool);
printed = 1;
}
}
break;
case 5:
/* NULL needs to be used without any content.
Omit "(empty)" in that case. */
if (content_len == 0)
{
psDynBufAppendStr(&str, tagstr);
printed = 1;
}
break;
case 6:
if (content_len > 0 && content_len <= MaxElementOutputD2)
{
/* OID: Pass formatter also tag id and tag length. */
size_t hlen = content - tagdata;
char *tmp_str = asnFormatOid(pool, tagdata, hlen + content_len);
if (tmp_str)
{
psDynBufAppendStrf(&str, "%s(%s)", tagstr, tmp_str);
psFree(tmp_str, pool);
printed = 1;
}
}
break;
default:
/* Constructed encodings perform recursion. */
if ((tagid[0] & 32) == 32 && content_len > 0 && MaxDepth > 1)
{
char *tmp_str = asnFormatDer(pool,
content,
content_len,
0,
MaxDepth - 1,
MaxElementOutput,
Flags);
if (tmp_str)
{
psDynBufAppendStrf(&str, "%s(%s)", tagstr, tmp_str);
psFree(tmp_str, pool);
printed = 1;
}
break;
}
/* Generic handling for 8-bit strings. */
if (asnFormatTagInfo[idx(tagid[0])].string8bit &&
content_len <= MaxElementOutput)
{
/* This is 8bit string. */
PS_PRNF_CTX;
char *tmp_str = psAsprnf(pool, PSF,
PSA_SSTR((const char *)content,
content_len));
if (tmp_str)
{
psDynBufAppendStrf(&str, "%s(\"%s\")", tagstr, tmp_str);
psFree(tmp_str, pool);
printed = 1;
}
}
}
/* Last resort: Indicate number of bytes. */
if (!printed)
{
psDynBufAppendStrf(&str, "%s(%s)",
tagstr, has_content? "..." : "empty");
}
(void)psParseBufFinish(&sub);
}
endlen = 0;
while(psParseCanRead(&pb, 1))
{
psParseBufSkipBytes(&pb, NULL, 1);
endlen++;
}
if (endlen > 0)
{
psDynBufAppendStrf(&str, "[%llu unprocessed bytes]",
(unsigned long long) endlen);
}
(void)psParseBufFinish(&pb);
psDynBufAppendChar(&str, 0); /* Add terminating NUL character. */
return psDynBufDetach(&str, &sz);
}
#endif /* compilation selector: full X.509 or OCSP enabled */