diff options
author | Stef Walter <stefw@gnome.org> | 2013-01-04 15:50:13 +0100 |
---|---|---|
committer | Stef Walter <stefw@gnome.org> | 2013-02-05 14:54:53 +0100 |
commit | 7e61265ced3f33685b68bb6e2c7505485cfe0177 (patch) | |
tree | 0c7be55f59d4032a091f3687559091e22aa95d0a /common | |
parent | 8b02ff64b30311a4730b60dd72590435f56fb3a2 (diff) |
Refactor how parsing of ASN.1 data and certificate extensions work
Diffstat (limited to 'common')
-rw-r--r-- | common/Makefile.am | 2 | ||||
-rw-r--r-- | common/asn1.c | 551 | ||||
-rw-r--r-- | common/asn1.h | 65 | ||||
-rw-r--r-- | common/oid.h | 12 | ||||
-rw-r--r-- | common/tests/Makefile.am | 2 | ||||
-rw-r--r-- | common/tests/test-asn1.c | 113 | ||||
-rw-r--r-- | common/tests/test-x509.c | 185 | ||||
-rw-r--r-- | common/x509.c | 152 | ||||
-rw-r--r-- | common/x509.h | 56 |
9 files changed, 1135 insertions, 3 deletions
diff --git a/common/Makefile.am b/common/Makefile.am index bed4630..145627c 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -39,12 +39,14 @@ noinst_LTLIBRARIES += \ $(NULL) libp11_data_la_SOURCES = \ + asn1.c asn1.h \ base64.c base64.h \ checksum.c checksum.h \ oid.c oid.h \ openssl.asn openssl.asn.h \ pem.c pem.h \ pkix.asn pkix.asn.h \ + x509.c x509.h \ $(NULL) asn: diff --git a/common/asn1.c b/common/asn1.c new file mode 100644 index 0000000..3df56d9 --- /dev/null +++ b/common/asn1.c @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2012 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include "config.h" + +#include "asn1.h" +#define P11_DEBUG_FLAG P11_DEBUG_TRUST +#include "debug.h" +#include "oid.h" + +#include "openssl.asn.h" +#include "pkix.asn.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +static void +free_asn1_def (void *data) +{ + node_asn *def = data; + asn1_delete_structure (&def); +} + +struct { + const ASN1_ARRAY_TYPE* tab; + const char *prefix; + int prefix_len; +} asn1_tabs[] = { + { pkix_asn1_tab, "PKIX1.", 6 }, + { openssl_asn1_tab, "OPENSSL.", 8 }, + { NULL, }, +}; + +p11_dict * +p11_asn1_defs_load (void) +{ + char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE] = { 0, }; + node_asn *def; + p11_dict *defs; + int ret; + int i; + + defs = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, NULL, free_asn1_def); + + for (i = 0; asn1_tabs[i].tab != NULL; i++) { + + def = NULL; + ret = asn1_array2tree (asn1_tabs[i].tab, &def, message); + if (ret != ASN1_SUCCESS) { + p11_debug_precond ("failed to load %s* definitions: %s: %s\n", + asn1_tabs[i].prefix, asn1_strerror (ret), message); + return NULL; + } + + if (!p11_dict_set (defs, (void *)asn1_tabs[i].prefix, def)) + return_val_if_reached (NULL); + } + + return defs; +} + +static node_asn * +lookup_def (p11_dict *asn1_defs, + const char *struct_name) +{ + int i; + + for (i = 0; asn1_tabs[i].tab != NULL; i++) { + if (strncmp (struct_name, asn1_tabs[i].prefix, asn1_tabs[i].prefix_len) == 0) + return p11_dict_get (asn1_defs, asn1_tabs[i].prefix); + } + + p11_debug_precond ("unknown prefix for element: %s\n", struct_name); + return NULL; +} + +node_asn * +p11_asn1_create (p11_dict *asn1_defs, + const char *struct_name) +{ + node_asn *def; + node_asn *asn; + int ret; + + return_val_if_fail (asn1_defs != NULL, NULL); + + def = lookup_def (asn1_defs, struct_name); + return_val_if_fail (def != NULL, NULL); + + ret = asn1_create_element (def, struct_name, &asn); + if (ret != ASN1_SUCCESS) { + p11_debug_precond ("failed to create element %s: %s\n", + struct_name, asn1_strerror (ret)); + return NULL; + } + + return asn; +} + +node_asn * +p11_asn1_decode (p11_dict *asn1_defs, + const char *struct_name, + const unsigned char *der, + size_t der_len, + char *message) +{ + char msg[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; + node_asn *asn = NULL; + int ret; + + return_val_if_fail (asn1_defs != NULL, NULL); + + if (message == NULL) + message = msg; + + asn = p11_asn1_create (asn1_defs, struct_name); + return_val_if_fail (asn != NULL, NULL); + + /* asn1_der_decoding destroys the element if fails */ + ret = asn1_der_decoding (&asn, der, der_len, message); + + if (ret != ASN1_SUCCESS) { + p11_debug ("couldn't parse %s: %s: %s", + struct_name, asn1_strerror (ret), message); + return NULL; + } + + return asn; +} + +unsigned char * +p11_asn1_encode (node_asn *asn, + size_t *der_len) +{ + char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE]; + struct asn1_data_node data = { NULL, }; + unsigned char *der; + int len; + int ret; + + return_val_if_fail (der_len != NULL, NULL); + + len = 0; + ret = asn1_der_coding (asn, "", NULL, &len, message); + return_val_if_fail (ret != ASN1_SUCCESS, NULL); + + if (ret == ASN1_MEM_ERROR) { + der = malloc (len); + return_val_if_fail (der != NULL, NULL); + + ret = asn1_der_coding (asn, "", der, &len, message); + } + + if (ret != ASN1_SUCCESS) { + asn1_read_node_value (asn, &data); + p11_debug_precond ("failed to encode %s: %s\n", + data.name ? data.name : "(unknown)", message); + return NULL; + } + + if (der_len) + *der_len = len; + return der; +} + +static int +atoin (const char *p, + int digits) +{ + int ret = 0, base = 1; + while(--digits >= 0) { + if (p[digits] < '0' || p[digits] > '9') + return -1; + ret += (p[digits] - '0') * base; + base *= 10; + } + return ret; +} + +static int +two_to_four_digit_year (int year) +{ + time_t now; + struct tm tm; + int century, current; + + return_val_if_fail (year >= 0 && year <= 99, -1); + + /* Get the current year */ + now = time (NULL); + return_val_if_fail (now >= 0, -1); + if (!gmtime_r (&now, &tm)) + return_val_if_reached (-1); + + current = (tm.tm_year % 100); + century = (tm.tm_year + 1900) - current; + + /* + * Check if it's within 40 years before the + * current date. + */ + if (current < 40) { + if (year < current) + return century + year; + if (year > 100 - (40 - current)) + return (century - 100) + year; + } else { + if (year < current && year > (current - 40)) + return century + year; + } + + /* + * If it's after then adjust for overflows to + * the next century. + */ + if (year < current) + return century + 100 + year; + else + return century + year; +} + +static int +parse_utc_time (const char *time, + size_t n_time, + struct tm *when, + int *offset) +{ + const char *p, *e; + int year; + + assert (when != NULL); + assert (time != NULL); + assert (offset != NULL); + + /* YYMMDDhhmmss.ffff Z | +0000 */ + if (n_time < 6 || n_time >= 28) + return 0; + + /* Reset everything to default legal values */ + memset (when, 0, sizeof (*when)); + *offset = 0; + when->tm_mday = 1; + + /* Select the digits part of it */ + p = time; + for (e = p; *e >= '0' && *e <= '9'; ++e); + + if (p + 2 <= e) { + year = atoin (p, 2); + p += 2; + + /* + * 40 years in the past is our century. 60 years + * in the future is the next century. + */ + when->tm_year = two_to_four_digit_year (year) - 1900; + } + if (p + 2 <= e) { + when->tm_mon = atoin (p, 2) - 1; + p += 2; + } + if (p + 2 <= e) { + when->tm_mday = atoin (p, 2); + p += 2; + } + if (p + 2 <= e) { + when->tm_hour = atoin (p, 2); + p += 2; + } + if (p + 2 <= e) { + when->tm_min = atoin (p, 2); + p += 2; + } + if (p + 2 <= e) { + when->tm_sec = atoin (p, 2); + p += 2; + } + + if (when->tm_year < 0 || when->tm_year > 9999 || + when->tm_mon < 0 || when->tm_mon > 11 || + when->tm_mday < 1 || when->tm_mday > 31 || + when->tm_hour < 0 || when->tm_hour > 23 || + when->tm_min < 0 || when->tm_min > 59 || + when->tm_sec < 0 || when->tm_sec > 59) + return 0; + + /* Make sure all that got parsed */ + if (p != e) + return 0; + + /* Now the remaining optional stuff */ + e = time + n_time; + + /* See if there's a fraction, and discard it if so */ + if (p < e && *p == '.' && p + 5 <= e) + p += 5; + + /* See if it's UTC */ + if (p < e && *p == 'Z') { + p += 1; + + /* See if it has a timezone */ + } else if ((*p == '-' || *p == '+') && p + 3 <= e) { + int off, neg; + + neg = *p == '-'; + ++p; + + off = atoin (p, 2) * 3600; + if (off < 0 || off > 86400) + return 0; + p += 2; + + if (p + 2 <= e) { + off += atoin (p, 2) * 60; + p += 2; + } + + /* Use TZ offset */ + if (neg) + *offset = 0 - off; + else + *offset = off; + } + + /* Make sure everything got parsed */ + if (p != e) + return 0; + + return 1; +} + +static int +parse_general_time (const char *time, + size_t n_time, + struct tm *when, + int *offset) +{ + const char *p, *e; + + assert (time != NULL); + assert (when != NULL); + assert (offset != NULL); + + /* YYYYMMDDhhmmss.ffff Z | +0000 */ + if (n_time < 8 || n_time >= 30) + return 0; + + /* Reset everything to default legal values */ + memset (when, 0, sizeof (*when)); + *offset = 0; + when->tm_mday = 1; + + /* Select the digits part of it */ + p = time; + for (e = p; *e >= '0' && *e <= '9'; ++e); + + if (p + 4 <= e) { + when->tm_year = atoin (p, 4) - 1900; + p += 4; + } + if (p + 2 <= e) { + when->tm_mon = atoin (p, 2) - 1; + p += 2; + } + if (p + 2 <= e) { + when->tm_mday = atoin (p, 2); + p += 2; + } + if (p + 2 <= e) { + when->tm_hour = atoin (p, 2); + p += 2; + } + if (p + 2 <= e) { + when->tm_min = atoin (p, 2); + p += 2; + } + if (p + 2 <= e) { + when->tm_sec = atoin (p, 2); + p += 2; + } + + if (when->tm_year < 0 || when->tm_year > 9999 || + when->tm_mon < 0 || when->tm_mon > 11 || + when->tm_mday < 1 || when->tm_mday > 31 || + when->tm_hour < 0 || when->tm_hour > 23 || + when->tm_min < 0 || when->tm_min > 59 || + when->tm_sec < 0 || when->tm_sec > 59) + return 0; + + /* Make sure all that got parsed */ + if (p != e) + return 0; + + /* Now the remaining optional stuff */ + e = time + n_time; + + /* See if there's a fraction, and discard it if so */ + if (p < e && *p == '.' && p + 5 <= e) + p += 5; + + /* See if it's UTC */ + if (p < e && *p == 'Z') { + p += 1; + + /* See if it has a timezone */ + } else if ((*p == '-' || *p == '+') && p + 3 <= e) { + int off, neg; + + neg = *p == '-'; + ++p; + + off = atoin (p, 2) * 3600; + if (off < 0 || off > 86400) + return 0; + p += 2; + + if (p + 2 <= e) { + off += atoin (p, 2) * 60; + p += 2; + } + + /* Use TZ offset */ + if (neg) + *offset = 0 - off; + else + *offset = off; + } + + /* Make sure everything got parsed */ + if (p != e) + return 0; + + return 1; +} + +static time_t +when_and_offset_to_time_t (struct tm *when, + int tz_offset) +{ + time_t timet; + + /* In order to work with 32 bit time_t. */ + if (sizeof (time_t) <= 4 && when->tm_year >= 2038) { + timet = (time_t)2145914603; /* 2037-12-31 23:23:23 */ + + /* Convert to seconds since epoch */ + } else { + timet = timegm (when); + return_val_if_fail (timet >= 0, -1); + timet += tz_offset; + } + + if (!gmtime_r (&timet, when)) + return_val_if_reached (-1); + + return timet; +} + +time_t +p11_asn1_parse_utc (const char *time_str, + struct tm *when) +{ + struct tm dummy; + int tz_offset; + int ret; + + if (!when) + when = &dummy; + + ret = parse_utc_time (time_str, strlen (time_str), + when, &tz_offset); + if (!ret) + return -1; + + return when_and_offset_to_time_t (when, tz_offset); +} + +time_t +p11_asn1_parse_general (const char *time_str, + struct tm *when) +{ + struct tm dummy; + int tz_offset; + int ret; + + if (!when) + when = &dummy; + + ret = parse_general_time (time_str, strlen (time_str), + when, &tz_offset); + if (!ret) + return -1; + + return when_and_offset_to_time_t (when, tz_offset); +} + +ssize_t +p11_asn1_tlv_length (const unsigned char *data, + size_t length) +{ + unsigned char cls; + int counter = 0; + int cb, len; + unsigned long tag; + + if (asn1_get_tag_der (data, length, &cls, &cb, &tag) == ASN1_SUCCESS) { + counter += cb; + len = asn1_get_length_der (data + cb, length - cb, &cb); + counter += cb; + if (len >= 0) { + len += counter; + if (length >= len) + return len; + } + } + + return -1; +} diff --git a/common/asn1.h b/common/asn1.h new file mode 100644 index 0000000..76a84ed --- /dev/null +++ b/common/asn1.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include <libtasn1.h> + +#include "dict.h" + +#ifndef P11_ASN1_H_ +#define P11_ASN1_H_ + +p11_dict * p11_asn1_defs_load (void); + +node_asn * p11_asn1_decode (p11_dict *asn1_defs, + const char *struct_name, + const unsigned char *der, + size_t der_len, + char *message); + +node_asn * p11_asn1_create (p11_dict *asn1_defs, + const char *struct_name); + +unsigned char * p11_asn1_encode (node_asn *asn, + size_t *der_len); + +time_t p11_asn1_parse_utc (const char *time_str, + struct tm *when); + +time_t p11_asn1_parse_general (const char *time_str, + struct tm *when); + +ssize_t p11_asn1_tlv_length (const unsigned char *data, + size_t length); + +#endif /* P11_ASN1_H_ */ diff --git a/common/oid.h b/common/oid.h index 181539a..a06054a 100644 --- a/common/oid.h +++ b/common/oid.h @@ -129,6 +129,7 @@ static const unsigned char P11_OID_OPENSSL_REJECT[] = */ static const unsigned char P11_OID_SERVER_AUTH[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 }; +static const char P11_OID_SERVER_AUTH_STR[] = "1.3.6.1.5.5.7.3.1"; /* * 1.3.6.1.5.5.7.3.2: Client Auth @@ -137,6 +138,7 @@ static const unsigned char P11_OID_SERVER_AUTH[] = */ static const unsigned char P11_OID_CLIENT_AUTH[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02 }; +static const char P11_OID_CLIENT_AUTH_STR[] = "1.3.6.1.5.5.7.3.2"; /* * 1.3.6.1.5.5.7.3.3: Code Signing @@ -145,6 +147,7 @@ static const unsigned char P11_OID_CLIENT_AUTH[] = */ static const unsigned char P11_OID_CODE_SIGNING[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03 }; +static const char P11_OID_CODE_SIGNING_STR[] = "1.3.6.1.5.5.7.3.3"; /* * 1.3.6.1.5.5.7.3.4: Email Protection @@ -153,6 +156,7 @@ static const unsigned char P11_OID_CODE_SIGNING[] = */ static const unsigned char P11_OID_EMAIL_PROTECTION[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04 }; +static const char P11_OID_EMAIL_PROTECTION_STR[] = "1.3.6.1.5.5.7.3.4"; /* * 1.3.6.1.5.5.7.3.5: IPSec End System @@ -161,6 +165,7 @@ static const unsigned char P11_OID_EMAIL_PROTECTION[] = */ static const unsigned char P11_OID_IPSEC_END_SYSTEM[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x05 }; +static const char P11_OID_IPSEC_END_SYSTEM_STR[] = "1.3.6.1.5.5.7.3.5"; /* * 1.3.6.1.5.5.7.3.6: IPSec Tunnel @@ -169,6 +174,7 @@ static const unsigned char P11_OID_IPSEC_END_SYSTEM[] = */ static const unsigned char P11_OID_IPSEC_TUNNEL[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x06 }; +static const char P11_OID_IPSEC_TUNNEL_STR[] = "1.3.6.1.5.5.7.3.6"; /* * 1.3.6.1.5.5.7.3.7: IPSec User @@ -177,6 +183,7 @@ static const unsigned char P11_OID_IPSEC_TUNNEL[] = */ static const unsigned char P11_OID_IPSEC_USER[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x07 }; +static const char P11_OID_IPSEC_USER_STR[] = "1.3.6.1.5.5.7.3.7"; /* * 1.3.6.1.5.5.7.3.8: Time Stamping @@ -185,7 +192,7 @@ static const unsigned char P11_OID_IPSEC_USER[] = */ static const unsigned char P11_OID_TIME_STAMPING[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x08 }; - +static const char P11_OID_TIME_STAMPING_STR[] = "1.3.6.1.5.5.7.3.8"; /* * 1.3.6.1.4.1.3319.6.10.16: Reserved key purpose * @@ -202,7 +209,6 @@ static const unsigned char P11_OID_TIME_STAMPING[] = */ static const unsigned char P11_OID_RESERVED_PURPOSE[] = { 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x99, 0x77, 0x06, 0x0a, 0x10 }; -static const char P11_OID_RESERVED_PURPOSE_STR[] = - "1.3.6.1.4.1.3319.6.10.16"; +static const char P11_OID_RESERVED_PURPOSE_STR[] = "1.3.6.1.4.1.3319.6.10.16"; #endif diff --git a/common/tests/Makefile.am b/common/tests/Makefile.am index 94c6556..6d8d76e 100644 --- a/common/tests/Makefile.am +++ b/common/tests/Makefile.am @@ -34,9 +34,11 @@ LDADD += \ $(NULL) CHECK_PROGS += \ + test-asn1 \ test-checksum \ test-pem \ test-oid \ + test-x509 \ $(NULL) noinst_PROGRAMS += \ diff --git a/common/tests/test-asn1.c b/common/tests/test-asn1.c new file mode 100644 index 0000000..34e9129 --- /dev/null +++ b/common/tests/test-asn1.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@gnome.org> + */ + +#include "config.h" +#include "CuTest.h" + +#include "asn1.h" +#include "debug.h" +#include "oid.h" +#include "x509.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +struct { + p11_dict *asn1_defs; +} test; + +static void +setup (CuTest *cu) +{ + test.asn1_defs = p11_asn1_defs_load (); + CuAssertPtrNotNull (cu, test.asn1_defs); +} + +static void +teardown (CuTest *cu) +{ + p11_dict_free (test.asn1_defs); + memset (&test, 0, sizeof (test)); +} + +static void +test_tlv_length (CuTest *cu) +{ + struct { + const char *der; + size_t der_len; + int expected; + } tlv_lengths[] = { + { "\x01\x01\x00", 3, 3 }, + { "\x01\x01\x00\x01\x02", 5, 3 }, + { "\x01\x05\x00", 3, -1 }, + { NULL } + }; + + int length; + int i; + + setup (cu); + + for (i = 0; tlv_lengths[i].der != NULL; i++) { + length = p11_asn1_tlv_length ((const unsigned char *)tlv_lengths[i].der, tlv_lengths[i].der_len); + CuAssertIntEquals (cu, tlv_lengths[i].expected, length); + } + + teardown (cu); +} + +int +main (void) +{ + CuString *output = CuStringNew (); + CuSuite* suite = CuSuiteNew (); + int ret; + + setenv ("P11_KIT_STRICT", "1", 1); + p11_debug_init (); + + SUITE_ADD_TEST (suite, test_tlv_length); + + CuSuiteRun (suite); + CuSuiteSummary (suite, output); + CuSuiteDetails (suite, output); + printf ("%s\n", output->buffer); + ret = suite->failCount; + CuSuiteDelete (suite); + CuStringDelete (output); + + return ret; +} diff --git a/common/tests/test-x509.c b/common/tests/test-x509.c new file mode 100644 index 0000000..17e25bf --- /dev/null +++ b/common/tests/test-x509.c @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2012 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@gnome.org> + */ + +#include "config.h" +#include "CuTest.h" + +#include "asn1.h" +#include "debug.h" +#include "oid.h" +#include "x509.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +struct { + p11_dict *asn1_defs; +} test; + +static void +setup (CuTest *cu) +{ + test.asn1_defs = p11_asn1_defs_load (); + CuAssertPtrNotNull (cu, test.asn1_defs); +} + +static void +teardown (CuTest *cu) +{ + p11_dict_free (test.asn1_defs); + memset (&test, 0, sizeof (test)); +} + +static const char test_ku_ds_and_np[] = { + 0x03, 0x03, 0x07, 0xc0, 0x00, +}; + +static const char test_ku_none[] = { + 0x03, 0x03, 0x07, 0x00, 0x00, +}; + +static const char test_ku_cert_crl_sign[] = { + 0x03, 0x03, 0x07, 0x06, 0x00, +}; + +static const char test_eku_server_and_client[] = { + 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, +}; + +static const char test_eku_none[] = { + 0x30, 0x00, +}; + +static const char test_eku_client_email_and_timestamp[] = { + 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x03, 0x04, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x08, +}; + +struct { + const char *eku; + size_t length; + const char *expected[16]; +} extended_key_usage_fixtures[] = { + { test_eku_server_and_client, sizeof (test_eku_server_and_client), + { P11_OID_CLIENT_AUTH_STR, P11_OID_SERVER_AUTH_STR, NULL }, }, + { test_eku_none, sizeof (test_eku_none), + { NULL, }, }, + { test_eku_client_email_and_timestamp, sizeof (test_eku_client_email_and_timestamp), + { P11_OID_CLIENT_AUTH_STR, P11_OID_EMAIL_PROTECTION_STR, P11_OID_TIME_STAMPING_STR }, }, + { NULL }, +}; + +static void +test_parse_extended_key_usage (CuTest *cu) +{ + p11_dict *ekus; + int i, j; + + setup (cu); + + for (i = 0; extended_key_usage_fixtures[i].eku != NULL; i++) { + ekus = p11_x509_parse_extended_key_usage (test.asn1_defs, + (const unsigned char *)extended_key_usage_fixtures[i].eku, + extended_key_usage_fixtures[i].length); + CuAssertPtrNotNull (cu, ekus); + + for (j = 0; extended_key_usage_fixtures[i].expected[j] != NULL; j++) + CuAssertTrue (cu, p11_dict_get (ekus, extended_key_usage_fixtures[i].expected[j]) != NULL); + CuAssertIntEquals (cu, j, p11_dict_size (ekus)); + + p11_dict_free (ekus); + } + + teardown (cu); +} + +struct { + const char *ku; + size_t length; + unsigned int expected; +} key_usage_fixtures[] = { + { test_ku_ds_and_np, sizeof (test_ku_ds_and_np), P11_KU_DIGITAL_SIGNATURE | P11_KU_NON_REPUDIATION }, + { test_ku_none, sizeof (test_ku_none), 0 }, + { test_ku_cert_crl_sign, sizeof (test_ku_cert_crl_sign), P11_KU_KEY_CERT_SIGN | P11_KU_CRL_SIGN }, + { NULL }, +}; + +static void +test_parse_key_usage (CuTest *cu) +{ + unsigned int ku; + int i; + bool ret; + + setup (cu); + + for (i = 0; key_usage_fixtures[i].ku != NULL; i++) { + ku = 0; + + ret = p11_x509_parse_key_usage (test.asn1_defs, + (const unsigned char *)key_usage_fixtures[i].ku, + key_usage_fixtures[i].length, &ku); + CuAssertIntEquals (cu, true, ret); + + CuAssertIntEquals (cu, key_usage_fixtures[i].expected, ku); + } + + teardown (cu); +} + +int +main (void) +{ + CuString *output = CuStringNew (); + CuSuite* suite = CuSuiteNew (); + int ret; + + setenv ("P11_KIT_STRICT", "1", 1); + p11_debug_init (); + + SUITE_ADD_TEST (suite, test_parse_extended_key_usage); + SUITE_ADD_TEST (suite, test_parse_key_usage); + + CuSuiteRun (suite); + CuSuiteSummary (suite, output); + CuSuiteDetails (suite, output); + printf ("%s\n", output->buffer); + ret = suite->failCount; + CuSuiteDelete (suite); + CuStringDelete (output); + + return ret; +} diff --git a/common/x509.c b/common/x509.c new file mode 100644 index 0000000..8eb513d --- /dev/null +++ b/common/x509.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2012 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include "config.h" + +#include "asn1.h" +#define P11_DEBUG_FLAG P11_DEBUG_TRUST +#include "debug.h" +#include "oid.h" +#include "x509.h" + +#include <stdlib.h> +#include <string.h> + +bool +p11_x509_parse_basic_constraints (p11_dict *asn1_defs, + const unsigned char *ext_der, + size_t ext_len, + bool *is_ca) +{ + char buffer[8]; + node_asn *ext; + int ret; + int len; + + return_val_if_fail (is_ca != NULL, false); + + ext = p11_asn1_decode (asn1_defs, "PKIX1.BasicConstraints", ext_der, ext_len, NULL); + if (ext == NULL) + return false; + + len = sizeof (buffer); + ret = asn1_read_value (ext, "cA", buffer, &len); + return_val_if_fail (ret == ASN1_SUCCESS, false); + + *is_ca = (strcmp (buffer, "TRUE") == 0); + asn1_delete_structure (&ext); + + return true; +} + +bool +p11_x509_parse_key_usage (p11_dict *asn1_defs, + const unsigned char *ext_der, + size_t ext_len, + unsigned int *ku) +{ + char message[ASN1_MAX_ERROR_DESCRIPTION_SIZE] = { 0, }; + unsigned char buf[2]; + node_asn *ext; + int len; + int ret; + + ext = p11_asn1_decode (asn1_defs, "PKIX1.KeyUsage", ext_der, ext_len, message); + if (ext == NULL) + return false; + + len = sizeof (buf); + ret = asn1_read_value (ext, "", buf, &len); + return_val_if_fail (ret == ASN1_SUCCESS, false); + + /* A bit string, so combine into one set of flags */ + *ku = buf[0] | (buf[1] << 8); + + asn1_delete_structure (&ext); + + return true; +} + +p11_dict * +p11_x509_parse_extended_key_usage (p11_dict *asn1_defs, + const unsigned char *ext_der, + size_t ext_len) +{ + node_asn *asn; + char field[128]; + p11_dict *ekus; + char *eku; + int ret; + int len; + int i; + + asn = p11_asn1_decode (asn1_defs, "PKIX1.ExtKeyUsageSyntax", ext_der, ext_len, NULL); + if (asn == NULL) + return NULL; + + ekus = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, free, NULL); + + for (i = 1; ; i++) { + if (snprintf (field, sizeof (field), "?%u", i) < 0) + return_val_if_reached (NULL); + + len = 0; + ret = asn1_read_value (asn, field, NULL, &len); + if (ret == ASN1_ELEMENT_NOT_FOUND) + break; + + return_val_if_fail (ret == ASN1_MEM_ERROR, NULL); + + eku = malloc (len + 1); + return_val_if_fail (eku != NULL, NULL); + + ret = asn1_read_value (asn, field, eku, &len); + return_val_if_fail (ret == ASN1_SUCCESS, NULL); + + eku[len] = 0; + + /* If it's our reserved OID, then skip */ + if (strcmp (eku, P11_OID_RESERVED_PURPOSE_STR) == 0) { + free (eku); + continue; + } + + if (!p11_dict_set (ekus, eku, eku)) + return_val_if_reached (NULL); + } + + asn1_delete_structure (&asn); + + return ekus; +} diff --git a/common/x509.h b/common/x509.h new file mode 100644 index 0000000..b7c9a5e --- /dev/null +++ b/common/x509.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 Red Hat Inc. + * + * 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. + * * The names of contributors to this software may not 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. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include <libtasn1.h> + +#include "dict.h" + +#ifndef P11_X509_H_ +#define P11_X509_H_ + +bool p11_x509_parse_basic_constraints (p11_dict *asn1_defs, + const unsigned char *ext_der, + size_t ext_len, + bool *is_ca); + +bool p11_x509_parse_key_usage (p11_dict *asn1_defs, + const unsigned char *data, + size_t length, + unsigned int *ku); + +p11_dict * p11_x509_parse_extended_key_usage (p11_dict *asn1_defs, + const unsigned char *ext_der, + size_t ext_len); + +#endif /* P11_X509_H_ */ |