| /* |
| * 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 |
| */ |
| |
| #include <openssl/core_names.h> |
| #include <openssl/decoder.h> |
| #include <openssl/evp.h> |
| #include "crypto/lms.h" |
| #include "internal/nelem.h" |
| #include "testutil.h" |
| #include "lms.inc" |
| |
| typedef enum OPTION_choice { |
| OPT_ERR = -1, |
| OPT_EOF = 0, |
| OPT_CONFIG_FILE, |
| OPT_TEST_ENUM |
| } OPTION_CHOICE; |
| |
| static OSSL_LIB_CTX *libctx = NULL; |
| static char *propq = NULL; |
| static OSSL_PROVIDER *nullprov = NULL; |
| static OSSL_PROVIDER *libprov = NULL; |
| |
| static EVP_PKEY *lms_pubkey_from_data(const unsigned char *data, size_t datalen) |
| { |
| int ret; |
| EVP_PKEY_CTX *ctx = NULL; |
| EVP_PKEY *key = NULL; |
| OSSL_PARAM params[2]; |
| |
| params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, |
| (unsigned char *)data, datalen); |
| params[1] = OSSL_PARAM_construct_end(); |
| ret = TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(libctx, "LMS", propq)) |
| && TEST_int_eq(EVP_PKEY_fromdata_init(ctx), 1) |
| && (EVP_PKEY_fromdata(ctx, &key, EVP_PKEY_PUBLIC_KEY, params) == 1); |
| if (ret == 0) { |
| EVP_PKEY_free(key); |
| key = NULL; |
| } |
| EVP_PKEY_CTX_free(ctx); |
| return key; |
| } |
| |
| static int lms_bad_pub_len_test(void) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[1]; |
| EVP_PKEY *pkey = NULL; |
| size_t publen = 0; |
| unsigned char pubdata[128]; |
| |
| if (!TEST_size_t_le(td->publen + 16, sizeof(pubdata))) |
| goto end; |
| |
| OPENSSL_cleanse(pubdata, sizeof(pubdata)); |
| memcpy(pubdata, td->pub, td->publen); |
| |
| for (publen = 0; publen <= td->publen + 16; publen += 3) { |
| if (publen == td->publen) |
| continue; |
| if (!TEST_ptr_null(pkey = lms_pubkey_from_data(pubdata, publen))) |
| goto end; |
| } |
| ret = 1; |
| end: |
| if (ret == 0) |
| TEST_note("Incorrectly accepted public key of length %u (expected %u)", |
| (unsigned)publen, (unsigned)td->publen); |
| EVP_PKEY_free(pkey); |
| |
| 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, (int)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[4]; |
| LMS_ACVP_TEST_DATA *td1 = &lms_testdata[0]; |
| LMS_ACVP_TEST_DATA *td2 = &lms_testdata[1]; |
| size_t i; |
| #ifndef OPENSSL_NO_EC |
| EVP_PKEY *eckey = NULL; |
| #endif |
| |
| for (i = 0; i < OSSL_NELEM(key); i++) |
| key[i] = NULL; |
| |
| 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[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_eq(EVP_PKEY_eq(key[0], key[3]), 1); |
| if (ret == 0) |
| goto end; |
| |
| #ifndef OPENSSL_NO_EC |
| if (!TEST_ptr(eckey = EVP_PKEY_Q_keygen(libctx, NULL, "EC", "P-256"))) |
| goto end; |
| ret = TEST_int_ne(EVP_PKEY_eq(key[0], eckey), 1); |
| 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]); |
| return ret; |
| } |
| |
| static int lms_key_validate_test(void) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[0]; |
| EVP_PKEY_CTX *vctx = NULL; |
| EVP_PKEY *key = NULL; |
| |
| if (!TEST_ptr(key = lms_pubkey_from_data(td->pub, td->publen))) |
| return 0; |
| if (!TEST_ptr(vctx = EVP_PKEY_CTX_new_from_pkey(libctx, key, NULL))) |
| goto end; |
| ret = TEST_int_eq(EVP_PKEY_check(vctx), 1); |
| EVP_PKEY_CTX_free(vctx); |
| end: |
| EVP_PKEY_free(key); |
| return ret; |
| } |
| |
| static int lms_verify_test(int tst) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[tst]; |
| EVP_PKEY_CTX *ctx = NULL; |
| EVP_PKEY *pkey = NULL; |
| EVP_SIGNATURE *sig = NULL; |
| |
| ret = TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen)) |
| && TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL)) |
| && TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL)) |
| && TEST_int_eq(EVP_PKEY_verify_message_init(ctx, sig, NULL), 1) |
| && TEST_int_eq(EVP_PKEY_verify(ctx, td->sig, td->siglen, |
| td->msg, td->msglen), 1); |
| |
| EVP_PKEY_free(pkey); |
| EVP_PKEY_CTX_free(ctx); |
| EVP_SIGNATURE_free(sig); |
| return ret; |
| } |
| |
| static int lms_digest_verify_fail_test(void) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[0]; |
| EVP_PKEY *pub = NULL; |
| EVP_MD_CTX *vctx = NULL; |
| |
| if (!TEST_ptr(pub = lms_pubkey_from_data(td->pub, td->publen))) |
| return 0; |
| if (!TEST_ptr(vctx = EVP_MD_CTX_new())) |
| goto err; |
| /* Only one shot mode is supported, streaming fails to initialise */ |
| if (!TEST_int_eq(EVP_DigestVerifyInit_ex(vctx, NULL, NULL, libctx, NULL, |
| pub, NULL), 0)) |
| goto err; |
| ret = 1; |
| err: |
| EVP_PKEY_free(pub); |
| EVP_MD_CTX_free(vctx); |
| return ret; |
| } |
| |
| static int lms_digest_signing_fail_test(void) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[0]; |
| EVP_PKEY *pub = NULL; |
| EVP_MD_CTX *vctx = NULL; |
| |
| if (!TEST_ptr(pub = lms_pubkey_from_data(td->pub, td->publen))) |
| return 0; |
| if (!TEST_ptr(vctx = EVP_MD_CTX_new())) |
| goto err; |
| if (!TEST_int_eq(EVP_DigestSignInit_ex(vctx, NULL, NULL, libctx, NULL, |
| pub, NULL), 0)) |
| goto err; |
| ret = 1; |
| err: |
| EVP_PKEY_free(pub); |
| EVP_MD_CTX_free(vctx); |
| return ret; |
| } |
| |
| static int lms_message_signing_fail_test(void) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[0]; |
| EVP_PKEY_CTX *ctx = NULL; |
| EVP_PKEY *pkey = NULL; |
| EVP_SIGNATURE *sig = NULL; |
| |
| ret = TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen)) |
| && TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL)) |
| && TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL)) |
| && TEST_int_eq(EVP_PKEY_sign_message_init(ctx, sig, NULL), -2); |
| |
| EVP_PKEY_free(pkey); |
| EVP_PKEY_CTX_free(ctx); |
| EVP_SIGNATURE_free(sig); |
| return ret; |
| } |
| |
| static int lms_paramgen_fail_test(void) |
| { |
| int ret; |
| EVP_PKEY_CTX *ctx = NULL; |
| |
| ret = TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(libctx, "LMS", NULL)) |
| && TEST_int_eq(EVP_PKEY_paramgen_init(ctx), -2); |
| |
| EVP_PKEY_CTX_free(ctx); |
| return ret; |
| } |
| |
| static int lms_keygen_fail_test(void) |
| { |
| int ret; |
| EVP_PKEY_CTX *ctx = NULL; |
| |
| ret = TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(libctx, "LMS", NULL)) |
| && TEST_int_eq(EVP_PKEY_keygen_init(ctx), -2); |
| |
| EVP_PKEY_CTX_free(ctx); |
| return ret; |
| } |
| |
| static int lms_verify_fail_test(void) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[0]; |
| EVP_PKEY_CTX *ctx = NULL; |
| EVP_PKEY *pkey = NULL; |
| |
| if (!TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen)) |
| || !TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL))) |
| goto end; |
| /* Only one shot mode is supported, streaming fails to initialise */ |
| if (!TEST_int_eq(EVP_PKEY_verify_init(ctx), -2)) |
| goto end; |
| ret = 1; |
| end: |
| EVP_PKEY_CTX_free(ctx); |
| EVP_PKEY_free(pkey); |
| return ret; |
| } |
| |
| static int lms_verify_bad_sig_test(void) |
| { |
| int ret = 0, i = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[1]; |
| EVP_PKEY *pkey = NULL; |
| EVP_SIGNATURE *sig = NULL; |
| EVP_PKEY_CTX *ctx = NULL; |
| unsigned char *sig_data = NULL, corrupt_mask = 0x01; |
| /* |
| * Corrupt every 3rd byte to run less tests. The smallest element of an XDR |
| * encoding is 4 bytes, so this will corrupt every element. |
| * Memory sanitisation is slow, so a larger step size is used for this. |
| */ |
| #if defined(OSSL_SANITIZE_MEMORY) |
| const int step = (int)(td->siglen >> 1); |
| #else |
| const int step = 3; |
| #endif |
| |
| /* Copy the signature so that we can corrupt it */ |
| sig_data = OPENSSL_memdup(td->sig, td->siglen); |
| if (sig_data == NULL) |
| return 0; |
| |
| if (!TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen)) |
| || !TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL)) |
| || !TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL))) |
| goto end; |
| |
| if (!TEST_int_eq(EVP_PKEY_verify_message_init(ctx, sig, NULL), 1)) |
| goto end; |
| |
| for (i = 0; i < (int)td->siglen; i += step) { |
| sig_data[i] ^= corrupt_mask; /* corrupt a byte */ |
| if (i > 0) |
| sig_data[i - step] ^= corrupt_mask; /* Reset the previously corrupt byte */ |
| |
| if (!TEST_int_eq(EVP_PKEY_verify(ctx, sig_data, td->siglen, |
| td->msg, td->msglen), 0)) { |
| TEST_note("Incorrectly passed when %dth byte of signature" |
| " was corrupted", i); |
| goto end; |
| } |
| } |
| |
| ret = 1; |
| end: |
| EVP_SIGNATURE_free(sig); |
| EVP_PKEY_CTX_free(ctx); |
| EVP_PKEY_free(pkey); |
| OPENSSL_free(sig_data); |
| |
| return ret; |
| } |
| |
| /* |
| * Test that using the incorrect signature lengths (both shorter and longer) |
| * fail. |
| * NOTE: It does not get an out of bounds read due to the signature |
| * knowing how large it should be |
| */ |
| static int lms_verify_bad_sig_len_test(void) |
| { |
| int ret = 0; |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[1]; |
| EVP_PKEY *pkey = NULL; |
| EVP_SIGNATURE *sig = NULL; |
| EVP_PKEY_CTX *ctx = NULL; |
| size_t siglen = 0; |
| const int step = 3; |
| unsigned char sigdata[4096]; |
| |
| if (!TEST_ptr(pkey = lms_pubkey_from_data(td->pub, td->publen)) |
| || !TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL)) |
| || !TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL))) |
| goto end; |
| |
| if (!TEST_size_t_le(td->siglen + 16, sizeof(sigdata))) |
| goto end; |
| |
| OPENSSL_cleanse(sigdata, sizeof(sigdata)); |
| memcpy(sigdata, td->sig, td->siglen); |
| |
| ret = 0; |
| for (siglen = 0; siglen < td->siglen + 16; siglen += step) { |
| if (siglen == td->siglen) /* ignore the size that should pass */ |
| continue; |
| if (!TEST_int_eq(EVP_PKEY_verify_message_init(ctx, sig, NULL), 1)) |
| goto end; |
| if (!TEST_int_eq(EVP_PKEY_verify(ctx, sigdata, siglen, |
| td->msg, td->msglen), 0)) { |
| TEST_note("Incorrectly accepted signature key of length" |
| " %u (expected %u)", (unsigned)siglen, (unsigned)td->siglen); |
| goto end; |
| } |
| } |
| |
| ret = 1; |
| end: |
| EVP_SIGNATURE_free(sig); |
| EVP_PKEY_CTX_free(ctx); |
| EVP_PKEY_free(pkey); |
| |
| return ret; |
| } |
| |
| static int lms_verify_bad_pub_sig_test(void) |
| { |
| LMS_ACVP_TEST_DATA *td = &lms_testdata[1]; |
| int ret = 0, i = 0; |
| EVP_PKEY *pkey = NULL; |
| EVP_SIGNATURE *sig = NULL; |
| EVP_PKEY_CTX *ctx = NULL; |
| unsigned char *pub = NULL; |
| const int step = 1; |
| |
| /* Copy the public key data so that we can corrupt it */ |
| if (!TEST_ptr(pub = OPENSSL_memdup(td->pub, td->publen))) |
| return 0; |
| |
| if (!TEST_ptr(sig = EVP_SIGNATURE_fetch(libctx, "LMS", NULL))) |
| goto end; |
| |
| for (i = 0; i < (int)td->publen; i += step) { |
| pub[i] ^= 1; /* corrupt a byte */ |
| /* Corrupting the public key may cause the key load to fail */ |
| pkey = lms_pubkey_from_data(pub, td->publen); |
| if (pkey != NULL) { |
| if (!TEST_ptr(ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL))) |
| goto end; |
| /* We expect the verify to fail */ |
| if ((EVP_PKEY_verify_message_init(ctx, sig, NULL) == 1) |
| && !TEST_int_eq(EVP_PKEY_verify(ctx, td->sig, td->siglen, |
| td->msg, td->msglen), 0)) { |
| TEST_note("Incorrectly passed when byte %d of the public key" |
| " was corrupted", i); |
| goto end; |
| } |
| EVP_PKEY_free(pkey); |
| pkey = NULL; |
| EVP_PKEY_CTX_free(ctx); |
| ctx = NULL; |
| } |
| pub[i] ^= 1; /* restore the corrupted byte */ |
| } |
| |
| ret = 1; |
| end: |
| EVP_SIGNATURE_free(sig); |
| EVP_PKEY_CTX_free(ctx); |
| EVP_PKEY_free(pkey); |
| OPENSSL_free(pub); |
| |
| return ret; |
| } |
| |
| const OPTIONS *test_get_options(void) |
| { |
| static const OPTIONS options[] = { |
| OPT_TEST_OPTIONS_DEFAULT_USAGE, |
| { "config", OPT_CONFIG_FILE, '<', |
| "The configuration file to use for the libctx" }, |
| { NULL } |
| }; |
| return options; |
| } |
| |
| int setup_tests(void) |
| { |
| OPTION_CHOICE o; |
| char *config_file = NULL; |
| EVP_PKEY_CTX *ctx = NULL; |
| |
| /* Swap the libctx to test non-default context only */ |
| propq = "provider=default"; |
| |
| while ((o = opt_next()) != OPT_EOF) { |
| switch (o) { |
| case OPT_CONFIG_FILE: |
| config_file = opt_arg(); |
| propq = ""; |
| break; |
| case OPT_TEST_CASES: |
| break; |
| default: |
| case OPT_ERR: |
| return 0; |
| } |
| } |
| if (!test_get_libctx(&libctx, &nullprov, config_file, &libprov, NULL)) |
| return 0; |
| |
| ctx = EVP_PKEY_CTX_new_from_name(libctx, "LMS", propq); |
| if (ctx == NULL && ERR_get_error() == EVP_R_UNSUPPORTED_ALGORITHM) |
| return TEST_skip("LMS algorithm is not available in provider"); |
| EVP_PKEY_CTX_free(ctx); |
| |
| 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); |
| ADD_ALL_TESTS(lms_verify_test, OSSL_NELEM(lms_testdata)); |
| ADD_TEST(lms_verify_fail_test); |
| ADD_TEST(lms_digest_verify_fail_test); |
| ADD_TEST(lms_digest_signing_fail_test); |
| ADD_TEST(lms_message_signing_fail_test); |
| ADD_TEST(lms_paramgen_fail_test); |
| ADD_TEST(lms_keygen_fail_test); |
| ADD_TEST(lms_verify_bad_sig_test); |
| ADD_TEST(lms_verify_bad_sig_len_test); |
| ADD_TEST(lms_verify_bad_pub_sig_test); |
| |
| return 1; |
| } |
| |
| void cleanup_tests(void) |
| { |
| OSSL_PROVIDER_unload(nullprov); |
| OSSL_PROVIDER_unload(libprov); |
| OSSL_LIB_CTX_free(libctx); |
| } |