root/regress/lib/libcrypto/x509/x509_extensions_test.c
/*      $OpenBSD: x509_extensions_test.c,v 1.4 2025/12/31 17:04:22 tb Exp $ */

/*
 * Copyright (c) 2024 Theo Buehler <tb@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 <openssl/asn1.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

#define ASN1_BOOLEAN_TRUE       0xff
#define ASN1_BOOLEAN_FALSE      0x00

#define X509V3_EXT_CRITICAL     1
#define X509V3_EXT_NONCRITICAL  0

static BASIC_CONSTRAINTS *
create_basic_constraints(int ca)
{
        BASIC_CONSTRAINTS *bc;

        if ((bc = BASIC_CONSTRAINTS_new()) == NULL)
                errx(1, "BASIC_CONSTRAINTS_new");

        bc->ca = ca ? ASN1_BOOLEAN_TRUE : ASN1_BOOLEAN_FALSE;

        return bc;
}

static X509_EXTENSION *
ext_create_basic_constraints(int ca, int critical)
{
        X509_EXTENSION *ext;
        BASIC_CONSTRAINTS *bc;

        bc = create_basic_constraints(ca);
        if ((ext = X509V3_EXT_i2d(NID_basic_constraints, critical, bc)) == NULL)
                errx(1, "X509V3_EXT_i2d");
        BASIC_CONSTRAINTS_free(bc);

        return ext;
}

static int
test_x509v3_add1_i2d_empty_stack(STACK_OF(X509_EXTENSION) **extensions)
{
        unsigned long error;
        int op, got;
        int nid = NID_basic_constraints;
        int failed = 1;

        if (X509v3_get_ext_count(*extensions) != 0) {
                fprintf(stderr, "%s: FAIL: need empty stack\n", __func__);
                goto err;
        }

        ERR_clear_error();

        op = X509V3_ADD_REPLACE_EXISTING;

        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE_EXISTING "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }

        error = ERR_get_error();
        if (ERR_GET_REASON(error) != X509V3_R_EXTENSION_NOT_FOUND) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE_EXISTING "
                    "pushed %d for empty stack, want %d.\n", __func__,
                    ERR_GET_REASON(error), X509V3_R_EXTENSION_NOT_FOUND);
                goto err;
        }
        if ((error = ERR_get_error()) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE_EXISTING "
                    "expected exactly one error.\n", __func__);
                goto err;
        }

        op = X509V3_ADD_REPLACE_EXISTING | X509V3_ADD_SILENT;

        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 0) {
                fprintf(stderr, "%s: FAIL: silent X509V3_ADD_REPLACE_EXISTING "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }
        if ((error = ERR_get_error()) != 0) {
                fprintf(stderr, "%s: FAIL: silent X509V3_ADD_REPLACE_EXISTING "
                    "added error %d, want %d.\n", __func__,
                    ERR_GET_REASON(error), 0);
                goto err;
        }

        op = X509V3_ADD_DELETE;
        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }

        error = ERR_get_error();
        if (ERR_GET_REASON(error) != X509V3_R_EXTENSION_NOT_FOUND) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "pushed %d for empty stack, want %d.\n", __func__,
                    ERR_GET_REASON(error), X509V3_R_EXTENSION_NOT_FOUND);
                goto err;
        }

        if ((error = ERR_get_error()) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "expected exactly one error.\n", __func__);
                goto err;
        }

        failed = 0;

 err:

        return failed;
}

static int
test_x509v3_add1_i2d_single_nid(STACK_OF(X509_EXTENSION) **extensions)
{
        BASIC_CONSTRAINTS *bc = NULL;
        unsigned long error;
        int crit, got, nid, op;
        int failed = 1;

        if (X509v3_get_ext_count(*extensions) != 0) {
                fprintf(stderr, "%s: FAIL: need an empty stack.\n", __func__);
                goto err;
        }

        /*
         * Add basic ca constraints.
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(1);
        op = X509V3_ADD_DEFAULT;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, 1, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DEFAULT failed to add "
                    "basic constraints to empty stack: want %d, got %d.\n",
                    __func__, 1, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        if ((got = X509v3_get_ext_count(*extensions)) != 1) {
                fprintf(stderr, "%s: FAIL: expected 1 extension, have %d.\n",
                    __func__, got);
                goto err;
        }

        /*
         * Can't delete or replace non-existent extension.
         */

        nid = NID_policy_constraints;
        op = X509V3_ADD_DELETE;
        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE non-existent "
                    "want %d, got %d,\n", __func__, 0, got);
                goto err;
        }
        nid = NID_policy_constraints;
        op = X509V3_ADD_REPLACE_EXISTING;
        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE_EXISTING non-existent "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }

        /*
         * X509V3_ADD_DEFAULT refuses to add second basic constraints extension.
         */

        ERR_clear_error();

        nid = NID_basic_constraints;
        bc = create_basic_constraints(0);
        op = X509V3_ADD_DEFAULT;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, 1, op)) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DEFAULT second constraints "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        error = ERR_get_error();
        if (ERR_GET_REASON(error) != X509V3_R_EXTENSION_EXISTS) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DEFAULT second constraints "
                    " pushed %d, want %d.\n", __func__,
                    ERR_GET_REASON(error), X509V3_R_EXTENSION_EXISTS);
                goto err;
        }

        if ((got = X509v3_get_ext_count(*extensions)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DEFAULT second constraints "
                    "expected 1 extension, have %d.\n", __func__, got);
                goto err;
        }

        /*
         * We can replace existing basic constraints using X509V3_ADD_REPLACE.
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(0);
        op = X509V3_ADD_REPLACE;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, 1, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "want %d, got %d.\n", __func__, 1, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        if ((got = X509v3_get_ext_count(*extensions)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected 1 extension, have %d.\n", __func__, got);
                goto err;
        }

        /* Check that the extension was actually replaced. */
        nid = NID_basic_constraints;
        if ((bc = X509V3_get_d2i(*extensions, nid, &crit, NULL)) == NULL) {
                if (crit != -1)
                        errx(1, "X509V3_get_d2i");
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected basic constraints\n", __func__);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_FALSE) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected cA = false in basic constraints\n", __func__);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * X509V3_ADD_KEEP_EXISTING existing does what it is supposed to do
         * if basic constraints are already present.
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(1);
        op = X509V3_ADD_KEEP_EXISTING;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, 1, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_KEEP_EXISTING "
                    "want %d, got %d.\n", __func__, 1, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * Check we still have non-ca basic constraints.
         */

        nid = NID_basic_constraints;
        if ((bc = X509V3_get_d2i(*extensions, nid, &crit, NULL)) == NULL) {
                if (crit != -1)
                        errx(1, "X509V3_get_d2i");
                fprintf(stderr, "%s: FAIL: X509V3_ADD_KEEP_EXISTING "
                   "expected basic constraints\n", __func__);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_FALSE) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_KEEP_EXISTING "
                   "expected non-ca basic constraints\n", __func__);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * X509V3_ADD_REPLACE_EXISTING also works.
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(1);
        op = X509V3_ADD_REPLACE_EXISTING;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, 1, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE_EXISTING "
                    "want %d, got %d.\n", __func__, 1, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * Check we again have ca basic constraints.
         */

        nid = NID_basic_constraints;
        if ((bc = X509V3_get_d2i(*extensions, nid, &crit, NULL)) == NULL) {
                if (crit != -1)
                        errx(1, "X509V3_get_d2i");
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE_EXISTING "
                   "expected basic constraints\n", __func__);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_TRUE) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE_EXISTING "
                   "expected ca basic constraints\n", __func__);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * And X509V3_ADD_DELETE now works.
         */

        nid = NID_basic_constraints;
        op = X509V3_ADD_DELETE;
        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }

        if ((got = X509v3_get_ext_count(*extensions)) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "expected 0 extensions, have %d.\n", __func__, got);
                goto err;
        }

        /*
         * X509V3_ADD_REPLACE adds the extension to empty stack as it should.
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(0);
        op = X509V3_ADD_REPLACE;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, 1, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE on empty stack "
                    "want %d, got %d.\n", __func__, 1, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        if ((got = X509v3_get_ext_count(*extensions)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected 1 extension, have %d.\n", __func__, got);
                goto err;
        }

        /*
         * And X509V3_ADD_DELETE works again.
         */

        nid = NID_basic_constraints;
        op = X509V3_ADD_DELETE;
        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE after add replace "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }

        if ((got = X509v3_get_ext_count(*extensions)) != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "expected 0 extensions, have %d.\n", __func__, got);
                goto err;
        }

        failed = 0;

 err:
        BASIC_CONSTRAINTS_free(bc);

        return failed;
}

static int
test_x509v3_add1_i2d_add_append(STACK_OF(X509_EXTENSION) **extensions)
{
        BASIC_CONSTRAINTS *bc = NULL;
        int crit, got, idx, nid, op;
        int failed = 1;

        if (X509v3_get_ext_count(*extensions) != 0) {
                fprintf(stderr, "%s: FAIL: need empty stack.\n", __func__);
                goto err;
        }

        /*
         * Let the toolkit add two basic constraints extensions.
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(1);
        crit = 1;
        op = X509V3_ADD_APPEND;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, crit, op)) != 1) {
                fprintf(stderr, "%s: FAIL: first X509V3_ADD_APPEND "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        nid = NID_basic_constraints;
        bc = create_basic_constraints(0);
        crit = 1;
        op = X509V3_ADD_APPEND;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, crit, op)) != 1) {
                fprintf(stderr, "%s: FAIL: second X509V3_ADD_APPEND "
                    "want %d, got %d.\n", __func__, 0, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        if ((got = X509v3_get_ext_count(*extensions)) != 2) {
                fprintf(stderr, "%s: FAIL: second X509V3_ADD_APPEND "
                    "expected 2 extensions, have %d.\n", __func__, got);
                goto err;
        }

        /*
         * Inspect the extensions on the stack. First we should get the one
         * with the ca bit set and it should be critical.
         */

        nid = NID_basic_constraints;
        idx = -1;
        if ((bc = X509V3_get_d2i(*extensions, nid, &crit, &idx)) == NULL) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_APPEND "
                    "expected basic constraints.\n", __func__);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_TRUE) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_APPEND "
                    "expected ca basic constraints.\n", __func__);
                goto err;
        }
        if (crit != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_APPEND "
                    "expected critical basic constraints.\n", __func__);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /* Redo the exercise and get the basic constraints with ca bit unset. */
        nid = NID_basic_constraints;
        if ((bc = X509V3_get_d2i(*extensions, nid, &crit, &idx)) == NULL) {
                fprintf(stderr, "%s: FAIL: second X509V3_ADD_APPEND "
                    "expected basic constraints.\n", __func__);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_FALSE) {
                fprintf(stderr, "%s: FAIL: second X509V3_ADD_APPEND "
                    "expected basic constraints to be non-ca.\n", __func__);
                goto err;
        }
        if (crit != 1) {
                fprintf(stderr, "%s: FAIL: second X509V3_ADD_APPEND "
                    "expected critical basic constraints.\n", __func__);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * Now X509V3_ADD_REPLACE non-critical ca constraints. They should
         * replace the critical ca constraints we added before.
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(1);
        crit = 0;
        op = X509V3_ADD_REPLACE;
        if ((got = X509V3_add1_i2d(extensions, nid, bc, crit, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "want %d, got %d\n", __func__, 1, got);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * If we get basic constraints now, we get the non-critical one with the
         * ca bit set.
         */

        nid = NID_basic_constraints;
        idx = -1;
        if ((bc = X509V3_get_d2i(*extensions, nid, &crit, &idx)) == NULL) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected basic constraints.\n", __func__);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_TRUE) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected ca basic constraints.\n", __func__);
                goto err;
        }
        if (crit != 0) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected non-critical basic constraints.\n", __func__);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        if ((got = X509v3_get_ext_count(*extensions)) != 2) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_REPLACE "
                    "expected 2 extensions, got %d.\n", __func__, got);
                goto err;
        }

        nid = NID_basic_constraints;
        op = X509V3_ADD_DELETE;
        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "want %d, got %d\n", __func__, 1, got);
                goto err;
        }

        if ((got = X509v3_get_ext_count(*extensions)) != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "expected 1 extension, got %d.\n", __func__, got);
                goto err;
        }

        /* The last deletion will have left the critical non-ca constraints. */
        nid = NID_basic_constraints;
        idx = -1;
        if ((bc = X509V3_get_d2i(*extensions, nid, &crit, &idx)) == NULL) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "expected basic constraints.\n", __func__);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_FALSE) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "expected ca basic constraints.\n", __func__);
                goto err;
        }
        if (crit != 1) {
                fprintf(stderr, "%s: FAIL: X509V3_ADD_DELETE "
                    "expected critical basic constraints.\n", __func__);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /* Now delete the last extension. */
        nid = NID_basic_constraints;
        op = X509V3_ADD_DELETE;
        if ((got = X509V3_add1_i2d(extensions, nid, NULL, 0, op)) != 1) {
                fprintf(stderr, "%s: FAIL: second X509V3_ADD_DELETE "
                    "want %d, got %d\n", __func__, 1, got);
                goto err;
        }

        if ((got = X509v3_get_ext_count(*extensions)) != 0) {
                fprintf(stderr, "%s: FAIL: second X509V3_ADD_DELETE "
                    "expected 0 extensions, got %d.\n", __func__, got);
                goto err;
        }

        failed = 0;

 err:
        BASIC_CONSTRAINTS_free(bc);

        return failed;
}

static int
test_x509v3_add1_i2d_invalid_operations(STACK_OF(X509_EXTENSION) **extensions)
{
        BASIC_CONSTRAINTS *bc = NULL;
        long error;
        int crit, got, nid, op;
        int failed = 1;

        if (X509v3_get_ext_count(*extensions) != 0) {
                fprintf(stderr, "%s: FAIL: need empty stack.\n", __func__);
                goto err;
        }

        /*
         * Attempt to add a basic constraint extension with invalid operations
         */

        nid = NID_basic_constraints;
        bc = create_basic_constraints(1);
        crit = 1;
        for (op = X509V3_ADD_DELETE + 1; op <= X509V3_ADD_OP_MASK; op++) {
                if ((got = X509V3_add1_i2d(extensions, nid, bc, crit, op)) != -1) {
                        fprintf(stderr, "%s: FAIL: operation %d "
                            "want %d, got %d.\n", __func__, op, -1, got);
                        goto err;
                }
                error = ERR_get_error();
                if (ERR_GET_REASON(error) != X509V3_R_UNSUPPORTED_OPTION) {
                        fprintf(stderr, "%s: FAIL: invalid operation %d "
                            " pushed %d, want %d.\n", __func__, op,
                            ERR_GET_REASON(error), X509V3_R_EXTENSION_EXISTS);
                        goto err;
                }
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        if ((got = X509v3_get_ext_count(*extensions)) != 0) {
                fprintf(stderr, "%s: FAIL: expected 0 extensions, have %d.\n",
                    __func__, got);
                goto err;
        }

        failed = 0;

 err:
        BASIC_CONSTRAINTS_free(bc);

        return failed;
}

static int
test_x509v3_add1_i2d(void)
{
        STACK_OF(X509_EXTENSION) *extensions;
        int failed = 0;

        if ((extensions = sk_X509_EXTENSION_new_null()) == NULL)
                errx(1, "sk_X509_EXTENSION_new_null");

        failed |= test_x509v3_add1_i2d_empty_stack(&extensions);
        failed |= test_x509v3_add1_i2d_single_nid(&extensions);
        failed |= test_x509v3_add1_i2d_add_append(&extensions);
        failed |= test_x509v3_add1_i2d_invalid_operations(&extensions);

        sk_X509_EXTENSION_pop_free(extensions, X509_EXTENSION_free);

        return failed;
}

static int
test_x509v3_get_d2i_null(void)
{
        X509_EXTENSION *ext;
        int crit, idx;
        int failed = 1;

        if ((ext = X509V3_get_d2i(NULL, NID_undef, NULL, NULL)) != NULL) {
                fprintf(stderr, "FAIL: %s: expected X509V3_get_d2i with three "
                    "NULL arguments to return NULL\n", __func__);
                goto err;
        }

        idx = -5;
        if (X509V3_get_d2i(NULL, NID_undef, &crit, &idx) != NULL) {
                /* Leaks whatever garbage libcrypto decoded. What to do... */
                fprintf(stderr, "FAIL: %s: expected X509V3_get_d2i NULL stack"
                    "to return NULL\n", __func__);
                goto err;
        }

        if (crit != -1 || idx != -1) {
                fprintf(stderr, "FAIL: %s: crit: want: %d, got: %d; "
                    "idx: want: %d, got: %d\n", __func__, -1, crit, -1, idx);
                goto err;
        }

        failed = 0;

 err:
        X509_EXTENSION_free(ext);

        return failed;
}

static int
test_x509v3_get_d2i_multiple_basic_constraints(void)
{
        STACK_OF(X509_EXTENSION) *exts = NULL;
        ASN1_BIT_STRING *abs = NULL;
        BASIC_CONSTRAINTS *bc = NULL;
        X509_EXTENSION *ext;
        int crit, idx;
        int ca, nid;
        int failed = 1;

        /*
         * Create extension stack containing three basic constraints extensions:
         * 1. critical CA basic constraints,
         * 2. non-critical CA basic constraints,
         * 3. critical non-CA basic constraints.
         */

        if ((exts = sk_X509_EXTENSION_new_null()) == NULL)
                errx(1, "sk_X509_EXTENSION_new_null");

        ca = 1;
        ext = ext_create_basic_constraints(ca, X509V3_EXT_CRITICAL);

        if (sk_X509_EXTENSION_push(exts, ext) <= 0)
                errx(1, "sk_X509_EXTENSION_push");
        ext = NULL;

        ca = 1;
        ext = ext_create_basic_constraints(ca, X509V3_EXT_NONCRITICAL);

        if (sk_X509_EXTENSION_push(exts, ext) <= 0)
                errx(1, "sk_X509_EXTENSION_push");
        ext = NULL;

        ca = 0;
        ext = ext_create_basic_constraints(ca, X509V3_EXT_CRITICAL);

        if (sk_X509_EXTENSION_push(exts, ext) <= 0)
                errx(1, "sk_X509_EXTENSION_push");
        ext = NULL;

        /*
         * There is no key usage in this stack, so we shouldn't find any.
         */

        nid = NID_key_usage;
        if ((abs = X509V3_get_d2i(exts, nid, &crit, NULL)) != NULL) {
                fprintf(stderr, "FAIL: %s: found key usage extension\n",
                    __func__);
                goto err;
        }
        if (crit != -1) {
                fprintf(stderr, "FAIL: %s: key usage: crit: want %d, got %d\n",
                    __func__, -1, crit);
                goto err;
        }

        /*
         * If we pass no idx and look for basic constraints,
         * we should fail with crit == -2.
         */

        nid = NID_basic_constraints;
        if ((bc = X509V3_get_d2i(exts, nid, &crit, NULL)) != NULL) {
                fprintf(stderr, "FAIL: %s (NULL idx): did not expect to find "
                    "basic constraints\n", __func__);
                goto err;
        }
        if (crit != -2) {
                fprintf(stderr, "FAIL: %s: basic constraints, no idx: \n"
                    "crit: want %d, got %d\n", __func__, -2, crit);
                goto err;
        }

        /*
         * If we pass idx = -1 and look for basic constraints, we should find
         * the first one: it is critical at idx = 0, with ca bit set to true.
         */

        nid = NID_basic_constraints;
        idx = -1;
        if ((bc = X509V3_get_d2i(exts, nid, &crit, &idx)) == NULL) {
                fprintf(stderr, "FAIL: %s (idx %d): expected to find"
                    "basic constraints\n", __func__, -1);
                goto err;
        }
        if (crit != 1) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "crit: want %d, got %d\n", __func__, -1, 1, crit);
                goto err;
        }
        if (idx != 0) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "idx: want %d, got %d\n", __func__, -1, 0, idx);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_TRUE) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "cA bit: want %x, got %x\n", __func__, -1,
                    ASN1_BOOLEAN_TRUE, bc->ca);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * Now pass idx = 0 and look for basic constraints, we should find
         * the second one: non-critical at idx = 1, with ca bit set to true.
         */

        nid = NID_basic_constraints;
        idx = 0;
        if ((bc = X509V3_get_d2i(exts, nid, &crit, &idx)) == NULL) {
                fprintf(stderr, "FAIL: %s (idx %d): expected to find"
                    "basic constraints\n", __func__, 0);
                goto err;
        }
        if (crit != 0) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "crit: want %d, got %d\n", __func__, 0, 0, crit);
                goto err;
        }
        if (idx != 1) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "idx: want %d, got %d\n", __func__, 0, 1, idx);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_TRUE) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "cA bit: want %x, got %x\n", __func__, 0,
                    ASN1_BOOLEAN_TRUE, bc->ca);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * Now pass idx = 1 and look for basic constraints, we should find the
         * third one: critical at idx = 2, with ca bit set to false.
         */

        nid = NID_basic_constraints;
        idx = 1;
        if ((bc = X509V3_get_d2i(exts, nid, &crit, &idx)) == NULL) {
                fprintf(stderr, "FAIL: %s (idx %d): expected to find"
                    "basic constraints\n", __func__, 1);
                goto err;
        }
        if (crit != 1) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "crit: want %d, got %d\n", __func__, 1, 0, crit);
                goto err;
        }
        if (idx != 2) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "idx: want %d, got %d\n", __func__, 1, 2, idx);
                goto err;
        }
        if (bc->ca != ASN1_BOOLEAN_FALSE) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "cA bit: want %x, got %x\n", __func__, 1,
                    ASN1_BOOLEAN_FALSE, bc->ca);
                goto err;
        }
        BASIC_CONSTRAINTS_free(bc);
        bc = NULL;

        /*
         * Finally, pass idx = 2 and we should find no basic constraints.
         */

        nid = NID_basic_constraints;
        idx = 2;
        if ((bc = X509V3_get_d2i(exts, nid, &crit, &idx)) != NULL) {
                fprintf(stderr, "FAIL: %s (idx %d): expected to find"
                    "no basic constraints\n", __func__, 2);
                goto err;
        }
        if (crit != -1) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "crit: want %d, got %d\n", __func__, 2, -1, crit);
                goto err;
        }
        if (idx != -1) {
                fprintf(stderr, "FAIL: %s: basic constraints (idx %d): "
                    "idx: want %d, got %d\n", __func__, 2, -1, idx);
                goto err;
        }

        failed = 0;

 err:
        sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
        ASN1_BIT_STRING_free(abs);
        BASIC_CONSTRAINTS_free(bc);

        return failed;
}

static int
test_x509v3_get_d2i(void)
{
        int failed = 0;

        failed |= test_x509v3_get_d2i_null();
        failed |= test_x509v3_get_d2i_multiple_basic_constraints();

        return failed;
}

int
main(void)
{
        int failed = 0;

        failed |= test_x509v3_add1_i2d();
        failed |= test_x509v3_get_d2i();

        return failed;
}