Skip to content

Commit

Permalink
add back ASN1_dup with tests (#1591)
Browse files Browse the repository at this point in the history
`ASN1_dup` was removed in 419144a in favor of `ASN1_Item_dup`.
This shouldn't normally be called directly, but Ruby happens to
consume the API in several instances.
I've optimized the function to allocate memory with `i2d` directly,
instead of the ancient OpenSSL allocate and pass into behavior.
Also added some tests for verification along with a do-not-use
warning.
  • Loading branch information
samuel40791765 authored Jun 3, 2024
1 parent bb40826 commit 8258d73
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 3 deletions.
20 changes: 20 additions & 0 deletions crypto/asn1/a_dup.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@
#include <openssl/err.h>
#include <openssl/mem.h>

void *ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, void *input) {
if (i2d == NULL || d2i == NULL || input == NULL) {
OPENSSL_PUT_ERROR(ASN1, ERR_R_PASSED_NULL_PARAMETER);
return NULL;
}

// Size and allocate |buf|.
unsigned char *buf = NULL;
int buf_len = i2d(input, &buf);
if (buf == NULL || buf_len < 0) {
return NULL;
}

// |buf| needs to be converted to |const| to be passed in.
const unsigned char *temp_input = buf;
char *ret = d2i(NULL, &temp_input, buf_len);
OPENSSL_free(buf);
return ret;
}

// ASN1_ITEM version of dup: this follows the model above except we don't
// need to allocate the buffer. At some point this could be rewritten to
// directly dup the underlying structure instead of doing and encode and
Expand Down
64 changes: 64 additions & 0 deletions crypto/asn1/asn1_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2412,6 +2412,70 @@ TEST(ASN1Test, LargeString) {
#endif
}


// Wrapper functions are needed to get around Control Flow Integrity Sanitizers.
static int i2d_ASN1_TYPE_void(const void *a, unsigned char **out) {
return i2d_ASN1_TYPE((ASN1_TYPE *)a, out);
}
static void *d2i_ASN1_TYPE_void(void **a, const unsigned char **in, long len) {
return d2i_ASN1_TYPE((ASN1_TYPE **)a, in, len);
}
static int i2d_ECPrivateKey_void(const void *a, unsigned char **out) {
return i2d_ECPrivateKey((EC_KEY *)a, out);
}
static void *d2i_ECPrivateKey_void(void **a, const unsigned char **in, long len) {
return d2i_ECPrivateKey((EC_KEY **)a, in, len);
}
static int i2d_X509_PUBKEY_void(const void *a, unsigned char **out) {
return i2d_X509_PUBKEY((X509_PUBKEY *)a, out);
}
static void *d2i_X509_PUBKEY_void(void **a, const unsigned char **in, long len) {
return d2i_X509_PUBKEY((X509_PUBKEY **)a, in, len);
}

TEST(ASN1Test, ASN1Dup) {
const uint8_t *tag = kTag128;
bssl::UniquePtr<ASN1_TYPE> asn1(
d2i_ASN1_TYPE(nullptr, &tag, sizeof(kTag128)));
ASSERT_TRUE(asn1);
EXPECT_EQ(128, asn1->type);
bssl::UniquePtr<ASN1_TYPE> asn1_copy((ASN1_TYPE *)ASN1_dup(
i2d_ASN1_TYPE_void, d2i_ASN1_TYPE_void, asn1.get()));
ASSERT_TRUE(asn1_copy);
EXPECT_EQ(ASN1_TYPE_cmp(asn1.get(), asn1_copy.get()), 0);

bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
ASSERT_TRUE(key);
ASSERT_TRUE(EC_KEY_generate_key(key.get()));
bssl::UniquePtr<EC_KEY> key_copy((EC_KEY *)ASN1_dup(
i2d_ECPrivateKey_void, d2i_ECPrivateKey_void, key.get()));
ASSERT_TRUE(key_copy);
EXPECT_EQ(BN_cmp(EC_KEY_get0_private_key(key.get()),
EC_KEY_get0_private_key(key_copy.get())),
0);
EXPECT_EQ(EC_GROUP_cmp(EC_KEY_get0_group(key.get()),
EC_KEY_get0_group(key_copy.get()), nullptr),
0);
EXPECT_EQ(EC_POINT_cmp(EC_KEY_get0_group(key_copy.get()),
EC_KEY_get0_public_key(key.get()),
EC_KEY_get0_public_key(key_copy.get()), nullptr),
0);

bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
X509_PUBKEY *tmp_key = nullptr;
ASSERT_TRUE(evp_pkey);
ASSERT_TRUE(EVP_PKEY_set1_EC_KEY(evp_pkey.get(), key.get()));
ASSERT_TRUE(X509_PUBKEY_set(&tmp_key, evp_pkey.get()));
bssl::UniquePtr<X509_PUBKEY> x509_pubkey(tmp_key);
bssl::UniquePtr<X509_PUBKEY> x509_pubkey_copy((X509_PUBKEY *)ASN1_dup(
i2d_X509_PUBKEY_void, d2i_X509_PUBKEY_void, x509_pubkey.get()));
ASSERT_TRUE(x509_pubkey_copy);
EXPECT_EQ(
ASN1_STRING_cmp(X509_PUBKEY_get0_public_key(x509_pubkey.get()),
X509_PUBKEY_get0_public_key(x509_pubkey_copy.get())),
0);
}

// The ASN.1 macros do not work on Windows shared library builds, where usage of
// |OPENSSL_EXPORT| is a bit stricter.
#if !defined(OPENSSL_WINDOWS) || !defined(BORINGSSL_SHARED_LIBRARY)
Expand Down
15 changes: 12 additions & 3 deletions include/openssl/asn1.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,7 @@ int i2d_SAMPLE(const SAMPLE *in, uint8_t **outp);

// CHECKED_I2D_OF casts a given pointer to i2d_of_void* and statically checks
// that it was a pointer to |type|'s |i2d| function.
#define CHECKED_I2D_OF(type, i2d) \
((i2d_of_void*) (1 ? i2d : ((I2D_OF(type))0)))
#define CHECKED_I2D_OF(type, i2d) ((i2d_of_void *)(1 ? i2d : ((I2D_OF(type))0)))

// The following typedefs are sometimes used for pointers to functions like
// |d2i_SAMPLE| and |i2d_SAMPLE|. Note, however, that these act on |void*|.
Expand Down Expand Up @@ -391,6 +390,16 @@ OPENSSL_EXPORT ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **out,
OPENSSL_EXPORT int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **outp,
const ASN1_ITEM *it);

// ASN1_dup returns a newly-allocated copy of |x| by re-encoding with |i2d| and
// |d2i|. |i2d| and |d2i| must be the corresponding type functions of |x|. NULL
// is returned on error.
//
// WARNING: DO NOT USE. Casting the result of this function to the wrong type,
// or passing a pointer of the wrong type into this function, are potentially
// exploitable memory errors. Prefer directly calling |i2d| and |d2i| or other
// type-specific functions.
OPENSSL_EXPORT void *ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, void *x);

// ASN1_item_dup returns a newly-allocated copy of |x|, or NULL on error. |x|
// must be an object of |it|'s C type.
//
Expand Down Expand Up @@ -443,7 +452,7 @@ OPENSSL_EXPORT int ASN1_i2d_bio(i2d_of_void *i2d, BIO *out, void *in);
// forces the user to use undefined C behavior and will cause failures when
// running against undefined behavior sanitizers in clang.
#define ASN1_i2d_bio_of(type, i2d, out, in) \
(ASN1_i2d_bio(CHECKED_I2D_OF(type, i2d), out, CHECKED_PTR_OF(type, in)))
(ASN1_i2d_bio(CHECKED_I2D_OF(type, i2d), out, CHECKED_PTR_OF(type, in)))

// ASN1_item_unpack parses |oct|'s contents as |it|'s ASN.1 type. It returns a
// newly-allocated instance of |it|'s C type on success, or NULL on error.
Expand Down

0 comments on commit 8258d73

Please sign in to comment.