From 957c941fc4f060cf834e6aa13680ac365257ebcc Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 14:37:31 -0600 Subject: [PATCH 01/10] F-4434: honor caller-provided saltLen in Rsa.wc_RsaPSS_VerifyCheck() JNI wrapper --- jni/jni_rsa.c | 33 +++++++++++++--- .../com/wolfssl/wolfcrypt/test/RsaTest.java | 38 +++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/jni/jni_rsa.c b/jni/jni_rsa.c index fce01c72..52c12682 100644 --- a/jni/jni_rsa.c +++ b/jni/jni_rsa.c @@ -46,6 +46,11 @@ #define RNG WC_RNG #endif +/* RSA_PSS_SALT_LEN_DEFAULT not in FIPSv2 */ +#ifndef RSA_PSS_SALT_LEN_DEFAULT + #define RSA_PSS_SALT_LEN_DEFAULT (-1) +#endif + JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_Rsa_mallocNativeStruct( JNIEnv* env, jobject this) @@ -1717,11 +1722,29 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPSS_1VerifyCheck( if (ret == 0) { XMEMSET(output, 0, outputSz); - ret = wc_RsaPSS_VerifyCheck(signature, signatureSz, output, outputSz, - digest, digestSz, (enum wc_HashType)hashType, mgf, key); - } - if (ret > 0) { - result = JNI_TRUE; + if ((saltLen == RSA_PSS_SALT_LEN_DEFAULT) || + (saltLen == (int)digestSz)) { + + ret = wc_RsaPSS_VerifyCheck(signature, signatureSz, output, + outputSz, digest, digestSz, (enum wc_HashType)hashType, + mgf, key); + if (ret > 0) { + result = JNI_TRUE; + } + } + else { + /* Non-default salt length */ + ret = wc_RsaPSS_Verify_ex(signature, signatureSz, output, + outputSz, (enum wc_HashType)hashType, mgf, saltLen, key); + if (ret > 0) { + /* Verify PSS padding against the provided digest */ + ret = wc_RsaPSS_CheckPadding_ex(digest, digestSz, output, + (word32)ret, (enum wc_HashType)hashType, saltLen, 0); + if (ret == 0) { + result = JNI_TRUE; + } + } + } } LogStr("wc_RsaPSS_VerifyCheck(sig, sigSz, out, outSz, digest, " diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java index 86aebf6c..29303e82 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java @@ -1261,6 +1261,44 @@ public void testRsaPssVerifyCheck() { digest, WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, 32); assertTrue("RSA-PSS check verification failed", verified); + /* Test with non-default salt length, + * saltLen != digest length must be honored on verify */ + signature = key.rsaPssSign(digest, + WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, 16, rng); + assertNotNull(signature); + assertTrue(signature.length > 0); + + verified = key.rsaPssVerifyWithDigest(signature, message, + digest, WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, 16); + assertTrue("RSA-PSS check verification failed with " + + "non-default salt length", verified); + + /* Verification with wrong salt length should fail */ + try { + verified = key.rsaPssVerifyWithDigest(signature, message, + digest, WolfCrypt.WC_HASH_TYPE_SHA256, + Rsa.WC_MGF1SHA256, 32); + } catch (WolfCryptException e) { + /* native error acceptable here, treat as not verified */ + verified = false; + } + assertFalse("RSA-PSS check verification passed with " + + "mismatched salt length", verified); + + /* Test special value RSA_PSS_SALT_LEN_DEFAULT, sign uses + * salt length equal to digest length */ + signature = key.rsaPssSign(digest, + WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, + Rsa.RSA_PSS_SALT_LEN_DEFAULT, rng); + assertNotNull(signature); + assertTrue(signature.length > 0); + + verified = key.rsaPssVerifyWithDigest(signature, message, + digest, WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, + Rsa.RSA_PSS_SALT_LEN_DEFAULT); + assertTrue("RSA-PSS check verification failed with " + + "RSA_PSS_SALT_LEN_DEFAULT", verified); + } catch (Exception e) { fail("RSA-PSS check verification test failed: " + e.getMessage()); } From 270e16c3e7709bf889f5a1ac96801d58af18712f Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 14:49:59 -0600 Subject: [PATCH 02/10] F-5402: update Android gradlew scripts to newer version with security hardening --- IDE/Android/gradlew | 236 ++++++++++++++++++++++------------------ IDE/Android/gradlew.bat | 160 +++++++++++++-------------- 2 files changed, 207 insertions(+), 189 deletions(-) diff --git a/IDE/Android/gradlew b/IDE/Android/gradlew index cccdd3d5..cf09c3c8 100755 --- a/IDE/Android/gradlew +++ b/IDE/Android/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/bin/sh ############################################################################## ## @@ -7,72 +7,65 @@ ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,88 +74,121 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then diff --git a/IDE/Android/gradlew.bat b/IDE/Android/gradlew.bat index e95643d6..cd1f2e6c 100644 --- a/IDE/Android/gradlew.bat +++ b/IDE/Android/gradlew.bat @@ -1,84 +1,76 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From d8a68e97c2100d103163c9860b35e9c343f27c36 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 15:01:31 -0600 Subject: [PATCH 03/10] F-3569: fix authTag native buffer leak in AesGcm.wc_AesGcmEncrypt() JNI wrapper --- jni/jni_aesgcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jni/jni_aesgcm.c b/jni/jni_aesgcm.c index 51e6818c..a55bea0a 100644 --- a/jni/jni_aesgcm.c +++ b/jni/jni_aesgcm.c @@ -234,11 +234,11 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncrypt } } - /* Commit authTag changes back to original Java array on success */ + /* Commit authTag changes back to original Java array on success. */ if (authTagArr != NULL) { if (ret == 0) { (*env)->ReleaseByteArrayElements(env, authTagArr, - (jbyte*)authTag, JNI_COMMIT); + (jbyte*)authTag, 0); } else { (*env)->ReleaseByteArrayElements(env, authTagArr, From bfd370e104c8a1aac9a30d96f695ba215efc7c81 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 15:05:18 -0600 Subject: [PATCH 04/10] F-3570: fix authTag native buffer leak in AesCcm.wc_AesCcmEncrypt() JNI wrapper --- jni/jni_aesccm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jni/jni_aesccm.c b/jni/jni_aesccm.c index 574231b2..b33ccf8c 100644 --- a/jni/jni_aesccm.c +++ b/jni/jni_aesccm.c @@ -233,11 +233,11 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesCcm_wc_1AesCcmEncrypt } } - /* Commit authTag changes back to original Java array on success */ + /* Commit authTag changes back to original Java array on success. */ if (authTagArr != NULL) { if (ret == 0) { (*env)->ReleaseByteArrayElements(env, authTagArr, - (jbyte*)authTag, JNI_COMMIT); + (jbyte*)authTag, 0); } else { (*env)->ReleaseByteArrayElements(env, authTagArr, From d4205956c8f6f19d0e07d7f699be98cf27a117ce Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 15:20:28 -0600 Subject: [PATCH 05/10] F-5397: handle null file/directory args in CertManagerLoadCA() JNI wrapper --- jni/jni_wolfssl_cert_manager.c | 26 +++- .../wolfcrypt/test/WolfCryptTestSuite.java | 1 + .../test/WolfSSLCertManagerTest.java | 133 ++++++++++++++++++ 3 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java diff --git a/jni/jni_wolfssl_cert_manager.c b/jni/jni_wolfssl_cert_manager.c index c14e33de..8a497ea9 100644 --- a/jni/jni_wolfssl_cert_manager.c +++ b/jni/jni_wolfssl_cert_manager.c @@ -369,13 +369,31 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager return (jint)BAD_FUNC_ARG; } - certFile = (*env)->GetStringUTFChars(env, f, 0); - certPath = (*env)->GetStringUTFChars(env, d, 0); + /* f and d are both optional, pass NULL through to native wolfSSL */ + if (f != NULL) { + certFile = (*env)->GetStringUTFChars(env, f, 0); + if (certFile == NULL) { + return (jint)MEMORY_E; + } + } + if (d != NULL) { + certPath = (*env)->GetStringUTFChars(env, d, 0); + if (certPath == NULL) { + if (certFile != NULL) { + (*env)->ReleaseStringUTFChars(env, f, certFile); + } + return (jint)MEMORY_E; + } + } ret = wolfSSL_CertManagerLoadCA(cm, certFile, certPath); - (*env)->ReleaseStringUTFChars(env, f, certFile); - (*env)->ReleaseStringUTFChars(env, d, certPath); + if (certFile != NULL) { + (*env)->ReleaseStringUTFChars(env, f, certFile); + } + if (certPath != NULL) { + (*env)->ReleaseStringUTFChars(env, d, certPath); + } return (jint)ret; #else diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java index f5b7b9ae..9b03a67a 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java @@ -58,6 +58,7 @@ XmssTest.class, LmsTest.class, WolfObjectTest.class, + WolfSSLCertManagerTest.class, WolfSSLCertManagerOCSPTest.class, WolfSSLX509StoreCtxTest.class, WolfCryptTest.class diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java new file mode 100644 index 00000000..e7da973b --- /dev/null +++ b/src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java @@ -0,0 +1,133 @@ +/* WolfSSLCertManagerTest.java + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.wolfcrypt.test; + +import static org.junit.Assert.*; + +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runners.model.Statement; +import org.junit.runner.Description; + +import java.io.File; + +import com.wolfssl.wolfcrypt.WolfSSLCertManager; +import com.wolfssl.wolfcrypt.WolfCryptError; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * JUnit4 test cases for WolfSSLCertManager CA loading functionality. + */ +public class WolfSSLCertManagerTest { + + private static String certPre = ""; + private static String caCertPem = null; + + @Rule(order = Integer.MIN_VALUE) + public TestRule testWatcher = TimedTestWatcher.create(); + + /* Rule to check if cert files are available, skips tests if not. */ + @Rule(order = Integer.MIN_VALUE + 1) + public TestRule certFilesAvailable = new TestRule() { + @Override + public Statement apply(final Statement base, + Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + File f = new File(caCertPem); + Assume.assumeTrue("Test cert files not available: " + + caCertPem, f.exists()); + base.evaluate(); + } + }; + } + }; + + /** + * Test if this environment is Android. + * @return true if Android, otherwise false + */ + private static boolean isAndroid() { + if (System.getProperty("java.runtime.name").contains("Android")) { + return true; + } + return false; + } + + @BeforeClass + public static void testSetup() throws Exception { + + System.out.println("JNI WolfSSLCertManager Class"); + + if (isAndroid()) { + /* On Android, example certs/keys are on SD card */ + certPre = "/data/local/tmp/"; + } + + /* Set paths to example certs */ + caCertPem = certPre.concat("examples/certs/ca-cert.pem"); + } + + @Test + public void testCertManagerLoadCAFromFileNullDir() throws Exception { + + WolfSSLCertManager cm = new WolfSSLCertManager(); + + /* Directory argument is documented as optional, null should + * be passed through to native wolfSSL without error */ + try { + cm.CertManagerLoadCA(caCertPem, null); + + } catch (WolfCryptException e) { + if (e.getError() == WolfCryptError.NOT_COMPILED_IN) { + /* Skip test if filesystem support not compiled in */ + Assume.assumeNoException(e); + } + throw e; + + } finally { + cm.free(); + } + } + + @Test + public void testCertManagerLoadCANullFileNullDir() throws Exception { + + WolfSSLCertManager cm = new WolfSSLCertManager(); + + /* Both arguments null should throw exception, not crash */ + try { + cm.CertManagerLoadCA(null, null); + fail("CertManagerLoadCA(null, null) should throw exception"); + + } catch (WolfCryptException e) { + /* expected */ + + } finally { + cm.free(); + } + } +} From bdf8f8ac2d1ed2d9d0c48aac43dd7fc7007afaa5 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 15:35:21 -0600 Subject: [PATCH 06/10] F-5398: honor caller-provided buffer sizes in WolfSSLCertManager JNI wrappers --- jni/jni_wolfssl_cert_manager.c | 40 ++--- .../test/WolfSSLCertManagerTest.java | 149 +++++++++++++++++- 2 files changed, 168 insertions(+), 21 deletions(-) diff --git a/jni/jni_wolfssl_cert_manager.c b/jni/jni_wolfssl_cert_manager.c index 8a497ea9..c7e7265a 100644 --- a/jni/jni_wolfssl_cert_manager.c +++ b/jni/jni_wolfssl_cert_manager.c @@ -415,12 +415,13 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr; (void)jcl; - if (env == NULL || in == NULL || (sz < 0)) { + if (env == NULL || in == NULL || (sz < 0) || + (sz > (jlong)(*env)->GetArrayLength(env, in))) { return BAD_FUNC_ARG; } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); - buffSz = (*env)->GetArrayLength(env, in); + buffSz = (word32)sz; ret = wolfSSL_CertManagerLoadCABuffer(cm, buff, buffSz, format); @@ -437,14 +438,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager byte* buff = NULL; WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr; (void)jcl; - (void)sz; - if (env == NULL || in == NULL) { + if (env == NULL || in == NULL || (sz < 0) || + (sz > (jlong)(*env)->GetArrayLength(env, in))) { return BAD_FUNC_ARG; } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); - buffSz = (*env)->GetArrayLength(env, in); + buffSz = (word32)sz; ret = wolfSSL_CertManagerLoadCABuffer_ex(cm, buff, buffSz, format, 0, (word32)flags); @@ -479,12 +480,13 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr; (void)jcl; - if (env == NULL || in == NULL || (sz < 0)) { + if (env == NULL || in == NULL || (sz < 0) || + (sz > (jlong)(*env)->GetArrayLength(env, in))) { return BAD_FUNC_ARG; } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); - buffSz = (*env)->GetArrayLength(env, in); + buffSz = (word32)sz; ret = wolfSSL_CertManagerVerifyBuffer(cm, buff, buffSz, format); @@ -546,12 +548,13 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr; (void)jcl; - if (env == NULL || in == NULL || (sz < 0)) { + if (env == NULL || in == NULL || (sz < 0) || + (sz > (jlong)(*env)->GetArrayLength(env, in))) { return BAD_FUNC_ARG; } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); - buffSz = (*env)->GetArrayLength(env, in); + buffSz = (word32)sz; ret = wolfSSL_CertManagerLoadCRLBuffer(cm, buff, buffSz, type); @@ -655,7 +658,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr; (void)jcl; - if (env == NULL || cm == NULL || cert == NULL || (sz < 0)) { + if (env == NULL || cm == NULL || cert == NULL || (sz < 0) || + (sz > (*env)->GetArrayLength(env, cert))) { return BAD_FUNC_ARG; } @@ -663,7 +667,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager if (certBuf == NULL) { return MEMORY_E; } - certSz = (*env)->GetArrayLength(env, cert); + certSz = (word32)sz; ret = wolfSSL_CertManagerCheckOCSP(cm, certBuf, certSz); @@ -710,10 +714,11 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager OcspRequest* request = NULL; DecodedCert* cert_decoded = NULL; (void)jcl; - (void)responseSz; if (env == NULL || cm == NULL || response == NULL || cert == NULL || - (responseSz < 0) || (certSz < 0)) { + (responseSz < 0) || (certSz < 0) || + (responseSz > (*env)->GetArrayLength(env, response)) || + (certSz > (*env)->GetArrayLength(env, cert))) { return BAD_FUNC_ARG; } @@ -722,7 +727,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager if (respBuf == NULL) { return MEMORY_E; } - respBufSz = (*env)->GetArrayLength(env, response); + respBufSz = (word32)responseSz; /* Get cert buffer and size */ certBuf = (byte*)(*env)->GetByteArrayElements(env, cert, NULL); @@ -731,7 +736,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager (jbyte*)respBuf, JNI_ABORT); return MEMORY_E; } - certBufSz = (*env)->GetArrayLength(env, cert); + certBufSz = (word32)certSz; /* Allocate request and decoded cert structures */ request = wolfSSL_OCSP_REQUEST_new(); @@ -833,14 +838,13 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_OcspRespons const unsigned char* p = NULL; OcspResponse* ocspResp = NULL; (void)jcl; - (void)responseSz; if (env == NULL || response == NULL) { return BAD_FUNC_ARG; } arrLen = (*env)->GetArrayLength(env, response); - if (arrLen <= 0) { + if (arrLen <= 0 || responseSz <= 0 || responseSz > arrLen) { return BAD_FUNC_ARG; } @@ -850,7 +854,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_OcspRespons } p = (const unsigned char*)respBuf; - ocspResp = wolfSSL_d2i_OCSP_RESPONSE(NULL, &p, (int)arrLen); + ocspResp = wolfSSL_d2i_OCSP_RESPONSE(NULL, &p, (int)responseSz); if (ocspResp != NULL) { ret = wolfSSL_OCSP_response_status(ocspResp); wolfSSL_OCSP_RESPONSE_free(ocspResp); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java index e7da973b..0e18c6f7 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/WolfSSLCertManagerTest.java @@ -32,7 +32,10 @@ import org.junit.runner.Description; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import com.wolfssl.wolfcrypt.WolfCrypt; import com.wolfssl.wolfcrypt.WolfSSLCertManager; import com.wolfssl.wolfcrypt.WolfCryptError; import com.wolfssl.wolfcrypt.WolfCryptException; @@ -44,6 +47,10 @@ public class WolfSSLCertManagerTest { private static String certPre = ""; private static String caCertPem = null; + private static String caCertDer = null; + private static String serverCertDer = null; + private static String caEccCertPem = null; + private static String serverEccDer = null; @Rule(order = Integer.MIN_VALUE) public TestRule testWatcher = TimedTestWatcher.create(); @@ -57,9 +64,13 @@ public Statement apply(final Statement base, return new Statement() { @Override public void evaluate() throws Throwable { - File f = new File(caCertPem); - Assume.assumeTrue("Test cert files not available: " + - caCertPem, f.exists()); + String[] certs = { caCertPem, caCertDer, serverCertDer, + caEccCertPem, serverEccDer }; + for (String cert : certs) { + File f = new File(cert); + Assume.assumeTrue("Test cert files not available: " + + cert, f.exists()); + } base.evaluate(); } }; @@ -89,6 +100,10 @@ public static void testSetup() throws Exception { /* Set paths to example certs */ caCertPem = certPre.concat("examples/certs/ca-cert.pem"); + caCertDer = certPre.concat("examples/certs/ca-cert.der"); + serverCertDer = certPre.concat("examples/certs/server-cert.der"); + caEccCertPem = certPre.concat("examples/certs/ca-ecc-cert.pem"); + serverEccDer = certPre.concat("examples/certs/server-ecc.der"); } @Test @@ -130,4 +145,132 @@ public void testCertManagerLoadCANullFileNullDir() throws Exception { cm.free(); } } + + @Test + public void testCertManagerLoadCABufferTrailingData() throws Exception { + + byte[] cert = Files.readAllBytes(Paths.get(caCertDer)); + + /* Place cert into larger array with trailing padding bytes, + * caller-provided sz should be honored over array length */ + byte[] padded = new byte[cert.length + 128]; + System.arraycopy(cert, 0, padded, 0, cert.length); + + WolfSSLCertManager cm = new WolfSSLCertManager(); + + try { + cm.CertManagerLoadCABuffer(padded, cert.length, + WolfCrypt.SSL_FILETYPE_ASN1); + + } finally { + cm.free(); + } + } + + @Test + public void testCertManagerLoadCABufferSzTooLarge() throws Exception { + + byte[] cert = Files.readAllBytes(Paths.get(caCertDer)); + + WolfSSLCertManager cm = new WolfSSLCertManager(); + + /* sz larger than array length should throw exception */ + try { + cm.CertManagerLoadCABuffer(cert, cert.length + 1, + WolfCrypt.SSL_FILETYPE_ASN1); + fail("CertManagerLoadCABuffer() with sz > array length " + + "should throw exception"); + + } catch (WolfCryptException e) { + /* expected */ + + } finally { + cm.free(); + } + } + + @Test + public void testCertManagerVerifyBufferTrailingData() throws Exception { + + byte[] caCert = Files.readAllBytes(Paths.get(caCertDer)); + byte[] peerCert = Files.readAllBytes(Paths.get(serverCertDer)); + + /* Place cert into larger array with trailing padding bytes, + * caller-provided sz should be honored over array length */ + byte[] padded = new byte[peerCert.length + 128]; + System.arraycopy(peerCert, 0, padded, 0, peerCert.length); + + WolfSSLCertManager cm = new WolfSSLCertManager(); + + try { + cm.CertManagerLoadCABuffer(caCert, caCert.length, + WolfCrypt.SSL_FILETYPE_ASN1); + cm.CertManagerVerifyBuffer(padded, peerCert.length, + WolfCrypt.SSL_FILETYPE_ASN1); + + } finally { + cm.free(); + } + } + + @Test + public void testCertManagerLoadCABufferSzLimitsTrust() throws Exception { + + byte[] caRsaPem = Files.readAllBytes(Paths.get(caCertPem)); + byte[] caEccPem = Files.readAllBytes(Paths.get(caEccCertPem)); + byte[] serverEcc = Files.readAllBytes(Paths.get(serverEccDer)); + + /* Two PEM CA certs in one array, sz covers only the first (RSA). + * Second (ECC) CA is past sz and must NOT be loaded as trusted */ + byte[] twoCAs = new byte[caRsaPem.length + caEccPem.length]; + System.arraycopy(caRsaPem, 0, twoCAs, 0, caRsaPem.length); + System.arraycopy(caEccPem, 0, twoCAs, caRsaPem.length, + caEccPem.length); + + WolfSSLCertManager cm = new WolfSSLCertManager(); + + try { + cm.CertManagerLoadCABuffer(twoCAs, caRsaPem.length, + WolfCrypt.SSL_FILETYPE_PEM); + + /* Cert signed by second CA should NOT verify */ + try { + cm.CertManagerVerifyBuffer(serverEcc, serverEcc.length, + WolfCrypt.SSL_FILETYPE_ASN1); + fail("Cert signed by CA past caller-provided sz should " + + "not verify"); + + } catch (WolfCryptException e) { + /* expected */ + } + + } finally { + cm.free(); + } + } + + @Test + public void testCertManagerVerifyBufferSzTooLarge() throws Exception { + + byte[] caCert = Files.readAllBytes(Paths.get(caCertDer)); + byte[] peerCert = Files.readAllBytes(Paths.get(serverCertDer)); + + WolfSSLCertManager cm = new WolfSSLCertManager(); + + /* sz larger than array length should throw exception */ + try { + cm.CertManagerLoadCABuffer(caCert, caCert.length, + WolfCrypt.SSL_FILETYPE_ASN1); + cm.CertManagerVerifyBuffer(peerCert, peerCert.length + 1, + WolfCrypt.SSL_FILETYPE_ASN1); + fail("CertManagerVerifyBuffer() with sz > array length " + + "should throw exception"); + + } catch (WolfCryptException e) { + /* expected */ + + } finally { + cm.free(); + } + } } From 0c9ba04644a5ad39ef8731b22f217c1ac7cdedcb Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 15:44:44 -0600 Subject: [PATCH 07/10] F-5399 / F-5400: validate caller-provided sizes against buffer lengths in Rsa JNI wrappers --- jni/jni_rsa.c | 12 ++- .../com/wolfssl/wolfcrypt/test/RsaTest.java | 80 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/jni/jni_rsa.c b/jni/jni_rsa.c index 52c12682..3a98eba7 100644 --- a/jni/jni_rsa.c +++ b/jni/jni_rsa.c @@ -156,7 +156,9 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPublicKeyDecodeRaw__Ljava_nio_ByteBuffer_2 n = getDirectBufferAddress(env, n_object); e = getDirectBufferAddress(env, e_object); - if (key == NULL || n == NULL || e == NULL) { + if (key == NULL || n == NULL || e == NULL || nSize < 0 || eSize < 0 || + (nSize > (jlong)getDirectBufferLimit(env, n_object)) || + (eSize > (jlong)getDirectBufferLimit(env, e_object))) { ret = BAD_FUNC_ARG; } else { @@ -197,7 +199,9 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPublicKeyDecodeRaw___3BJ_3BJ( n = getByteArray(env, n_object); e = getByteArray(env, e_object); - if (key == NULL || n == NULL || e == NULL) { + if (key == NULL || n == NULL || e == NULL || nSize < 0 || eSize < 0 || + (nSize > (jlong)getByteArrayLength(env, n_object)) || + (eSize > (jlong)getByteArrayLength(env, e_object))) { ret = BAD_FUNC_ARG; } else { @@ -316,7 +320,9 @@ Java_com_wolfssl_wolfcrypt_Rsa_RsaFlattenPublicKey___3B_3J_3B_3J( nSz32 = (word32)nSz; eSz32 = (word32)eSz; - if (key == NULL || n == NULL || e == NULL) { + if (key == NULL || n == NULL || e == NULL || nSz < 0 || eSz < 0 || + (nSz > (jlong)getByteArrayLength(env, n_object)) || + (eSz > (jlong)getByteArrayLength(env, e_object))) { ret = BAD_FUNC_ARG; } else { diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java index 29303e82..ebed96a4 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java @@ -480,6 +480,86 @@ public void publicKeyDecodeAndEncodeWithByteArray() { assertArrayEquals(e_in, e_out); } + @Test + public void publicKeyDecodeRawOversizedSizesRejected() { + Rsa key = new Rsa(); + + byte[] n_in = new byte[8]; + byte[] e_in = new byte[3]; + + /* nSize larger than n array length should throw exception */ + try { + key.decodeRawPublicKey(n_in, n_in.length + 1, e_in, e_in.length); + fail("decodeRawPublicKey() with nSize > n.length should " + + "throw exception"); + } catch (WolfCryptException e) { + /* expected */ + } + + /* eSize larger than e array length should throw exception */ + try { + key.decodeRawPublicKey(n_in, n_in.length, e_in, e_in.length + 1); + fail("decodeRawPublicKey() with eSize > e.length should " + + "throw exception"); + } catch (WolfCryptException e) { + /* expected */ + } + + ByteBuffer n_buf = ByteBuffer.allocateDirect(8); + ByteBuffer e_buf = ByteBuffer.allocateDirect(3); + + /* nSize larger than n buffer limit should throw exception */ + try { + key.decodeRawPublicKey(n_buf, n_buf.limit() + 1, e_buf, + e_buf.limit()); + fail("decodeRawPublicKey() with nSize > n.limit() should " + + "throw exception"); + } catch (WolfCryptException e) { + /* expected */ + } + + key.releaseNativeStruct(); + } + + @Test + public void exportRawPublicKeyOversizedSizesRejected() { + Rsa key = new Rsa(); + + byte[] n_in = Util + .h2b("aff5f9e2e2622320d44dbf54f2274a0f96fa7d70a63ddaa563f48811" + + "43112bb3c36fe65ba0c9ad99d6fb6e53cb08e3938ee415b3a8cb" + + "7f9602f2154fab83dd160fa6f509ba2c41295af9eea8787d333e" + + "961461447fc60b3c61616ef5b94e822114e6fad44d1f2c476bc2" + + "3bc03609e2e70a483d826409fdb7c50a91269a773976ef137e7f" + + "a477c3951e8fbcb48f2378aa5e430e8c60b481beeb63df9abe10" + + "c7ccf266e394fbd925e8725e4675fb6ad895caed4b31d751c871" + + "2533e1c42ebefe9166e1aa20631521858c7548c61626ede105f2" + + "812632bac96eb769c9be560beef4200b86409727a5a61d1cc583" + + "1785ba4d42f02dd298a56bbbd6c479ce724d5bb5"); + byte[] e_in = Util.h2b("d0ee61"); + + key.decodeRawPublicKey(n_in, e_in); + + /* Output arrays too small for key, with in/out sizes that lie + * about capacity. Native must not write past the Java arrays */ + byte[] n_out = new byte[4]; + byte[] e_out = new byte[4]; + long[] n_len = new long[1]; + long[] e_len = new long[1]; + n_len[0] = 512; + e_len[0] = e_out.length; + + try { + key.exportRawPublicKey(n_out, n_len, e_out, e_len); + fail("exportRawPublicKey() with nSz > n.length should " + + "throw exception"); + } catch (WolfCryptException e) { + /* expected */ + } + + key.releaseNativeStruct(); + } + @Test public void exportRawPublicKeySizesCorrect() { Rsa key = new Rsa(); From a85511edf15c6d9a26ef01576d465e755dace223 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 2 Jul 2026 17:25:29 -0600 Subject: [PATCH 08/10] F-6436: gate ML-KEM JNI code on WOLFSSL_HAVE_MLKEM only, remove unused HAVE_MLKEM --- jni/jni_mlkem.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/jni/jni_mlkem.c b/jni/jni_mlkem.c index c18de968..d3eea734 100644 --- a/jni/jni_mlkem.c +++ b/jni/jni_mlkem.c @@ -29,7 +29,7 @@ #include #include #include -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM #include #endif @@ -44,7 +44,7 @@ #define RNG WC_RNG #endif -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM /* Zeroize sensitive buffer 'buf' of size 'sz' bytes, using wc_ForceZero * when available, otherwise XMEMSET. */ @@ -70,12 +70,12 @@ static int mlkem_level_to_type(jint level) } } -#endif /* WOLFSSL_HAVE_MLKEM || HAVE_MLKEM */ +#endif /* WOLFSSL_HAVE_MLKEM */ JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_MlKem_mallocNativeStruct( JNIEnv* env, jobject this) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM MlKemKey* key = NULL; key = (MlKemKey*)XMALLOC(sizeof(MlKemKey), NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -100,7 +100,7 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_MlKem_mallocNativeStruct( JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1init( JNIEnv* env, jobject this, jint level) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; int type = 0; MlKemKey* key = NULL; @@ -134,7 +134,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1init( JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1free( JNIEnv* env, jobject this) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM MlKemKey* key = (MlKemKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { /* getNativeStruct may throw exception */ @@ -155,7 +155,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1free( JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1make_1key( JNIEnv* env, jobject this, jobject rng_object) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; RNG* rng = NULL; @@ -192,7 +192,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1make_1key( JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1make_1key_1from_1seed( JNIEnv* env, jobject this, jbyteArray seed_object) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; byte* seed = NULL; @@ -238,7 +238,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1encapsu { jbyteArray result = NULL; -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; RNG* rng = NULL; @@ -316,7 +316,7 @@ Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1encapsulate_1with_1random( { jbyteArray result = NULL; -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; byte* rand = NULL; @@ -402,7 +402,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1decapsu { jbyteArray result = NULL; -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; byte* ct = NULL; @@ -480,7 +480,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1export_ { jbyteArray result = NULL; -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; byte* output = NULL; @@ -542,7 +542,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1export_ { jbyteArray result = NULL; -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; byte* output = NULL; @@ -603,7 +603,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1export_ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1import_1public( JNIEnv* env, jobject this, jbyteArray pub_object) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; byte* pub = NULL; @@ -647,7 +647,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1import_1publi JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1import_1private( JNIEnv* env, jobject this, jbyteArray priv_object) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; byte* priv = NULL; @@ -691,7 +691,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1import_1priva JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1public_1key_1size( JNIEnv* env, jobject this) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; word32 sz = 0; @@ -724,7 +724,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1public_1key_1 JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1private_1key_1size( JNIEnv* env, jobject this) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; word32 sz = 0; @@ -757,7 +757,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1private_1key_ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1ciphertext_1size( JNIEnv* env, jobject this) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; word32 sz = 0; @@ -790,7 +790,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1ciphertext_1s JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1shared_1secret_1size( JNIEnv* env, jobject this) { -#if defined(WOLFSSL_HAVE_MLKEM) || defined(HAVE_MLKEM) +#ifdef WOLFSSL_HAVE_MLKEM int ret = 0; MlKemKey* key = NULL; word32 sz = 0; From 9d01c586f7d254f2486ee9050dbc1239c712d5d6 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Mon, 6 Jul 2026 09:09:29 -0600 Subject: [PATCH 09/10] F-3997: validate hash and output buffer sizes in Asn.encodeSignature() JNI wrappers --- jni/jni_asn.c | 14 +++- .../com/wolfssl/wolfcrypt/test/AsnTest.java | 67 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/jni/jni_asn.c b/jni/jni_asn.c index 21550103..0ffd349d 100644 --- a/jni/jni_asn.c +++ b/jni/jni_asn.c @@ -141,7 +141,12 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Asn_encodeSignature__Ljava_nio byte* encoded = getDirectBufferAddress(env, encoded_object); byte* hash = getDirectBufferAddress(env, hash_object); - if (encoded == NULL || hash == NULL) { + /* Validate hashSize against hash buffer, and that the output buffer is + * large enough to hold DER encoded digest */ + if (encoded == NULL || hash == NULL || hashSize < 0 || + (hashSize > (jlong)getDirectBufferLimit(env, hash_object)) || + ((jlong)getDirectBufferLimit(env, encoded_object) < + (hashSize + MAX_DER_DIGEST_ASN_SZ))) { throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG); } else { @@ -158,7 +163,12 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_Asn_encodeSignature___3B_3BJI byte* hash = getByteArray(env, hash_object); jlong ret = 0; - if (encoded == NULL || hash == NULL) { + /* Validate hashSize against hash array, and that the output array is + * large enough to hold DER encoded digest */ + if (encoded == NULL || hash == NULL || hashSize < 0 || + (hashSize > (jlong)getByteArrayLength(env, hash_object)) || + ((jlong)getByteArrayLength(env, encoded_object) < + hashSize + MAX_DER_DIGEST_ASN_SZ)) { ret = BAD_FUNC_ARG; } else { diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/AsnTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/AsnTest.java index 2ea5fe19..28fba6e3 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/AsnTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/AsnTest.java @@ -23,10 +23,13 @@ import static org.junit.Assert.*; +import java.nio.ByteBuffer; + import org.junit.Test; import org.junit.BeforeClass; import com.wolfssl.wolfcrypt.Asn; +import com.wolfssl.wolfcrypt.WolfCryptException; /** * Unit tests for Asn class, particularly dynamic OID retrieval @@ -104,6 +107,70 @@ public void testOIDEncodingWithNewValues() { encodedLength < 100); } + @Test + public void testEncodeSignatureRejectsBadSizes() { + + byte[] hash = new byte[32]; + + /* Undersized output array should be rejected, not overflowed */ + byte[] tinyOut = new byte[4]; + assertTrue("undersized output array should be rejected", + Asn.encodeSignature(tinyOut, hash, hash.length, + Asn.SHA256h) < 0); + + /* hashSize larger than hash array should be rejected, not overread */ + byte[] out = new byte[512]; + assertTrue("hashSize > hash.length should be rejected", + Asn.encodeSignature(out, hash, hash.length + 100, + Asn.SHA256h) < 0); + + /* Negative hashSize should be rejected, not cast to huge word32 */ + assertTrue("negative hashSize should be rejected", + Asn.encodeSignature(out, hash, -1, Asn.SHA256h) < 0); + + /* Properly sized buffers should still succeed */ + assertTrue("valid sizes should still encode", + Asn.encodeSignature(out, hash, hash.length, + Asn.SHA256h) > 0); + } + + @Test + public void testEncodeSignatureByteBufferRejectsBadSizes() { + + ByteBuffer hash = ByteBuffer.allocateDirect(32); + + /* Undersized output buffer should be rejected, not overflowed */ + ByteBuffer tinyOut = ByteBuffer.allocateDirect(4); + try { + Asn.encodeSignature(tinyOut, hash, hash.limit(), Asn.SHA256h); + fail("undersized output buffer should throw"); + } catch (WolfCryptException e) { + /* expected */ + } + + /* hashSize larger than hash buffer should be rejected */ + ByteBuffer out = ByteBuffer.allocateDirect(512); + try { + Asn.encodeSignature(out, hash, hash.limit() + 100, Asn.SHA256h); + fail("hashSize > hash.limit() should throw"); + } catch (WolfCryptException e) { + /* expected */ + } + + /* Negative hashSize should be rejected */ + try { + Asn.encodeSignature(out, hash, -1, Asn.SHA256h); + fail("negative hashSize should throw"); + } catch (WolfCryptException e) { + /* expected */ + } + + /* Properly sized buffers should still succeed */ + out.limit(out.capacity()); + Asn.encodeSignature(out, hash, hash.limit(), Asn.SHA256h); + assertTrue("valid sizes should still encode", out.limit() > 0); + } + @Test public void testOIDUniqueness() { From 398b61a8bb94e45139e083a8e1cc4951f0996984 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Mon, 6 Jul 2026 09:19:17 -0600 Subject: [PATCH 10/10] F-5403: pin Ant classpath to wolfcrypt-jni.jar --- build.xml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index cc708920..f89622ca 100644 --- a/build.xml +++ b/build.xml @@ -117,9 +117,7 @@ - - - + @@ -591,9 +589,7 @@ class="com.wolfssl.provider.jce.WolfCryptProvider"> - - - +