496 lines
11 KiB
C
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);
|
|
}
|