curl/lib/x509asn1.c
Daniel Stenberg deb9462ff2
wolfssl: refer to it as wolfSSL only
Remove support for, references to and use of "cyaSSL" from the source
and docs. wolfSSL is the current name and there's no point in keeping
references to ancient history.

Assisted-by: Daniel Gustafsson

Closes #3903
2019-06-10 09:18:16 +02:00

1284 lines
35 KiB
C

/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include "curl_setup.h"
#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \
defined(USE_WOLFSSL) || defined(USE_SCHANNEL)
#include <curl/curl.h>
#include "urldata.h"
#include "strcase.h"
#include "hostcheck.h"
#include "vtls/vtls.h"
#include "sendf.h"
#include "inet_pton.h"
#include "curl_base64.h"
#include "x509asn1.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
/* ASN.1 OIDs. */
static const char cnOID[] = "2.5.4.3"; /* Common name. */
static const char sanOID[] = "2.5.29.17"; /* Subject alternative name. */
static const curl_OID OIDtable[] = {
{ "1.2.840.10040.4.1", "dsa" },
{ "1.2.840.10040.4.3", "dsa-with-sha1" },
{ "1.2.840.10045.2.1", "ecPublicKey" },
{ "1.2.840.10045.3.0.1", "c2pnb163v1" },
{ "1.2.840.10045.4.1", "ecdsa-with-SHA1" },
{ "1.2.840.10046.2.1", "dhpublicnumber" },
{ "1.2.840.113549.1.1.1", "rsaEncryption" },
{ "1.2.840.113549.1.1.2", "md2WithRSAEncryption" },
{ "1.2.840.113549.1.1.4", "md5WithRSAEncryption" },
{ "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" },
{ "1.2.840.113549.1.1.10", "RSASSA-PSS" },
{ "1.2.840.113549.1.1.14", "sha224WithRSAEncryption" },
{ "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" },
{ "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" },
{ "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" },
{ "1.2.840.113549.2.2", "md2" },
{ "1.2.840.113549.2.5", "md5" },
{ "1.3.14.3.2.26", "sha1" },
{ cnOID, "CN" },
{ "2.5.4.4", "SN" },
{ "2.5.4.5", "serialNumber" },
{ "2.5.4.6", "C" },
{ "2.5.4.7", "L" },
{ "2.5.4.8", "ST" },
{ "2.5.4.9", "streetAddress" },
{ "2.5.4.10", "O" },
{ "2.5.4.11", "OU" },
{ "2.5.4.12", "title" },
{ "2.5.4.13", "description" },
{ "2.5.4.17", "postalCode" },
{ "2.5.4.41", "name" },
{ "2.5.4.42", "givenName" },
{ "2.5.4.43", "initials" },
{ "2.5.4.44", "generationQualifier" },
{ "2.5.4.45", "X500UniqueIdentifier" },
{ "2.5.4.46", "dnQualifier" },
{ "2.5.4.65", "pseudonym" },
{ "1.2.840.113549.1.9.1", "emailAddress" },
{ "2.5.4.72", "role" },
{ sanOID, "subjectAltName" },
{ "2.5.29.18", "issuerAltName" },
{ "2.5.29.19", "basicConstraints" },
{ "2.16.840.1.101.3.4.2.4", "sha224" },
{ "2.16.840.1.101.3.4.2.1", "sha256" },
{ "2.16.840.1.101.3.4.2.2", "sha384" },
{ "2.16.840.1.101.3.4.2.3", "sha512" },
{ (const char *) NULL, (const char *) NULL }
};
/*
* Lightweight ASN.1 parser.
* In particular, it does not check for syntactic/lexical errors.
* It is intended to support certificate information gathering for SSL backends
* that offer a mean to get certificates as a whole, but do not supply
* entry points to get particular certificate sub-fields.
* Please note there is no pretention here to rewrite a full SSL library.
*/
static const char *getASN1Element(curl_asn1Element *elem,
const char *beg, const char *end)
WARN_UNUSED_RESULT;
static const char *getASN1Element(curl_asn1Element *elem,
const char *beg, const char *end)
{
unsigned char b;
unsigned long len;
curl_asn1Element lelem;
/* Get a single ASN.1 element into `elem', parse ASN.1 string at `beg'
ending at `end'.
Returns a pointer in source string after the parsed element, or NULL
if an error occurs. */
if(!beg || !end || beg >= end || !*beg ||
(size_t)(end - beg) > CURL_ASN1_MAX)
return NULL;
/* Process header byte. */
elem->header = beg;
b = (unsigned char) *beg++;
elem->constructed = (b & 0x20) != 0;
elem->class = (b >> 6) & 3;
b &= 0x1F;
if(b == 0x1F)
return NULL; /* Long tag values not supported here. */
elem->tag = b;
/* Process length. */
if(beg >= end)
return NULL;
b = (unsigned char) *beg++;
if(!(b & 0x80))
len = b;
else if(!(b &= 0x7F)) {
/* Unspecified length. Since we have all the data, we can determine the
effective length by skipping element until an end element is found. */
if(!elem->constructed)
return NULL;
elem->beg = beg;
while(beg < end && *beg) {
beg = getASN1Element(&lelem, beg, end);
if(!beg)
return NULL;
}
if(beg >= end)
return NULL;
elem->end = beg;
return beg + 1;
}
else if((unsigned)b > (size_t)(end - beg))
return NULL; /* Does not fit in source. */
else {
/* Get long length. */
len = 0;
do {
if(len & 0xFF000000L)
return NULL; /* Lengths > 32 bits are not supported. */
len = (len << 8) | (unsigned char) *beg++;
} while(--b);
}
if(len > (size_t)(end - beg))
return NULL; /* Element data does not fit in source. */
elem->beg = beg;
elem->end = beg + len;
return elem->end;
}
/*
* Search the null terminated OID or OID identifier in local table.
* Return the table entry pointer or NULL if not found.
*/
static const curl_OID * searchOID(const char *oid)
{
const curl_OID *op;
for(op = OIDtable; op->numoid; op++)
if(!strcmp(op->numoid, oid) || strcasecompare(op->textoid, oid))
return op;
return NULL;
}
/*
* Convert an ASN.1 Boolean value into its string representation. Return the
* dynamically allocated string, or NULL if source is not an ASN.1 Boolean
* value.
*/
static const char *bool2str(const char *beg, const char *end)
{
if(end - beg != 1)
return NULL;
return strdup(*beg? "TRUE": "FALSE");
}
/*
* Convert an ASN.1 octet string to a printable string.
* Return the dynamically allocated string, or NULL if an error occurs.
*/
static const char *octet2str(const char *beg, const char *end)
{
size_t n = end - beg;
char *buf = NULL;
if(n <= (SIZE_T_MAX - 1) / 3) {
buf = malloc(3 * n + 1);
if(buf)
for(n = 0; beg < end; n += 3)
msnprintf(buf + n, 4, "%02x:", *(const unsigned char *) beg++);
}
return buf;
}
static const char *bit2str(const char *beg, const char *end)
{
/* Convert an ASN.1 bit string to a printable string.
Return the dynamically allocated string, or NULL if an error occurs. */
if(++beg > end)
return NULL;
return octet2str(beg, end);
}
/*
* Convert an ASN.1 integer value into its string representation.
* Return the dynamically allocated string, or NULL if source is not an
* ASN.1 integer value.
*/
static const char *int2str(const char *beg, const char *end)
{
unsigned long val = 0;
size_t n = end - beg;
if(!n)
return NULL;
if(n > 4)
return octet2str(beg, end);
/* Represent integers <= 32-bit as a single value. */
if(*beg & 0x80)
val = ~val;
do
val = (val << 8) | *(const unsigned char *) beg++;
while(beg < end);
return curl_maprintf("%s%lx", val >= 10? "0x": "", val);
}
/*
* Perform a lazy conversion from an ASN.1 typed string to UTF8. Allocate the
* destination buffer dynamically. The allocation size will normally be too
* large: this is to avoid buffer overflows.
* Terminate the string with a nul byte and return the converted
* string length.
*/
static ssize_t
utf8asn1str(char **to, int type, const char *from, const char *end)
{
size_t inlength = end - from;
int size = 1;
size_t outlength;
char *buf;
*to = NULL;
switch(type) {
case CURL_ASN1_BMP_STRING:
size = 2;
break;
case CURL_ASN1_UNIVERSAL_STRING:
size = 4;
break;
case CURL_ASN1_NUMERIC_STRING:
case CURL_ASN1_PRINTABLE_STRING:
case CURL_ASN1_TELETEX_STRING:
case CURL_ASN1_IA5_STRING:
case CURL_ASN1_VISIBLE_STRING:
case CURL_ASN1_UTF8_STRING:
break;
default:
return -1; /* Conversion not supported. */
}
if(inlength % size)
return -1; /* Length inconsistent with character size. */
if(inlength / size > (SIZE_T_MAX - 1) / 4)
return -1; /* Too big. */
buf = malloc(4 * (inlength / size) + 1);
if(!buf)
return -1; /* Not enough memory. */
if(type == CURL_ASN1_UTF8_STRING) {
/* Just copy. */
outlength = inlength;
if(outlength)
memcpy(buf, from, outlength);
}
else {
for(outlength = 0; from < end;) {
int charsize;
unsigned int wc;
wc = 0;
switch(size) {
case 4:
wc = (wc << 8) | *(const unsigned char *) from++;
wc = (wc << 8) | *(const unsigned char *) from++;
/* FALLTHROUGH */
case 2:
wc = (wc << 8) | *(const unsigned char *) from++;
/* FALLTHROUGH */
default: /* case 1: */
wc = (wc << 8) | *(const unsigned char *) from++;
}
charsize = 1;
if(wc >= 0x00000080) {
if(wc >= 0x00000800) {
if(wc >= 0x00010000) {
if(wc >= 0x00200000) {
free(buf);
return -1; /* Invalid char. size for target encoding. */
}
buf[outlength + 3] = (char) (0x80 | (wc & 0x3F));
wc = (wc >> 6) | 0x00010000;
charsize++;
}
buf[outlength + 2] = (char) (0x80 | (wc & 0x3F));
wc = (wc >> 6) | 0x00000800;
charsize++;
}
buf[outlength + 1] = (char) (0x80 | (wc & 0x3F));
wc = (wc >> 6) | 0x000000C0;
charsize++;
}
buf[outlength] = (char) wc;
outlength += charsize;
}
}
buf[outlength] = '\0';
*to = buf;
return outlength;
}
/*
* Convert an ASN.1 String into its UTF-8 string representation.
* Return the dynamically allocated string, or NULL if an error occurs.
*/
static const char *string2str(int type, const char *beg, const char *end)
{
char *buf;
if(utf8asn1str(&buf, type, beg, end) < 0)
return NULL;
return buf;
}
/*
* Decimal ASCII encode unsigned integer `x' into the buflen sized buffer at
* buf. Return the total number of encoded digits, even if larger than
* `buflen'.
*/
static size_t encodeUint(char *buf, size_t buflen, unsigned int x)
{
size_t i = 0;
unsigned int y = x / 10;
if(y) {
i = encodeUint(buf, buflen, y);
x -= y * 10;
}
if(i < buflen)
buf[i] = (char) ('0' + x);
i++;
if(i < buflen)
buf[i] = '\0'; /* Store a terminator if possible. */
return i;
}
/*
* Convert an ASN.1 OID into its dotted string representation.
* Store the result in th `n'-byte buffer at `buf'.
* Return the converted string length, or 0 on errors.
*/
static size_t encodeOID(char *buf, size_t buflen,
const char *beg, const char *end)
{
size_t i;
unsigned int x;
unsigned int y;
/* Process the first two numbers. */
y = *(const unsigned char *) beg++;
x = y / 40;
y -= x * 40;
i = encodeUint(buf, buflen, x);
if(i < buflen)
buf[i] = '.';
i++;
if(i >= buflen)
i += encodeUint(NULL, 0, y);
else
i += encodeUint(buf + i, buflen - i, y);
/* Process the trailing numbers. */
while(beg < end) {
if(i < buflen)
buf[i] = '.';
i++;
x = 0;
do {
if(x & 0xFF000000)
return 0;
y = *(const unsigned char *) beg++;
x = (x << 7) | (y & 0x7F);
} while(y & 0x80);
if(i >= buflen)
i += encodeUint(NULL, 0, x);
else
i += encodeUint(buf + i, buflen - i, x);
}
if(i < buflen)
buf[i] = '\0';
return i;
}
/*
* Convert an ASN.1 OID into its dotted or symbolic string representation.
* Return the dynamically allocated string, or NULL if an error occurs.
*/
static const char *OID2str(const char *beg, const char *end, bool symbolic)
{
char *buf = NULL;
if(beg < end) {
size_t buflen = encodeOID(NULL, 0, beg, end);
if(buflen) {
buf = malloc(buflen + 1); /* one extra for the zero byte */
if(buf) {
encodeOID(buf, buflen, beg, end);
buf[buflen] = '\0';
if(symbolic) {
const curl_OID *op = searchOID(buf);
if(op) {
free(buf);
buf = strdup(op->textoid);
}
}
}
}
}
return buf;
}
static const char *GTime2str(const char *beg, const char *end)
{
const char *tzp;
const char *fracp;
char sec1, sec2;
size_t fracl;
size_t tzl;
const char *sep = "";
/* Convert an ASN.1 Generalized time to a printable string.
Return the dynamically allocated string, or NULL if an error occurs. */
for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++)
;
/* Get seconds digits. */
sec1 = '0';
switch(fracp - beg - 12) {
case 0:
sec2 = '0';
break;
case 2:
sec1 = fracp[-2];
/* FALLTHROUGH */
case 1:
sec2 = fracp[-1];
break;
default:
return NULL;
}
/* Scan for timezone, measure fractional seconds. */
tzp = fracp;
fracl = 0;
if(fracp < end && (*fracp == '.' || *fracp == ',')) {
fracp++;
do
tzp++;
while(tzp < end && *tzp >= '0' && *tzp <= '9');
/* Strip leading zeroes in fractional seconds. */
for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--)
;
}
/* Process timezone. */
if(tzp >= end)
; /* Nothing to do. */
else if(*tzp == 'Z') {
tzp = " GMT";
end = tzp + 4;
}
else {
sep = " ";
tzp++;
}
tzl = end - tzp;
return curl_maprintf("%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s",
beg, beg + 4, beg + 6,
beg + 8, beg + 10, sec1, sec2,
fracl? ".": "", fracl, fracp,
sep, tzl, tzp);
}
/*
* Convert an ASN.1 UTC time to a printable string.
* Return the dynamically allocated string, or NULL if an error occurs.
*/
static const char *UTime2str(const char *beg, const char *end)
{
const char *tzp;
size_t tzl;
const char *sec;
for(tzp = beg; tzp < end && *tzp >= '0' && *tzp <= '9'; tzp++)
;
/* Get the seconds. */
sec = beg + 10;
switch(tzp - sec) {
case 0:
sec = "00";
case 2:
break;
default:
return NULL;
}
/* Process timezone. */
if(tzp >= end)
return NULL;
if(*tzp == 'Z') {
tzp = "GMT";
end = tzp + 3;
}
else
tzp++;
tzl = end - tzp;
return curl_maprintf("%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s",
20 - (*beg >= '5'), beg, beg + 2, beg + 4,
beg + 6, beg + 8, sec,
tzl, tzp);
}
/*
* Convert an ASN.1 element to a printable string.
* Return the dynamically allocated string, or NULL if an error occurs.
*/
static const char *ASN1tostr(curl_asn1Element *elem, int type)
{
if(elem->constructed)
return NULL; /* No conversion of structured elements. */
if(!type)
type = elem->tag; /* Type not forced: use element tag as type. */
switch(type) {
case CURL_ASN1_BOOLEAN:
return bool2str(elem->beg, elem->end);
case CURL_ASN1_INTEGER:
case CURL_ASN1_ENUMERATED:
return int2str(elem->beg, elem->end);
case CURL_ASN1_BIT_STRING:
return bit2str(elem->beg, elem->end);
case CURL_ASN1_OCTET_STRING:
return octet2str(elem->beg, elem->end);
case CURL_ASN1_NULL:
return strdup("");
case CURL_ASN1_OBJECT_IDENTIFIER:
return OID2str(elem->beg, elem->end, TRUE);
case CURL_ASN1_UTC_TIME:
return UTime2str(elem->beg, elem->end);
case CURL_ASN1_GENERALIZED_TIME:
return GTime2str(elem->beg, elem->end);
case CURL_ASN1_UTF8_STRING:
case CURL_ASN1_NUMERIC_STRING:
case CURL_ASN1_PRINTABLE_STRING:
case CURL_ASN1_TELETEX_STRING:
case CURL_ASN1_IA5_STRING:
case CURL_ASN1_VISIBLE_STRING:
case CURL_ASN1_UNIVERSAL_STRING:
case CURL_ASN1_BMP_STRING:
return string2str(type, elem->beg, elem->end);
}
return NULL; /* Unsupported. */
}
/*
* ASCII encode distinguished name at `dn' into the `buflen'-sized buffer at
* `buf'. Return the total string length, even if larger than `buflen'.
*/
static ssize_t encodeDN(char *buf, size_t buflen, curl_asn1Element *dn)
{
curl_asn1Element rdn;
curl_asn1Element atv;
curl_asn1Element oid;
curl_asn1Element value;
size_t l = 0;
const char *p1;
const char *p2;
const char *p3;
const char *str;
for(p1 = dn->beg; p1 < dn->end;) {
p1 = getASN1Element(&rdn, p1, dn->end);
if(!p1)
return -1;
for(p2 = rdn.beg; p2 < rdn.end;) {
p2 = getASN1Element(&atv, p2, rdn.end);
if(!p2)
return -1;
p3 = getASN1Element(&oid, atv.beg, atv.end);
if(!p3)
return -1;
if(!getASN1Element(&value, p3, atv.end))
return -1;
str = ASN1tostr(&oid, 0);
if(!str)
return -1;
/* Encode delimiter.
If attribute has a short uppercase name, delimiter is ", ". */
if(l) {
for(p3 = str; isupper(*p3); p3++)
;
for(p3 = (*p3 || p3 - str > 2)? "/": ", "; *p3; p3++) {
if(l < buflen)
buf[l] = *p3;
l++;
}
}
/* Encode attribute name. */
for(p3 = str; *p3; p3++) {
if(l < buflen)
buf[l] = *p3;
l++;
}
free((char *) str);
/* Generate equal sign. */
if(l < buflen)
buf[l] = '=';
l++;
/* Generate value. */
str = ASN1tostr(&value, 0);
if(!str)
return -1;
for(p3 = str; *p3; p3++) {
if(l < buflen)
buf[l] = *p3;
l++;
}
free((char *) str);
}
}
return l;
}
/*
* Convert an ASN.1 distinguished name into a printable string.
* Return the dynamically allocated string, or NULL if an error occurs.
*/
static const char *DNtostr(curl_asn1Element *dn)
{
char *buf = NULL;
ssize_t buflen = encodeDN(NULL, 0, dn);
if(buflen >= 0) {
buf = malloc(buflen + 1);
if(buf) {
encodeDN(buf, buflen + 1, dn);
buf[buflen] = '\0';
}
}
return buf;
}
/*
* ASN.1 parse an X509 certificate into structure subfields.
* Syntax is assumed to have already been checked by the SSL backend.
* See RFC 5280.
*/
int Curl_parseX509(curl_X509certificate *cert,
const char *beg, const char *end)
{
curl_asn1Element elem;
curl_asn1Element tbsCertificate;
const char *ccp;
static const char defaultVersion = 0; /* v1. */
cert->certificate.header = NULL;
cert->certificate.beg = beg;
cert->certificate.end = end;
/* Get the sequence content. */
if(!getASN1Element(&elem, beg, end))
return -1; /* Invalid bounds/size. */
beg = elem.beg;
end = elem.end;
/* Get tbsCertificate. */
beg = getASN1Element(&tbsCertificate, beg, end);
if(!beg)
return -1;
/* Skip the signatureAlgorithm. */
beg = getASN1Element(&cert->signatureAlgorithm, beg, end);
if(!beg)
return -1;
/* Get the signatureValue. */
if(!getASN1Element(&cert->signature, beg, end))
return -1;
/* Parse TBSCertificate. */
beg = tbsCertificate.beg;
end = tbsCertificate.end;
/* Get optional version, get serialNumber. */
cert->version.header = NULL;
cert->version.beg = &defaultVersion;
cert->version.end = &defaultVersion + sizeof(defaultVersion);
beg = getASN1Element(&elem, beg, end);
if(!beg)
return -1;
if(elem.tag == 0) {
if(!getASN1Element(&cert->version, elem.beg, elem.end))
return -1;
beg = getASN1Element(&elem, beg, end);
if(!beg)
return -1;
}
cert->serialNumber = elem;
/* Get signature algorithm. */
beg = getASN1Element(&cert->signatureAlgorithm, beg, end);
/* Get issuer. */
beg = getASN1Element(&cert->issuer, beg, end);
if(!beg)
return -1;
/* Get notBefore and notAfter. */
beg = getASN1Element(&elem, beg, end);
if(!beg)
return -1;
ccp = getASN1Element(&cert->notBefore, elem.beg, elem.end);
if(!ccp)
return -1;
if(!getASN1Element(&cert->notAfter, ccp, elem.end))
return -1;
/* Get subject. */
beg = getASN1Element(&cert->subject, beg, end);
if(!beg)
return -1;
/* Get subjectPublicKeyAlgorithm and subjectPublicKey. */
beg = getASN1Element(&cert->subjectPublicKeyInfo, beg, end);
if(!beg)
return -1;
ccp = getASN1Element(&cert->subjectPublicKeyAlgorithm,
cert->subjectPublicKeyInfo.beg,
cert->subjectPublicKeyInfo.end);
if(!ccp)
return -1;
if(!getASN1Element(&cert->subjectPublicKey, ccp,
cert->subjectPublicKeyInfo.end))
return -1;
/* Get optional issuerUiqueID, subjectUniqueID and extensions. */
cert->issuerUniqueID.tag = cert->subjectUniqueID.tag = 0;
cert->extensions.tag = elem.tag = 0;
cert->issuerUniqueID.header = cert->subjectUniqueID.header = NULL;
cert->issuerUniqueID.beg = cert->issuerUniqueID.end = "";
cert->subjectUniqueID.beg = cert->subjectUniqueID.end = "";
cert->extensions.header = NULL;
cert->extensions.beg = cert->extensions.end = "";
if(beg < end) {
beg = getASN1Element(&elem, beg, end);
if(!beg)
return -1;
}
if(elem.tag == 1) {
cert->issuerUniqueID = elem;
if(beg < end) {
beg = getASN1Element(&elem, beg, end);
if(!beg)
return -1;
}
}
if(elem.tag == 2) {
cert->subjectUniqueID = elem;
if(beg < end) {
beg = getASN1Element(&elem, beg, end);
if(!beg)
return -1;
}
}
if(elem.tag == 3)
if(!getASN1Element(&cert->extensions, elem.beg, elem.end))
return -1;
return 0;
}
/*
* Copy at most 64-characters, terminate with a newline and returns the
* effective number of stored characters.
*/
static size_t copySubstring(char *to, const char *from)
{
size_t i;
for(i = 0; i < 64; i++) {
to[i] = *from;
if(!*from++)
break;
}
to[i++] = '\n';
return i;
}
static const char *dumpAlgo(curl_asn1Element *param,
const char *beg, const char *end)
{
curl_asn1Element oid;
/* Get algorithm parameters and return algorithm name. */
beg = getASN1Element(&oid, beg, end);
if(!beg)
return NULL;
param->header = NULL;
param->tag = 0;
param->beg = param->end = end;
if(beg < end)
if(!getASN1Element(param, beg, end))
return NULL;
return OID2str(oid.beg, oid.end, TRUE);
}
static void do_pubkey_field(struct Curl_easy *data, int certnum,
const char *label, curl_asn1Element *elem)
{
const char *output;
/* Generate a certificate information record for the public key. */
output = ASN1tostr(elem, 0);
if(output) {
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, label, output);
if(!certnum)
infof(data, " %s: %s\n", label, output);
free((char *) output);
}
}
static void do_pubkey(struct Curl_easy *data, int certnum,
const char *algo, curl_asn1Element *param,
curl_asn1Element *pubkey)
{
curl_asn1Element elem;
curl_asn1Element pk;
const char *p;
/* Generate all information records for the public key. */
/* Get the public key (single element). */
if(!getASN1Element(&pk, pubkey->beg + 1, pubkey->end))
return;
if(strcasecompare(algo, "rsaEncryption")) {
const char *q;
unsigned long len;
p = getASN1Element(&elem, pk.beg, pk.end);
if(!p)
return;
/* Compute key length. */
for(q = elem.beg; !*q && q < elem.end; q++)
;
len = (unsigned long)((elem.end - q) * 8);
if(len) {
unsigned int i;
for(i = *(unsigned char *) q; !(i & 0x80); i <<= 1)
len--;
}
if(len > 32)
elem.beg = q; /* Strip leading zero bytes. */
if(!certnum)
infof(data, " RSA Public Key (%lu bits)\n", len);
if(data->set.ssl.certinfo) {
q = curl_maprintf("%lu", len);
if(q) {
Curl_ssl_push_certinfo(data, certnum, "RSA Public Key", q);
free((char *) q);
}
}
/* Generate coefficients. */
do_pubkey_field(data, certnum, "rsa(n)", &elem);
if(!getASN1Element(&elem, p, pk.end))
return;
do_pubkey_field(data, certnum, "rsa(e)", &elem);
}
else if(strcasecompare(algo, "dsa")) {
p = getASN1Element(&elem, param->beg, param->end);
if(p) {
do_pubkey_field(data, certnum, "dsa(p)", &elem);
p = getASN1Element(&elem, p, param->end);
if(p) {
do_pubkey_field(data, certnum, "dsa(q)", &elem);
if(getASN1Element(&elem, p, param->end)) {
do_pubkey_field(data, certnum, "dsa(g)", &elem);
do_pubkey_field(data, certnum, "dsa(pub_key)", &pk);
}
}
}
}
else if(strcasecompare(algo, "dhpublicnumber")) {
p = getASN1Element(&elem, param->beg, param->end);
if(p) {
do_pubkey_field(data, certnum, "dh(p)", &elem);
if(getASN1Element(&elem, param->beg, param->end)) {
do_pubkey_field(data, certnum, "dh(g)", &elem);
do_pubkey_field(data, certnum, "dh(pub_key)", &pk);
}
}
}
}
CURLcode Curl_extract_certinfo(struct connectdata *conn,
int certnum,
const char *beg,
const char *end)
{
curl_X509certificate cert;
struct Curl_easy *data = conn->data;
curl_asn1Element param;
const char *ccp;
char *cp1;
size_t cl1;
char *cp2;
CURLcode result;
unsigned long version;
size_t i;
size_t j;
if(!data->set.ssl.certinfo)
if(certnum)
return CURLE_OK;
/* Prepare the certificate information for curl_easy_getinfo(). */
/* Extract the certificate ASN.1 elements. */
if(Curl_parseX509(&cert, beg, end))
return CURLE_PEER_FAILED_VERIFICATION;
/* Subject. */
ccp = DNtostr(&cert.subject);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Subject", ccp);
if(!certnum)
infof(data, "%2d Subject: %s\n", certnum, ccp);
free((char *) ccp);
/* Issuer. */
ccp = DNtostr(&cert.issuer);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Issuer", ccp);
if(!certnum)
infof(data, " Issuer: %s\n", ccp);
free((char *) ccp);
/* Version (always fits in less than 32 bits). */
version = 0;
for(ccp = cert.version.beg; ccp < cert.version.end; ccp++)
version = (version << 8) | *(const unsigned char *) ccp;
if(data->set.ssl.certinfo) {
ccp = curl_maprintf("%lx", version);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
Curl_ssl_push_certinfo(data, certnum, "Version", ccp);
free((char *) ccp);
}
if(!certnum)
infof(data, " Version: %lu (0x%lx)\n", version + 1, version);
/* Serial number. */
ccp = ASN1tostr(&cert.serialNumber, 0);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Serial Number", ccp);
if(!certnum)
infof(data, " Serial Number: %s\n", ccp);
free((char *) ccp);
/* Signature algorithm .*/
ccp = dumpAlgo(&param, cert.signatureAlgorithm.beg,
cert.signatureAlgorithm.end);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp);
if(!certnum)
infof(data, " Signature Algorithm: %s\n", ccp);
free((char *) ccp);
/* Start Date. */
ccp = ASN1tostr(&cert.notBefore, 0);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Start Date", ccp);
if(!certnum)
infof(data, " Start Date: %s\n", ccp);
free((char *) ccp);
/* Expire Date. */
ccp = ASN1tostr(&cert.notAfter, 0);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Expire Date", ccp);
if(!certnum)
infof(data, " Expire Date: %s\n", ccp);
free((char *) ccp);
/* Public Key Algorithm. */
ccp = dumpAlgo(&param, cert.subjectPublicKeyAlgorithm.beg,
cert.subjectPublicKeyAlgorithm.end);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Public Key Algorithm", ccp);
if(!certnum)
infof(data, " Public Key Algorithm: %s\n", ccp);
do_pubkey(data, certnum, ccp, &param, &cert.subjectPublicKey);
free((char *) ccp);
/* Signature. */
ccp = ASN1tostr(&cert.signature, 0);
if(!ccp)
return CURLE_OUT_OF_MEMORY;
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Signature", ccp);
if(!certnum)
infof(data, " Signature: %s\n", ccp);
free((char *) ccp);
/* Generate PEM certificate. */
result = Curl_base64_encode(data, cert.certificate.beg,
cert.certificate.end - cert.certificate.beg,
&cp1, &cl1);
if(result)
return result;
/* Compute the number of characters in final certificate string. Format is:
-----BEGIN CERTIFICATE-----\n
<max 64 base64 characters>\n
.
.
.
-----END CERTIFICATE-----\n
*/
i = 28 + cl1 + (cl1 + 64 - 1) / 64 + 26;
cp2 = malloc(i + 1);
if(!cp2) {
free(cp1);
return CURLE_OUT_OF_MEMORY;
}
/* Build the certificate string. */
i = copySubstring(cp2, "-----BEGIN CERTIFICATE-----");
for(j = 0; j < cl1; j += 64)
i += copySubstring(cp2 + i, cp1 + j);
i += copySubstring(cp2 + i, "-----END CERTIFICATE-----");
cp2[i] = '\0';
free(cp1);
if(data->set.ssl.certinfo)
Curl_ssl_push_certinfo(data, certnum, "Cert", cp2);
if(!certnum)
infof(data, "%s\n", cp2);
free(cp2);
return CURLE_OK;
}
#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL */
#if defined(USE_GSKIT)
static const char *checkOID(const char *beg, const char *end,
const char *oid)
{
curl_asn1Element e;
const char *ccp;
const char *p;
bool matched;
/* Check if first ASN.1 element at `beg' is the given OID.
Return a pointer in the source after the OID if found, else NULL. */
ccp = getASN1Element(&e, beg, end);
if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER)
return NULL;
p = OID2str(e.beg, e.end, FALSE);
if(!p)
return NULL;
matched = !strcmp(p, oid);
free((char *) p);
return matched? ccp: NULL;
}
CURLcode Curl_verifyhost(struct connectdata *conn,
const char *beg, const char *end)
{
struct Curl_easy *data = conn->data;
curl_X509certificate cert;
curl_asn1Element dn;
curl_asn1Element elem;
curl_asn1Element ext;
curl_asn1Element name;
const char *p;
const char *q;
char *dnsname;
int matched = -1;
size_t addrlen = (size_t) -1;
ssize_t len;
const char * const hostname = SSL_IS_PROXY()? conn->http_proxy.host.name:
conn->host.name;
const char * const dispname = SSL_IS_PROXY()?
conn->http_proxy.host.dispname:
conn->host.dispname;
#ifdef ENABLE_IPV6
struct in6_addr addr;
#else
struct in_addr addr;
#endif
/* Verify that connection server matches info in X509 certificate at
`beg'..`end'. */
if(!SSL_CONN_CONFIG(verifyhost))
return CURLE_OK;
if(Curl_parseX509(&cert, beg, end))
return CURLE_PEER_FAILED_VERIFICATION;
/* Get the server IP address. */
#ifdef ENABLE_IPV6
if(conn->bits.ipv6_ip && Curl_inet_pton(AF_INET6, hostname, &addr))
addrlen = sizeof(struct in6_addr);
else
#endif
if(Curl_inet_pton(AF_INET, hostname, &addr))
addrlen = sizeof(struct in_addr);
/* Process extensions. */
for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) {
p = getASN1Element(&ext, p, cert.extensions.end);
if(!p)
return CURLE_PEER_FAILED_VERIFICATION;
/* Check if extension is a subjectAlternativeName. */
ext.beg = checkOID(ext.beg, ext.end, sanOID);
if(ext.beg) {
ext.beg = getASN1Element(&elem, ext.beg, ext.end);
if(!ext.beg)
return CURLE_PEER_FAILED_VERIFICATION;
/* Skip critical if present. */
if(elem.tag == CURL_ASN1_BOOLEAN) {
ext.beg = getASN1Element(&elem, ext.beg, ext.end);
if(!ext.beg)
return CURLE_PEER_FAILED_VERIFICATION;
}
/* Parse the octet string contents: is a single sequence. */
if(!getASN1Element(&elem, elem.beg, elem.end))
return CURLE_PEER_FAILED_VERIFICATION;
/* Check all GeneralNames. */
for(q = elem.beg; matched != 1 && q < elem.end;) {
q = getASN1Element(&name, q, elem.end);
if(!q)
break;
switch(name.tag) {
case 2: /* DNS name. */
len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING,
name.beg, name.end);
if(len > 0 && (size_t)len == strlen(dnsname))
matched = Curl_cert_hostcheck(dnsname, hostname);
else
matched = 0;
free(dnsname);
break;
case 7: /* IP address. */
matched = (size_t) (name.end - name.beg) == addrlen &&
!memcmp(&addr, name.beg, addrlen);
break;
}
}
}
}
switch(matched) {
case 1:
/* an alternative name matched the server hostname */
infof(data, "\t subjectAltName: %s matched\n", dispname);
return CURLE_OK;
case 0:
/* an alternative name field existed, but didn't match and then
we MUST fail */
infof(data, "\t subjectAltName does not match %s\n", dispname);
return CURLE_PEER_FAILED_VERIFICATION;
}
/* Process subject. */
name.header = NULL;
name.beg = name.end = "";
q = cert.subject.beg;
/* we have to look to the last occurrence of a commonName in the
distinguished one to get the most significant one. */
while(q < cert.subject.end) {
q = getASN1Element(&dn, q, cert.subject.end);
if(!q)
break;
for(p = dn.beg; p < dn.end;) {
p = getASN1Element(&elem, p, dn.end);
if(!p)
return CURLE_PEER_FAILED_VERIFICATION;
/* We have a DN's AttributeTypeAndValue: check it in case it's a CN. */
elem.beg = checkOID(elem.beg, elem.end, cnOID);
if(elem.beg)
name = elem; /* Latch CN. */
}
}
/* Check the CN if found. */
if(!getASN1Element(&elem, name.beg, name.end))
failf(data, "SSL: unable to obtain common name from peer certificate");
else {
len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end);
if(len < 0) {
free(dnsname);
return CURLE_OUT_OF_MEMORY;
}
if(strlen(dnsname) != (size_t) len) /* Nul byte in string ? */
failf(data, "SSL: illegal cert name field");
else if(Curl_cert_hostcheck((const char *) dnsname, hostname)) {
infof(data, "\t common name: %s (matched)\n", dnsname);
free(dnsname);
return CURLE_OK;
}
else
failf(data, "SSL: certificate subject name '%s' does not match "
"target host name '%s'", dnsname, dispname);
free(dnsname);
}
return CURLE_PEER_FAILED_VERIFICATION;
}
#endif /* USE_GSKIT */