summaryrefslogtreecommitdiff
path: root/subversion/libsvn_subr/x509parse.c
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-08-05 16:22:51 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-08-05 16:22:51 +0000
commitcf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch)
treeda27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_subr/x509parse.c
parentbb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff)
downloadsubversion-tarball-master.tar.gz
Diffstat (limited to 'subversion/libsvn_subr/x509parse.c')
-rw-r--r--subversion/libsvn_subr/x509parse.c1200
1 files changed, 1200 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/x509parse.c b/subversion/libsvn_subr/x509parse.c
new file mode 100644
index 0000000..32af4a7
--- /dev/null
+++ b/subversion/libsvn_subr/x509parse.c
@@ -0,0 +1,1200 @@
+/*
+ * X.509 certificate and private key decoding
+ *
+ * Based on XySSL: Copyright (C) 2006-2008 Christophe Devine
+ *
+ * Copyright (C) 2009 Paul Bakker <polarssl_maintainer at polarssl dot org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the names of PolarSSL or XySSL nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * The ITU-T X.509 standard defines a certificate format for PKI.
+ *
+ * http://www.ietf.org/rfc/rfc5280.txt
+ * http://www.ietf.org/rfc/rfc3279.txt
+ * http://www.ietf.org/rfc/rfc6818.txt
+ *
+ * ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-1v2.asc
+ *
+ * http://www.itu.int/ITU-T/studygroups/com17/languages/X.680-0207.pdf
+ * http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
+ */
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+#include "svn_hash.h"
+#include "svn_string.h"
+#include "svn_time.h"
+#include "svn_checksum.h"
+#include "svn_utf.h"
+#include "svn_ctype.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_string_private.h"
+
+#include "x509.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * ASN.1 DER decoding routines
+ */
+static svn_error_t *
+asn1_get_len(const unsigned char **p, const unsigned char *end,
+ ptrdiff_t *len)
+{
+ if ((end - *p) < 1)
+ return svn_error_create(SVN_ERR_ASN1_OUT_OF_DATA, NULL, NULL);
+
+ if ((**p & 0x80) == 0)
+ *len = *(*p)++;
+ else
+ switch (**p & 0x7F)
+ {
+ case 1:
+ if ((end - *p) < 2)
+ return svn_error_create(SVN_ERR_ASN1_OUT_OF_DATA, NULL, NULL);
+
+ *len = (*p)[1];
+ (*p) += 2;
+ break;
+
+ case 2:
+ if ((end - *p) < 3)
+ return svn_error_create(SVN_ERR_ASN1_OUT_OF_DATA, NULL, NULL);
+
+ *len = ((*p)[1] << 8) | (*p)[2];
+ (*p) += 3;
+ break;
+
+ default:
+ return svn_error_create(SVN_ERR_ASN1_INVALID_LENGTH, NULL, NULL);
+ break;
+ }
+
+ if (*len > (end - *p))
+ return svn_error_create(SVN_ERR_ASN1_OUT_OF_DATA, NULL, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+asn1_get_tag(const unsigned char **p,
+ const unsigned char *end, ptrdiff_t *len, int tag)
+{
+ if ((end - *p) < 1)
+ return svn_error_create(SVN_ERR_ASN1_OUT_OF_DATA, NULL, NULL);
+
+ if (**p != tag)
+ return svn_error_create(SVN_ERR_ASN1_UNEXPECTED_TAG, NULL, NULL);
+
+ (*p)++;
+
+ return svn_error_trace(asn1_get_len(p, end, len));
+}
+
+static svn_error_t *
+asn1_get_int(const unsigned char **p, const unsigned char *end, int *val)
+{
+ ptrdiff_t len;
+
+ SVN_ERR(asn1_get_tag(p, end, &len, ASN1_INTEGER));
+
+ /* Reject bit patterns that would overflow the output and those that
+ represent negative values. */
+ if (len > (int)sizeof(int) || (**p & 0x80) != 0)
+ return svn_error_create(SVN_ERR_ASN1_INVALID_LENGTH, NULL, NULL);
+
+ *val = 0;
+
+ while (len-- > 0) {
+ /* This would be undefined for bit-patterns of negative values. */
+ *val = (*val << 8) | **p;
+ (*p)++;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+equal(const void *left, apr_size_t left_len,
+ const void *right, apr_size_t right_len)
+{
+ if (left_len != right_len)
+ return FALSE;
+
+ return memcmp(left, right, right_len) == 0;
+}
+
+static svn_boolean_t
+oids_equal(x509_buf *left, x509_buf *right)
+{
+ return equal(left->p, left->len,
+ right->p, right->len);
+}
+
+/*
+ * Version ::= INTEGER { v1(0), v2(1), v3(2) }
+ */
+static svn_error_t *
+x509_get_version(const unsigned char **p, const unsigned char *end, int *ver)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+
+ /*
+ * As defined in the Basic Certificate fields:
+ * version [0] EXPLICIT Version DEFAULT v1,
+ * the version is the context specific tag 0.
+ */
+ err = asn1_get_tag(p, end, &len,
+ ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 0);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_ASN1_UNEXPECTED_TAG)
+ {
+ svn_error_clear(err);
+ *ver = 0;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+ }
+
+ end = *p + len;
+
+ err = asn1_get_int(p, end, ver);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_VERSION, err, NULL);
+
+ if (*p != end)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_VERSION, err, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * CertificateSerialNumber ::= INTEGER
+ */
+static svn_error_t *
+x509_get_serial(const unsigned char **p,
+ const unsigned char *end, x509_buf * serial)
+{
+ svn_error_t *err;
+
+ if ((end - *p) < 1)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_OUT_OF_DATA, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_SERIAL, err, NULL);
+ }
+
+ if (**p != (ASN1_CONTEXT_SPECIFIC | ASN1_PRIMITIVE | 2) &&
+ **p != ASN1_INTEGER)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_UNEXPECTED_TAG, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_SERIAL, err, NULL);
+ }
+
+ serial->tag = *(*p)++;
+
+ err = asn1_get_len(p, end, &serial->len);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_SERIAL, err, NULL);
+
+ serial->p = *p;
+ *p += serial->len;
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * AlgorithmIdentifier ::= SEQUENCE {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL }
+ */
+static svn_error_t *
+x509_get_alg(const unsigned char **p, const unsigned char *end, x509_buf * alg)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+
+ err = asn1_get_tag(p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_ALG, err, NULL);
+
+ end = *p + len;
+ alg->tag = **p;
+
+ err = asn1_get_tag(p, end, &alg->len, ASN1_OID);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_ALG, err, NULL);
+
+ alg->p = *p;
+ *p += alg->len;
+
+ if (*p == end)
+ return SVN_NO_ERROR;
+
+ /*
+ * assume the algorithm parameters must be NULL
+ */
+ err = asn1_get_tag(p, end, &len, ASN1_NULL);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_ALG, err, NULL);
+
+ if (*p != end)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_ALG, err, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * AttributeTypeAndValue ::= SEQUENCE {
+ * type AttributeType,
+ * value AttributeValue }
+ *
+ * AttributeType ::= OBJECT IDENTIFIER
+ *
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ */
+static svn_error_t *
+x509_get_attribute(const unsigned char **p, const unsigned char *end,
+ x509_name *cur, apr_pool_t *result_pool)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+ x509_buf *oid;
+ x509_buf *val;
+
+ err = asn1_get_tag(p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_NAME, err, NULL);
+
+ end = *p + len;
+
+ oid = &cur->oid;
+
+ err = asn1_get_tag(p, end, &oid->len, ASN1_OID);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_NAME, err, NULL);
+
+ oid->tag = ASN1_OID;
+ oid->p = *p;
+ *p += oid->len;
+
+ if ((end - *p) < 1)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_OUT_OF_DATA, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_NAME, err, NULL);
+ }
+
+ if (**p != ASN1_BMP_STRING && **p != ASN1_UTF8_STRING &&
+ **p != ASN1_T61_STRING && **p != ASN1_PRINTABLE_STRING &&
+ **p != ASN1_IA5_STRING && **p != ASN1_UNIVERSAL_STRING)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_UNEXPECTED_TAG, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_NAME, err, NULL);
+ }
+
+ val = &cur->val;
+ val->tag = *(*p)++;
+
+ err = asn1_get_len(p, end, &val->len);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_NAME, err, NULL);
+
+ val->p = *p;
+ *p += val->len;
+
+ cur->next = NULL;
+
+ if (*p != end)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_NAME, err, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * RelativeDistinguishedName ::=
+ * SET SIZE (1..MAX) OF AttributeTypeAndValue
+ */
+static svn_error_t *
+x509_get_name(const unsigned char **p, const unsigned char *name_end,
+ x509_name *name, apr_pool_t *result_pool)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+ const unsigned char *set_end;
+ x509_name *cur = NULL;
+
+ err = asn1_get_tag(p, name_end, &len, ASN1_CONSTRUCTED | ASN1_SET);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_NAME, err, NULL);
+
+ set_end = *p + len;
+
+ /*
+ * iterate until the end of the SET is reached
+ */
+ while (*p < set_end)
+ {
+ if (!cur)
+ {
+ cur = name;
+ }
+ else
+ {
+ cur->next = apr_palloc(result_pool, sizeof(x509_name));
+ cur = cur->next;
+ }
+ SVN_ERR(x509_get_attribute(p, set_end, cur, result_pool));
+ }
+
+ /*
+ * recurse until end of SEQUENCE (name) is reached
+ */
+ if (*p == name_end)
+ return SVN_NO_ERROR;
+
+ cur->next = apr_palloc(result_pool, sizeof(x509_name));
+
+ return svn_error_trace(x509_get_name(p, name_end, cur->next, result_pool));
+}
+
+/* Retrieve the date from the X.509 cert data between *P and END in either
+ * UTCTime or GeneralizedTime format (as defined in RFC 5280 s. 4.1.2.5.1 and
+ * 4.1.2.5.2 respectively) and place the result in WHEN using SCRATCH_POOL
+ * for temporary allocations. */
+static svn_error_t *
+x509_get_date(apr_time_t *when,
+ const unsigned char **p,
+ const unsigned char *end,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ apr_status_t ret;
+ int tag;
+ ptrdiff_t len;
+ char *date;
+ apr_time_exp_t xt = { 0 };
+ char tz;
+
+ err = asn1_get_tag(p, end, &len, ASN1_UTC_TIME);
+ if (err && err->apr_err == SVN_ERR_ASN1_UNEXPECTED_TAG)
+ {
+ svn_error_clear(err);
+ err = asn1_get_tag(p, end, &len, ASN1_GENERALIZED_TIME);
+ tag = ASN1_GENERALIZED_TIME;
+ }
+ else
+ {
+ tag = ASN1_UTC_TIME;
+ }
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_DATE, err, NULL);
+
+ date = apr_pstrndup(scratch_pool, (const char *) *p, len);
+ switch (tag)
+ {
+ case ASN1_UTC_TIME:
+ if (sscanf(date, "%2d%2d%2d%2d%2d%2d%c",
+ &xt.tm_year, &xt.tm_mon, &xt.tm_mday,
+ &xt.tm_hour, &xt.tm_min, &xt.tm_sec, &tz) < 6)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_DATE, NULL, NULL);
+
+ /* UTCTime only provides a 2 digit year. X.509 specifies that years
+ * greater than or equal to 50 must be interpreted as 19YY and years
+ * less than 50 be interpreted as 20YY. This format is not used for
+ * years greater than 2049. apr_time_exp_t wants years as the number
+ * of years since 1900, so don't convert to 4 digits here. */
+ xt.tm_year += 100 * (xt.tm_year < 50);
+ break;
+
+ case ASN1_GENERALIZED_TIME:
+ if (sscanf(date, "%4d%2d%2d%2d%2d%2d%c",
+ &xt.tm_year, &xt.tm_mon, &xt.tm_mday,
+ &xt.tm_hour, &xt.tm_min, &xt.tm_sec, &tz) < 6)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_DATE, NULL, NULL);
+
+ /* GeneralizedTime has the full 4 digit year. But apr_time_exp_t
+ * wants years as the number of years since 1900. */
+ xt.tm_year -= 1900;
+ break;
+
+ default:
+ /* shouldn't ever get here because we should error out above in the
+ * asn1_get_tag() bits but doesn't hurt to be extra paranoid. */
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_DATE, NULL, NULL);
+ break;
+ }
+
+ /* check that the timezone is GMT
+ * ASN.1 allows for the timezone to be specified but X.509 says it must
+ * always be GMT. A little bit of extra paranoia here seems like a good
+ * idea. */
+ if (tz != 'Z')
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_DATE, NULL, NULL);
+
+ /* apr_time_exp_t expects months to be zero indexed, 0=Jan, 11=Dec. */
+ xt.tm_mon -= 1;
+
+ ret = apr_time_exp_gmt_get(when, &xt);
+ if (ret)
+ return svn_error_wrap_apr(ret, NULL);
+
+ *p += len;
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Validity ::= SEQUENCE {
+ * notBefore Time,
+ * notAfter Time }
+ *
+ * Time ::= CHOICE {
+ * utcTime UTCTime,
+ * generalTime GeneralizedTime }
+ */
+static svn_error_t *
+x509_get_dates(apr_time_t *from,
+ apr_time_t *to,
+ const unsigned char **p,
+ const unsigned char *end,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+
+ err = asn1_get_tag(p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_DATE, err, NULL);
+
+ end = *p + len;
+
+ SVN_ERR(x509_get_date(from, p, end, scratch_pool));
+
+ SVN_ERR(x509_get_date(to, p, end, scratch_pool));
+
+ if (*p != end)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_DATE, err, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+x509_get_sig(const unsigned char **p, const unsigned char *end, x509_buf * sig)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+
+ err = asn1_get_tag(p, end, &len, ASN1_BIT_STRING);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_SIGNATURE, err, NULL);
+
+ sig->tag = ASN1_BIT_STRING;
+
+ if (--len < 1 || *(*p)++ != 0)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_SIGNATURE, NULL, NULL);
+
+ sig->len = len;
+ sig->p = *p;
+
+ *p += len;
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * X.509 v2/v3 unique identifier (not parsed)
+ */
+static svn_error_t *
+x509_get_uid(const unsigned char **p,
+ const unsigned char *end, x509_buf * uid, int n)
+{
+ svn_error_t *err;
+
+ if (*p == end)
+ return SVN_NO_ERROR;
+
+ err = asn1_get_tag(p, end, &uid->len,
+ ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | n);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_ASN1_UNEXPECTED_TAG)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+ }
+
+ uid->tag = ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | n;
+ uid->p = *p;
+ *p += uid->len;
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * X.509 v3 extensions (not parsed)
+ */
+static svn_error_t *
+x509_get_ext(apr_array_header_t *dnsnames,
+ const unsigned char **p,
+ const unsigned char *end)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+
+ if (*p == end)
+ return SVN_NO_ERROR;
+
+ err = asn1_get_tag(p, end, &len,
+ ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 3);
+ if (err)
+ {
+ /* If there aren't extensions that's ok they aren't required */
+ if (err->apr_err == SVN_ERR_ASN1_UNEXPECTED_TAG)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+ }
+
+ end = *p + len;
+
+ SVN_ERR(asn1_get_tag(p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE));
+
+ if (end != *p + len)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_EXTENSIONS, err, NULL);
+ }
+
+ while (*p < end)
+ {
+ ptrdiff_t ext_len;
+ const unsigned char *ext_start, *sna_end;
+ err = asn1_get_tag(p, end, &ext_len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_EXTENSIONS, err,
+ NULL);
+ ext_start = *p;
+
+ err = asn1_get_tag(p, end, &len, ASN1_OID);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_EXTENSIONS, err,
+ NULL);
+
+ /* skip all extensions except SubjectAltName */
+ if (!equal(*p, len,
+ OID_SUBJECT_ALT_NAME, sizeof(OID_SUBJECT_ALT_NAME) - 1))
+ {
+ *p += ext_len - (*p - ext_start);
+ continue;
+ }
+ *p += len;
+
+ err = asn1_get_tag(p, end, &len, ASN1_OCTET_STRING);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_EXTENSIONS, err,
+ NULL);
+
+ /* SubjectAltName ::= GeneralNames
+
+ GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+
+ GeneralName ::= CHOICE {
+ other Name [0] OtherName,
+ rfc822Name [1] IA5String,
+ dNSName [2] IA5String,
+ x400Address [3] ORAddress,
+ directoryName [4] Name,
+ ediPartyName [5] EDIPartyName,
+ uniformResourceIdentifier [6] IA5String,
+ iPAddress [7] OCTET STRING,
+ registeredID [8] OBJECT IDENTIFIER } */
+ sna_end = *p + len;
+
+ err = asn1_get_tag(p, sna_end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_EXTENSIONS, err,
+ NULL);
+
+ if (sna_end != *p + len)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_EXTENSIONS, err, NULL);
+ }
+
+ while (*p < sna_end)
+ {
+ err = asn1_get_tag(p, sna_end, &len, ASN1_CONTEXT_SPECIFIC |
+ ASN1_PRIMITIVE | 2);
+ if (err)
+ {
+ /* not not a dNSName */
+ if (err->apr_err == SVN_ERR_ASN1_UNEXPECTED_TAG)
+ {
+ svn_error_clear(err);
+ /* need to skip the tag and then find the length to
+ * skip to ignore this SNA entry. */
+ (*p)++;
+ SVN_ERR(asn1_get_len(p, sna_end, &len));
+ *p += len;
+ continue;
+ }
+
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* We found a dNSName entry */
+ x509_buf *dnsname = apr_palloc(dnsnames->pool,
+ sizeof(x509_buf));
+ dnsname->tag = ASN1_IA5_STRING; /* implicit based on dNSName */
+ dnsname->len = len;
+ dnsname->p = *p;
+ APR_ARRAY_PUSH(dnsnames, x509_buf *) = dnsname;
+ }
+
+ *p += len;
+ }
+
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Escape all non-ascii or control characters similar to
+ * svn_xml_fuzzy_escape() and svn_utf_cstring_from_utf8_fuzzy().
+ * All of the encoding formats somewhat overlap with ascii (BMPString
+ * and UniversalString are actually always wider so you'll end up
+ * with a bunch of escaped nul bytes, but ideally we don't get here
+ * for those). The result is always a nul-terminated C string. */
+static const char *
+fuzzy_escape(const svn_string_t *src, apr_pool_t *result_pool)
+{
+ const char *end = src->data + src->len;
+ const char *p = src->data, *q;
+ svn_stringbuf_t *outstr;
+ char escaped_char[6]; /* ? \ u u u \0 */
+
+ for (q = p; q < end; q++)
+ {
+ if (!svn_ctype_isascii(*q) || svn_ctype_iscntrl(*q))
+ break;
+ }
+
+ if (q == end)
+ return src->data;
+
+ outstr = svn_stringbuf_create_empty(result_pool);
+ while (1)
+ {
+ q = p;
+
+ /* Traverse till either unsafe character or eos. */
+ while (q < end && svn_ctype_isascii(*q) && !svn_ctype_iscntrl(*q))
+ q++;
+
+ /* copy chunk before marker */
+ svn_stringbuf_appendbytes(outstr, p, q - p);
+
+ if (q == end)
+ break;
+
+ apr_snprintf(escaped_char, sizeof(escaped_char), "?\\%03u",
+ (unsigned char) *q);
+ svn_stringbuf_appendcstr(outstr, escaped_char);
+
+ p = q + 1;
+ }
+
+ return outstr->data;
+}
+
+/* Escape only NUL characters from a string that is presumed to
+ * be UTF-8 encoded and return a nul-terminated C string. */
+static const char *
+nul_escape(const svn_string_t *src, apr_pool_t *result_pool)
+{
+ const char *end = src->data + src->len;
+ const char *p = src->data, *q;
+ svn_stringbuf_t *outstr;
+
+ for (q = p; q < end; q++)
+ {
+ if (*q == '\0')
+ break;
+ }
+
+ if (q == end)
+ return src->data;
+
+ outstr = svn_stringbuf_create_empty(result_pool);
+ while (1)
+ {
+ q = p;
+
+ /* Traverse till either unsafe character or eos. */
+ while (q < end && *q != '\0')
+ q++;
+
+ /* copy chunk before marker */
+ svn_stringbuf_appendbytes(outstr, p, q - p);
+
+ if (q == end)
+ break;
+
+ svn_stringbuf_appendcstr(outstr, "?\\000");
+
+ p = q + 1;
+ }
+
+ return outstr->data;
+}
+
+
+/* Convert an ISO-8859-1 (Latin-1) string to UTF-8.
+ ISO-8859-1 is a strict subset of Unicode. */
+static svn_error_t *
+latin1_to_utf8(const svn_string_t **result, const svn_string_t *src,
+ apr_pool_t *result_pool)
+{
+ apr_int32_t *ucs4buf;
+ svn_membuf_t resultbuf;
+ apr_size_t length;
+ apr_size_t i;
+ svn_string_t *res;
+
+ ucs4buf = apr_palloc(result_pool, src->len * sizeof(*ucs4buf));
+ for (i = 0; i < src->len; ++i)
+ ucs4buf[i] = (unsigned char)(src->data[i]);
+
+ svn_membuf__create(&resultbuf, 2 * src->len, result_pool);
+ SVN_ERR(svn_utf__encode_ucs4_string(
+ &resultbuf, ucs4buf, src->len, &length));
+
+ res = apr_palloc(result_pool, sizeof(*res));
+ res->data = resultbuf.data;
+ res->len = length;
+ *result = res;
+ return SVN_NO_ERROR;
+}
+
+/* Make a best effort to convert a X.509 name to a UTF-8 encoded
+ * string and return it. If we can't properly convert just do a
+ * fuzzy conversion so we have something to display. */
+static const char *
+x509name_to_utf8_string(const x509_name *name, apr_pool_t *result_pool)
+{
+ const svn_string_t *src_string;
+ const svn_string_t *utf8_string;
+ svn_error_t *err;
+
+ src_string = svn_string_ncreate((const char *)name->val.p,
+ name->val.len,
+ result_pool);
+ switch (name->val.tag)
+ {
+ case ASN1_UTF8_STRING:
+ if (svn_utf__is_valid(src_string->data, src_string->len))
+ return nul_escape(src_string, result_pool);
+ else
+ /* not a valid UTF-8 string, who knows what it is,
+ * so run it through the fuzzy_escape code. */
+ return fuzzy_escape(src_string, result_pool);
+ break;
+
+ /* Both BMP and UNIVERSAL should always be in Big Endian (aka
+ * network byte order). But rumor has it that there are certs
+ * out there with other endianess and even Byte Order Marks.
+ * If we actually run into these, we might need to do something
+ * about it. */
+
+ case ASN1_BMP_STRING:
+ if (0 != src_string->len % sizeof(apr_uint16_t))
+ return fuzzy_escape(src_string, result_pool);
+ err = svn_utf__utf16_to_utf8(&utf8_string,
+ (const void*)(src_string->data),
+ src_string->len / sizeof(apr_uint16_t),
+ TRUE, result_pool, result_pool);
+ break;
+
+ case ASN1_UNIVERSAL_STRING:
+ if (0 != src_string->len % sizeof(apr_int32_t))
+ return fuzzy_escape(src_string, result_pool);
+ err = svn_utf__utf32_to_utf8(&utf8_string,
+ (const void*)(src_string->data),
+ src_string->len / sizeof(apr_int32_t),
+ TRUE, result_pool, result_pool);
+ break;
+
+ /* Despite what all the IETF, ISO, ITU bits say everything out
+ * on the Internet that I can find treats this as ISO-8859-1.
+ * Even the name is misleading, it's not actually T.61. All the
+ * gory details can be found in the Character Sets section of:
+ * https://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt
+ */
+ case ASN1_T61_STRING:
+ err = latin1_to_utf8(&utf8_string, src_string, result_pool);
+ break;
+
+ /* This leaves two types out there in the wild. PrintableString,
+ * which is just a subset of ASCII and IA5 which is ASCII (though
+ * 0x24 '$' and 0x23 '#' may be defined with differnet symbols
+ * depending on the location, in practice it seems everyone just
+ * treats it as ASCII). Since these are just ASCII run through
+ * the fuzzy_escape code to deal with anything that isn't actually
+ * ASCII. There shouldn't be any other types here but if we find
+ * a cert with some other encoding, the best we can do is the
+ * fuzzy_escape(). Note: Technically IA5 isn't valid in this
+ * context, however in the real world it may pop up. */
+ default:
+ return fuzzy_escape(src_string, result_pool);
+ }
+
+ if (err)
+ {
+ svn_error_clear(err);
+ return fuzzy_escape(src_string, result_pool);
+ }
+
+ return nul_escape(utf8_string, result_pool);
+}
+
+static svn_error_t *
+x509_name_to_certinfo(apr_array_header_t **result,
+ const x509_name *dn,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *result_pool)
+{
+ const x509_name *name = dn;
+
+ *result = apr_array_make(result_pool, 6, sizeof(svn_x509_name_attr_t *));
+
+ while (name != NULL) {
+ svn_x509_name_attr_t *attr = apr_palloc(result_pool, sizeof(svn_x509_name_attr_t));
+
+ attr->oid_len = name->oid.len;
+ attr->oid = apr_palloc(result_pool, attr->oid_len);
+ memcpy(attr->oid, name->oid.p, attr->oid_len);
+ attr->utf8_value = x509name_to_utf8_string(name, result_pool);
+ if (!attr->utf8_value)
+ /* this should never happen */
+ attr->utf8_value = apr_pstrdup(result_pool, "??");
+ APR_ARRAY_PUSH(*result, const svn_x509_name_attr_t *) = attr;
+
+ name = name->next;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+is_hostname(const char *str)
+{
+ apr_size_t i, len = strlen(str);
+
+ for (i = 0; i < len; i++)
+ {
+ char c = str[i];
+
+ /* '-' is only legal when not at the start or end of a label */
+ if (c == '-')
+ {
+ if (i + 1 != len)
+ {
+ if (str[i + 1] == '.')
+ return FALSE; /* '-' preceeds a '.' */
+ }
+ else
+ return FALSE; /* '-' is at end of string */
+
+ /* determine the previous character. */
+ if (i == 0)
+ return FALSE; /* '-' is at start of string */
+ else
+ if (str[i - 1] == '.')
+ return FALSE; /* '-' follows a '.' */
+ }
+ else if (c != '*' && c != '.' && !svn_ctype_isalnum(c))
+ return FALSE; /* some character not allowed */
+ }
+
+ return TRUE;
+}
+
+static const char *
+x509parse_get_cn(apr_array_header_t *subject)
+{
+ int i;
+
+ for (i = 0; i < subject->nelts; ++i)
+ {
+ const svn_x509_name_attr_t *attr = APR_ARRAY_IDX(subject, i, const svn_x509_name_attr_t *);
+ if (equal(attr->oid, attr->oid_len,
+ SVN_X509_OID_COMMON_NAME, sizeof(SVN_X509_OID_COMMON_NAME) - 1))
+ return attr->utf8_value;
+ }
+
+ return NULL;
+}
+
+
+static void
+x509parse_get_hostnames(svn_x509_certinfo_t *ci, x509_cert *crt,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ ci->hostnames = NULL;
+
+ if (crt->dnsnames->nelts > 0)
+ {
+ int i;
+
+ ci->hostnames = apr_array_make(result_pool, crt->dnsnames->nelts,
+ sizeof(const char*));
+
+ /* Subject Alt Names take priority */
+ for (i = 0; i < crt->dnsnames->nelts; i++)
+ {
+ x509_buf *dnsname = APR_ARRAY_IDX(crt->dnsnames, i, x509_buf *);
+ const svn_string_t *temp = svn_string_ncreate((const char *)dnsname->p,
+ dnsname->len,
+ scratch_pool);
+
+ APR_ARRAY_PUSH(ci->hostnames, const char*)
+ = fuzzy_escape(temp, result_pool);
+ }
+ }
+ else
+ {
+ /* no SAN then get the hostname from the CommonName on the cert */
+ const char *utf8_value;
+
+ utf8_value = x509parse_get_cn(ci->subject);
+
+ if (utf8_value && is_hostname(utf8_value))
+ {
+ ci->hostnames = apr_array_make(result_pool, 1, sizeof(const char*));
+ APR_ARRAY_PUSH(ci->hostnames, const char*) = utf8_value;
+ }
+ }
+}
+
+/*
+ * Parse one certificate.
+ */
+svn_error_t *
+svn_x509_parse_cert(svn_x509_certinfo_t **certinfo,
+ const char *buf,
+ apr_size_t buflen,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ ptrdiff_t len;
+ const unsigned char *p;
+ const unsigned char *end;
+ x509_cert *crt;
+ svn_x509_certinfo_t *ci;
+
+ crt = apr_pcalloc(scratch_pool, sizeof(*crt));
+ p = (const unsigned char *)buf;
+ len = buflen;
+ end = p + len;
+
+ /*
+ * Certificate ::= SEQUENCE {
+ * tbsCertificate TBSCertificate,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT STRING }
+ */
+ err = asn1_get_tag(&p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+
+ if (len != (end - p))
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+ }
+
+ /*
+ * TBSCertificate ::= SEQUENCE {
+ */
+ err = asn1_get_tag(&p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+
+ end = p + len;
+
+ /*
+ * Version ::= INTEGER { v1(0), v2(1), v3(2) }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * signature AlgorithmIdentifier
+ */
+ SVN_ERR(x509_get_version(&p, end, &crt->version));
+ SVN_ERR(x509_get_serial(&p, end, &crt->serial));
+ SVN_ERR(x509_get_alg(&p, end, &crt->sig_oid1));
+
+ crt->version++;
+
+ if (crt->version > 3)
+ return svn_error_create(SVN_ERR_X509_CERT_UNKNOWN_VERSION, NULL, NULL);
+
+ /*
+ * issuer Name
+ */
+ err = asn1_get_tag(&p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+
+ SVN_ERR(x509_get_name(&p, p + len, &crt->issuer, scratch_pool));
+
+ /*
+ * Validity ::= SEQUENCE {
+ * notBefore Time,
+ * notAfter Time }
+ *
+ */
+ SVN_ERR(x509_get_dates(&crt->valid_from, &crt->valid_to, &p, end,
+ scratch_pool));
+
+ /*
+ * subject Name
+ */
+ err = asn1_get_tag(&p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+
+ SVN_ERR(x509_get_name(&p, p + len, &crt->subject, scratch_pool));
+
+ /*
+ * SubjectPublicKeyInfo ::= SEQUENCE
+ * algorithm AlgorithmIdentifier,
+ * subjectPublicKey BIT STRING }
+ */
+ err = asn1_get_tag(&p, end, &len, ASN1_CONSTRUCTED | ASN1_SEQUENCE);
+ if (err)
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+
+ /* Skip pubkey. */
+ p += len;
+
+ /*
+ * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ * -- If present, version shall be v2 or v3
+ * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ * -- If present, version shall be v2 or v3
+ * extensions [3] EXPLICIT Extensions OPTIONAL
+ * -- If present, version shall be v3
+ */
+ crt->dnsnames = apr_array_make(scratch_pool, 3, sizeof(x509_buf *));
+
+ /* Try to parse issuerUniqueID, subjectUniqueID and extensions for *every*
+ * version (X.509 v1, v2 and v3), not just v2 or v3. If they aren't present,
+ * we are fine, but we don't want to throw an error if they are. v1 and v2
+ * certificates with the corresponding extra fields are ill-formed per RFC
+ * 5280 s. 4.1, but we suspect they could exist in the real world. Other
+ * X.509 parsers (e.g., within OpenSSL or Microsoft CryptoAPI) aren't picky
+ * about these certificates, and we also allow them. */
+ SVN_ERR(x509_get_uid(&p, end, &crt->issuer_id, 1));
+ SVN_ERR(x509_get_uid(&p, end, &crt->subject_id, 2));
+ SVN_ERR(x509_get_ext(crt->dnsnames, &p, end));
+
+ if (p != end)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+ }
+
+ end = (const unsigned char*) buf + buflen;
+
+ /*
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT STRING
+ */
+ SVN_ERR(x509_get_alg(&p, end, &crt->sig_oid2));
+
+ if (!oids_equal(&crt->sig_oid1, &crt->sig_oid2))
+ return svn_error_create(SVN_ERR_X509_CERT_SIG_MISMATCH, NULL, NULL);
+
+ SVN_ERR(x509_get_sig(&p, end, &crt->sig));
+
+ if (p != end)
+ {
+ err = svn_error_create(SVN_ERR_ASN1_LENGTH_MISMATCH, NULL, NULL);
+ return svn_error_create(SVN_ERR_X509_CERT_INVALID_FORMAT, err, NULL);
+ }
+
+ ci = apr_pcalloc(result_pool, sizeof(*ci));
+
+ /* Get the subject name */
+ SVN_ERR(x509_name_to_certinfo(&ci->subject, &crt->subject,
+ scratch_pool, result_pool));
+
+ /* Get the issuer name */
+ SVN_ERR(x509_name_to_certinfo(&ci->issuer, &crt->issuer,
+ scratch_pool, result_pool));
+
+ /* Copy the validity range */
+ ci->valid_from = crt->valid_from;
+ ci->valid_to = crt->valid_to;
+
+ /* Calculate the SHA1 digest of the certificate, otherwise known as
+ the fingerprint */
+ SVN_ERR(svn_checksum(&ci->digest, svn_checksum_sha1, buf, buflen,
+ result_pool));
+
+ /* Construct the array of host names */
+ x509parse_get_hostnames(ci, crt, result_pool, scratch_pool);
+
+ *certinfo = ci;
+ return SVN_NO_ERROR;
+}
+