Added macro that indicates presence of the time callback feature. Also added C++...
[BearSSL] / test / test_x509.c
index a591b46..c007068 100644 (file)
 #include <string.h>
 #include <stdint.h>
 
 #include <string.h>
 #include <stdint.h>
 
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
 #include "bearssl.h"
 
 #include "bearssl.h"
 
+#define STR(x)    STR_(x)
+#define STR_(x)   #x
+#ifdef SRCDIRNAME
+#define DIRNAME        STR(SRCDIRNAME) "/test/x509"
+#else
 #define DIRNAME        "test/x509"
 #define DIRNAME        "test/x509"
-#define CONFFILE       (DIRNAME "/alltests.txt")
+#endif
+#define CONFFILE       DIRNAME "/alltests.txt"
 #define DEFAULT_TIME   "2016-08-30T18:00:00Z"
 
 static void *
 #define DEFAULT_TIME   "2016-08-30T18:00:00Z"
 
 static void *
@@ -258,6 +270,9 @@ HT_expand(HT *ht)
        n = ht->num_buckets;
        n2 = n << 1;
        new_buckets = xmalloc(n2 * sizeof *new_buckets);
        n = ht->num_buckets;
        n2 = n << 1;
        new_buckets = xmalloc(n2 * sizeof *new_buckets);
+       for (u = 0; u < n2; u ++) {
+               new_buckets[u] = NULL;
+       }
        for (u = 0; u < n; u ++) {
                ht_elt *e, *f;
 
        for (u = 0; u < n; u ++) {
                ht_elt *e, *f;
 
@@ -509,7 +524,7 @@ static int
 string_to_time(const char *s, uint32_t *days, uint32_t *seconds)
 {
        int year, month, day, hour, minute, second;
 string_to_time(const char *s, uint32_t *days, uint32_t *seconds)
 {
        int year, month, day, hour, minute, second;
-       int day_of_year, leaps;
+       int day_of_year, leaps, i;
 
        if (parse_dec(s, 4, &year) < 0) {
                return -1;
 
        if (parse_dec(s, 4, &year) < 0) {
                return -1;
@@ -568,7 +583,7 @@ string_to_time(const char *s, uint32_t *days, uint32_t *seconds)
                return -1;
        }
        day_of_year = 0;
                return -1;
        }
        day_of_year = 0;
-       for (int i = 1; i < month; i ++) {
+       for (i = 1; i < month; i ++) {
                day_of_year += month_length(year, i);
        }
        if (day < 1 || day > month_length(year, month)) {
                day_of_year += month_length(year, i);
        }
        if (day < 1 || day > month_length(year, month)) {
@@ -1423,6 +1438,21 @@ eqpkey(const br_x509_pkey *pk1, const br_x509_pkey *pk2)
 static size_t max_dp_usage;
 static size_t max_rp_usage;
 
 static size_t max_dp_usage;
 static size_t max_rp_usage;
 
+static int
+check_time(void *ctx, uint32_t nbd, uint32_t nbs, uint32_t nad, uint32_t nas)
+{
+       test_case *tc;
+
+       tc = ctx;
+       if (tc->days < nbd || (tc->days == nbd && tc->seconds < nbs)) {
+               return -1;
+       }
+       if (tc->days > nad || (tc->days == nad && tc->seconds > nas)) {
+               return 1;
+       }
+       return 0;
+}
+
 static void
 run_test_case(test_case *tc)
 {
 static void
 run_test_case(test_case *tc)
 {
@@ -1435,7 +1465,9 @@ run_test_case(test_case *tc)
        blob *certs;
        br_x509_pkey *ee_pkey_ref;
        const br_x509_pkey *ee_pkey;
        blob *certs;
        br_x509_pkey *ee_pkey_ref;
        const br_x509_pkey *ee_pkey;
+       unsigned usages;
        unsigned status;
        unsigned status;
+       int j;
 
        printf("%s: ", tc->name);
        fflush(stdout);
 
        printf("%s: ", tc->name);
        fflush(stdout);
@@ -1474,8 +1506,8 @@ run_test_case(test_case *tc)
                                tta->key_name);
                        exit(EXIT_FAILURE);
                }
                                tta->key_name);
                        exit(EXIT_FAILURE);
                }
-               anchors[u].dn = tta->dn;
-               anchors[u].dn_len = tta->dn_len;
+               anchors[u].dn.data = tta->dn;
+               anchors[u].dn.len = tta->dn_len;
                anchors[u].flags = tta->flags;
                anchors[u].pkey = *tak;
        }
                anchors[u].flags = tta->flags;
                anchors[u].pkey = *tak;
        }
@@ -1504,25 +1536,370 @@ run_test_case(test_case *tc)
        }
 
        /*
        }
 
        /*
-        * Initialise the engine.
+        * We do the test twice, to exercise distinct API functions.
         */
         */
-       br_x509_minimal_init(&ctx, dnhash, anchors, num_anchors);
-       for (u = 0; hash_impls[u].id; u ++) {
-               int id;
+       for (j = 0; j < 2; j ++) {
+               /*
+                * Initialise the engine.
+                */
+               br_x509_minimal_init(&ctx, dnhash, anchors, num_anchors);
+               for (u = 0; hash_impls[u].id; u ++) {
+                       int id;
+
+                       id = hash_impls[u].id;
+                       if ((tc->hashes & ((unsigned)1 << id)) != 0) {
+                               br_x509_minimal_set_hash(&ctx,
+                                       id, hash_impls[u].impl);
+                       }
+               }
+               br_x509_minimal_set_rsa(&ctx, br_rsa_pkcs1_vrfy_get_default());
+               br_x509_minimal_set_ecdsa(&ctx,
+                       br_ec_get_default(), br_ecdsa_vrfy_asn1_get_default());
 
 
-               id = hash_impls[u].id;
-               if ((tc->hashes & ((unsigned)1 << id)) != 0) {
-                       br_x509_minimal_set_hash(&ctx, id, hash_impls[u].impl);
+               /*
+                * Set the validation date.
+                */
+               if (j == 0) {
+                       br_x509_minimal_set_time(&ctx, tc->days, tc->seconds);
+               } else {
+                       br_x509_minimal_set_time_callback(&ctx,
+                               tc, &check_time);
+               }
+
+               /*
+                * Put "canaries" to detect actual stack usage.
+                */
+               for (u = 0; u < (sizeof ctx.dp_stack) / sizeof(uint32_t);
+                       u ++)
+               {
+                       ctx.dp_stack[u] = 0xA7C083FE;
+               }
+               for (u = 0; u < (sizeof ctx.rp_stack) / sizeof(uint32_t);
+                       u ++)
+               {
+                       ctx.rp_stack[u] = 0xA7C083FE;
+               }
+
+               /*
+                * Run the engine. We inject certificates by chunks of 100
+                * bytes in order to exercise the coroutine API.
+                */
+               ctx.vtable->start_chain(&ctx.vtable, tc->servername);
+               for (u = 0; u < num_certs; u ++) {
+                       size_t v;
+
+                       ctx.vtable->start_cert(&ctx.vtable, certs[u].len);
+                       v = 0;
+                       while (v < certs[u].len) {
+                               size_t w;
+
+                               w = certs[u].len - v;
+                               if (w > 100) {
+                                       w = 100;
+                               }
+                               ctx.vtable->append(&ctx.vtable,
+                                       certs[u].data + v, w);
+                               v += w;
+                       }
+                       ctx.vtable->end_cert(&ctx.vtable);
+               }
+               status = ctx.vtable->end_chain(&ctx.vtable);
+               ee_pkey = ctx.vtable->get_pkey(&ctx.vtable, &usages);
+
+               /*
+                * Check key type and usage.
+                */
+               if (ee_pkey != NULL) {
+                       unsigned ktu;
+
+                       ktu = ee_pkey->key_type | usages;
+                       if (tc->key_type_usage != (ktu & tc->key_type_usage)) {
+                               fprintf(stderr, "wrong key type + usage"
+                                       " (expected 0x%02X, got 0x%02X)\n",
+                                       tc->key_type_usage, ktu);
+                               exit(EXIT_FAILURE);
+                       }
+               }
+
+               /*
+                * Check results. Note that we may still get a public key if
+                * the path is "not trusted" (but otherwise fine).
+                */
+               if (status != tc->status) {
+                       fprintf(stderr, "wrong status (got %d, expected %d)\n",
+                               status, tc->status);
+                       exit(EXIT_FAILURE);
+               }
+               if (status == BR_ERR_X509_NOT_TRUSTED) {
+                       ee_pkey = NULL;
+               }
+               if (!eqpkey(ee_pkey, ee_pkey_ref)) {
+                       fprintf(stderr, "wrong EE public key\n");
+                       exit(EXIT_FAILURE);
+               }
+
+               /*
+                * Check stack usage.
+                */
+               for (u = (sizeof ctx.dp_stack) / sizeof(uint32_t);
+                       u > 0; u --)
+               {
+                       if (ctx.dp_stack[u - 1] != 0xA7C083FE) {
+                               if (max_dp_usage < u) {
+                                       max_dp_usage = u;
+                               }
+                               break;
+                       }
+               }
+               for (u = (sizeof ctx.rp_stack) / sizeof(uint32_t);
+                       u > 0; u --)
+               {
+                       if (ctx.rp_stack[u - 1] != 0xA7C083FE) {
+                               if (max_rp_usage < u) {
+                                       max_rp_usage = u;
+                               }
+                               break;
+                       }
                }
        }
                }
        }
-       br_x509_minimal_set_rsa(&ctx, br_rsa_i32_pkcs1_vrfy);
-       br_x509_minimal_set_ecdsa(&ctx,
-               &br_ec_prime_i31, br_ecdsa_i31_vrfy_asn1);
 
        /*
 
        /*
-        * Set the validation date.
+        * Release everything.
         */
         */
-       br_x509_minimal_set_time(&ctx, tc->days, tc->seconds);
+       for (u = 0; u < num_certs; u ++) {
+               xfree(certs[u].data);
+       }
+       xfree(certs);
+       xfree(anchors);
+       printf("OK\n");
+}
+
+/*
+ * A custom structure for tests, synchronised with the test certificate
+ * names.crt.
+ *
+ * If num is 1 or more, then this is a DN element with OID '1.1.1.1.num'.
+ * If num is -1 or less, then this is a SAN element of type -num.
+ * If num is 0, then this is a SAN element of type OtherName with
+ * OID 1.3.6.1.4.1.311.20.2.3 (Microsoft UPN).
+ */
+typedef struct {
+       int num;
+       int status;
+       const char *expected;
+} name_element_test;
+
+static name_element_test names_ref[] = {
+       /* === DN tests === */
+       {
+               /* [12] 66:6f:6f */
+               1, 1, "foo"
+       },
+       {
+               /* [12] 62:61:72 */
+               1, 1, "bar"
+       },
+       {
+               /* [18] 31:32:33:34 */
+               2, 1, "1234"
+       },
+       {
+               /* [19] 66:6f:6f */
+               3, 1, "foo"
+       },
+       {
+               /* [20] 66:6f:6f */
+               4, 1, "foo"
+       },
+       {
+               /* [22] 66:6f:6f */
+               5, 1, "foo"
+       },
+       {
+               /* [30] 00:66:00:6f:00:6f */
+               6, 1, "foo"
+       },
+       {
+               /* [30] fe:ff:00:66:00:6f:00:6f */
+               7, 1, "foo"
+       },
+       {
+               /* [30] ff:fe:66:00:6f:00:6f:00 */
+               8, 1, "foo"
+       },
+       {
+               /* [20] 63:61:66:e9 */
+               9, 1, "caf\xC3\xA9"
+       },
+       {
+               /* [12] 63:61:66:c3:a9 */
+               10, 1, "caf\xC3\xA9"
+       },
+       {
+               /* [12] 63:61:66:e0:83:a9 */
+               11, -1, NULL
+       },
+       {
+               /* [12] 63:61:66:e3:90:8c */
+               12, 1, "caf\xE3\x90\x8C"
+       },
+       {
+               /* [30] 00:63:00:61:00:66:34:0c */
+               13, 1, "caf\xE3\x90\x8C"
+       },
+       {
+               /* [12] 63:61:66:c3 */
+               14, -1, NULL
+       },
+       {
+               /* [30] d8:42:df:f4:00:67:00:6f */
+               15, 1, "\xF0\xA0\xAF\xB4go"
+       },
+       {
+               /* [30] 00:66:d8:42 */
+               16, -1, NULL
+       },
+       {
+               /* [30] d8:42:00:66 */
+               17, -1, NULL
+       },
+       {
+               /* [30] df:f4:00:66 */
+               18, -1, NULL
+       },
+       {
+               /* [12] 66:00:6f */
+               19, -1, NULL
+       },
+       {
+               /* [30] 00:00:34:0c */
+               20, -1, NULL
+       },
+       {
+               /* [30] 34:0c:00:00:00:66 */
+               21, -1, NULL
+       },
+       {
+               /* [12] ef:bb:bf:66:6f:6f */
+               22, 1, "foo"
+       },
+       {
+               /* [30] 00:66:ff:fe:00:6f */
+               23, -1, NULL
+       },
+       {
+               /* [30] 00:66:ff:fd:00:6f */
+               24, 1, "f\xEF\xBF\xBDo"
+       },
+
+       /* === Value not found in the DN === */
+       {
+               127, 0, NULL
+       },
+
+       /* === SAN tests === */
+       {
+               /* SAN OtherName (Microsoft UPN) */
+               0, 1, "foo@bar.com"
+       },
+       {
+               /* SAN rfc822Name */
+               -1, 1, "bar@foo.com"
+       },
+       {
+               /* SAN dNSName */
+               -2, 1, "example.com"
+       },
+       {
+               /* SAN dNSName */
+               -2, 1, "www.example.com"
+       },
+       {
+               /* uniformResourceIdentifier */
+               -6, 1, "http://www.example.com/"
+       }
+};
+
+static void
+free_name_elements(br_name_element *elts, size_t num)
+{
+       size_t u;
+
+       for (u = 0; u < num; u ++) {
+               xfree((void *)elts[u].oid);
+               xfree(elts[u].buf);
+       }
+       xfree(elts);
+}
+
+static void
+test_name_extraction(void)
+{
+       unsigned char *data;
+       size_t len;
+       br_x509_minimal_context ctx;
+       uint32_t days, seconds;
+       size_t u;
+       unsigned status;
+       br_name_element *names;
+       size_t num_names;
+       int good;
+
+       printf("Name extraction: ");
+       fflush(stdout);
+       data = read_file("names.crt", &len);
+       br_x509_minimal_init(&ctx, &br_sha256_vtable, NULL, 0);
+       for (u = 0; hash_impls[u].id; u ++) {
+               int id;
+
+               id = hash_impls[u].id;
+               br_x509_minimal_set_hash(&ctx, id, hash_impls[u].impl);
+       }
+       br_x509_minimal_set_rsa(&ctx, br_rsa_pkcs1_vrfy_get_default());
+       br_x509_minimal_set_ecdsa(&ctx,
+               br_ec_get_default(), br_ecdsa_vrfy_asn1_get_default());
+       string_to_time(DEFAULT_TIME, &days, &seconds);
+       br_x509_minimal_set_time(&ctx, days, seconds);
+
+       num_names = (sizeof names_ref) / (sizeof names_ref[0]);
+       names = xmalloc(num_names * sizeof *names);
+       for (u = 0; u < num_names; u ++) {
+               int num;
+               unsigned char *oid;
+
+               num = names_ref[u].num;
+               if (num > 0) {
+                       oid = xmalloc(5);
+                       oid[0] = 4;
+                       oid[1] = 0x29;
+                       oid[2] = 0x01;
+                       oid[3] = 0x01;
+                       oid[4] = num;
+               } else if (num == 0) {
+                       oid = xmalloc(13);
+                       oid[0] = 0x00;
+                       oid[1] = 0x00;
+                       oid[2] = 0x0A;
+                       oid[3] = 0x2B;
+                       oid[4] = 0x06;
+                       oid[5] = 0x01;
+                       oid[6] = 0x04;
+                       oid[7] = 0x01;
+                       oid[8] = 0x82;
+                       oid[9] = 0x37;
+                       oid[10] = 0x14;
+                       oid[11] = 0x02;
+                       oid[12] = 0x03;
+               } else {
+                       oid = xmalloc(2);
+                       oid[0] = 0x00;
+                       oid[1] = -num;
+               }
+               names[u].oid = oid;
+               names[u].buf = xmalloc(256);
+               names[u].len = 256;
+       }
+       br_x509_minimal_set_name_elements(&ctx, names, num_names);
 
        /*
         * Put "canaries" to detect actual stack usage.
 
        /*
         * Put "canaries" to detect actual stack usage.
@@ -1535,45 +1912,16 @@ run_test_case(test_case *tc)
        }
 
        /*
        }
 
        /*
-        * Run the engine. We inject certificates by chunks of 100 bytes
-        * in order to exercise the coroutine API.
+        * Run the engine. Since we set no trust anchor, we expect a status
+        * of "not trusted".
         */
         */
-       ctx.vtable->start_chain(&ctx.vtable,
-               tc->key_type_usage, tc->servername);
-       for (u = 0; u < num_certs; u ++) {
-               size_t v;
-
-               ctx.vtable->start_cert(&ctx.vtable, certs[u].len);
-               v = 0;
-               while (v < certs[u].len) {
-                       size_t w;
-
-                       w = certs[u].len - v;
-                       if (w > 100) {
-                               w = 100;
-                       }
-                       ctx.vtable->append(&ctx.vtable, certs[u].data + v, w);
-                       v += w;
-               }
-               ctx.vtable->end_cert(&ctx.vtable);
-       }
+       ctx.vtable->start_chain(&ctx.vtable, NULL);
+       ctx.vtable->start_cert(&ctx.vtable, len);
+       ctx.vtable->append(&ctx.vtable, data, len);
+       ctx.vtable->end_cert(&ctx.vtable);
        status = ctx.vtable->end_chain(&ctx.vtable);
        status = ctx.vtable->end_chain(&ctx.vtable);
-       ee_pkey = ctx.vtable->get_pkey(&ctx.vtable);
-
-       /*
-        * Check results. Note that we may still get a public key if
-        * the path is "not trusted" (but otherwise fine).
-        */
-       if (status != tc->status) {
-               fprintf(stderr, "wrong status (got %d, expected %d)\n",
-                       status, tc->status);
-               exit(EXIT_FAILURE);
-       }
-       if (status == BR_ERR_X509_NOT_TRUSTED) {
-               ee_pkey = NULL;
-       }
-       if (!eqpkey(ee_pkey, ee_pkey_ref)) {
-               fprintf(stderr, "wrong EE public key\n");
+       if (status != BR_ERR_X509_NOT_TRUSTED) {
+               fprintf(stderr, "wrong status: %u\n", status);
                exit(EXIT_FAILURE);
        }
 
                exit(EXIT_FAILURE);
        }
 
@@ -1597,22 +1945,132 @@ run_test_case(test_case *tc)
                }
        }
 
                }
        }
 
+       good = 1;
+       for (u = 0; u < num_names; u ++) {
+               if (names[u].status != names_ref[u].status) {
+                       printf("ERR: name %u (id=%d): status=%d, expected=%d\n",
+                               (unsigned)u, names_ref[u].num,
+                               names[u].status, names_ref[u].status);
+                       if (names[u].status > 0) {
+                               unsigned char *p;
+
+                               printf("  obtained:");
+                               p = (unsigned char *)names[u].buf;
+                               while (*p) {
+                                       printf(" %02X", *p ++);
+                               }
+                               printf("\n");
+                       }
+                       good = 0;
+                       continue;
+               }
+               if (names_ref[u].expected == NULL) {
+                       if (names[u].buf[0] != 0) {
+                               printf("ERR: name %u not zero-terminated\n",
+                                       (unsigned)u);
+                               good = 0;
+                               continue;
+                       }
+               } else {
+                       if (strcmp(names[u].buf, names_ref[u].expected) != 0) {
+                               unsigned char *p;
+
+                               printf("ERR: name %u (id=%d): wrong value\n",
+                                       (unsigned)u, names_ref[u].num);
+                               printf("  expected:");
+                               p = (unsigned char *)names_ref[u].expected;
+                               while (*p) {
+                                       printf(" %02X", *p ++);
+                               }
+                               printf("\n");
+                               printf("  obtained:");
+                               p = (unsigned char *)names[u].buf;
+                               while (*p) {
+                                       printf(" %02X", *p ++);
+                               }
+                               printf("\n");
+                               good = 0;
+                               continue;
+                       }
+               }
+       }
+       if (!good) {
+               exit(EXIT_FAILURE);
+       }
+
        /*
        /*
-        * Release everything.
-        */
-       for (u = 0; u < num_certs; u ++) {
-               xfree(certs[u].data);
+       for (u = 0; u < num_names; u ++) {
+               printf("%u: (%d)", (unsigned)u, names[u].status);
+               if (names[u].status > 0) {
+                       size_t v;
+
+                       for (v = 0; names[u].buf[v]; v ++) {
+                               printf(" %02x", names[u].buf[v]);
+                       }
+               }
+               printf("\n");
        }
        }
-       xfree(certs);
-       xfree(anchors);
+       */
+
+       xfree(data);
+       free_name_elements(names, num_names);
        printf("OK\n");
 }
 
 int
        printf("OK\n");
 }
 
 int
-main(void)
+main(int argc, const char *argv[])
 {
        size_t u;
 
 {
        size_t u;
 
+#ifdef SRCDIRNAME
+       /*
+        * We want to change the current directory to that of the
+        * executable, so that test files are reliably located. We
+        * do that only if SRCDIRNAME is defined (old Makefile would
+        * not do that).
+        */
+       if (argc >= 1) {
+               const char *arg, *c;
+
+               arg = argv[0];
+               for (c = arg + strlen(arg);; c --) {
+                       int sep, r;
+
+#ifdef _WIN32
+                       sep = (*c == '/') || (*c == '\\');
+#else
+                       sep = (*c == '/');
+#endif
+                       if (sep) {
+                               size_t len;
+                               char *dn;
+
+                               len = 1 + (c - arg);
+                               dn = xmalloc(len + 1);
+                               memcpy(dn, arg, len);
+                               dn[len] = 0;
+#ifdef _WIN32
+                               r = _chdir(dn);
+#else
+                               r = chdir(dn);
+#endif
+                               if (r != 0) {
+                                       fprintf(stderr, "warning: could not"
+                                               " set directory to '%s'\n", dn);
+                               }
+                               xfree(dn);
+                               break;
+                       }
+                       if (c == arg) {
+                               break;
+                       }
+               }
+       }
+#else
+       (void)argc;
+       (void)argv;
+#endif
+
        process_conf_file(CONFFILE);
 
        max_dp_usage = 0;
        process_conf_file(CONFFILE);
 
        max_dp_usage = 0;
@@ -1620,6 +2078,7 @@ main(void)
        for (u = 0; u < all_chains_ptr; u ++) {
                run_test_case(&all_chains[u]);
        }
        for (u = 0; u < all_chains_ptr; u ++) {
                run_test_case(&all_chains[u]);
        }
+       test_name_extraction();
 
        printf("Maximum data stack usage:    %u\n", (unsigned)max_dp_usage);
        printf("Maximum return stack usage:  %u\n", (unsigned)max_rp_usage);
 
        printf("Maximum data stack usage:    %u\n", (unsigned)max_dp_usage);
        printf("Maximum return stack usage:  %u\n", (unsigned)max_rp_usage);
@@ -1630,5 +2089,6 @@ main(void)
                free_test_case_contents(&all_chains[u]);
        }
        xfree(all_chains);
                free_test_case_contents(&all_chains[u]);
        }
        xfree(all_chains);
+
        return 0;
 }
        return 0;
 }