root/regress/lib/libcrypto/asn1/asn1api.c
/* $OpenBSD: asn1api.c,v 1.3 2022/07/09 14:47:42 tb Exp $ */
/*
 * Copyright (c) 2021 Joel Sing <jsing@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 <openssl/asn1.h>
#include <openssl/err.h>

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

const long asn1_tag2bits[] = {
        [0] = 0,
        [1] = 0,
        [2] = 0,
        [3] = B_ASN1_BIT_STRING,
        [4] = B_ASN1_OCTET_STRING,
        [5] = 0,
        [6] = 0,
        [7] = B_ASN1_UNKNOWN,
        [8] = B_ASN1_UNKNOWN,
        [9] = B_ASN1_UNKNOWN,
        [10] = B_ASN1_UNKNOWN,
        [11] = B_ASN1_UNKNOWN,
        [12] = B_ASN1_UTF8STRING,
        [13] = B_ASN1_UNKNOWN,
        [14] = B_ASN1_UNKNOWN,
        [15] = B_ASN1_UNKNOWN,
        [16] = B_ASN1_SEQUENCE,
        [17] = 0,
        [18] = B_ASN1_NUMERICSTRING,
        [19] = B_ASN1_PRINTABLESTRING,
        [20] = B_ASN1_T61STRING,
        [21] = B_ASN1_VIDEOTEXSTRING,
        [22] = B_ASN1_IA5STRING,
        [23] = B_ASN1_UTCTIME,
        [24] = B_ASN1_GENERALIZEDTIME,
        [25] = B_ASN1_GRAPHICSTRING,
        [26] = B_ASN1_ISO64STRING,
        [27] = B_ASN1_GENERALSTRING,
        [28] = B_ASN1_UNIVERSALSTRING,
        [29] = B_ASN1_UNKNOWN,
        [30] = B_ASN1_BMPSTRING,
};

static int
asn1_tag2bit(void)
{
        int failed = 1;
        long bit;
        int i;

        for (i = -3; i <= V_ASN1_NEG + 30; i++) {
                bit = ASN1_tag2bit(i);
                if (i >= 0 && i <= 30) {
                        if (bit != asn1_tag2bits[i]) {
                                fprintf(stderr, "FAIL: ASN1_tag2bit(%d) = 0x%lx,"
                                    " want 0x%lx\n", i, bit, asn1_tag2bits[i]);
                                goto failed;
                        }
                } else {
                        if (bit != 0) {
                                fprintf(stderr, "FAIL: ASN1_tag2bit(%d) = 0x%lx,"
                                    " want 0x0\n", i, bit);
                                goto failed;
                        }
                }
        }

        failed = 0;

 failed:
        return failed;
}

static int
asn1_tag2str(void)
{
        int failed = 1;
        const char *s;
        int i;

        for (i = -3; i <= V_ASN1_NEG + 30; i++) {
                if ((s = ASN1_tag2str(i)) == NULL) {
                        fprintf(stderr, "FAIL: ASN1_tag2str(%d) returned "
                            "NULL\n", i);
                        goto failed;
                }
                if ((i >= 0 && i <= 30) || i == V_ASN1_NEG_INTEGER ||
                    i == V_ASN1_NEG_ENUMERATED) {
                        if (strcmp(s, "(unknown)") == 0) {
                                fprintf(stderr, "FAIL: ASN1_tag2str(%d) = '%s',"
                                    " want tag name\n", i, s);
                                goto failed;
                        }
                } else {
                        if (strcmp(s, "(unknown)") != 0) {
                                fprintf(stderr, "FAIL: ASN1_tag2str(%d) = '%s',"
                                    " want '(unknown')\n", i, s);
                                goto failed;
                        }
                }
        }

        failed = 0;

 failed:
        return failed;
}

struct asn1_get_object_test {
        const uint8_t asn1[64];
        size_t asn1_len;
        size_t asn1_hdr_len;
        int want_ret;
        long want_length;
        int want_tag;
        int want_class;
        int want_error;
};

const struct asn1_get_object_test asn1_get_object_tests[] = {
        {
                /* Zero tag and zero length (EOC). */
                .asn1 = {0x00, 0x00},
                .asn1_len = 2,
                .asn1_hdr_len = 2,
                .want_ret = 0x00,
                .want_length = 0,
                .want_tag = 0,
                .want_class = 0,
        },
        {
                /* Boolean with short form length. */
                .asn1 = {0x01, 0x01},
                .asn1_len = 3,
                .asn1_hdr_len = 2,
                .want_ret = 0x00,
                .want_length = 1,
                .want_tag = 1,
                .want_class = 0,
        },
        {
                /* Long form tag. */
                .asn1 = {0x1f, 0x7f, 0x01},
                .asn1_len = 3 + 128,
                .asn1_hdr_len = 3,
                .want_ret = 0x00,
                .want_length = 1,
                .want_tag = 127,
                .want_class = 0,
        },
        {
                /* Long form tag with class application. */
                .asn1 = {0x5f, 0x7f, 0x01},
                .asn1_len = 3 + 128,
                .asn1_hdr_len = 3,
                .want_ret = 0x00,
                .want_length = 1,
                .want_tag = 127,
                .want_class = 1 << 6,
        },
        {
                /* Long form tag with class context-specific. */
                .asn1 = {0x9f, 0x7f, 0x01},
                .asn1_len = 3 + 128,
                .asn1_hdr_len = 3,
                .want_ret = 0x00,
                .want_length = 1,
                .want_tag = 127,
                .want_class = 2 << 6,
        },
        {
                /* Long form tag with class private. */
                .asn1 = {0xdf, 0x7f, 0x01},
                .asn1_len = 3 + 128,
                .asn1_hdr_len = 3,
                .want_ret = 0x00,
                .want_length = 1,
                .want_tag = 127,
                .want_class = 3 << 6,
        },
        {
                /* Long form tag (maximum). */
                .asn1 = {0x1f, 0x87, 0xff, 0xff, 0xff, 0x7f, 0x01},
                .asn1_len = 8,
                .asn1_hdr_len = 7,
                .want_ret = 0x00,
                .want_length = 1,
                .want_tag = 0x7fffffff,
                .want_class = 0,
        },
        {
                /* Long form tag (maximum + 1). */
                .asn1 = {0x1f, 0x88, 0x80, 0x80, 0x80, 0x00, 0x01},
                .asn1_len = 8,
                .asn1_hdr_len = 7,
                .want_ret = 0x80,
                .want_error = ASN1_R_HEADER_TOO_LONG,
        },
        {
                /* OctetString with long form length. */
                .asn1 = {0x04, 0x81, 0x80},
                .asn1_len = 3 + 128,
                .asn1_hdr_len = 3,
                .want_ret = 0x00,
                .want_length = 128,
                .want_tag = 4,
                .want_class = 0,
        },
        {
                /* OctetString with long form length. */
                .asn1 = {0x04, 0x84, 0x7f, 0xff, 0xff, 0xf9},
                .asn1_len = 0x7fffffff,
                .asn1_hdr_len = 6,
                .want_ret = 0x00,
                .want_length = 0x7ffffff9,
                .want_tag = 4,
                .want_class = 0,
        },
        {
                /* Long form tag and long form length. */
                .asn1 = {0x1f, 0x87, 0xff, 0xff, 0xff, 0x7f, 0x84, 0x7f, 0xff, 0xff, 0xf4},
                .asn1_len = 0x7fffffff,
                .asn1_hdr_len = 11,
                .want_ret = 0x00,
                .want_length = 0x7ffffff4,
                .want_tag = 0x7fffffff,
                .want_class = 0,
        },
        {
                /* Constructed OctetString with definite length. */
                .asn1 = {0x24, 0x03},
                .asn1_len = 5,
                .asn1_hdr_len = 2,
                .want_ret = 0x20,
                .want_length = 3,
                .want_tag = 4,
                .want_class = 0,
        },
        {
                /* Constructed OctetString with indefinite length. */
                .asn1 = {0x24, 0x80},
                .asn1_len = 5,
                .asn1_hdr_len = 2,
                .want_ret = 0x21,
                .want_length = 0,
                .want_tag = 4,
                .want_class = 0,
        },
        {
                /* Boolean with indefinite length (invalid). */
                .asn1 = {0x01, 0x80},
                .asn1_len = 3,
                .want_ret = 0x80,
                .want_error = ASN1_R_HEADER_TOO_LONG,
        },
        {
                /* OctetString with insufficient data (only tag). */
                .asn1 = {0x04, 0x04},
                .asn1_len = 1,
                .want_ret = 0x80,
                .want_error = ASN1_R_HEADER_TOO_LONG,
        },
        {
                /* OctetString with insufficient data (missing content). */
                .asn1 = {0x04, 0x04},
                .asn1_len = 2,
                .asn1_hdr_len = 2,
                .want_ret = 0x80,
                .want_length = 4,
                .want_tag = 4,
                .want_class = 0,
                .want_error = ASN1_R_TOO_LONG,
        },
        {
                /* OctetString with insufficient data (partial content). */
                .asn1 = {0x04, 0x04},
                .asn1_len = 5,
                .asn1_hdr_len = 2,
                .want_ret = 0x80,
                .want_length = 4,
                .want_tag = 4,
                .want_class = 0,
                .want_error = ASN1_R_TOO_LONG,
        },
        {
                /* Constructed OctetString with insufficient data (only tag/len). */
                .asn1 = {0x24, 0x04},
                .asn1_len = 2,
                .asn1_hdr_len = 2,
                .want_ret = 0xa0,
                .want_length = 4,
                .want_tag = 4,
                .want_class = 0,
                .want_error = ASN1_R_TOO_LONG,
        },
};

#define N_ASN1_GET_OBJECT_TESTS \
    (sizeof(asn1_get_object_tests) / sizeof(*asn1_get_object_tests))

static int
asn1_get_object(void)
{
        const struct asn1_get_object_test *agot;
        const uint8_t *p;
        int ret, tag, tag_class;
        long err, length;
        size_t i;
        int failed = 1;

        for (i = 0; i < N_ASN1_GET_OBJECT_TESTS; i++) {
                agot = &asn1_get_object_tests[i];

                ERR_clear_error();

                p = agot->asn1;
                ret = ASN1_get_object(&p, &length, &tag, &tag_class, agot->asn1_len);

                if (ret != agot->want_ret) {
                        fprintf(stderr, "FAIL: %zu - got return value %x, want %x\n",
                            i, ret, agot->want_ret);
                        goto failed;
                }
                if (ret & 0x80) {
                        err = ERR_peek_error();
                        if (ERR_GET_REASON(err) != agot->want_error) {
                                fprintf(stderr, "FAIL: %zu - got error reason %d, "
                                    "want %d\n", i, ERR_GET_REASON(err),
                                    agot->want_error);
                                goto failed;
                        }
                        if (ERR_GET_REASON(err) == ASN1_R_HEADER_TOO_LONG) {
                                if (p != agot->asn1) {
                                        fprintf(stderr, "FAIL: %zu - got ber_in %p, "
                                            "want %p\n", i, p, agot->asn1);
                                        goto failed;
                                }
                                continue;
                        }
                }
                if (length != agot->want_length) {
                        fprintf(stderr, "FAIL: %zu - got length %ld, want %ld\n",
                            i, length, agot->want_length);
                        goto failed;
                }
                if (tag != agot->want_tag) {
                        fprintf(stderr, "FAIL: %zu - got tag %d, want %d\n",
                            i, tag, agot->want_tag);
                        goto failed;
                }
                if (tag_class != agot->want_class) {
                        fprintf(stderr, "FAIL: %zu - got class %d, want %d\n",
                            i, tag_class, agot->want_class);
                        goto failed;
                }
                if (p != agot->asn1 + agot->asn1_hdr_len) {
                        fprintf(stderr, "FAIL: %zu - got ber_in %p, want %p\n",
                            i, p, agot->asn1 + agot->asn1_len);
                        goto failed;
                }
        }

        failed = 0;

 failed:
        return failed;
}

static int
asn1_integer_get_null_test(void)
{
        int failed = 0;
        long ret;

        if ((ret = ASN1_INTEGER_get(NULL)) != 0) {
                fprintf(stderr, "FAIL: ASN1_INTEGER_get(NULL) %ld != 0\n", ret);
                failed |= 1;
        }

        if ((ret = ASN1_ENUMERATED_get(NULL)) != 0) {
                fprintf(stderr, "FAIL: ASN1_ENUMERATED_get(NULL) %ld != 0\n",
                    ret);
                failed |= 1;
        }

        return failed;
}

int
main(int argc, char **argv)
{
        int failed = 0;

        failed |= asn1_tag2bit();
        failed |= asn1_tag2str();
        failed |= asn1_get_object();
        failed |= asn1_integer_get_null_test();

        return (failed);
}