diff --git a/crypto/asn1/a_bitstr.c b/crypto/asn1/a_bitstr.c index 223efc2bb1..1d58c9cbbf 100644 --- a/crypto/asn1/a_bitstr.c +++ b/crypto/asn1/a_bitstr.c @@ -220,3 +220,36 @@ int ASN1_BIT_STRING_check(const ASN1_BIT_STRING *a, } return ok; } + +int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *abs, size_t *out_length, + int *out_unused_bits) +{ + size_t length; + int unused_bits; + + if (abs == NULL || abs->type != V_ASN1_BIT_STRING) + return 0; + + if (out_length == NULL || out_unused_bits == NULL) + return 0; + + length = abs->length; + unused_bits = 0; + + if ((abs->flags & ASN1_STRING_FLAG_BITS_LEFT) != 0) + unused_bits = abs->flags & 0x07; + + if (length == 0 && unused_bits != 0) + return 0; + + if (unused_bits != 0) { + unsigned char mask = (1 << unused_bits) - 1; + if ((abs->data[length - 1] & mask) != 0) + return 0; + } + + *out_length = length; + *out_unused_bits = unused_bits; + + return 1; +} diff --git a/doc/build.info b/doc/build.info index fdaa35df76..d53aed8df8 100644 --- a/doc/build.info +++ b/doc/build.info @@ -479,6 +479,10 @@ DEPEND[html/man3/ADMISSIONS.html]=man3/ADMISSIONS.pod GENERATE[html/man3/ADMISSIONS.html]=man3/ADMISSIONS.pod DEPEND[man/man3/ADMISSIONS.3]=man3/ADMISSIONS.pod GENERATE[man/man3/ADMISSIONS.3]=man3/ADMISSIONS.pod +DEPEND[html/man3/ASN1_BIT_STRING_get_length.html]=man3/ASN1_BIT_STRING_get_length.pod +GENERATE[html/man3/ASN1_BIT_STRING_get_length.html]=man3/ASN1_BIT_STRING_get_length.pod +DEPEND[man/man3/ASN1_BIT_STRING_get_length.3]=man3/ASN1_BIT_STRING_get_length.pod +GENERATE[man/man3/ASN1_BIT_STRING_get_length.3]=man3/ASN1_BIT_STRING_get_length.pod DEPEND[html/man3/ASN1_EXTERN_FUNCS.html]=man3/ASN1_EXTERN_FUNCS.pod GENERATE[html/man3/ASN1_EXTERN_FUNCS.html]=man3/ASN1_EXTERN_FUNCS.pod DEPEND[man/man3/ASN1_EXTERN_FUNCS.3]=man3/ASN1_EXTERN_FUNCS.pod @@ -3165,6 +3169,7 @@ DEPEND[man/man3/s2i_ASN1_IA5STRING.3]=man3/s2i_ASN1_IA5STRING.pod GENERATE[man/man3/s2i_ASN1_IA5STRING.3]=man3/s2i_ASN1_IA5STRING.pod IMAGEDOCS[man3]= HTMLDOCS[man3]=html/man3/ADMISSIONS.html \ +html/man3/ASN1_BIT_STRING_get_length.html \ html/man3/ASN1_EXTERN_FUNCS.html \ html/man3/ASN1_INTEGER_get_int64.html \ html/man3/ASN1_INTEGER_new.html \ @@ -3837,6 +3842,7 @@ html/man3/i2d_re_X509_tbs.html \ html/man3/o2i_SCT_LIST.html \ html/man3/s2i_ASN1_IA5STRING.html MANDOCS[man3]=man/man3/ADMISSIONS.3 \ +man/man3/ASN1_BIT_STRING_get_length.3 \ man/man3/ASN1_EXTERN_FUNCS.3 \ man/man3/ASN1_INTEGER_get_int64.3 \ man/man3/ASN1_INTEGER_new.3 \ diff --git a/doc/man3/ASN1_BIT_STRING_get_length.pod b/doc/man3/ASN1_BIT_STRING_get_length.pod new file mode 100644 index 0000000000..79497f41ec --- /dev/null +++ b/doc/man3/ASN1_BIT_STRING_get_length.pod @@ -0,0 +1,35 @@ +=pod + +=head1 NAME + +ASN1_BIT_STRING_get_length - ASN1_BIT_STRING accessors + +=head1 SYNOPSIS + + #include + + int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *bitstr, size_t *length, int *unused_bits); + +=head1 DESCRIPTION + +ASN1_BIT_STRING_get_length() returns the number of octets in I +containing bit values in I and the number of unused bits in +the last octet in I. The value returned in +I is guaranteed to be between 0 and 7, inclusive. + +=head1 RETURN VALUES + +ASN1_BIT_STRING_get_length() returns 1 on success or 0 if the encoding +of I is internally inconsistent, or if one of I, +I, or I is NULL. + +=head1 COPYRIGHT + +Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the Apache License 2.0 (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/include/openssl/asn1.h.in b/include/openssl/asn1.h.in index 72c20f76d7..1b7dd66d68 100644 --- a/include/openssl/asn1.h.in +++ b/include/openssl/asn1.h.in @@ -586,6 +586,8 @@ int ASN1_BIT_STRING_name_print(BIO *out, ASN1_BIT_STRING *bs, int ASN1_BIT_STRING_num_asc(const char *name, BIT_STRING_BITNAME *tbl); int ASN1_BIT_STRING_set_asc(ASN1_BIT_STRING *bs, const char *name, int value, BIT_STRING_BITNAME *tbl); +int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *abs, size_t *length, + int *unused_bits); /* clang-format off */ {- diff --git a/test/asn1_string_test.c b/test/asn1_string_test.c new file mode 100644 index 0000000000..7428597621 --- /dev/null +++ b/test/asn1_string_test.c @@ -0,0 +1,268 @@ +/* + * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* ASN1_STRING tests */ + +#include + +#include +#include "testutil.h" + +struct abs_get_length_test { + const char *descr; + int valid; + const unsigned char der[20]; + int der_len; + size_t length; + int unused_bits; +}; + +static const struct abs_get_length_test abs_get_length_tests[] = { + { + .descr = "zero bits", + .valid = 1, + .der = { + 0x03, + 0x01, + 0x00, + }, + .der_len = 3, + .length = 0, + .unused_bits = 0, + }, + { + .descr = "zero bits one unused", + .valid = 0, + .der = { + 0x03, + 0x01, + 0x01, + }, + .der_len = 3, + }, + { + .descr = "single zero bit", + .valid = 1, + .der = { + 0x03, + 0x02, + 0x07, + 0x00, + }, + .der_len = 4, + .length = 1, + .unused_bits = 7, + }, + { + .descr = "single one bit", + .valid = 1, + .der = { + 0x03, + 0x02, + 0x07, + 0x80, + }, + .der_len = 4, + .length = 1, + .unused_bits = 7, + }, + { + /* XXX - the library pretends this is 03 02 07 80 */ + .descr = "invalid: single one bit, seventh bit set", + .valid = 1, + .der = { + 0x03, + 0x02, + 0x07, + 0xc0, + }, + .der_len = 4, + .length = 1, + .unused_bits = 7, + }, + { + .descr = "x.690, primitive encoding in example 8.6.4.2", + .valid = 1, + .der = { + 0x03, + 0x07, + 0x04, + 0x0A, + 0x3b, + 0x5F, + 0x29, + 0x1c, + 0xd0, + }, + .der_len = 9, + .length = 6, + .unused_bits = 4, + }, + { + /* + * XXX - the library thinks it "decodes" this but gets it + * quite wrong. Looks like it uses the unused bits of the + * first component, and the unused bits octet 04 of the + * second component somehow becomes part of the value. + */ + .descr = "x.690, constructed encoding in example 8.6.4.2", + .valid = 1, + .der = { + 0x23, + 0x80, + 0x03, + 0x03, + 0x00, + 0x0A, + 0x3b, + 0x03, + 0x05, + 0x04, + 0x5F, + 0x29, + 0x1c, + 0xd0, + 0x00, + 0x00, + }, + .der_len = 16, + .length = 7, /* XXX - should be 6. */ + .unused_bits = 0, /* XXX - should be 4. */ + }, + { + .descr = "RFC 3779, 2.1.1, IPv4 address 10.5.0.4", + .valid = 1, + .der = { + 0x03, + 0x05, + 0x00, + 0x0a, + 0x05, + 0x00, + 0x04, + }, + .der_len = 7, + .length = 4, + .unused_bits = 0, + }, + { + .descr = "RFC 3779, 2.1.1, IPv4 prefix 10.5.0/23", + .valid = 1, + .der = { + 0x03, + 0x04, + 0x01, + 0x0a, + 0x05, + 0x00, + }, + .der_len = 6, + .length = 3, + .unused_bits = 1, + }, + { + .descr = "RFC 3779, 2.1.1, IPv6 address 2001:0:200:3::1", + .valid = 1, + .der = { + 0x03, + 0x11, + 0x00, + 0x20, + 0x01, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + }, + .der_len = 19, + .length = 16, + .unused_bits = 0, + }, + { + .descr = "RFC 3779, 2.1.1, IPv6 prefix 2001:0:200/39", + .valid = 1, + .der = { + 0x03, + 0x06, + 0x01, + 0x20, + 0x01, + 0x00, + 0x00, + 0x02, + }, + .der_len = 8, + .length = 5, + .unused_bits = 1, + }, +}; + +static int +abs_get_length_test(const struct abs_get_length_test *tbl, int idx) +{ + const struct abs_get_length_test *test = &tbl[idx]; + ASN1_BIT_STRING *abs = NULL; + const unsigned char *p; + int unused_bits, ret; + size_t length; + int success = 0; + + p = test->der; + if (!TEST_ptr(abs = d2i_ASN1_BIT_STRING(NULL, &p, test->der_len))) { + TEST_info("%s, (idx=%d) - d2i_ASN1_BIT_STRING faled", __func__, idx); + goto err; + } + + ret = ASN1_BIT_STRING_get_length(abs, &length, &unused_bits); + if (!TEST_int_eq(test->valid, ret)) { + TEST_info("%s (idx=%d): %s ASN1_BIT_STRING_get_length want %d, got %d\n", + __func__, idx, test->descr, test->valid, ret); + goto err; + } + if (!test->valid) + goto done; + + if (!TEST_size_t_eq(length, test->length) + || !TEST_int_eq(unused_bits, test->unused_bits)) { + TEST_info("%s: (idx=%d) %s: want (%zu, %d), got (%zu, %d)\n", __func__, + idx, test->descr, test->length, test->unused_bits, length, + unused_bits); + goto err; + } + +done: + success = 1; + +err: + ASN1_STRING_free(abs); + + return success; +} + +static int +asn1_bit_string_get_length_test(int idx) +{ + return abs_get_length_test(abs_get_length_tests, idx); +} + +int setup_tests(void) +{ + ADD_ALL_TESTS(asn1_bit_string_get_length_test, OSSL_NELEM(abs_get_length_tests)); + return 1; +} diff --git a/test/build.info b/test/build.info index e5990247d8..6890544f21 100644 --- a/test/build.info +++ b/test/build.info @@ -1096,6 +1096,11 @@ IF[{- !$disabled{tests} -}] INCLUDE[asn1_time_test]=../include ../apps/include DEPEND[asn1_time_test]=../libcrypto libtestutil.a + PROGRAMS{noinst}=asn1_string_test + SOURCE[asn1_string_test]=asn1_string_test.c + INCLUDE[asn1_string_test]=../include ../apps/include + DEPEND[asn1_string_test]=../libcrypto libtestutil.a + # We disable this test completely in a shared build because it deliberately # redefines some internal libssl symbols. This doesn't work in a non-shared # build diff --git a/test/recipes/90-test_asn1_string.t b/test/recipes/90-test_asn1_string.t new file mode 100644 index 0000000000..ce0db28a18 --- /dev/null +++ b/test/recipes/90-test_asn1_string.t @@ -0,0 +1,12 @@ +#! /usr/bin/env perl +# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + + +use OpenSSL::Test::Simple; + +simple_test("test_asn1_string", "asn1_string_test"); diff --git a/util/libcrypto.num b/util/libcrypto.num index 16a60ba98c..c1b1367028 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -2614,6 +2614,7 @@ ASN1_BIT_STRING_set ? 4_0_0 EXIST::FUNCTION: ASN1_BIT_STRING_set_bit ? 4_0_0 EXIST::FUNCTION: ASN1_BIT_STRING_get_bit ? 4_0_0 EXIST::FUNCTION: ASN1_BIT_STRING_check ? 4_0_0 EXIST::FUNCTION: +ASN1_BIT_STRING_get_length ? 4_0_0 EXIST::FUNCTION: ASN1_BIT_STRING_name_print ? 4_0_0 EXIST::FUNCTION: ASN1_BIT_STRING_num_asc ? 4_0_0 EXIST::FUNCTION: ASN1_BIT_STRING_set_asc ? 4_0_0 EXIST::FUNCTION: