2023-09-03 18:11:42 -07:00

496 lines
11 KiB
C

/* $OpenBSD: asn1object.c,v 1.10 2022/11/26 16:08:56 tb Exp $ */
/*
* Copyright (c) 2017, 2021, 2022 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>
#include "asn1_local.h"
static void
hexdump(const unsigned char *buf, size_t len)
{
size_t i;
for (i = 1; i <= len; i++)
fprintf(stderr, " 0x%02hhx,%s", buf[i - 1], i % 8 ? "" : "\n");
fprintf(stderr, "\n");
}
static int
asn1_compare_bytes(const char *label, const unsigned char *d1, int len1,
const unsigned char *d2, int len2)
{
if (len1 != len2) {
fprintf(stderr, "FAIL: %s - byte lengths differ "
"(%d != %d)\n", label, len1, len2);
fprintf(stderr, "Got:\n");
hexdump(d1, len1);
fprintf(stderr, "Want:\n");
hexdump(d2, len2);
return 0;
}
if (memcmp(d1, d2, len1) != 0) {
fprintf(stderr, "FAIL: %s - bytes differ\n", label);
fprintf(stderr, "Got:\n");
hexdump(d1, len1);
fprintf(stderr, "Want:\n");
hexdump(d2, len2);
return 0;
}
return 1;
}
struct asn1_object_test {
const char *oid;
const char *txt;
const uint8_t content[255];
size_t content_len;
const uint8_t der[255];
size_t der_len;
int want_error;
};
struct asn1_object_test asn1_object_tests[] = {
{
.oid = "2.5",
.txt = "directory services (X.500)",
.content = {
0x55,
},
.content_len = 1,
.der = {
0x06, 0x01, 0x55,
},
.der_len = 3,
},
{
.oid = "2.5.4",
.txt = "X509",
.content = {
0x55, 0x04,
},
.content_len = 2,
.der = {
0x06, 0x02, 0x55, 0x04,
},
.der_len = 4,
},
{
.oid = "2.5.4.10",
.txt = "organizationName",
.content = {
0x55, 0x04, 0x0a,
},
.content_len = 3,
.der = {
0x06, 0x03, 0x55, 0x04, 0x0a,
},
.der_len = 5,
},
{
.oid = "2 5 4 10",
.txt = "organizationName",
.content = {
0x55, 0x04, 0x0a,
},
.content_len = 3,
.der = {
0x06, 0x03, 0x55, 0x04, 0x0a,
},
.der_len = 5,
},
{
.oid = "2.5.0.0",
.txt = "2.5.0.0",
.content = {
0x55, 0x00, 0x00,
},
.content_len = 3,
.der = {
0x06, 0x03, 0x55, 0x00, 0x00,
},
.der_len = 5,
},
{
.oid = "0.0.0.0",
.txt = "0.0.0.0",
.content = {
0x00, 0x00, 0x00,
},
.content_len = 3,
.der = {
0x06, 0x03, 0x00, 0x00, 0x00,
},
.der_len = 5,
},
{
.oid = "1.3.6.1.4.1.11129.2.4.5",
.txt = "CT Certificate SCTs",
.content = {
0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02,
0x04, 0x05,
},
.content_len = 10,
.der = {
0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6,
0x79, 0x02, 0x04, 0x05,
},
.der_len = 12,
},
{
.oid = "2.00005.0000000000004.10",
.want_error = ASN1_R_INVALID_NUMBER,
},
{
.oid = "2..5.4.10",
.want_error = ASN1_R_INVALID_NUMBER,
},
{
.oid = "2.5..4.10",
.want_error = ASN1_R_INVALID_NUMBER,
},
{
.oid = "2.5.4..10",
.want_error = ASN1_R_INVALID_NUMBER,
},
{
.oid = "2.5.4.10.",
.want_error = ASN1_R_INVALID_NUMBER,
},
{
.oid = "3.5.4.10",
.want_error = ASN1_R_FIRST_NUM_TOO_LARGE,
},
{
.oid = "0.40.4.10",
.want_error = ASN1_R_SECOND_NUMBER_TOO_LARGE,
},
{
.oid = "1.40.4.10",
.want_error = ASN1_R_SECOND_NUMBER_TOO_LARGE,
},
{
.oid = "2",
.want_error = ASN1_R_MISSING_SECOND_NUMBER,
},
{
.oid = "2.5 4.10",
.want_error = ASN1_R_INVALID_SEPARATOR,
},
{
.oid = "2,5,4,10",
.want_error = ASN1_R_INVALID_SEPARATOR,
},
{
.oid = "2.5,4.10",
.want_error = ASN1_R_INVALID_DIGIT,
},
{
.oid = "2a.5.4.10",
.want_error = ASN1_R_INVALID_SEPARATOR,
},
{
.oid = "2.5a.4.10",
.want_error = ASN1_R_INVALID_DIGIT,
},
};
#define N_ASN1_OBJECT_TESTS \
(sizeof(asn1_object_tests) / sizeof(*asn1_object_tests))
static int
do_asn1_object_test(struct asn1_object_test *aot)
{
ASN1_OBJECT *aobj = NULL;
uint8_t buf[1024];
const uint8_t *p;
uint8_t *q;
int err, ret;
int failed = 1;
ERR_clear_error();
ret = a2d_ASN1_OBJECT(NULL, 0, aot->oid, -1);
if (ret < 0 || (size_t)ret != aot->content_len) {
fprintf(stderr, "FAIL: a2d_ASN1_OBJECT('%s') = %d, want %zu\n",
aot->oid, ret, aot->content_len);
goto failed;
}
ret = a2d_ASN1_OBJECT(buf, sizeof(buf), aot->oid, -1);
if (ret < 0 || (size_t)ret != aot->content_len) {
fprintf(stderr, "FAIL: a2d_ASN1_OBJECT('%s') = %d, want %zu\n",
aot->oid, ret, aot->content_len);
goto failed;
}
if (aot->content_len == 0) {
err = ERR_peek_error();
if (ERR_GET_REASON(err) != aot->want_error) {
fprintf(stderr, "FAIL: a2d_ASN1_OBJECT('%s') - got "
"error reason %d, want %d\n", aot->oid,
ERR_GET_REASON(err), aot->want_error);
goto failed;
}
goto done;
}
if (!asn1_compare_bytes("ASN1_OBJECT content", buf, ret, aot->content,
aot->content_len))
goto failed;
p = aot->content;
if ((aobj = c2i_ASN1_OBJECT(NULL, &p, aot->content_len)) == NULL) {
fprintf(stderr, "FAIL: c2i_ASN1_OBJECT() failed\n");
goto failed;
}
q = buf;
ret = i2d_ASN1_OBJECT(aobj, &q);
if (!asn1_compare_bytes("ASN1_OBJECT DER", buf, ret, aot->der,
aot->der_len))
goto failed;
ASN1_OBJECT_free(aobj);
aobj = NULL;
p = aot->der;
if ((aobj = d2i_ASN1_OBJECT(NULL, &p, aot->der_len)) == NULL) {
fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() failed\n");
goto failed;
}
if (p != aot->der + aot->der_len) {
fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() p = %p, want %p\n",
p, aot->der + aot->der_len);
goto failed;
}
if (aot->txt != NULL) {
ret = i2t_ASN1_OBJECT(buf, sizeof(buf), aobj);
if (ret <= 0 || (size_t)ret >= sizeof(buf)) {
fprintf(stderr, "FAIL: i2t_ASN1_OBJECT() failed\n");
goto failed;
}
if (strcmp(aot->txt, buf) != 0) {
fprintf(stderr, "FAIL: i2t_ASN1_OBJECT() = '%s', "
"want '%s'\n", buf, aot->txt);
goto failed;
}
}
done:
failed = 0;
failed:
ASN1_OBJECT_free(aobj);
return failed;
}
static int
asn1_object_test(void)
{
int failed = 0;
size_t i;
for (i = 0; i < N_ASN1_OBJECT_TESTS; i++)
failed |= do_asn1_object_test(&asn1_object_tests[i]);
return failed;
}
const uint8_t asn1_object_bad_content1[] = {
0x55, 0x80, 0x04, 0x0a,
};
const uint8_t asn1_object_bad_content2[] = {
0x55, 0x04, 0x8a,
};
static int
asn1_object_bad_content_test(void)
{
ASN1_OBJECT *aobj = NULL;
const uint8_t *p;
size_t len;
int failed = 1;
p = asn1_object_bad_content1;
len = sizeof(asn1_object_bad_content1);
if ((aobj = c2i_ASN1_OBJECT(NULL, &p, len)) != NULL) {
fprintf(stderr, "FAIL: c2i_ASN1_OBJECT() succeeded with bad "
"content 1\n");
goto failed;
}
p = asn1_object_bad_content2;
len = sizeof(asn1_object_bad_content2);
if ((aobj = c2i_ASN1_OBJECT(NULL, &p, len)) != NULL) {
fprintf(stderr, "FAIL: c2i_ASN1_OBJECT() succeeded with bad "
"content 2\n");
goto failed;
}
failed = 0;
failed:
ASN1_OBJECT_free(aobj);
return failed;
}
static int
asn1_object_txt_test(void)
{
const char *obj_txt = "organizationName";
ASN1_OBJECT *aobj = NULL;
uint8_t small_buf[2];
const uint8_t *p;
int err, len, ret;
BIO *bio = NULL;
char *data;
long data_len;
int failed = 1;
ERR_clear_error();
ret = a2d_ASN1_OBJECT(small_buf, sizeof(small_buf), "1.2.3.4", -1);
if (ret != 0) {
fprintf(stderr, "FAIL: a2d_ASN1_OBJECT() with small buffer "
"returned %d, want %d\n", ret, 0);
goto failed;
}
err = ERR_peek_error();
if (ERR_GET_REASON(err) != ASN1_R_BUFFER_TOO_SMALL) {
fprintf(stderr, "FAIL: Got error reason %d, want %d\n",
ERR_GET_REASON(err), ASN1_R_BUFFER_TOO_SMALL);
goto failed;
}
p = &asn1_object_tests[2].der[0];
len = asn1_object_tests[2].der_len;
aobj = d2i_ASN1_OBJECT(NULL, &p, len);
if (aobj == NULL) {
fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() failed\n");
goto failed;
}
ret = i2t_ASN1_OBJECT(small_buf, sizeof(small_buf), aobj);
if (ret < 0 || (unsigned long)ret != strlen(obj_txt)) {
fprintf(stderr, "FAIL: i2t_ASN1_OBJECT() with small buffer "
"returned %d, want %zu\n", ret, strlen(obj_txt));
goto failed;
}
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
fprintf(stderr, "FAIL: BIO_new() returned NULL\n");
goto failed;
}
ret = i2a_ASN1_OBJECT(bio, NULL);
if (ret != 4) {
fprintf(stderr, "FAIL: i2a_ASN1_OBJECT(_, NULL) returned %d, "
"want 4\n", ret);
goto failed;
}
data_len = BIO_get_mem_data(bio, &data);
if (ret != data_len || memcmp("NULL", data, data_len) != 0) {
fprintf(stderr, "FAIL: i2a_ASN1_OBJECT(_, NULL) did not return "
"'NULL'\n");
goto failed;
}
if ((ret = BIO_reset(bio)) <= 0) {
fprintf(stderr, "FAIL: BIO_reset failed: ret = %d\n", ret);
goto failed;
}
ret = i2a_ASN1_OBJECT(bio, aobj);
if (ret < 0 || (unsigned long)ret != strlen(obj_txt)) {
fprintf(stderr, "FAIL: i2a_ASN1_OBJECT() returned %d, "
"want %zu\n", ret, strlen(obj_txt));
goto failed;
}
data_len = BIO_get_mem_data(bio, &data);
if (ret != data_len || memcmp(obj_txt, data, data_len) != 0) {
fprintf(stderr, "FAIL: i2a_ASN1_OBJECT() did not return "
"'%s'\n", obj_txt);
goto failed;
}
failed = 0;
failed:
ASN1_OBJECT_free(aobj);
BIO_free(bio);
return failed;
}
const uint8_t asn1_large_oid_der[] = {
0x06, 0x26,
0x2b, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
};
static int
asn1_object_large_oid_test(void)
{
ASN1_OBJECT *aobj = NULL;
uint8_t buf[1024];
const uint8_t *p;
uint8_t *q;
int ret;
int failed = 1;
failed = 0;
p = asn1_large_oid_der;
aobj = d2i_ASN1_OBJECT(NULL, &p, sizeof(asn1_large_oid_der));
if (aobj == NULL) {
fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() failed with "
"large oid\n");
goto failed;
}
q = buf;
ret = i2d_ASN1_OBJECT(aobj, &q);
if (!asn1_compare_bytes("ASN1_OBJECT DER", buf, ret, asn1_large_oid_der,
sizeof(asn1_large_oid_der)))
goto failed;
failed:
ASN1_OBJECT_free(aobj);
return failed;
}
int
main(int argc, char **argv)
{
int failed = 0;
failed |= asn1_object_test();
failed |= asn1_object_bad_content_test();
failed |= asn1_object_txt_test();
failed |= asn1_object_large_oid_test();
return (failed);
}