blob: c081146cab45d4d9233a05bc7fe86ed62e497361 [file] [log] [blame]
/* Copyright 2013 Justin Erenkrantz and Greg Stein
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef SERF_HAVE_MACOSXSSL
#include <stdlib.h>
#include "serf.h"
#include "serf_private.h"
#include "serf_bucket_util.h"
#include "bucket_private.h"
#include <apr_strings.h>
#include <apr_sha1.h>
#include <Security/SecCertificate.h>
#include <Security/SecCertificateOIDs.h>
#define ST_DEBUG 0
/* This code reads all items DER-encoded certificate in X.509 format, as
provided by the Keychain services API. */
/* Read ASN.1 data type OID from a DER encoded buffer. */
static const char *
read_DER_OID(const unsigned char* ptr, int clen, apr_pool_t *pool)
{
unsigned char b;
char *value;
/* First two components of the OID are encoded in the first byte. */
b = *ptr++;
clen--;
value = apr_psprintf(pool, "%d.%d", b/40, b%40);
/* one or two bytes per component */
while (clen > 0) {
b = *ptr++;
clen--;
if (! (b & 0x80)) {
value = apr_psprintf(pool, "%s.%d", value, b);
} else {
unsigned comp = 0;
while (b & 0x80 && clen > 0) {
comp <<= 7;
comp |= (b & 0x7f);
b = *ptr++;
clen--;
}
comp <<= 7;
comp |= (b & 0x7f);
value = apr_psprintf(pool, "%s.%d", value, comp);
}
}
serf__log(ST_DEBUG, __FILE__, "OID of length %d, value: %s .\n",
clen, value);
return value;
}
/* Read ASN.1 data type PrintableString from a DER encoded buffer.
TODO: currently also used for UTF8String, use specific function for those. */
static const char *
read_DER_string(const unsigned char* ptr, int clen, apr_pool_t *pool)
{
const char *value = apr_pstrndup(pool, (const char*)ptr, clen);
serf__log(ST_DEBUG, __FILE__, "string of length %d, value: %s.\n",
clen, value);
return value;
}
/* Read ASN.1 data type BOOLEAN from a DER encoded buffer. */
static const char *
read_DER_boolean(const unsigned char* ptr, int clen, apr_pool_t *pool)
{
unsigned char v = *ptr++;
serf__log(ST_DEBUG, __FILE__, "Boolean of length %d, value: %s.\n",
clen, v ? "TRUE" : "FALSE");
return v ? "TRUE" : "FALSE";
}
/* Read ASN.1 data type BIT STRING from a DER encoded buffer. */
static const char *
read_DER_bitstring(const unsigned char* ptr, int clen, apr_pool_t *pool)
{
char *value = "";
/* TODO: take into account unused_bits. */
/* unsigned char unused_bits = *ptr++; */
ptr++;
clen--;
while (clen-- > 0) {
unsigned char b = *ptr++;
value = apr_psprintf(pool, "%s %2x", value, b);
}
serf__log(ST_DEBUG, __FILE__, "Bitstring of length %d, value: %s.\n",
clen, value);
return value;
}
/* Read ASN.1 data type INTEGER from a DER encoded buffer. */
static const char *
read_DER_integer(const unsigned char* ptr, int clen, apr_pool_t *pool)
{
unsigned char lb = *ptr++;
int positive = 0;
int i;
unsigned long value = 0;
if (lb == 0x00) {
positive = 1;
clen -= positive;
if (clen <= sizeof(long))
for (i = 0; i < clen; i++)
value = (value << 8) + *ptr++;
} else if (! (lb & 0x80)) {
positive = 1;
value = lb;
} else {
/* negative number */
return apr_psprintf(pool, "Negative integer not supported.");
}
serf__log(ST_DEBUG, __FILE__, "%s integer of length %d, value: %lx.\n",
positive ? "Positive" : "Negative", clen, value);
return apr_psprintf(pool, "%lx", value);
}
/* Read DER Tag and Length from a DER encoded buffer. */
static apr_status_t
read_DER_TL(const unsigned char* ptr, unsigned char *tag,
long *value_len, long *consumed)
{
int constr_enc;
char lb1;
long clen = 0;
/* read tag */
*tag = *ptr++;
constr_enc = *tag & 0x20;
*consumed = 1;
/* read length */
lb1 = *ptr++;
if (! (lb1 & 0x80)) {
clen = lb1;
(*consumed)++;
} else {
int i;
lb1 &= 0x7f;
if (lb1 > sizeof(long))
return APR_ENOTIMPL;
for (i = 0; i < lb1; i++)
clen = (clen << 8) + *ptr++;
*consumed += (lb1 + 1);
}
*value_len = clen;
serf__log(ST_DEBUG, __FILE__,
"tag: %x, value length: %d, header length: %d.\n",
*tag, *value_len, *consumed);
return APR_SUCCESS;
}
/* Read DER Tag, Length and primitive Value from a DER encoded buffer.
Don't use this for constructed types SEQUENCE or SET! */
static apr_status_t
read_DER_TLvalue(const unsigned char *ptr, unsigned char *tag, long *consumed,
const char **value, apr_pool_t *pool)
{
long tl_len, value_len;
read_DER_TL(ptr, tag, &value_len, &tl_len);
ptr += tl_len;
switch (*tag) {
case 0x01:
*value = read_DER_boolean(ptr, value_len, pool);
break;
case 0x02:
*value = read_DER_integer(ptr, value_len, pool);
break;
case 0x03:
*value = read_DER_bitstring(ptr, value_len, pool);
break;
case 0x05:
*value = "";
serf__log(ST_DEBUG, __FILE__, "NULL value.\n");
break;
case 0x06:
*value = read_DER_OID(ptr, value_len, pool);
break;
case 0x17: /* Date */
case 0x16: /* IA5String */
case 0x13: /* PrintableString */
case 0x14: /* TeletexString */
case 0x0c: /* UTF8String */
*value = read_DER_string(ptr, value_len, pool);
break;
case 0xa0: /* Explicit tag 0, in X509 used for version. */
default:
{
*value = "ERROR";
serf__log(ST_DEBUG, __FILE__, "UNSUPPORTED TAG TYPE %2x.\n", *tag);
return APR_ENOTIMPL;
break;
}
}
*consumed = tl_len + value_len;
return APR_SUCCESS;
}
/* Recursively skip an entire Tag-Length-Value block. */
static apr_status_t
skip_DER_TLV(const unsigned char *ptr, unsigned char *tag, long *consumed)
{
long tl_len, value_len;
apr_status_t status;
status = read_DER_TL(ptr, tag, &value_len, &tl_len);
if (status)
return status;
*consumed = tl_len + value_len;
return APR_SUCCESS;
}
#define SERF_ERR(x) status = (x);\
if (status) goto cleanup;
/* Reads an issuer or subject structure from PTR, which should point to the
value of tag type 0x30 grouping either issuer or subject.
Caller should clean up out_der. */
static apr_status_t
read_X509_DER_DistinguishedName(apr_hash_t **o, CFDataRef *out_der,
const unsigned char *ptr,
long *total_len, apr_pool_t *pool)
{
unsigned char tag;
long len, object_len, consumed;
apr_status_t status;
apr_hash_t *tgt;
char *tmp;
tgt = apr_hash_make(pool);
/* RelativeDistinguishedName Sequence. */
SERF_ERR(read_DER_TL(ptr, &tag, &object_len, &consumed));
/* Copy this whole structure in out_der. */
tmp = apr_palloc(pool, object_len);
memcpy(tmp, ptr, object_len);
*out_der = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
(unsigned char *)tmp,
object_len,
kCFAllocatorNull);
ptr += consumed;
*total_len = object_len + consumed;
/* For each component */
while (object_len > 0) {
const char *key, *value;
long consumed;
CFStringRef keyref;
/* RelativeDistinguishedName Set. */
SERF_ERR(read_DER_TL(ptr, &tag, &len, &consumed));
ptr += consumed; object_len -= consumed;
/* AttributeTypeAndValue, containing OID-value pair. */
SERF_ERR(read_DER_TL(ptr, &tag, &len, &consumed));
ptr += consumed; object_len -= consumed;
/* Read key OID */
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &key, pool));
ptr += consumed; object_len -= consumed;
/* OID constants are CFStringRef, so need to use CFStringCompare. */
keyref = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
(unsigned char *)key,
strlen(key),
kCFStringEncodingMacRoman,
false,
kCFAllocatorNull);
if (CFStringCompare(keyref,
kSecOIDOrganizationalUnitName, 0) == kCFCompareEqualTo)
key = "OU";
else if (CFStringCompare(keyref,
kSecOIDOrganizationName, 0) == kCFCompareEqualTo)
key = "O";
else if (CFStringCompare(keyref,
kSecOIDLocalityName, 0) == kCFCompareEqualTo)
key = "L";
else if (CFStringCompare(keyref,
kSecOIDStateProvinceName, 0) == kCFCompareEqualTo)
key = "ST";
else if (CFStringCompare(keyref,
kSecOIDCountryName, 0) == kCFCompareEqualTo)
key = "C";
else if (CFStringCompare(keyref,
kSecOIDEmailAddress, 0) == kCFCompareEqualTo)
key = "E";
else if (CFStringCompare(keyref,
kSecOIDCommonName, 0) == kCFCompareEqualTo)
key = "CN";
CFRelease(keyref);
/* Read value */
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
ptr += consumed; object_len -= consumed;
serf__log(ST_DEBUG, __FILE__, "Remaining: %d.\n", object_len);
apr_hash_set(tgt, key, APR_HASH_KEY_STRING, value);
}
*o = tgt;
return APR_SUCCESS;
cleanup:
return status;
}
/* Reads date/time string formatted as "YYMMDDHHmmSSZ" and converts it to
"MMM dd HH:mm:ss yyyy GMT". Allocate output string from pool.
Example: in: 130821204210Z out:Aug 13 20:42:10 2013 GMT . */
static const char *
convert_cert_date(const char *in, apr_pool_t *pool)
{
apr_size_t len;
apr_time_exp_t te;
const int bufsize = 64;
char *datestr;
memset(&te, 0, sizeof(te));
datestr = apr_palloc(pool, bufsize);
/* Read 2000+yy into 1900+100+yy */
te.tm_year = 100 + (*in -'0') * 10 + *(in+1) - '0'; in+=2;
te.tm_mon = (*in -'0') * 10 + *(in+1) - '0' - 1; in+=2;
te.tm_mday = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
te.tm_hour = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
te.tm_min = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
te.tm_sec = (*in -'0') * 10 + *(in+1) - '0'; in+=2;
apr_strftime(datestr, &len, bufsize, "%b %d %T %Y GMT", &te);
return datestr;
}
/* Calculate the sha1 hash value of data of length len. Resulting string will be
allocated from pool and encoded as hex bytes : separated (A1:81:3F ... ). */
static const unsigned char *
sha1digest(const unsigned char *data, long len, apr_pool_t *pool)
{
apr_sha1_ctx_t context;
unsigned char *sha1 = apr_pcalloc(pool, APR_SHA1_DIGESTSIZE);
unsigned char *sha1hex = apr_pcalloc(pool, APR_SHA1_DIGESTSIZE * 3);
unsigned char *inptr = sha1, *outptr = sha1hex;
const char al[] = "0123456789ABCDEF";
int i;
apr_sha1_init(&context);
apr_sha1_update_binary(&context, data, len);
apr_sha1_final(sha1, &context);
/* concert to :-separated hex bytes */
for (i = 0; i < APR_SHA1_DIGESTSIZE; i++) {
unsigned char c = *inptr++;
*outptr++ = al[(c & 0xf0) >> 4];
*outptr++ = al[c & 0x0f];
*outptr++ = ':';
}
*(outptr-1) = '\0';
return sha1hex;
}
static apr_status_t dataref_cleanup(void *data)
{
CFDataRef derdata = data;
CFRelease(derdata);
return APR_SUCCESS;
}
/* Read a Distinquished Name from a DER-encoded DN in X.509 format.
The resulting hash table will have following keys:
- CN, O, OU, L, ST, C, E.
Internal use only:
- _der
*/
apr_status_t
serf__macosxssl_read_X509_DER_DN(apr_hash_t **o, CFDataRef dndata,
apr_pool_t *pool)
{
CFDataRef dnder;
apr_hash_t *dn;
long consumed;
const unsigned char *data = CFDataGetBytePtr(dndata);
apr_status_t status;
SERF_ERR(read_X509_DER_DistinguishedName(&dn, &dnder,
data, &consumed, pool));
apr_hash_set(dn, "_der", APR_HASH_KEY_STRING, dnder);
*o = dn;
cleanup:
return status;
}
/* Read all interesting data from a DER-encoded certificate in X.509 format.
The resulting hash table will have following keys:
- sha1
- serial_number (TODO: not used, can be removed if that remains so. )
- issuer: hash table with keys CN, O, OU, L, ST, C, E.
- subject: hash table with keys CN, O, OU, L, ST, C, E.
- notBefore
- notAfter
Internal use only:
- _issuer_der
- _subject_der
*/
apr_status_t
serf__macosxssl_read_X509_DER_certificate(apr_hash_t **o,
const macosxssl_certificate_t *cert,
apr_pool_t *pool)
{
apr_hash_t *x509_cert, *issuer, *subject;
CFDataRef issuer_der, subject_der;
long consumed, x509_len, value_len, signature_start;
unsigned char tag;
apr_status_t status;
const char *serial, *tmp, *key, *value;
const unsigned char *ptr, *sha1;
int version;
SecCertificateRef certref = cert->certref;
CFDataRef dataref = SecCertificateCopyData(certref);
const unsigned char *data = CFDataGetBytePtr(dataref);
CFIndex totlen = CFDataGetLength(dataref);
if (!totlen)
return SERF_ERROR_SSL_CERT_FAILED;
x509_cert = apr_hash_make(pool);
/* SHA1 fingerprint of the full DER encoded cert. */
sha1 = sha1digest(data, totlen, pool);
apr_hash_set(x509_cert, "sha1", APR_HASH_KEY_STRING, sha1);
serf__log(ST_DEBUG, __FILE__, "SHA1 fingerprint:%s.\n", sha1);
ptr = data;
/* 4.1.1 Certificate sequence */
SERF_ERR(read_DER_TL(ptr, &tag, &x509_len, &consumed));
ptr += consumed;
signature_start = consumed;
serf__log(ST_DEBUG, __FILE__, "Parsing DER encoding of cert length: %d.\n",
x509_len + consumed);
/* 4.1.2 TBSCertificate (required) */
serf__log(ST_DEBUG, __FILE__, "---- TBSCertificate ----.\n");
SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
ptr += consumed; x509_len -= consumed;
signature_start += (value_len + consumed);
/* 4.1.2.1 Version (optional, default v1 (0x00)) */
serf__log(ST_DEBUG, __FILE__, "---- Version ----.\n");
SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
if (tag == 0xa0) {
ptr += consumed; x509_len -= consumed;
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &tmp, pool));
version = atoi(tmp) + 1;
ptr += consumed; x509_len -= consumed;
} else {
/* this was another tag than expected. Means that version wasn't set,
used the default v1. */
version = 1;
}
/* 4.1.2.2 Serial number */
serf__log(ST_DEBUG, __FILE__, "---- Serial Number ----.\n");
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &serial, pool));
ptr += consumed; x509_len -= consumed;
apr_hash_set(x509_cert, "serial_number", APR_HASH_KEY_STRING, serial);
/* 4.1.2.3 Signature (it's actually the Algorithm used to sign). */
serf__log(ST_DEBUG, __FILE__, "---- Algorithm ----.\n");
skip_DER_TLV(ptr, &tag, &consumed);
ptr += consumed; x509_len -= consumed;
/* 4.1.2.4 Issuer */
serf__log(ST_DEBUG, __FILE__, "---- Issuer ----.\n");
SERF_ERR(read_X509_DER_DistinguishedName(&issuer, &issuer_der,
ptr, &consumed, pool));
ptr += consumed; x509_len -= consumed;
apr_hash_set(x509_cert, "issuer", APR_HASH_KEY_STRING, issuer);
/* store the original der data buffer of the issuer, for internal use
(comparison of certificates). */
apr_hash_set(x509_cert, "_issuer_der", APR_HASH_KEY_STRING, issuer_der);
/* 4.1.2.5 Validity */
serf__log(ST_DEBUG, __FILE__, "---- Validity ----.\n");
SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
ptr += consumed; x509_len -= consumed;
/* notBefore */
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &tmp, pool));
ptr += consumed; x509_len -= consumed;
apr_hash_set(x509_cert, "notBefore", APR_HASH_KEY_STRING,
convert_cert_date(tmp, pool));
/* notAfter */
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &tmp, pool));
ptr += consumed; x509_len -= consumed;
apr_hash_set(x509_cert, "notAfter", APR_HASH_KEY_STRING,
convert_cert_date(tmp, pool));
/* 4.1.2.6 Subject */
serf__log(ST_DEBUG, __FILE__, "---- Subject ----.\n");
SERF_ERR(read_X509_DER_DistinguishedName(&subject, &subject_der,
ptr, &consumed, pool));
ptr += consumed; x509_len -= consumed;
apr_hash_set(x509_cert, "subject", APR_HASH_KEY_STRING, subject);
/* store the original der data buffer of the subject, for internal use
(comparison of certificates). */
apr_hash_set(x509_cert, "_subject_der", APR_HASH_KEY_STRING, subject_der);
/* 4.1.2.7 Subject Public Key Info */
serf__log(ST_DEBUG, __FILE__, "---- Subject Public Key Info ----.\n");
SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
ptr += consumed; x509_len -= consumed;
/* AlgorithmIdentifier */
SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
ptr += consumed; x509_len -= consumed;
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &key, pool));
ptr += consumed; x509_len -= consumed;
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
ptr += consumed; x509_len -= consumed;
/* TODO: read subjectAltName's from extensions. */
/* Skip the remainder of TBSCertificate*/
ptr = data + signature_start;
#if 0 /* Not needed, cleanup if this remains so. */
/* 4.1.1.2 signatureAlgorithm (required) */
SERF_ERR(read_DER_TL(ptr, &tag, &value_len, &consumed));
ptr += consumed; x509_len -= consumed;
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &key, pool));
ptr += consumed; x509_len -= consumed;
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
ptr += consumed; x509_len -= consumed;
/* 4.1.1.3 signatureValue (required) */
SERF_ERR(read_DER_TLvalue(ptr, &tag, &consumed, &value, pool));
ptr += consumed; x509_len -= consumed;
serf__log(ST_DEBUG, __FILE__, "Remaining to read: %d.\n", x509_len);
#endif
*o = x509_cert;
apr_pool_cleanup_register(pool, issuer_der, dataref_cleanup,
dataref_cleanup);
apr_pool_cleanup_register(pool, subject_der, dataref_cleanup,
dataref_cleanup);
return APR_SUCCESS;
cleanup:
CFRelease(dataref);
return status;
}
#endif /* SERF_HAVE_MACOSXSSL */