root/regress/lib/libcrypto/x509/x509_name_test.c
/*      $OpenBSD: x509_name_test.c,v 1.3 2025/05/05 06:33:34 tb Exp $ */

/*
 * Copyright (c) 2025 Theo Buehler <tb@openbsd.org>
 * Copyright (c) 2025 Kenjiro Nakayama <nakayamakenjiro@gmail.com>
 * Copyright (c) 2018 Ingo Schwarze <schwarze@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <err.h>
#include <stdio.h>
#include <string.h>

#include <openssl/x509.h>

static const struct x509_name_legacy {
        const char      *compat;
        const char      *oneline;
        const uint8_t    der[255];
        size_t           der_len;
} x509_name_legacy_test[] = {
        {
                .compat =
                    "C=HU, "
                    "L=Budapest, "
                    "O=Microsec Ltd., "
                    "CN=Microsec e-Szigno Root CA 2009, "
                    "emailAddress=info@e-szigno.hu",
                .oneline =
                    "/C=HU"
                    "/L=Budapest"
                    "/O=Microsec Ltd."
                    "/CN=Microsec e-Szigno Root CA 2009"
                    "/emailAddress=info@e-szigno.hu",
                .der = {
                        0x30, 0x81, 0x82, 0x31, 0x0b, 0x30, 0x09, 0x06,
                        0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x48, 0x55,
                        0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04,
                        0x07, 0x0c, 0x08, 0x42, 0x75, 0x64, 0x61, 0x70,
                        0x65, 0x73, 0x74, 0x31, 0x16, 0x30, 0x14, 0x06,
                        0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0d, 0x4d, 0x69,
                        0x63, 0x72, 0x6f, 0x73, 0x65, 0x63, 0x20, 0x4c,
                        0x74, 0x64, 0x2e, 0x31, 0x27, 0x30, 0x25, 0x06,
                        0x03, 0x55, 0x04, 0x03, 0x0c, 0x1e, 0x4d, 0x69,
                        0x63, 0x72, 0x6f, 0x73, 0x65, 0x63, 0x20, 0x65,
                        0x2d, 0x53, 0x7a, 0x69, 0x67, 0x6e, 0x6f, 0x20,
                        0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20,
                        0x32, 0x30, 0x30, 0x39, 0x31, 0x1f, 0x30, 0x1d,
                        0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
                        0x01, 0x09, 0x01, 0x16, 0x10, 0x69, 0x6e, 0x66,
                        0x6f, 0x40, 0x65, 0x2d, 0x73, 0x7a, 0x69, 0x67,
                        0x6e, 0x6f, 0x2e, 0x68, 0x75,
                },
                .der_len = 133,
        },

        {
                .compat =
                    "serialNumber=G63287510, "
                    "C=ES, "
                    "O=ANF Autoridad de Certificacion, "
                    "OU=ANF CA Raiz, "
                    "CN=ANF Secure Server Root CA",
                .oneline =
                    "/serialNumber=G63287510"
                    "/C=ES"
                    "/O=ANF Autoridad de Certificacion"
                    "/OU=ANF CA Raiz"
                    "/CN=ANF Secure Server Root CA",
                .der = {
                        0x30, 0x81, 0x84, 0x31, 0x12, 0x30, 0x10, 0x06,
                        0x03, 0x55, 0x04, 0x05, 0x13, 0x09, 0x47, 0x36,
                        0x33, 0x32, 0x38, 0x37, 0x35, 0x31, 0x30, 0x31,
                        0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
                        0x13, 0x02, 0x45, 0x53, 0x31, 0x27, 0x30, 0x25,
                        0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1e, 0x41,
                        0x4e, 0x46, 0x20, 0x41, 0x75, 0x74, 0x6f, 0x72,
                        0x69, 0x64, 0x61, 0x64, 0x20, 0x64, 0x65, 0x20,
                        0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
                        0x61, 0x63, 0x69, 0x6f, 0x6e, 0x31, 0x14, 0x30,
                        0x12, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0b,
                        0x41, 0x4e, 0x46, 0x20, 0x43, 0x41, 0x20, 0x52,
                        0x61, 0x69, 0x7a, 0x31, 0x22, 0x30, 0x20, 0x06,
                        0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x41, 0x4e,
                        0x46, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65,
                        0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20,
                        0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
                },
                .der_len = 135,
        },

        {
                .compat =
                    "C=GB, "
                    "ST=Greater Manchester, "
                    "L=Salford, "
                    "O=COMODO CA Limited, "
                    "CN=COMODO Certification Authority",
                .oneline =
                    "/C=GB"
                    "/ST=Greater Manchester"
                    "/L=Salford"
                    "/O=COMODO CA Limited"
                    "/CN=COMODO Certification Authority",
                .der = {
                        0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06,
                        0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42,
                        0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04,
                        0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74,
                        0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68,
                        0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30,
                        0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07,
                        0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31,
                        0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a,
                        0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f,
                        0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69,
                        0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06,
                        0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f,
                        0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x65, 0x72,
                        0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
                        0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
                        0x72, 0x69, 0x74, 0x79,
                },
                .der_len = 132,
        },

        {
                .compat =
                    "C=HU, "
                    "L=Budapest, "
                    "O=Microsec Ltd., "
                    "2.5.4.97=VATHU-23584497, "
                    "CN=e-Szigno Root CA 2017",
                .oneline =
                    "/C=HU"
                    "/L=Budapest"
                    "/O=Microsec Ltd."
                    "/2.5.4.97=VATHU-23584497"
                    "/CN=e-Szigno Root CA 2017",
                .der = {
                        0x30, 0x71, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
                        0x55, 0x04, 0x06, 0x13, 0x02, 0x48, 0x55, 0x31,
                        0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07,
                        0x0c, 0x08, 0x42, 0x75, 0x64, 0x61, 0x70, 0x65,
                        0x73, 0x74, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03,
                        0x55, 0x04, 0x0a, 0x0c, 0x0d, 0x4d, 0x69, 0x63,
                        0x72, 0x6f, 0x73, 0x65, 0x63, 0x20, 0x4c, 0x74,
                        0x64, 0x2e, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03,
                        0x55, 0x04, 0x61, 0x0c, 0x0e, 0x56, 0x41, 0x54,
                        0x48, 0x55, 0x2d, 0x32, 0x33, 0x35, 0x38, 0x34,
                        0x34, 0x39, 0x37, 0x31, 0x1e, 0x30, 0x1c, 0x06,
                        0x03, 0x55, 0x04, 0x03, 0x0c, 0x15, 0x65, 0x2d,
                        0x53, 0x7a, 0x69, 0x67, 0x6e, 0x6f, 0x20, 0x52,
                        0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x32,
                        0x30, 0x31, 0x37,
                },
                .der_len = 115,
        },

        {

                .compat =
                    "C=ES, "
                    "O=FNMT-RCM, "
                    "OU=Ceres, "
                    "2.5.4.97=VATES-Q2826004J, "
                    "CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS",
                .oneline =
                    "/C=ES"
                    "/O=FNMT-RCM"
                    "/OU=Ceres"
                    "/2.5.4.97=VATES-Q2826004J"
                    "/CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS",
                .der = {
                        0x30, 0x78, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
                        0x55, 0x04, 0x06, 0x13, 0x02, 0x45, 0x53, 0x31,
                        0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a,
                        0x0c, 0x08, 0x46, 0x4e, 0x4d, 0x54, 0x2d, 0x52,
                        0x43, 0x4d, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03,
                        0x55, 0x04, 0x0b, 0x0c, 0x05, 0x43, 0x65, 0x72,
                        0x65, 0x73, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03,
                        0x55, 0x04, 0x61, 0x0c, 0x0f, 0x56, 0x41, 0x54,
                        0x45, 0x53, 0x2d, 0x51, 0x32, 0x38, 0x32, 0x36,
                        0x30, 0x30, 0x34, 0x4a, 0x31, 0x2c, 0x30, 0x2a,
                        0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x23, 0x41,
                        0x43, 0x20, 0x52, 0x41, 0x49, 0x5a, 0x20, 0x46,
                        0x4e, 0x4d, 0x54, 0x2d, 0x52, 0x43, 0x4d, 0x20,
                        0x53, 0x45, 0x52, 0x56, 0x49, 0x44, 0x4f, 0x52,
                        0x45, 0x53, 0x20, 0x53, 0x45, 0x47, 0x55, 0x52,
                        0x4f, 0x53
                },
                .der_len = 122,
        },
};

#define N_X509_NAME_COMPAT \
        (sizeof(x509_name_legacy_test) / sizeof(x509_name_legacy_test[0]))

static int
x509_name_compat_testcase(const struct x509_name_legacy *test)
{
        const uint8_t *p;
        X509_NAME *name = NULL;
        unsigned char *der = NULL;
        int der_len = 0;
        BIO *bio = NULL;
        char *got;
        int got_len;
        char *buf = NULL;
        int failed = 1;

        p = test->der;
        if ((name = d2i_X509_NAME(NULL, &p, test->der_len)) == NULL)
                errx(1, "d2i_X509_NAME");

        if ((der_len = i2d_X509_NAME(name, &der)) <= 0) {
                fprintf(stderr, "FAIL: %s: i2d_X509_NAME", __func__);
                der_len = 0;
                goto err;
        }

        if (test->der_len != (size_t)der_len) {
                fprintf(stderr, "FAIL: %s: der len: want %zu, got %d\n",
                    __func__, test->der_len, der_len);
                goto err;
        }

        if (memcmp(test->der, der, test->der_len) != 0) {
                fprintf(stderr, "FAIL: %s: DER mismatch\n", __func__);
                goto err;
        }

        if ((bio = BIO_new(BIO_s_mem())) == NULL)
                errx(1, "BIO_new");

        if (!X509_NAME_print_ex(bio, name, 0, XN_FLAG_COMPAT)) {
                fprintf(stderr, "FAIL: %s: X509_NAME_print_ex", __func__);
                goto err;
        }

        if ((got_len = BIO_get_mem_data(bio, &got)) < 0)
                errx(1, "BIO_get_mem_data");

        if (strcmp(test->compat, got) != 0) {
                fprintf(stderr, "FAIL: %s compat:\nwant: \"%s\",\ngot:  \"%s\"\n",
                    __func__, test->compat, got);
                goto err;
        }

        if ((buf = X509_NAME_oneline(name, NULL, 0)) == NULL)
                errx(1, "X509_NAME_oneline");

        if (strcmp(test->oneline, buf) != 0) {
                fprintf(stderr, "FAIL: %s oneline:\nwant: \"%s\",\ngot:  \"%s\"\n",
                    __func__, test->compat, got);
                goto err;
        }

        failed = 0;

 err:
        BIO_free(bio);
        free(buf);
        X509_NAME_free(name);
        freezero(der, der_len);

        return failed;
}

static int
x509_name_compat_test(void)
{
        size_t i;
        int failed = 0;

        for (i = 0; i < N_X509_NAME_COMPAT; i++)
                failed |= x509_name_compat_testcase(&x509_name_legacy_test[i]);

        return failed;
}

static const struct x509_name_entry_test {
        const char *field;
        const char *value;
        int loc;
        int set;
        const char *expected_str;
        const int expected_set[4];
        const int expected_count;
} entry_tests[] = {
        {
                .field = "ST",
                .value = "BaWue",
                .loc = -1,
                .set = 0,
                .expected_str = "ST=BaWue",
                .expected_set = { 0 },
                .expected_count = 1,
        },
        {
                .field = "O",
                .value = "KIT",
                .loc = -1,
                .set = 0,
                .expected_str = "ST=BaWue, O=KIT",
                .expected_set = { 0, 1 },
                .expected_count = 2,
        },
        {
                .field = "L",
                .value = "Karlsruhe",
                .loc = 1,
                .set = 0,
                .expected_str = "ST=BaWue, L=Karlsruhe, O=KIT",
                .expected_set = { 0, 1, 2 },
                .expected_count = 3,
        },
        {
                .field = "C",
                .value = "DE",
                .loc = 0,
                .set = 1,
                .expected_str = "C=DE + ST=BaWue, L=Karlsruhe, O=KIT",
                .expected_set = { 0, 0, 1, 2 },
                .expected_count = 4,
        },
};

#define N_ENTRY_TESTS (sizeof(entry_tests) / sizeof(entry_tests[0]))

static int
verify_x509_name_output(X509_NAME *name, const struct x509_name_entry_test *tc)
{
        BIO *bio;
        char *got;
        long got_len;
        int loc, ret;
        int failed = 1;

        if ((bio = BIO_new(BIO_s_mem())) == NULL)
                goto fail;

        if ((ret = X509_NAME_print_ex(bio, name, 0, XN_FLAG_SEP_CPLUS_SPC)) == -1)
                goto fail;

        if ((got_len = BIO_get_mem_data(bio, &got)) < 0)
                goto fail;

        if (ret != got_len || strlen(tc->expected_str) != (size_t)ret)
                goto fail;

        if (strncmp(tc->expected_str, got, got_len) != 0)
                goto fail;

        if (X509_NAME_entry_count(name) != tc->expected_count)
                goto fail;

        for (loc = 0; loc < X509_NAME_entry_count(name); loc++) {
                X509_NAME_ENTRY *e = X509_NAME_get_entry(name, loc);
                if (e == NULL || X509_NAME_ENTRY_set(e) != tc->expected_set[loc])
                        goto fail;
        }

        failed = 0;

 fail:
        BIO_free(bio);

        return failed;
}

static int
x509_name_add_entry_test(void)
{
        X509_NAME *name;
        int failed = 1;

        if ((name = X509_NAME_new()) == NULL)
                goto done;

        for (size_t i = 0; i < N_ENTRY_TESTS; i++) {
                const struct x509_name_entry_test *t = &entry_tests[i];

                if (!X509_NAME_add_entry_by_txt(name, t->field, MBSTRING_ASC,
                    (const unsigned char *)t->value, -1, t->loc, t->set))
                        goto done;

                if (verify_x509_name_output(name, t))
                        goto done;
        }

        failed = 0;

 done:
        X509_NAME_free(name);

        return failed;
}

int
main(void)
{
        int failed = 0;

        failed |= x509_name_compat_test();
        failed |= x509_name_add_entry_test();

        return failed;
}