From: slontis Date: Mon, 30 Sep 2024 16:38:58 +0000 (+1000) Subject: Add LMS public key decoder. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e9eb032e1550086dd52c37e206886e1c8138f097;p=thirdparty%2Fopenssl.git Add LMS public key decoder. This uses OSSL_DECODER_CTX_new_for_pkey(). "XDR" can be specified for the input type, and the key type is "LMS" Reviewed-by: Hugo Landau Reviewed-by: Dmitry Belyavskiy Reviewed-by: Paul Dale (Merged from https://github.com/openssl/openssl/pull/25598) --- diff --git a/crypto/lms/lms_pubkey_decode.c b/crypto/lms/lms_pubkey_decode.c index 59e4e929967..2a4362d8f6a 100644 --- a/crypto/lms/lms_pubkey_decode.c +++ b/crypto/lms/lms_pubkey_decode.c @@ -11,6 +11,27 @@ #include "crypto/lms.h" #include "crypto/lms_util.h" +/** + * @brief Calculate the size of a public key in XDR format. + * + * @param data A byte array of XDR data for a LMS public key. + * The first 4 bytes are looked at. + * @param datalen The size of |data|. + * @returns The calculated size, or 0 on error. + */ +size_t ossl_lms_pubkey_length(const unsigned char *data, size_t datalen) +{ + PACKET pkt; + uint32_t lms_type; + const LMS_PARAMS *params; + + if (!PACKET_buf_init(&pkt, data, datalen) + || !PACKET_get_4_len(&pkt, &lms_type) + || (params = ossl_lms_params_get(lms_type)) == NULL) + return 0; + return LMS_SIZE_LMS_TYPE + LMS_SIZE_OTS_TYPE + LMS_SIZE_I + params->n; +} + /* * @brief Decode LMS public key data in XDR format into a LMS_KEY object. * @@ -64,17 +85,14 @@ err: * @returns 1 on success, or 0 otherwise. 0 is returned if either |pub| is * invalid or |publen| is not the correct size (i.e. trailing data is not allowed) */ -static -int lms_pubkey_decode(const unsigned char *pub, size_t publen, LMS_KEY *lmskey) +int ossl_lms_pubkey_decode(const unsigned char *pub, size_t publen, + LMS_KEY *lmskey) { PACKET pkt; LMS_PUB_KEY *pkey = &lmskey->pub; if (pkey->encoded != NULL && pkey->encodedlen != publen) { - if (pkey->allocated) { - OPENSSL_free(pkey->encoded); - pkey->allocated = 0; - } + OPENSSL_free(pkey->encoded); pkey->encodedlen = 0; } pkey->encoded = OPENSSL_memdup(pub, publen); @@ -86,7 +104,6 @@ int lms_pubkey_decode(const unsigned char *pub, size_t publen, LMS_KEY *lmskey) || (PACKET_remaining(&pkt) > 0)) goto err; pkey->encodedlen = publen; - pkey->allocated = 1; return 1; err: OPENSSL_free(pkey->encoded); @@ -107,9 +124,9 @@ int ossl_lms_pubkey_from_params(const OSSL_PARAM params[], LMS_KEY *lmskey) p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY); if (p != NULL) { - if (p->data == NULL || p->data_type != OSSL_PARAM_OCTET_STRING) - return 0; - if (!lms_pubkey_decode(p->data, p->data_size, lmskey)) + if (p->data == NULL + || p->data_type != OSSL_PARAM_OCTET_STRING + || !ossl_lms_pubkey_decode(p->data, p->data_size, lmskey)) return 0; } return 1; diff --git a/doc/man7/EVP_PKEY-LMS.pod b/doc/man7/EVP_PKEY-LMS.pod index 9f16d15289d..db08c33e577 100644 --- a/doc/man7/EVP_PKEY-LMS.pod +++ b/doc/man7/EVP_PKEY-LMS.pod @@ -47,7 +47,19 @@ Commercial National Security Algorithm Suite NOTE error checking has been omitted in these examples An B context can be obtained by calling: - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "LMS", NULL); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(libctx, "LMS", propq); + +An B public key can be loaded simply like this: + + EVP_PKEY *pkey = NULL; + OSSL_DECODER_CTX *dctx = NULL; + int selection = OSSL_KEYMGMT_SELECT_PUBLIC_KEY; + + dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "XDR", NULL, + "LMS", selection, libctx, propq); + ret = OSSL_DECODER_from_bio(dctx, bio); + OSSL_DECODER_CTX_free(dctx); To load a LMS key from XDR encoded "data" of size "datalen": diff --git a/include/crypto/lms.h b/include/crypto/lms.h index bea0909f36f..be06ff5d587 100644 --- a/include/crypto/lms.h +++ b/include/crypto/lms.h @@ -67,6 +67,9 @@ # define LMS_SIZE_I 16 # define LMS_SIZE_LMS_TYPE 4 # define LMS_SIZE_OTS_TYPE 4 +# define LMS_MAX_DIGEST_SIZE 32 +# define LMS_MAX_PUBKEY \ + (LMS_SIZE_LMS_TYPE + LMS_SIZE_OTS_TYPE + LMS_SIZE_I + LMS_MAX_DIGEST_SIZE) /* * Refer to RFC 8554 Section 4.1. @@ -141,6 +144,9 @@ int ossl_lms_key_valid(const LMS_KEY *key, int selection); int ossl_lms_key_has(const LMS_KEY *key, int selection); int ossl_lms_pubkey_from_params(const OSSL_PARAM params[], LMS_KEY *lmskey); +int ossl_lms_pubkey_decode(const unsigned char *pub, size_t publen, + LMS_KEY *lmskey); +size_t ossl_lms_pubkey_length(const unsigned char *data, size_t datalen); # endif /* OPENSSL_NO_LMS */ #endif /* OSSL_CRYPTO_LMS_H */ diff --git a/providers/decoders.inc b/providers/decoders.inc index 0191aa771ea..4c92a47cad7 100644 --- a/providers/decoders.inc +++ b/providers/decoders.inc @@ -95,3 +95,7 @@ DECODER("DER", pem, der, yes), * form to the next decoder. */ DECODER_w_structure("DER", der, EncryptedPrivateKeyInfo, der, yes), + +#ifndef OPENSSL_NO_LMS +DECODER("LMS", xdr, lms, yes), +#endif diff --git a/providers/implementations/encode_decode/build.info b/providers/implementations/encode_decode/build.info index d3f6ca4abde..f8898b0791b 100644 --- a/providers/implementations/encode_decode/build.info +++ b/providers/implementations/encode_decode/build.info @@ -18,3 +18,7 @@ IF[{- !$disabled{ec} -}] SOURCE[$ENCODER_GOAL]=encode_key2blob.c ENDIF DEPEND[encode_key2any.o]=../../common/include/prov/der_rsa.h + +IF[{- !$disabled{lms} -}] + SOURCE[$DECODER_GOAL]=decode_lmsxdr2key.c +ENDIF diff --git a/providers/implementations/encode_decode/decode_lmsxdr2key.c b/providers/implementations/encode_decode/decode_lmsxdr2key.c new file mode 100644 index 00000000000..5528cd430d9 --- /dev/null +++ b/providers/implementations/encode_decode/decode_lmsxdr2key.c @@ -0,0 +1,162 @@ +/* + * Copyright 2024 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 + */ + +#include +#include +#include +#include +#include +#include "endecoder_local.h" +#include "crypto/lms.h" +#include "prov/bio.h" +#include "prov/implementations.h" + +static OSSL_FUNC_decoder_newctx_fn lmsxdr2key_newctx; +static OSSL_FUNC_decoder_freectx_fn lmsxdr2key_freectx; +static OSSL_FUNC_decoder_decode_fn lmsxdr2key_decode; +static OSSL_FUNC_decoder_export_object_fn lmsxdr2key_export_object; + +/* Context used for xdr to key decoding. */ +struct lmsxdr2key_ctx_st { + PROV_CTX *provctx; + int selection; /* The selection that is passed to lmsxdr2key_decode() */ +}; + +static void *lmsxdr2key_newctx(void *provctx) +{ + struct lmsxdr2key_ctx_st *ctx = OPENSSL_zalloc(sizeof(*ctx)); + + if (ctx != NULL) + ctx->provctx = provctx; + return ctx; +} + +static void lmsxdr2key_freectx(void *vctx) +{ + struct lmsxdr2key_ctx_st *ctx = vctx; + + OPENSSL_free(ctx); +} + +static int lmsxdr2key_does_selection(void *provctx, int selection) +{ + if (selection == 0) + return 1; + + if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) + return 1; + + return 0; +} + +static int lmsxdr2key_decode(void *vctx, OSSL_CORE_BIO *cin, int selection, + OSSL_CALLBACK *data_cb, void *data_cbarg, + OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg) +{ + struct lmsxdr2key_ctx_st *ctx = vctx; + LMS_KEY *key = NULL; + unsigned char buf[LMS_MAX_PUBKEY]; + size_t length; + int ok = 0; + BIO *in; + + in = ossl_bio_new_from_core_bio(ctx->provctx, cin); + if (in == NULL) + return 0; + + ERR_set_mark(); + ctx->selection = selection; + /* Read the header to determine the size */ + if (BIO_read(in, buf, 4) != 4) + goto next; + + length = ossl_lms_pubkey_length(buf, 4); + if (length == 0) + goto next; + if (BIO_read(in, buf + 4, length - 4) != (int)(length - 4)) + goto next; + if (selection == 0 || (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) { + key = ossl_lms_key_new(PROV_LIBCTX_OF(ctx->provctx)); + if (key == NULL || !ossl_lms_pubkey_decode(buf, length, key)) { + ossl_lms_key_free(key); + key = NULL; + } + } + next: + ERR_clear_last_mark(); + /* + * Indicated that we successfully decoded something, or not at all. + * Ending up "empty handed" is not an error. + */ + ok = 1; + + /* + * We free resources here so it's not held up during the callback, because + * we know the process is recursive and the allocated chunks of memory + * add up. + */ + BIO_free(in); + in = NULL; + + if (key != NULL) { + OSSL_PARAM params[4]; + int object_type = OSSL_OBJECT_PKEY; + + params[0] = + OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, &object_type); + params[1] = + OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE, + (char *)"lms", 0); + /* The address of the key becomes the octet string */ + params[2] = + OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_REFERENCE, + &key, sizeof(key)); + params[3] = OSSL_PARAM_construct_end(); + + ok = data_cb(params, data_cbarg); + } + + BIO_free(in); + ossl_lms_key_free(key); + return ok; +} + +static int lmsxdr2key_export_object(void *vctx, + const void *reference, size_t reference_sz, + OSSL_CALLBACK *export_cb, + void *export_cbarg) +{ + struct lmsxdr2key_ctx_st *ctx = vctx; + OSSL_FUNC_keymgmt_export_fn *export = + ossl_prov_get_keymgmt_export(ossl_lms_keymgmt_functions); + void *keydata; + + if (reference_sz == sizeof(keydata) && export != NULL) { + int selection = ctx->selection; + + if (selection == 0) + selection = OSSL_KEYMGMT_SELECT_PUBLIC_KEY; + /* The contents of the reference is the address to our object */ + keydata = *(void **)reference; + + return export(keydata, selection, export_cb, export_cbarg); + } + return 0; +} + +const OSSL_DISPATCH ossl_xdr_to_lms_decoder_functions[] = { + { OSSL_FUNC_DECODER_NEWCTX, (void (*)(void))lmsxdr2key_newctx }, + { OSSL_FUNC_DECODER_FREECTX, (void (*)(void))lmsxdr2key_freectx }, + { OSSL_FUNC_DECODER_DOES_SELECTION, + (void (*)(void))lmsxdr2key_does_selection }, + { OSSL_FUNC_DECODER_DECODE, (void (*)(void))lmsxdr2key_decode }, + { OSSL_FUNC_DECODER_EXPORT_OBJECT, + (void (*)(void))lmsxdr2key_export_object }, + OSSL_DISPATCH_END +}; diff --git a/providers/implementations/include/prov/implementations.h b/providers/implementations/include/prov/implementations.h index 0555ddee8c3..73cc8b9a9f2 100644 --- a/providers/implementations/include/prov/implementations.h +++ b/providers/implementations/include/prov/implementations.h @@ -590,3 +590,5 @@ extern const OSSL_DISPATCH ossl_pem_to_der_decoder_functions[]; extern const OSSL_DISPATCH ossl_file_store_functions[]; extern const OSSL_DISPATCH ossl_winstore_store_functions[]; + +extern const OSSL_DISPATCH ossl_xdr_to_lms_decoder_functions[]; diff --git a/providers/implementations/keymgmt/lms_kmgmt.c b/providers/implementations/keymgmt/lms_kmgmt.c index ac9764672e9..330528f91af 100644 --- a/providers/implementations/keymgmt/lms_kmgmt.c +++ b/providers/implementations/keymgmt/lms_kmgmt.c @@ -22,6 +22,7 @@ static OSSL_FUNC_keymgmt_match_fn lms_match; static OSSL_FUNC_keymgmt_validate_fn lms_validate; static OSSL_FUNC_keymgmt_import_fn lms_import; static OSSL_FUNC_keymgmt_import_types_fn lms_imexport_types; +static OSSL_FUNC_keymgmt_load_fn lms_load; #define LMS_POSSIBLE_SELECTIONS (OSSL_KEYMGMT_SELECT_PUBLIC_KEY) @@ -99,6 +100,20 @@ static int lms_validate(const void *keydata, int selection, int checktype) return ossl_lms_key_valid(lmskey, selection); } +static void *lms_load(const void *reference, size_t reference_sz) +{ + LMS_KEY *key = NULL; + + if (ossl_prov_is_running() && reference_sz == sizeof(key)) { + /* The contents of the reference is the address to our object */ + key = *(LMS_KEY **)reference; + /* We grabbed, so we detach it */ + *(LMS_KEY **)reference = NULL; + return key; + } + return NULL; +} + const OSSL_DISPATCH ossl_lms_keymgmt_functions[] = { { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))lms_new_key }, { OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))lms_free_key }, @@ -107,5 +122,6 @@ const OSSL_DISPATCH ossl_lms_keymgmt_functions[] = { { OSSL_FUNC_KEYMGMT_VALIDATE, (void (*)(void))lms_validate }, { OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))lms_import }, { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))lms_imexport_types }, + { OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))lms_load }, OSSL_DISPATCH_END }; diff --git a/test/lms_test.c b/test/lms_test.c index 6c90b6f7ee1..29b63cee8c1 100644 --- a/test/lms_test.c +++ b/test/lms_test.c @@ -8,6 +8,7 @@ */ #include +#include #include #include "crypto/lms.h" #include "internal/nelem.h" @@ -67,10 +68,126 @@ end: return ret == 1; } +static int lms_pubkey_decoder_fail_test(void) +{ + int ret = 0; + EVP_PKEY *pkey = NULL; + OSSL_DECODER_CTX *dctx = NULL; + int selection = 0; + LMS_ACVP_TEST_DATA *td = &lms_testdata[0]; + const unsigned char *pdata; + size_t pdatalen; + static const unsigned char pub_bad_LMSType[] = { + 0x00, 0x00, 0x00, 0xAA + }; + static const unsigned char pub_bad_OTSType[] = { + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAA + }; + + if (!TEST_ptr(dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, NULL, NULL, "LMS", + selection, + libctx, NULL))) + return 0; + + pdata = td->pub; + pdatalen = 3; + if (!TEST_false(OSSL_DECODER_from_data(dctx, &pdata, &pdatalen))) + goto end; + + pdatalen = SIZE_MAX; + if (!TEST_false(OSSL_DECODER_from_data(dctx, &pdata, &pdatalen))) + goto end; + + pdata = pub_bad_LMSType; + pdatalen = sizeof(pub_bad_LMSType); + if (!TEST_false(OSSL_DECODER_from_data(dctx, &pdata, &pdatalen))) + goto end; + + pdata = pub_bad_OTSType; + pdatalen = sizeof(pub_bad_OTSType); + if (!TEST_false(OSSL_DECODER_from_data(dctx, &pdata, &pdatalen))) + goto end; + + ret = 1; +end: + EVP_PKEY_free(pkey); + OSSL_DECODER_CTX_free(dctx); + return ret; +} + +static EVP_PKEY *key_decode_from_bio(BIO *bio, const char *keytype) +{ + EVP_PKEY *pkey = NULL; + OSSL_DECODER_CTX *dctx = NULL; + int selection = 0; + + if (!TEST_ptr(dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, NULL, NULL, + keytype, + selection, + libctx, NULL))) + return NULL; + + if (!TEST_true(OSSL_DECODER_from_bio(dctx, bio))) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + OSSL_DECODER_CTX_free(dctx); + return pkey; +} + +static EVP_PKEY *key_decode_from_data(const unsigned char *data, size_t datalen, + const char *keytype) +{ + BIO *bio; + EVP_PKEY *key = NULL; + + if (!TEST_ptr(bio = BIO_new_mem_buf(data, datalen))) + return NULL; + key = key_decode_from_bio(bio, keytype); + BIO_free(bio); + return key; +} + +static int lms_key_decode_test(void) +{ + int ret = 0; + LMS_ACVP_TEST_DATA *td1 = &lms_testdata[0]; + EVP_PKEY *key = NULL; + + ret = TEST_ptr(key = key_decode_from_data(td1->pub, td1->publen, NULL)); + EVP_PKEY_free(key); + return ret; +} + +static int lms_pubkey_decoder_test(void) +{ + int ret = 0; + LMS_ACVP_TEST_DATA *td = &lms_testdata[0]; + EVP_PKEY *pub = NULL; + OSSL_DECODER_CTX *dctx = NULL; + const unsigned char *data; + size_t data_len; + + if (!TEST_ptr(dctx = OSSL_DECODER_CTX_new_for_pkey(&pub, "xdr", NULL, + "LMS", + OSSL_KEYMGMT_SELECT_PUBLIC_KEY, + libctx, NULL))) + goto err; + data = td->pub; + data_len = td->publen; + if (!TEST_true(OSSL_DECODER_from_data(dctx, &data, &data_len))) + goto err; + ret = 1; +err: + EVP_PKEY_free(pub); + OSSL_DECODER_CTX_free(dctx); + return ret; +} + static int lms_key_eq_test(void) { int ret = 0; - EVP_PKEY *key[3] = { NULL, NULL, NULL }; + EVP_PKEY *key[4] = { NULL, NULL, NULL }; LMS_ACVP_TEST_DATA *td1 = &lms_testdata[0]; LMS_ACVP_TEST_DATA *td2 = &lms_testdata[1]; #ifndef OPENSSL_NO_EC @@ -79,11 +196,14 @@ static int lms_key_eq_test(void) if (!TEST_ptr(key[0] = lms_pubkey_from_data(td1->pub, td1->publen)) || !TEST_ptr(key[1] = lms_pubkey_from_data(td1->pub, td1->publen)) - || !TEST_ptr(key[2] = lms_pubkey_from_data(td2->pub, td2->publen))) + || !TEST_ptr(key[2] = lms_pubkey_from_data(td2->pub, td2->publen)) + || !TEST_ptr(key[3] = key_decode_from_data(td1->pub, td1->publen, + NULL))) goto end; ret = TEST_int_eq(EVP_PKEY_eq(key[0], key[1]), 1) - && TEST_int_ne(EVP_PKEY_eq(key[0], key[2]), 1); + && TEST_int_ne(EVP_PKEY_eq(key[0], key[2]), 1) + && TEST_int_eq(EVP_PKEY_eq(key[0], key[3]), 1); if (ret == 0) goto end; @@ -94,6 +214,7 @@ static int lms_key_eq_test(void) EVP_PKEY_free(eckey); #endif end: + EVP_PKEY_free(key[3]); EVP_PKEY_free(key[2]); EVP_PKEY_free(key[1]); EVP_PKEY_free(key[0]); @@ -123,5 +244,8 @@ int setup_tests(void) ADD_TEST(lms_bad_pub_len_test); ADD_TEST(lms_key_validate_test); ADD_TEST(lms_key_eq_test); + ADD_TEST(lms_key_decode_test); + ADD_TEST(lms_pubkey_decoder_test); + ADD_TEST(lms_pubkey_decoder_fail_test); return 1; }