Skip to content

Commit ec3e26d

Browse files
committed
PQC: address skoll review (empty msg, NULL-key reinit, KEM mixed-NULL, get_params, KEM op name, debian pqc)
1 parent c2bd794 commit ec3e26d

9 files changed

Lines changed: 182 additions & 28 deletions

File tree

debian/install-wolfssl.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ install_wolfssl_from_git() {
4040
local debug_mode="$3"
4141
local reinstall_mode="$4"
4242
local no_install="$5"
43+
local pqc_mode="$6"
4344
local main_branch="master"
4445

4546
# If no working directory specified, create one using mktemp
@@ -173,6 +174,11 @@ AC_CONFIG_FILES([debian/rules],[chmod +x debian/rules])' configure.ac
173174
echo "Debug mode enabled"
174175
fi
175176

177+
if [ "$pqc_mode" = "true" ]; then
178+
configure_opts="$configure_opts --enable-mlkem --enable-mldsa"
179+
echo "PQC (ML-KEM/ML-DSA) enabled"
180+
fi
181+
176182
./configure $configure_opts \
177183
CFLAGS="-DWOLFSSL_OLD_OID_SUM \
178184
-DWOLFSSL_PUBLIC_ASN \
@@ -220,6 +226,7 @@ main() {
220226
local debug_mode="false"
221227
local reinstall_mode="false"
222228
local no_install="false"
229+
local pqc_mode="false"
223230

224231
# Parse command line arguments
225232
while [[ $# -gt 0 ]]; do
@@ -233,6 +240,7 @@ main() {
233240
echo " -d, --debug Enable debug build mode (adds --enable-debug)"
234241
echo " -r, --reinstall Force reinstall even if packages are already installed"
235242
echo " -n, --no-install Build only, do not install packages"
243+
echo " --enable-pqc Enable ML-KEM and ML-DSA (FIPS 203/204)"
236244
echo " -h, --help Show this help message"
237245
echo ""
238246
echo "Arguments:"
@@ -264,6 +272,10 @@ main() {
264272
no_install="true"
265273
shift
266274
;;
275+
--enable-pqc)
276+
pqc_mode="true"
277+
shift
278+
;;
267279
-*)
268280
echo "Unknown option: $1" >&2
269281
echo "Use --help for usage information" >&2
@@ -300,7 +312,7 @@ main() {
300312
echo "Building wolfSSL master branch"
301313
fi
302314

303-
install_wolfssl_from_git "$work_dir" "$git_tag" "$debug_mode" "$reinstall_mode" "$no_install"
315+
install_wolfssl_from_git "$work_dir" "$git_tag" "$debug_mode" "$reinstall_mode" "$no_install" "$pqc_mode"
304316

305317
echo "WolfSSL installation completed successfully"
306318
}

scripts/build-wolfprovider.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ if [ -n "$build_debian" ]; then
216216
if [ "$WOLFPROV_REPLACE_DEFAULT" = "1" ]; then
217217
OPENSSL_OPTS+=" --replace-default"
218218
fi
219+
if [ "$WOLFPROV_PQC" = "1" ]; then
220+
WOLFSSL_OPTS+=" --enable-pqc"
221+
fi
219222

220223
# wolfSSL and OpenSSL are independent and must be built first
221224
debian/install-wolfssl.sh $WOLFSSL_OPTS --no-install -r $DEB_OUTPUT_DIR

src/wp_mldsa_kmgmt.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,8 +774,10 @@ static int wp_mldsa_get_params(wp_MlDsa* mldsa, OSSL_PARAM params[])
774774
p->return_size = outLen;
775775
}
776776
else if (p->data_size < outLen) {
777-
/* Buffer too small: report required size, let caller retry. */
777+
/* Buffer too small: report required size and fail so the
778+
* caller can retry; do not claim a completed export. */
778779
p->return_size = outLen;
780+
ok = 0;
779781
}
780782
else {
781783
outLen = (word32)p->data_size;
@@ -802,6 +804,7 @@ static int wp_mldsa_get_params(wp_MlDsa* mldsa, OSSL_PARAM params[])
802804
}
803805
else if (p->data_size < outLen) {
804806
p->return_size = outLen;
807+
ok = 0;
805808
}
806809
else {
807810
outLen = (word32)p->data_size;

src/wp_mldsa_sig.c

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,24 @@ static int wp_mldsa_init(wp_MlDsaSigCtx* ctx, wp_MlDsa* mldsa,
224224

225225
(void)params;
226226

227-
if ((ctx == NULL) || (mldsa == NULL)) {
227+
if (ctx == NULL) {
228228
ok = 0;
229229
}
230-
if (ok && !wp_mldsa_up_ref(mldsa)) {
230+
/* NULL key means "reinit, reuse the key already on the context" -- only
231+
* valid if the context actually has one. */
232+
if (ok && (mldsa == NULL) && (ctx->mldsa == NULL)) {
231233
ok = 0;
232234
}
235+
if (ok && (mldsa != NULL)) {
236+
if (!wp_mldsa_up_ref(mldsa)) {
237+
ok = 0;
238+
}
239+
if (ok) {
240+
wp_mldsa_free(ctx->mldsa);
241+
ctx->mldsa = mldsa;
242+
}
243+
}
233244
if (ok) {
234-
wp_mldsa_free(ctx->mldsa);
235-
ctx->mldsa = mldsa;
236245
wp_mldsa_buf_reset(ctx);
237246
}
238247
return ok;
@@ -269,12 +278,22 @@ static int wp_mldsa_sign(wp_MlDsaSigCtx* ctx, unsigned char* sig,
269278
int ok = 1;
270279
int rc;
271280
word32 sigSz;
281+
/* FIPS 204 permits an empty message; give wolfSSL a valid pointer so a
282+
* NULL+0 message does not become a backend-dependent NULL deref. */
283+
unsigned char dummy = 0;
284+
const unsigned char* m = msg;
272285

273286
(void)sigSize;
274287

275288
if ((ctx == NULL) || (ctx->mldsa == NULL) || (sigLen == NULL)) {
276289
return 0;
277290
}
291+
if ((msg == NULL) && (msgLen != 0)) {
292+
return 0;
293+
}
294+
if (m == NULL) {
295+
m = &dummy;
296+
}
278297

279298
sigSz = (word32)wp_mldsa_get_sig_size(ctx->mldsa);
280299

@@ -297,7 +316,7 @@ static int wp_mldsa_sign(wp_MlDsaSigCtx* ctx, unsigned char* sig,
297316
* default; use the ctx variant with empty ctx to interop. */
298317
rc = wc_MlDsaKey_SignCtx(
299318
(wc_MlDsaKey*)wp_mldsa_get_key(ctx->mldsa), NULL, 0, sig, &outLen,
300-
msg, (word32)msgLen, &ctx->rng);
319+
m, (word32)msgLen, &ctx->rng);
301320
if (rc != 0) {
302321
ok = 0;
303322
}
@@ -324,11 +343,19 @@ static int wp_mldsa_verify(wp_MlDsaSigCtx* ctx, const unsigned char* sig,
324343
int ok = 1;
325344
int rc;
326345
int res = 0;
346+
/* FIPS 204 permits an empty message; give wolfSSL a valid pointer. */
347+
unsigned char dummy = 0;
348+
const unsigned char* m = msg;
327349

328-
if ((ctx == NULL) || (ctx->mldsa == NULL) || (sig == NULL) ||
329-
(msg == NULL)) {
350+
if ((ctx == NULL) || (ctx->mldsa == NULL) || (sig == NULL)) {
330351
return 0;
331352
}
353+
if ((msg == NULL) && (msgLen != 0)) {
354+
return 0;
355+
}
356+
if (m == NULL) {
357+
m = &dummy;
358+
}
332359
/* wolfSSL's ML-DSA API takes 32-bit lengths. Reject oversize inputs
333360
* explicitly rather than silently truncating. */
334361
if ((sigLen > 0xFFFFFFFFU) || (msgLen > 0xFFFFFFFFU)) {
@@ -338,7 +365,7 @@ static int wp_mldsa_verify(wp_MlDsaSigCtx* ctx, const unsigned char* sig,
338365
/* Match the sign path: FIPS 204 pure ML-DSA with empty context. */
339366
rc = wc_MlDsaKey_VerifyCtx(
340367
(wc_MlDsaKey*)wp_mldsa_get_key(ctx->mldsa), sig, (word32)sigLen,
341-
NULL, 0, msg, (word32)msgLen, &res);
368+
NULL, 0, m, (word32)msgLen, &res);
342369
if ((rc != 0) || (res != 1)) {
343370
ok = 0;
344371
}

src/wp_mlkem_kem.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,9 @@ static int wp_mlkem_kem_encapsulate(wp_MlKemCtx* ctx, unsigned char* out,
181181
ctSize = wp_mlkem_data_ct_size(data);
182182
ssSize = WP_MLKEM_SS_SIZE;
183183

184-
/* Size-only query: out == NULL with outLen/secretLen set per OpenSSL
185-
* KEM encapsulate contract. Mixed-NULL is a caller bug, not a size
186-
* query, so reject it explicitly. */
187-
if (out == NULL) {
184+
/* Size-only query: both output buffers NULL. A mixed-NULL request (one
185+
* buffer NULL, the other not) is a caller bug, not a size query. */
186+
if ((out == NULL) && (secret == NULL)) {
188187
if (outLen != NULL) {
189188
*outLen = ctSize;
190189
}
@@ -193,7 +192,8 @@ static int wp_mlkem_kem_encapsulate(wp_MlKemCtx* ctx, unsigned char* out,
193192
}
194193
return 1;
195194
}
196-
if ((secret == NULL) || (outLen == NULL) || (secretLen == NULL)) {
195+
if ((out == NULL) || (secret == NULL) || (outLen == NULL) ||
196+
(secretLen == NULL)) {
197197
return 0;
198198
}
199199

src/wp_mlkem_kmgmt.c

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -754,8 +754,10 @@ static int wp_mlkem_get_params(wp_MlKem* mlkem, OSSL_PARAM params[])
754754
p->return_size = outLen;
755755
}
756756
else if (p->data_size < outLen) {
757-
/* Buffer too small: report required size, let caller retry. */
757+
/* Buffer too small: report required size and fail so the
758+
* caller can retry; do not claim a completed export. */
758759
p->return_size = outLen;
760+
ok = 0;
759761
}
760762
else {
761763
rc = wc_MlKemKey_EncodePublicKey(&mlkem->key,
@@ -781,6 +783,7 @@ static int wp_mlkem_get_params(wp_MlKem* mlkem, OSSL_PARAM params[])
781783
}
782784
else if (p->data_size < outLen) {
783785
p->return_size = outLen;
786+
ok = 0;
784787
}
785788
else {
786789
rc = wc_MlKemKey_EncodePrivateKey(&mlkem->key,
@@ -949,19 +952,24 @@ static void wp_mlkem_gen_cleanup(wp_MlKemGenCtx* ctx)
949952
}
950953
}
951954

952-
/**
953-
* Return the algorithm name for OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME.
954-
*
955-
* ML-KEM has no associated operation name lookup; return NULL so OpenSSL
956-
* falls back to the algorithm name from the dispatch table.
957-
*
958-
* @param [in] op Operation type. Unused.
959-
* @return NULL.
960-
*/
961-
static const char* wp_mlkem_query_operation_name(int op)
955+
/* Map each ML-KEM key type to its KEM operation name so OpenSSL fetches the
956+
* matching KEM implementation without relying on fallback lookup. */
957+
static const char* wp_mlkem512_query_operation_name(int op)
958+
{
959+
(void)op;
960+
return WP_NAMES_ML_KEM_512;
961+
}
962+
963+
static const char* wp_mlkem768_query_operation_name(int op)
964+
{
965+
(void)op;
966+
return WP_NAMES_ML_KEM_768;
967+
}
968+
969+
static const char* wp_mlkem1024_query_operation_name(int op)
962970
{
963971
(void)op;
964-
return NULL;
972+
return WP_NAMES_ML_KEM_1024;
965973
}
966974

967975
/* Per-level new() and gen_init() trampolines. */
@@ -1036,7 +1044,7 @@ const OSSL_DISPATCH wp_##alg##_keymgmt_functions[] = { \
10361044
{ OSSL_FUNC_KEYMGMT_EXPORT_TYPES, \
10371045
(DFUNC)wp_mlkem_export_types }, \
10381046
{ OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, \
1039-
(DFUNC)wp_mlkem_query_operation_name }, \
1047+
(DFUNC)wp_##alg##_query_operation_name }, \
10401048
{ 0, NULL } \
10411049
};
10421050

test/test_mldsa.c

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,4 +838,101 @@ int test_mldsa_import_mismatched_pubpriv(void* data)
838838
return err;
839839
}
840840

841+
/* FIPS 204 permits an empty message: sign and verify zero-length input. */
842+
int test_mldsa_empty_message(void* data)
843+
{
844+
int err = 0;
845+
size_t i;
846+
EVP_PKEY* k = NULL;
847+
EVP_MD_CTX* mdctx = NULL;
848+
unsigned char* sig = NULL;
849+
size_t sigLen = 0;
850+
851+
(void)data;
852+
for (i = 0; (err == 0) && (i < MLDSA_LEVEL_COUNT); i++) {
853+
PRINT_MSG("Empty message %s", mldsa_levels[i].name);
854+
855+
err = mldsa_keygen(mldsa_levels[i].name, &k);
856+
if (err == 0) {
857+
mdctx = EVP_MD_CTX_new();
858+
err = (mdctx == NULL);
859+
}
860+
if (err == 0) {
861+
err = EVP_DigestSignInit_ex(mdctx, NULL, NULL, wpLibCtx, NULL, k,
862+
NULL) != 1;
863+
}
864+
/* No update calls: message is the empty string. */
865+
if (err == 0) {
866+
err = EVP_DigestSign(mdctx, NULL, &sigLen, NULL, 0) != 1;
867+
}
868+
if (err == 0) {
869+
sig = (unsigned char*)OPENSSL_malloc(sigLen);
870+
err = (sig == NULL);
871+
}
872+
if (err == 0) {
873+
err = EVP_DigestSign(mdctx, sig, &sigLen, NULL, 0) != 1;
874+
if (err) PRINT_ERR_MSG("Empty-message sign failed");
875+
}
876+
if (err == 0) {
877+
err = mldsa_verify_msg(k, NULL, 0, sig, sigLen) != 1;
878+
if (err) PRINT_ERR_MSG("Empty-message verify failed");
879+
}
880+
881+
OPENSSL_free(sig); sig = NULL; sigLen = 0;
882+
EVP_MD_CTX_free(mdctx); mdctx = NULL;
883+
EVP_PKEY_free(k); k = NULL;
884+
}
885+
return err;
886+
}
887+
888+
/* Reinitialize a sign context with a NULL key: the key already on the
889+
* context must be reused (OpenSSL reinit contract). */
890+
int test_mldsa_reinit_null_key(void* data)
891+
{
892+
static const unsigned char msg[16] = "mldsa-reinit-msg";
893+
int err = 0;
894+
EVP_PKEY* k = NULL;
895+
EVP_MD_CTX* mdctx = NULL;
896+
unsigned char* sig = NULL;
897+
size_t sigLen = 0;
898+
899+
(void)data;
900+
PRINT_MSG("Reinit with NULL key reuses context key");
901+
902+
err = mldsa_keygen("ML-DSA-44", &k);
903+
if (err == 0) {
904+
mdctx = EVP_MD_CTX_new();
905+
err = (mdctx == NULL);
906+
}
907+
if (err == 0) {
908+
err = EVP_DigestSignInit_ex(mdctx, NULL, NULL, wpLibCtx, NULL, k,
909+
NULL) != 1;
910+
}
911+
/* Reinit with NULL pkey: reuse the key already attached. */
912+
if (err == 0) {
913+
err = EVP_DigestSignInit_ex(mdctx, NULL, NULL, wpLibCtx, NULL, NULL,
914+
NULL) != 1;
915+
if (err) PRINT_ERR_MSG("Reinit with NULL key failed");
916+
}
917+
if (err == 0) {
918+
err = EVP_DigestSign(mdctx, NULL, &sigLen, msg, sizeof(msg)) != 1;
919+
}
920+
if (err == 0) {
921+
sig = (unsigned char*)OPENSSL_malloc(sigLen);
922+
err = (sig == NULL);
923+
}
924+
if (err == 0) {
925+
err = EVP_DigestSign(mdctx, sig, &sigLen, msg, sizeof(msg)) != 1;
926+
}
927+
if (err == 0) {
928+
err = mldsa_verify_msg(k, msg, sizeof(msg), sig, sigLen) != 1;
929+
if (err) PRINT_ERR_MSG("Sign after NULL-key reinit did not verify");
930+
}
931+
932+
OPENSSL_free(sig);
933+
EVP_MD_CTX_free(mdctx);
934+
EVP_PKEY_free(k);
935+
return err;
936+
}
937+
841938
#endif /* WP_HAVE_MLDSA */

test/unit.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,8 @@ TEST_CASE test_case[] = {
506506
TEST_DECL(test_mldsa_get_params, NULL),
507507
TEST_DECL(test_mldsa_digest_sign_init_rejects_md, NULL),
508508
TEST_DECL(test_mldsa_import_mismatched_pubpriv, NULL),
509+
TEST_DECL(test_mldsa_empty_message, NULL),
510+
TEST_DECL(test_mldsa_reinit_null_key, NULL),
509511
#endif
510512
};
511513
#define TEST_CASE_CNT (int)(sizeof(test_case) / sizeof(*test_case))

test/unit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,8 @@ int test_mldsa_oneshot_sign_verify(void *data);
504504
int test_mldsa_get_params(void *data);
505505
int test_mldsa_digest_sign_init_rejects_md(void *data);
506506
int test_mldsa_import_mismatched_pubpriv(void *data);
507+
int test_mldsa_empty_message(void *data);
508+
int test_mldsa_reinit_null_key(void *data);
507509
#endif
508510

509511
#endif /* UNIT_H */

0 commit comments

Comments
 (0)