Merge pull request #245 from cconlon/fenrirJuly23

Fenrir correctness and robustness fixes
pull/250/head
Ruby Martin 2026-07-29 17:50:57 -05:00 committed by GitHub
commit 90c50b850b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1004 additions and 138 deletions

View File

@ -57,51 +57,20 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# Cache AVD snapshot for faster emulator boot. The v2 key retires
# snapshots saved before the settle script below was added.
- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-wolfcryptjni-30-x86_64-google_apis-v2
# Create AVD and generate snapshot for caching. Wait for the
# system to fully settle (boot complete plus the input service
# registered) before the emulator exits and the snapshot is
# saved. Snapshots saved mid-boot resume into a half-settled
# system on restore, where the emulator-runner action's keyguard
# dismiss (input keyevent 82) intermittently fails with
# "No service published for: input".
- name: Create AVD and generate snapshot
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2.37.0
with:
api-level: 30
arch: x86_64
target: google_apis
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
adb wait-for-device
adb shell 'while [ "$(getprop sys.boot_completed)" != "1" ]; do sleep 1; done'
adb shell 'while ! service check input | grep -q found; do sleep 1; done'
sleep 15
echo "Generated AVD snapshot for caching"
# Run instrumented tests on Android emulator
# Cold boot with -no-snapshot. Restoring a snapshot brings back
# sys.boot_completed already set, so the action runs its keyguard
# dismiss (input keyevent 82) before the input service registers
# and fails with "No service published for: input".
# AOSP "default" image, these tests need no Play services.
- name: Run Android Instrumented Tests
uses: reactivecircus/android-emulator-runner@v2.37.0
timeout-minutes: 15
timeout-minutes: 20
with:
api-level: 30
arch: x86_64
target: google_apis
target: default
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
emulator-options: -no-snapshot -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
adb wait-for-device
@ -110,7 +79,11 @@ jobs:
adb shell mkdir -p /data/local/tmp/examples/certs/crl
adb push ./examples/certs/ /data/local/tmp/examples/
adb logcat -c
cd IDE/Android && ./gradlew connectedDebugAndroidTest --no-daemon --no-watch-fs || { adb logcat -d > /tmp/logcat.txt 2>&1; echo "=== LOGCAT (errors) ==="; grep -i "exception\|error\|fatal" /tmp/logcat.txt || true; exit 1; }
# Shard the run, AGP returns results over gRPC with a 4MB cap
# that the full suite exceeds, failing with RESOURCE_EXHAUSTED
# even when every test passes. The action runs each line below
# as its own shell, so the whole loop must stay on one line.
cd IDE/Android && for S in 0 1; do echo "=== test shard $S of 2 ==="; ./gradlew connectedDebugAndroidTest --no-daemon --no-watch-fs -Pandroid.testInstrumentationRunnerArguments.numShards=2 -Pandroid.testInstrumentationRunnerArguments.shardIndex=$S || { adb logcat -d > /tmp/logcat.txt 2>&1; echo "=== LOGCAT (errors) ==="; grep -i "exception\|error\|fatal" /tmp/logcat.txt || true; exit 1; }; cp -r app/build/reports/androidTests/connected/debug app/build/reports/androidTests/shard-$S 2>/dev/null || true; done
adb logcat -d > /tmp/logcat.txt 2>&1 || true
pgrep -f '[q]emu-system' | xargs -r kill -9 2>/dev/null || true
pgrep -f '[c]rashpad' | xargs -r kill -9 2>/dev/null || true

View File

@ -102,54 +102,25 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# Cache AVD snapshot for faster emulator boot. The v2 key retires
# snapshots saved before the settle script below was added.
- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-wolfcryptjni-fips-30-x86_64-google_apis-v2
# Create AVD and generate snapshot for caching. Wait for the
# system to fully settle (boot complete plus the input service
# registered) before the emulator exits and the snapshot is
# saved. Snapshots saved mid-boot resume into a half-settled
# system on restore, where the emulator-runner action's keyguard
# dismiss (input keyevent 82) intermittently fails with
# "No service published for: input".
- name: Create AVD and generate snapshot
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2.37.0
with:
api-level: 30
arch: x86_64
target: google_apis
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
adb wait-for-device
adb shell 'while [ "$(getprop sys.boot_completed)" != "1" ]; do sleep 1; done'
adb shell 'while ! service check input | grep -q found; do sleep 1; done'
sleep 15
echo "Generated AVD snapshot for caching"
# Launch app briefly to capture FIPS in-core hash from logcat.
# The FIPS error callback prints the expected verifyCore hash on
# startup if there is a mismatch.
#
# Cold boot with -no-snapshot. Restoring a snapshot brings back
# sys.boot_completed already set, so the action runs its keyguard
# dismiss (input keyevent 82) before the input service registers
# and fails with "No service published for: input".
# AOSP "default" image, these tests need no Play services.
- name: Capture FIPS in-core hash
id: fips-hash
uses: reactivecircus/android-emulator-runner@v2.37.0
timeout-minutes: 5
timeout-minutes: 10
with:
api-level: 30
arch: x86_64
target: google_apis
target: default
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
emulator-options: -no-snapshot -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
adb wait-for-device
@ -181,16 +152,17 @@ jobs:
(cd /tmp && echo "$(cat ${BCPROV_JAR}.sha256) ${BCPROV_JAR}" | sha256sum -c -)
cd examples/certs && ./convert-to-bks.sh "/tmp/${BCPROV_JAR}"
# Run instrumented tests on Android emulator
# Run instrumented tests on Android emulator, cold booted for the
# same reason as the FIPS hash capture step above.
- name: Run Android Instrumented Tests
uses: reactivecircus/android-emulator-runner@v2.37.0
timeout-minutes: 15
timeout-minutes: 20
with:
api-level: 30
arch: x86_64
target: google_apis
target: default
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
emulator-options: -no-snapshot -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
adb wait-for-device
@ -199,10 +171,14 @@ jobs:
adb shell mkdir -p /data/local/tmp/examples/certs/crl
adb push ./examples/certs/ /data/local/tmp/examples/
adb logcat -c
cd IDE/Android && ./gradlew connectedDebugAndroidTest --no-daemon --no-watch-fs || { adb logcat -d > /tmp/logcat.txt 2>&1; echo "=== LOGCAT (errors) ==="; grep -i "exception\|error\|fatal" /tmp/logcat.txt || true; exit 1; }
# Shard the run, AGP returns results over gRPC with a 4MB cap
# that the full suite exceeds, failing with RESOURCE_EXHAUSTED
# even when every test passes. The action runs each line below
# as its own shell, so the whole loop must stay on one line.
cd IDE/Android && for S in 0 1; do echo "=== test shard $S of 2 ==="; ./gradlew connectedDebugAndroidTest --no-daemon --no-watch-fs -Pandroid.testInstrumentationRunnerArguments.numShards=2 -Pandroid.testInstrumentationRunnerArguments.shardIndex=$S || { adb logcat -d > /tmp/logcat.txt 2>&1; echo "=== LOGCAT (errors) ==="; grep -i "exception\|error\|fatal" /tmp/logcat.txt || true; exit 1; }; cp -r app/build/reports/androidTests/connected/debug app/build/reports/androidTests/shard-$S 2>/dev/null || true; done
adb logcat -d > /tmp/logcat.txt 2>&1 || true
# Clean up emulator processes. Safe to kill -9 since
# -no-snapshot-save is used (no snapshot to corrupt).
# -no-snapshot is used (no snapshot to corrupt).
pgrep -f '[q]emu-system' | xargs -r kill -9 2>/dev/null || true
pgrep -f '[c]rashpad' | xargs -r kill -9 2>/dev/null || true
sleep 2

View File

@ -359,6 +359,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaOaepEnabl
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhEnabled
(JNIEnv *, jclass);
/*
* Class: com_wolfssl_wolfcrypt_FeatureDetect
* Method: DhExtraEnabled
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhExtraEnabled
(JNIEnv *, jclass);
/*
* Class: com_wolfssl_wolfcrypt_FeatureDetect
* Method: EccEnabled

View File

@ -1298,9 +1298,38 @@ Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyDecode(
/* Decode X.509 public key */
ret = wc_DhKeyDecode(x509, &idx, key, x509Sz);
LogStr("wc_DhKeyDecode(x509=%p, key=%p) = %d\n", x509, key, ret);
#ifdef WOLFSSL_DH_EXTRA
if (ret == 0) {
/* Fail fast on an out of range public key. wc_DhAgree also validates
* at use. Export the decoded Y to validate. */
byte* pubVal = NULL;
word32 pubValSz = (DH_MAX_SIZE + 7) / 8;
pubVal = (byte*)XMALLOC(pubValSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (pubVal == NULL) {
ret = MEMORY_E;
}
else {
ret = wc_DhExportKeyPair(key, NULL, NULL, pubVal, &pubValSz);
if (ret == 0) {
ret = wc_DhCheckPubKey(key, pubVal, pubValSz);
}
#if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \
!defined(WOLFSSL_NO_FORCE_ZERO)
wc_ForceZero(pubVal, (DH_MAX_SIZE + 7) / 8);
#else
XMEMSET(pubVal, 0, (DH_MAX_SIZE + 7) / 8);
#endif
XFREE(pubVal, NULL, DYNAMIC_TYPE_TMP_BUFFER);
}
LogStr("DH public key validation = %d\n", ret);
}
#endif
if (ret == 0) {
/* Return the same DER data (validated) */
/* Return the DER as passed in */
result = (*env)->NewByteArray(env, x509Sz);
if (result) {
(*env)->SetByteArrayRegion(env, result, 0, x509Sz,
@ -1312,8 +1341,6 @@ Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyDecode(
}
}
LogStr("wc_DhKeyDecode(x509=%p, key=%p) = %d\n", x509, key, ret);
releaseByteArray(env, x509_object, x509, JNI_ABORT);
if (ret != 0) {

View File

@ -558,6 +558,18 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhEnabled
#endif
}
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhExtraEnabled
(JNIEnv* env, jclass jcl)
{
(void)env;
(void)jcl;
#if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA)
return JNI_TRUE;
#else
return JNI_FALSE;
#endif
}
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_EccEnabled
(JNIEnv* env, jclass jcl)
{

View File

@ -48,6 +48,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PKCS12_1PBK
byte* pass = NULL;
byte* salt = NULL;
byte* outKey = NULL;
jboolean passIsCopy = JNI_FALSE;
jbyteArray result = NULL;
(void)jcl;
@ -64,7 +65,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PKCS12_1PBK
XMEMSET(outKey, 0, kLen);
if (passBuf != NULL) {
pass = (byte*)(*env)->GetByteArrayElements(env, passBuf, NULL);
pass = (byte*)(*env)->GetByteArrayElements(env, passBuf, &passIsCopy);
}
if (saltBuf != NULL) {
salt = (byte*)(*env)->GetByteArrayElements(env, saltBuf, NULL);
@ -96,6 +97,15 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PKCS12_1PBK
}
if (pass != NULL) {
/* Zero native copy of password, JNI_ABORT does not copy back */
if (passIsCopy == JNI_TRUE && passBufLen > 0) {
#if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \
!defined(WOLFSSL_NO_FORCE_ZERO)
wc_ForceZero(pass, passBufLen);
#else
XMEMSET(pass, 0, passBufLen);
#endif
}
(*env)->ReleaseByteArrayElements(env, passBuf, (jbyte*)pass, JNI_ABORT);
}
if (salt != NULL) {
@ -133,6 +143,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PBKDF2
byte* pass = NULL;
byte* salt = NULL;
byte* outKey = NULL;
jboolean passIsCopy = JNI_FALSE;
jbyteArray result = NULL;
(void)jcl;
@ -149,7 +160,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PBKDF2
XMEMSET(outKey, 0, kLen);
if ((passBuf != NULL) && (passBufLen > 0)) {
pass = (byte*)(*env)->GetByteArrayElements(env, passBuf, NULL);
pass = (byte*)(*env)->GetByteArrayElements(env, passBuf, &passIsCopy);
}
if (saltBuf != NULL) {
@ -182,6 +193,15 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PBKDF2
}
if (pass != NULL) {
/* Zero native copy of password, JNI_ABORT does not copy back */
if (passIsCopy == JNI_TRUE && passBufLen > 0) {
#if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \
!defined(WOLFSSL_NO_FORCE_ZERO)
wc_ForceZero(pass, passBufLen);
#else
XMEMSET(pass, 0, passBufLen);
#endif
}
(*env)->ReleaseByteArrayElements(env, passBuf, (jbyte*)pass, JNI_ABORT);
}
if (salt != NULL) {

View File

@ -225,15 +225,26 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store)
return 0;
}
/* Try to find callback by WOLFSSL_CERT_MANAGER if cm available in store */
if (store->store != NULL && store->store->cm != NULL) {
/* wolfSSL sets store->userCtx to the WOLFSSL_CERT_MANAGER that
* initiated verification, use it to find the matching callback */
if (store->userCtx != NULL) {
ctx = findCallbackCtx((WOLFSSL_CERT_MANAGER*)store->userCtx);
}
/* Fall back to the cm pointer in store if the userCtx lookup missed.
* Both are exact cm matches, so neither can dispatch to the wrong CM. */
if (ctx == NULL && store->store != NULL && store->store->cm != NULL) {
ctx = findCallbackCtx(store->store->cm);
}
else if (g_callbackList != NULL) {
/* When using CertManagerVerifyBuffer, we can't look up by cm pointer.
* Use the first callback in the list as a fallback. This works for
* single-threaded verification or when only one CertManager in use. */
ctx = g_callbackList->ctx;
/* No callbacks found, verification falls back to preverify. Logging it
* here for visibility. */
if (ctx == NULL) {
LogStr("nativeVerifyCallback: no callback ctx found for "
"userCtx=%p, cm=%p, using preverify=%d\n",
store->userCtx,
(store->store != NULL) ? (void*)store->store->cm : NULL,
preverify);
}
/* No callback registered, use preverify result */

View File

@ -414,8 +414,9 @@ public class WolfCryptASN1Util {
*
* @return decoded length value
*
* @throws IllegalArgumentException if data is null or index is invalid
* @throws ArrayIndexOutOfBoundsException if data is too short
* @throws IllegalArgumentException if data is null, index is invalid,
* the length uses more than 4 bytes, or the encoded length
* extends beyond the available data
*/
public static int getDERLength(byte[] data, int idx)
throws IllegalArgumentException {
@ -432,17 +433,40 @@ public class WolfCryptASN1Util {
len = data[idx] & 0xFF;
if ((len & 0x80) == 0) {
/* Short form */
/* Short form, content must fit in the buffer */
if (len > data.length - (idx + 1)) {
throw new IllegalArgumentException(
"DER length exceeds available data");
}
return len;
}
/* Long form */
numBytes = len & 0x7F;
if (numBytes == 0) {
throw new IllegalArgumentException(
"Indefinite length encoding not supported");
}
if (numBytes > 4) {
throw new IllegalArgumentException("DER length too large");
}
if (idx + 1 + numBytes > data.length) {
throw new IllegalArgumentException(
"DER length bytes extend beyond data");
}
result = 0;
for (int i = 0; i < numBytes; i++) {
result = (result << 8) | (data[idx + 1 + i] & 0xFF);
}
/* Reject a negative length or content extending beyond the buffer */
if (result < 0 || result > data.length - (idx + 1 + numBytes)) {
throw new IllegalArgumentException(
"DER length exceeds available data");
}
return result;
}

View File

@ -23,6 +23,7 @@ package com.wolfssl.provider.jce;
import java.util.Arrays;
import java.nio.ByteBuffer;
import java.io.ByteArrayOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
@ -152,8 +153,8 @@ public class WolfCryptCipher extends CipherSpi {
/* AES-GCM/CCM tag length (bytes), default to 128 bits */
private int gcmTagLen = 16;
/* AAD data for AES-GCM, populated via engineUpdateAAD() */
private byte[] aadData = null;
/* AAD data for AES-GCM/CCM, accumulated via engineUpdateAAD() */
private ByteArrayOutputStream aadStream = null;
/* Last (key, IV) set for AES-GCM encryption at init time, tracked to
* reject GCM nonce reuse on re-initialization. A digest of the encoded
@ -1552,6 +1553,10 @@ public class WolfCryptCipher extends CipherSpi {
}
}
/* Flatten accumulated AAD to a single array for GCM/CCM calls below */
byte[] aad = (this.aadStream != null) ?
this.aadStream.toByteArray() : null;
switch (this.cipherType) {
case WC_AES:
@ -1568,7 +1573,7 @@ public class WolfCryptCipher extends CipherSpi {
byte[] tag = new byte[this.gcmTagLen];
tmpOut = this.aesGcm.encrypt(tmpIn, this.iv, tag,
this.aadData);
aad);
this.gcmEncryptNeedsReinit = true;
@ -1600,7 +1605,7 @@ public class WolfCryptCipher extends CipherSpi {
try {
tmpOut = this.aesGcm.decrypt(tmpIn, this.iv,
tag, this.aadData);
tag, aad);
} catch (WolfCryptException e) {
/* Convert to AEADBadTagException */
@ -1617,7 +1622,7 @@ public class WolfCryptCipher extends CipherSpi {
if (this.direction == OpMode.WC_ENCRYPT) {
byte[] tag = new byte[this.gcmTagLen];
tmpOut = this.aesCcm.encrypt(tmpIn, this.iv, tag,
this.aadData);
aad);
/* Concatenate auth tag to end of ciphertext */
byte[] totalOut = new byte[tmpOut.length + tag.length];
@ -1646,7 +1651,7 @@ public class WolfCryptCipher extends CipherSpi {
tmpIn.length - this.gcmTagLen);
tmpOut = this.aesCcm.decrypt(tmpIn, this.iv, tag,
this.aadData);
aad);
}
}
else if (cipherMode == CipherMode.WC_ECB) {
@ -1804,7 +1809,7 @@ public class WolfCryptCipher extends CipherSpi {
wolfCryptSetKey(storedKey);
this.aadData = null;
this.aadStream = null;
this.operationStarted = false;
this.cipherInitialized = true;
@ -2054,18 +2059,11 @@ public class WolfCryptCipher extends CipherSpi {
"Source buffer is null or bad offset/len");
}
if (this.aadData == null) {
/* Store as new array inside object */
this.aadData = new byte[len];
System.arraycopy(src, offset, this.aadData, 0, len);
}
else {
/* Append to existing AAD array held inside object */
byte[] tmp = new byte[this.aadData.length + len];
System.arraycopy(this.aadData, 0, tmp, 0, this.aadData.length);
System.arraycopy(src, offset, tmp, this.aadData.length, len);
this.aadData = tmp;
if (this.aadStream == null) {
this.aadStream = new ByteArrayOutputStream();
}
this.aadStream.write(src, offset, len);
}
@Override

View File

@ -127,9 +127,9 @@ public class WolfCryptDhParameters extends AlgorithmParametersSpi {
/* Private value length not encoded in standard DH params */
this.l = 0;
} catch (ArrayIndexOutOfBoundsException e) {
throw new IOException(
"Invalid DH parameters encoding: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
throw new IOException("Invalid DH parameters encoding: " +
e.getMessage());
}
}

View File

@ -459,6 +459,27 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
}
}
/**
* Check if a certificate is self-issued, subject and issuer names match.
*
* Unlike self-signed, this does not require a self-signature. Per RFC 5280
* section 6.1.4(l), self-issued intermediates do not count against a CA's
* pathLenConstraint.
*
* @param cert certificate to check
*
* @return true if subject equals issuer, otherwise false
*/
private boolean isSelfIssued(X509Certificate cert) {
if (cert == null) {
return false;
}
return cert.getSubjectX500Principal().equals(
cert.getIssuerX500Principal());
}
/**
* Find potential issuer certificates for a given certificate.
*
@ -468,11 +489,14 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
* @param cert certificate for which to find issuers
* @param certStores list of CertStores to search
* @param anchors set of trust anchors (also potential issuers)
* @param minPathLen minimum BasicConstraints pathLenConstraint a
* candidate issuer must have, the number of CA certs already
* below it in the chain.
*
* @return list of potential issuer certificates
*/
private List<X509Certificate> findIssuers(X509Certificate cert,
List<CertStore> certStores, Set<TrustAnchor> anchors) {
List<CertStore> certStores, Set<TrustAnchor> anchors, int minPathLen) {
if (cert == null) {
return new ArrayList<>();
@ -498,8 +522,9 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
for (Certificate c : certs) {
if (c instanceof X509Certificate) {
X509Certificate x509Cert = (X509Certificate) c;
/* Must be a CA certificate */
if (x509Cert.getBasicConstraints() >= 0) {
/* getBasicConstraints() returns -1 for non-CA, so
* this enforces both CA and pathLenConstraint. */
if (x509Cert.getBasicConstraints() >= minPathLen) {
issuers.add(x509Cert);
}
}
@ -600,9 +625,17 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
current.getSubjectX500Principal().getName());
}
/* Find potential issuers */
/* Issuer's pathLenConstraint required to allow the non-self-issued
* CA certs below it. path[0] is the target, not a CA, so it is
* excluded. */
int minPathLen = 0;
for (int i = 1; i < path.size(); i++) {
if (!isSelfIssued(path.get(i))) {
minPathLen++;
}
}
List<X509Certificate> issuers =
findIssuers(current, certStores, anchors);
findIssuers(current, certStores, anchors, minPathLen);
if (issuers.isEmpty()) {
throw new CertPathBuilderException(

View File

@ -25,6 +25,8 @@ import java.util.Arrays;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactorySpi;
import javax.crypto.spec.PBEKeySpec;
@ -283,11 +285,26 @@ public class WolfCryptSecretKeyFactory extends SecretKeyFactorySpi {
return null;
}
/* Encode into single worst case sized buffer. */
CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
passBuf = CharBuffer.wrap(pass);
utf8Buf = StandardCharsets.UTF_8.encode(passBuf);
passBytes = new byte[utf8Buf.limit()];
utf8Buf = ByteBuffer.allocate(
(int)Math.ceil(pass.length * (double)encoder.maxBytesPerChar()));
encoder.encode(passBuf, utf8Buf, true);
encoder.flush(utf8Buf);
utf8Buf.flip();
passBytes = new byte[utf8Buf.remaining()];
utf8Buf.get(passBytes);
/* Zero the encoder buffer, it holds another UTF-8 copy */
if (utf8Buf.hasArray()) {
Arrays.fill(utf8Buf.array(), (byte)0);
}
return passBytes;
}
@ -305,6 +322,7 @@ public class WolfCryptSecretKeyFactory extends SecretKeyFactorySpi {
int kLen;
byte[] salt = null;
char[] pass = null;
byte[] passBytes = null;
byte[] derivedKey = null;
SecretKey key = null;
@ -334,8 +352,9 @@ public class WolfCryptSecretKeyFactory extends SecretKeyFactorySpi {
log("generating PBEKey (iterations: " + iterations +
", key len: " + kLen + " bytes)");
derivedKey = Pwdbased.PBKDF2(passwordToByteArray(pass),
salt, iterations, kLen, this.hashType);
passBytes = passwordToByteArray(pass);
derivedKey = Pwdbased.PBKDF2(passBytes, salt, iterations, kLen,
this.hashType);
if (derivedKey == null || derivedKey.length == 0) {
throw new InvalidKeySpecException(
@ -356,6 +375,9 @@ public class WolfCryptSecretKeyFactory extends SecretKeyFactorySpi {
if (pass != null) {
Arrays.fill(pass, (char)0);
}
if (passBytes != null) {
Arrays.fill(passBytes, (byte)0);
}
if (derivedKey != null) {
Arrays.fill(derivedKey, (byte)0);
}

View File

@ -888,6 +888,7 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
byte[] salt, int iterations, int kLen) throws KeyStoreException {
byte[] kek = null;
byte[] passBytes = null;
if (pass == null || pass.length == 0 || salt == null ||
salt.length == 0 || iterations <= 0 || kLen <= 0) {
@ -896,9 +897,9 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
}
try {
kek = Pwdbased.PBKDF2(
WolfCryptSecretKeyFactory.passwordToByteArray(pass),
salt, iterations, kLen, WKS_PBKDF2_TYPE);
passBytes = WolfCryptSecretKeyFactory.passwordToByteArray(pass);
kek = Pwdbased.PBKDF2(passBytes, salt, iterations, kLen,
WKS_PBKDF2_TYPE);
if (kek == null) {
throw new KeyStoreException(
@ -910,6 +911,11 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
Arrays.fill(kek, (byte)0);
}
throw new KeyStoreException(e);
} finally {
if (passBytes != null) {
Arrays.fill(passBytes, (byte)0);
}
}
return kek;
@ -2218,6 +2224,7 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
byte[] encoded = null;
byte[] derivedKey = null;
byte[] passBytes = null;
byte[] hmac = null;
byte[] encodedEntry = null;
byte[] salt = new byte[WKS_PBKDF2_SALT_SIZE];
@ -2333,9 +2340,10 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
/* Derive HMAC key from password with PBKDF2 */
log("deriving HMAC-SHA512 key with PKCS#5 PBKDF2-HMAC-SHA512");
derivedKey = Pwdbased.PBKDF2(
WolfCryptSecretKeyFactory.passwordToByteArray(password),
salt, WKS_PBKDF2_ITERATION_COUNT, WKS_HMAC_KEY_LENGTH,
passBytes =
WolfCryptSecretKeyFactory.passwordToByteArray(password);
derivedKey = Pwdbased.PBKDF2(passBytes, salt,
WKS_PBKDF2_ITERATION_COUNT, WKS_HMAC_KEY_LENGTH,
WKS_PBKDF2_TYPE);
if (derivedKey == null) {
throw new IOException("Error deriving key with PBKDF2");
@ -2377,6 +2385,9 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
if (derivedKey != null) {
Arrays.fill(derivedKey, (byte)0);
}
if (passBytes != null) {
Arrays.fill(passBytes, (byte)0);
}
if (hmac != null) {
Arrays.fill(hmac, (byte)0);
}
@ -2472,6 +2483,7 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
Mac mac = null;
SecretKeySpec keySpec = null;
byte[] derivedKey = null;
byte[] passBytes = null;
byte[] buffered = null;
byte[] hmac = null;
@ -2482,9 +2494,16 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
}
/* Derive HMAC key from password using PBKDF2 */
derivedKey = Pwdbased.PBKDF2(
WolfCryptSecretKeyFactory.passwordToByteArray(password),
salt, iterations, WKS_HMAC_KEY_LENGTH, WKS_PBKDF2_TYPE);
try {
passBytes =
WolfCryptSecretKeyFactory.passwordToByteArray(password);
derivedKey = Pwdbased.PBKDF2(passBytes, salt, iterations,
WKS_HMAC_KEY_LENGTH, WKS_PBKDF2_TYPE);
} finally {
if (passBytes != null) {
Arrays.fill(passBytes, (byte)0);
}
}
if (derivedKey == null) {
throw new IOException("Error deriving key with PBKDF2");
}

View File

@ -347,6 +347,13 @@ public class FeatureDetect {
*/
public static native boolean DhEnabled();
/**
* Tests if WOLFSSL_DH_EXTRA is compiled into the native wolfSSL library.
*
* @return true if enabled, otherwise false if not compiled in.
*/
public static native boolean DhExtraEnabled();
/**
* Tests if ECC is compiled into the native wolfSSL library.
*

View File

@ -538,5 +538,77 @@ public class WolfCryptASN1UtilTest {
assertEquals("Should contain parameters SEQUENCE", ASN1_SEQUENCE,
result[idx]);
}
@Test
public void testGetDERLengthShortForm() throws Exception {
/* Length byte plus that many content bytes present */
assertEquals(0, WolfCryptASN1Util.getDERLength(
new byte[] { 0x00 }, 0));
assertEquals(1, WolfCryptASN1Util.getDERLength(
new byte[] { 0x01, 0x00 }, 0));
byte[] data = new byte[1 + 127];
data[0] = 0x7F;
assertEquals(127, WolfCryptASN1Util.getDERLength(data, 0));
}
/* Short form length larger than the content present must be rejected */
@Test(expected = IllegalArgumentException.class)
public void testGetDERLengthShortFormBeyondDataThrows() throws Exception {
WolfCryptASN1Util.getDERLength(new byte[] { 0x7F }, 0);
}
@Test
public void testGetDERLengthLongForm() throws Exception {
/* 1 length byte, value 200, plus 200 content bytes */
byte[] data = new byte[2 + 200];
data[0] = (byte)0x81;
data[1] = (byte)0xC8;
assertEquals(200, WolfCryptASN1Util.getDERLength(data, 0));
/* 2 length bytes, value 256, plus 256 content bytes */
data = new byte[3 + 256];
data[0] = (byte)0x82;
data[1] = 0x01;
data[2] = 0x00;
assertEquals(256, WolfCryptASN1Util.getDERLength(data, 0));
}
/* More than 4 length bytes must be rejected, not truncated */
@Test(expected = IllegalArgumentException.class)
public void testGetDERLengthFiveBytesThrows() throws Exception {
byte[] data = new byte[] {
(byte)0x85, 0x01, 0x7F, (byte)0xFF, (byte)0xFF, (byte)0xFF };
WolfCryptASN1Util.getDERLength(data, 0);
}
/* A 5 byte length whose low 32 bits are a small positive must be
* rejected, not truncated to that small value */
@Test(expected = IllegalArgumentException.class)
public void testGetDERLengthTruncationToSmallThrows() throws Exception {
byte[] data = new byte[] {
(byte)0x85, 0x01, 0x00, 0x00, 0x00, 0x01, (byte)0xAA };
WolfCryptASN1Util.getDERLength(data, 0);
}
/* A 4 byte length near Integer.MAX_VALUE must be rejected before a
* caller attempts the allocation */
@Test(expected = IllegalArgumentException.class)
public void testGetDERLengthHugeFourByteThrows() throws Exception {
byte[] data = new byte[] {
(byte)0x84, 0x7F, (byte)0xFF, (byte)0xFF, (byte)0xFF };
WolfCryptASN1Util.getDERLength(data, 0);
}
@Test(expected = IllegalArgumentException.class)
public void testGetDERLengthIndefiniteThrows() throws Exception {
WolfCryptASN1Util.getDERLength(new byte[] { (byte)0x80 }, 0);
}
/* Claims 200 content bytes but only 1 is present */
@Test(expected = IllegalArgumentException.class)
public void testGetDERLengthContentBeyondDataThrows() throws Exception {
byte[] data = new byte[] { (byte)0x81, (byte)0xC8, 0x00 };
WolfCryptASN1Util.getDERLength(data, 0);
}
}

View File

@ -2304,6 +2304,70 @@ public class WolfCryptCipherTest {
}
}
/*
* Test that AAD supplied over many updateAAD() calls produces the same
* result as a single call. Guards the incremental AAD accumulator.
*/
@Test
public void testAesGcmChunkedAADMatchesSingle()
throws NoSuchAlgorithmException, InvalidKeyException,
IllegalBlockSizeException, NoSuchProviderException,
InvalidAlgorithmParameterException, BadPaddingException,
NoSuchPaddingException {
if (!enabledJCEAlgos.contains("AES/GCM/NoPadding") ||
!FeatureDetect.Aes256Enabled()) {
/* skip if AES-256-GCM is not enabled */
return;
}
byte[] keyBytes = new byte[32];
byte[] iv = new byte[12];
byte[] aad = new byte[257];
byte[] plaintext = new byte[64];
for (int i = 0; i < keyBytes.length; i++) {
keyBytes[i] = (byte)i;
}
for (int i = 0; i < iv.length; i++) {
iv[i] = (byte)(0xA0 + i);
}
for (int i = 0; i < aad.length; i++) {
aad[i] = (byte)(i & 0xFF);
}
for (int i = 0; i < plaintext.length; i++) {
plaintext[i] = (byte)(0x5A ^ i);
}
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
/* Reference, AAD supplied in a single call */
Cipher c1 = Cipher.getInstance("AES/GCM/NoPadding", jceProvider);
c1.init(Cipher.ENCRYPT_MODE, key, spec);
c1.updateAAD(aad);
byte[] single = c1.doFinal(plaintext);
/* AAD supplied one byte per call */
Cipher c2 = Cipher.getInstance("AES/GCM/NoPadding", jceProvider);
c2.init(Cipher.ENCRYPT_MODE, key, spec);
for (int i = 0; i < aad.length; i++) {
c2.updateAAD(aad, i, 1);
}
byte[] chunked = c2.doFinal(plaintext);
assertArrayEquals(
"Chunked AAD must match single-call AAD", single, chunked);
/* Round trip decrypt with AAD split into 7-byte chunks */
Cipher dec = Cipher.getInstance("AES/GCM/NoPadding", jceProvider);
dec.init(Cipher.DECRYPT_MODE, key, spec);
for (int i = 0; i < aad.length; i += 7) {
dec.updateAAD(aad, i, Math.min(7, aad.length - i));
}
byte[] recovered = dec.doFinal(chunked);
assertArrayEquals(plaintext, recovered);
}
/*
* Test Cipher("AES/GCM/NoPadding") to make sure updateAAD() correctly
* throws an exception when called after a call to update or doFinal.
@ -5532,6 +5596,72 @@ public class WolfCryptCipherTest {
plaintext, decrypted);
}
/*
* Test that AAD supplied over many updateAAD() calls produces the same
* result as a single call for AES-CCM, the second consumer of the
* incremental AAD accumulator.
*/
@Test
public void testAesCcmChunkedAADMatchesSingle()
throws NoSuchProviderException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, InvalidAlgorithmParameterException,
BadPaddingException {
if (!enabledJCEAlgos.contains("AES/CCM/NoPadding")) {
/* algorithm not enabled */
return;
}
byte[] key = {
(byte)0x2b, (byte)0x7e, (byte)0x15, (byte)0x16,
(byte)0x28, (byte)0xae, (byte)0xd2, (byte)0xa6,
(byte)0xab, (byte)0xf7, (byte)0x15, (byte)0x88,
(byte)0x09, (byte)0xcf, (byte)0x4f, (byte)0x3c
};
byte[] nonce = new byte[12];
byte[] aad = new byte[257];
byte[] plaintext = new byte[32];
for (int i = 0; i < nonce.length; i++) {
nonce[i] = (byte)(0x0B - i);
}
for (int i = 0; i < aad.length; i++) {
aad[i] = (byte)(i & 0xFF);
}
for (int i = 0; i < plaintext.length; i++) {
plaintext[i] = (byte)(0x5A ^ i);
}
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
GCMParameterSpec ccmSpec = new GCMParameterSpec(128, nonce);
/* Reference, AAD supplied in a single call */
Cipher c1 = Cipher.getInstance("AES/CCM/NoPadding", jceProvider);
c1.init(Cipher.ENCRYPT_MODE, keySpec, ccmSpec);
c1.updateAAD(aad);
byte[] single = c1.doFinal(plaintext);
/* AAD supplied one byte per call */
Cipher c2 = Cipher.getInstance("AES/CCM/NoPadding", jceProvider);
c2.init(Cipher.ENCRYPT_MODE, keySpec, ccmSpec);
for (int i = 0; i < aad.length; i++) {
c2.updateAAD(aad, i, 1);
}
byte[] chunked = c2.doFinal(plaintext);
assertArrayEquals(
"Chunked AAD must match single-call AAD", single, chunked);
/* Round trip decrypt with AAD split into 7-byte chunks */
Cipher dec = Cipher.getInstance("AES/CCM/NoPadding", jceProvider);
dec.init(Cipher.DECRYPT_MODE, keySpec, ccmSpec);
for (int i = 0; i < aad.length; i += 7) {
dec.updateAAD(aad, i, Math.min(7, aad.length - i));
}
byte[] recovered = dec.doFinal(chunked);
assertArrayEquals(plaintext, recovered);
}
@Test
public void testAesCcmNoPaddingWithUpdate()
throws NoSuchProviderException, NoSuchAlgorithmException,

View File

@ -197,6 +197,134 @@ public class WolfCryptPKIXCertPathBuilderTest {
"cBdF0KRyJbeBYow7CUSMWYeODNLm+4A=\n" +
"-----END CERTIFICATE-----\n";
/* Certificate chain for BasicConstraints pathLenConstraint tests.
* Root CA (unlimited pathLen) -> Intermediate CA with pathLen:0 ->
* Sub CA (a CA cert) -> end entity. The Sub CA under a pathLen:0
* Intermediate is not permitted by RFC 5280 section 6.1.4. Valid from
* 2020 until 2126 so the current date is always in range. */
/* Root CA, self-signed, CA:TRUE with no pathLenConstraint.
* Subject: CN=wolfSSL PathLen Test Root CA */
private static final String PATHLEN_ROOT_PEM =
"-----BEGIN CERTIFICATE-----\n" +
"MIIDQTCCAimgAwIBAgIUeU7mQnTh+kWmT4Nb+GTzIjeBGZ4wDQYJKoZIhvcNAQEL\n" +
"BQAwJzElMCMGA1UEAwwcd29sZlNTTCBQYXRoTGVuIFRlc3QgUm9vdCBDQTAgFw0y\n" +
"MDAxMDEwMDAwMDBaGA8yMTI2MDEwMTAwMDAwMFowJzElMCMGA1UEAwwcd29sZlNT\n" +
"TCBQYXRoTGVuIFRlc3QgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" +
"AQoCggEBAJoAdHAjP/fkOONISSbCLeTF/7JvtE9U6RBB+ru2zA+E9otDd/1X1LSE\n" +
"ZU3OKl7/Q0uwZXAaAgYIFc3/91lEhypIaC4qYw4P6Pq+Y1m3SfVhlokw/lO3EhQ1\n" +
"71g8vxkD4VXTOimC1bTxUVekFM47Pmyb1cfP8vevcqHio/m42LYFwYJs6U/h3OAm\n" +
"2CqTAw6/cd5/TaUCjkfRz7ke1dSNe+cx5uQXoMAhKzY4DH22XzZu56xyELspKHxg\n" +
"rlmzaXjJsLRkABAOwBr7xB4vB8A7185eaZJZ5q5Nmyjtpszboh3rKGP+7q9l4Awf\n" +
"sdswNDEhr8EHzQ6nqO7hwDYNfCUKxs0CAwEAAaNjMGEwHQYDVR0OBBYEFKrsMwsj\n" +
"mnbMDPEpn5sC/5R3IX8sMB8GA1UdIwQYMBaAFKrsMwsjmnbMDPEpn5sC/5R3IX8s\n" +
"MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUA\n" +
"A4IBAQBpbla03+czktTC90uTL2c/K6Xxp2Gps8QF97Xg2+Mp7+L8ve02OM+y2r7e\n" +
"LLxJJ2/QLjmqToNht8MZXYwZGo0jzDJpwxP2Uz6tzzsTygOcvsT04cErTwB5dAWr\n" +
"s5ibqcQWbsMTB7z2dr7ZcQn8RekwyhEOe+NnzxIxSbfACiW/vHqH/nWpPOgWGGHe\n" +
"kqVxcwfz3gqSXPvNmx+0sC4seVtCy72LvYKGLKcLNHqlG0C1c7CR2T1Lo9clMSJJ\n" +
"1oEQd0fsI6Y/Kzkpywv68gooLxp0HrJqHI20IuVLWg/UxTA/MznF7ZbeAwp6wEJe\n" +
"KCO/ZK+KCniMT3fRq5VNN19Wumon\n" +
"-----END CERTIFICATE-----\n";
/* Intermediate CA, signed by Root, CA:TRUE with pathLenConstraint:0.
* Subject: CN=wolfSSL PathLen0 Intermediate CA */
private static final String PATHLEN0_INT_PEM =
"-----BEGIN CERTIFICATE-----\n" +
"MIIDSDCCAjCgAwIBAgIUVQlgyjkiLNbQkXTqEtBhFL8RCAAwDQYJKoZIhvcNAQEL\n" +
"BQAwJzElMCMGA1UEAwwcd29sZlNTTCBQYXRoTGVuIFRlc3QgUm9vdCBDQTAgFw0y\n" +
"MDAxMDEwMDAwMDBaGA8yMTI2MDEwMTAwMDAwMFowKzEpMCcGA1UEAwwgd29sZlNT\n" +
"TCBQYXRoTGVuMCBJbnRlcm1lZGlhdGUgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB\n" +
"DwAwggEKAoIBAQDX/cMcir/6pFUVd1BYAGdGSqwQvqyBrqoo4w2OPJw1cP5uworg\n" +
"OVoBXZjuwYp1FCZchnIdfDyTVwYovUdCt7mZWruFjDUq/E2X2rMSOsgleuECS/pc\n" +
"RBI629Si9fNzgDDilbD00C43WsCWBoGehNoadOVESYSlXucGh/Iq6sTuRnKgziF1\n" +
"CKr7P/LK6fcHm9ox0lHD9KTcNyTx2z1M3tR47ViBeyxpipuxaDtUKC6+2wx/CZpV\n" +
"elUkgBrATN/rAjhdC5dSq5uCf6XTCiD2CJUkUlXYLwcQ6RqK8GuTBr2+iUBFzx9G\n" +
"kQWq08TIu28sHGMAHgXL6gDVZX5pvDXLHfkNAgMBAAGjZjBkMBIGA1UdEwEB/wQI\n" +
"MAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTfG+5pJI8xIIoVEot2\n" +
"XhOc5hsipzAfBgNVHSMEGDAWgBSq7DMLI5p2zAzxKZ+bAv+UdyF/LDANBgkqhkiG\n" +
"9w0BAQsFAAOCAQEAlLKElEwhLpeVhgsYHAIHqsK1qicVxuSxiRcdkdNs+ztk6+mS\n" +
"WQIcNyTYcsX8a8n3ymowF/7ybxccI9Fn8j4VkjvOusZGEbE2Xrz+MBSUKXT1PKAy\n" +
"peTS4XlBKWDT1OXY+7T1A2Lzghm8udKDg88+3r6W3C7FkckuMGZSeaCsBZLTS4zQ\n" +
"KwlAcWmCNSu8v6eXRoBGT3MDSSd40X8kSKZXN3YGdKWbeTMwYFOHs7SEaiPZQ4nd\n" +
"9hEZTFkmBUOIy4n34BVPPEKVIGPOc1v8vWwQTWgMVjiN9PoGbVjno9GTFV3+wIsv\n" +
"NjVs0BiHsRYfI1KW5swaOYlFoH+dT5Kmrx+VzQ==\n" +
"-----END CERTIFICATE-----\n";
/* Sub CA, signed by the pathLen:0 Intermediate, CA:TRUE. Its presence
* as a CA below a pathLen:0 CA is the constraint violation.
* Subject: CN=wolfSSL Unauthorized Sub CA */
private static final String PATHLEN_SUBCA_PEM =
"-----BEGIN CERTIFICATE-----\n" +
"MIIDRDCCAiygAwIBAgIUH4x1nW6+pxYALTxAgK7yqyNpp7UwDQYJKoZIhvcNAQEL\n" +
"BQAwKzEpMCcGA1UEAwwgd29sZlNTTCBQYXRoTGVuMCBJbnRlcm1lZGlhdGUgQ0Ew\n" +
"IBcNMjAwMTAxMDAwMDAwWhgPMjEyNjAxMDEwMDAwMDBaMCYxJDAiBgNVBAMMG3dv\n" +
"bGZTU0wgVW5hdXRob3JpemVkIFN1YiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" +
"ADCCAQoCggEBAMRelX6hXnQ7g2e2Y3UIUGcZdD7tKo683wdkb3ayJzoUYGr9zzyR\n" +
"j3CKIONSuKhXWRtC6ZrPlNDS5yStjiPeEaiGA+mVPL9mA3JK57E+cI3NewYBT3Fz\n" +
"WFSQvQYZvjEqkx8umCgwNUIaQjkc1cVXpSftsU1sHNJu5B350KQ1riVYidmUR9kd\n" +
"ZYKN0Enq2IrAd2SPhgK72ZbY5sEykNBLe3VcxDUCtBLXHic91dtenzwMR/0Gjr4z\n" +
"EV7YcdaBUHj9m9u1EPatP0v9M+C88nCwbwoygyMC4wFb5M+xFBANYDOKrHP8EBqZ\n" +
"7aoTJb7Suj1EoQB6PGNiuYQKDRjc4GHawV8CAwEAAaNjMGEwDwYDVR0TAQH/BAUw\n" +
"AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJuuszq8/p+ug1wfxFdIYM8w\n" +
"ddgFMB8GA1UdIwQYMBaAFN8b7mkkjzEgihUSi3ZeE5zmGyKnMA0GCSqGSIb3DQEB\n" +
"CwUAA4IBAQBiwJdMdlwjxSs3i9O6W9VXoxHH14/vjGPilIxJOMKehw8bOfxGN95I\n" +
"HJBaC4wnltud/pvHcnAyVloure41igSnAVRFP16upRgj8qt3WCnNJa0pfKc9DiiN\n" +
"KJBr58w8/fI5WF4DX6oQtWznvO45dopJqeVq+Qam46yMayycs5JOTBhh5PoXjDvw\n" +
"7iyrSzjmTP6gsxQ0Pod2AbGIKEdwQbK6CxE0mGbO+vWaBzmE6x41RHRQCQJ85FFU\n" +
"XGn/Wm0ofAunaZVixf/MWQUEeFevUf/apuz7Oms4F0NRJexHxQOrCHQbp6W/Kf5F\n" +
"unWeUKNgfmnn8aeYrdpCBVeAqv8+8y1c\n" +
"-----END CERTIFICATE-----\n";
/* End entity, signed by the Sub CA.
* Subject: CN=wolfSSL PathLen Test User */
private static final String PATHLEN_EE_PEM =
"-----BEGIN CERTIFICATE-----\n" +
"MIIDOjCCAiKgAwIBAgIUENF0NIvYBd5mYcIIK5rEE3cyHoAwDQYJKoZIhvcNAQEL\n" +
"BQAwJjEkMCIGA1UEAwwbd29sZlNTTCBVbmF1dGhvcml6ZWQgU3ViIENBMCAXDTIw\n" +
"MDEwMTAwMDAwMFoYDzIxMjYwMTAxMDAwMDAwWjAkMSIwIAYDVQQDDBl3b2xmU1NM\n" +
"IFBhdGhMZW4gVGVzdCBVc2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" +
"AQEA0h0R6LDY06gfti/aWfZpZo7rM+p1Pbldvy/JbfCgjvTsQuZFFAY2VUrlzJfS\n" +
"tJOI2Ud9ArEfbaQrd9qZRBkqNAyiB0LKIiJ01cJMjZzfYUUm78DU5r9fcjV5bJOT\n" +
"dwCt2h37yXctkjXttu8RXgDb05yX9tjvnI8duZeQq9MMKOuqnFAAMUQO3O1U2CHT\n" +
"IjRY2x9P7VtwXdCr0Fd6aEBb4tPkyH3pDHlnG48SofKuuA11mByN50VzwMEftHF6\n" +
"1/Y2Cha2SG9WM3MfLMAcCbic6a0xEytqyf/6pqAF1dt3hvdPV6aj3FkJ7Unbs/oJ\n" +
"IzD1C8YNfaLCeGXSP1bO0JIqZwIDAQABo2AwXjAMBgNVHRMBAf8EAjAAMA4GA1Ud\n" +
"DwEB/wQEAwIHgDAdBgNVHQ4EFgQUxFsxbZFBtSTeqCIyLgIROSCHzWMwHwYDVR0j\n" +
"BBgwFoAUm66zOrz+n66DXB/EV0hgzzB12AUwDQYJKoZIhvcNAQELBQADggEBAICe\n" +
"GChCpADRSb97+KYCI4oQrCBVXBYPgBlbArGS031h7syipZO8xq8IxNJmA+Agxs5Z\n" +
"sbwi0NcutI1xYxpm5fkuh/Ly+F452l2Sug4CYMHc0fscgS7rQtB2YM5opOInptzF\n" +
"VI5rC8vF0artFfGaQttG5mWimOlENLqxeQTk//pbmiEsC/3G0D8B4C3y64NovEc1\n" +
"QYvDfb3BLTB1yNyhJwu3LGsgmRb19WRWD7WIygJpDqNlEXHQOawffqUKvnSUEQlJ\n" +
"1PtGE28/Wfsr9ROLFxtaNaTrZFiIh3qNctb8vHo4KtPBXANuj2JI5sFNr15Be633\n" +
"/rtSLpxeZgmMjufX8vo=\n" +
"-----END CERTIFICATE-----\n";
/* End entity, signed directly by the pathLen:0 Intermediate. A pathLen:0
* CA issuing an end entity directly is permitted by RFC 5280.
* Subject: CN=wolfSSL PathLen0 Direct User */
private static final String PATHLEN0_DIRECT_EE_PEM =
"-----BEGIN CERTIFICATE-----\n" +
"MIIDQjCCAiqgAwIBAgIUH4x1nW6+pxYALTxAgK7yqyNpp7YwDQYJKoZIhvcNAQEL\n" +
"BQAwKzEpMCcGA1UEAwwgd29sZlNTTCBQYXRoTGVuMCBJbnRlcm1lZGlhdGUgQ0Ew\n" +
"IBcNMjAwMTAxMDAwMDAwWhgPMjEyNjAxMDEwMDAwMDBaMCcxJTAjBgNVBAMMHHdv\n" +
"bGZTU0wgUGF0aExlbjAgRGlyZWN0IFVzZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IB\n" +
"DwAwggEKAoIBAQC9syWEdkNvVPZ3Vr+ZE5TsdK9nAX/Fbdj2ycd1qu+0Y40zhsoB\n" +
"AI6nEdfB/3bYxHeIrX//cpLmiUbBGZd4XSHtdHwFHVnL3547FwaHm29gRM/rJWsP\n" +
"O7vb6pPEhcZbWACDx2E0Ds2/mbnhXy+F7ioYyT40WjcwDwN+7psR9o8iKpsqLYnu\n" +
"fMwjNPdfD3uGc2ITsTLUzaIPwRxx5m55Wy/n5Jn76AM6cFLe0PJbehZeEFmFIdNb\n" +
"MgUiumzijw1Nu58EZJ2qy8Q6PVjIn7sI7dn2WoehC6a0dh4RQtnBfqGrucmQyI76\n" +
"X/9GgRRjad9ShSdygF+kvq+WS6vAS4JO9jhHAgMBAAGjYDBeMAwGA1UdEwEB/wQC\n" +
"MAAwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBRh/GIDY1/jS9Sce6qVZxKhcDlJ\n" +
"qzAfBgNVHSMEGDAWgBTfG+5pJI8xIIoVEot2XhOc5hsipzANBgkqhkiG9w0BAQsF\n" +
"AAOCAQEAZsqPP9a1DKRIB2HKoxBMKXiVwsY6B+e5wrqKWYdrVimdHSak9fy3/a4s\n" +
"aHVML4PoO2B+ILOFxHlY6VKwcn0/CeNFXC/OJlZ6d5+LfnRWzq2k1rY66YsxO72m\n" +
"uLwY90jzj3hokQil9PkmoLk1izg8ZJ5Ecd4pqDXOj2DcVO/sh/M3OD6fGONXEe2r\n" +
"OnQQL90nbs8XOdd24vZ3k1FeB/hTyuQJxCV184MmwROYtFXiQ5LPtsur4Qf7A7T3\n" +
"s6ldtOUGtiKSVuukPd/Q65+e5ySnLluMWGBZZicgizZVInLJ6CCth100JeYiop38\n" +
"yzU0/5Kv1+CkhN2sIKDHjXRNJxAXvQ==\n" +
"-----END CERTIFICATE-----\n";
/**
* Test if this environment is Android.
* @return true if Android, otherwise false
@ -3564,6 +3692,96 @@ public class WolfCryptPKIXCertPathBuilderTest {
intermediateCert, path.getCertificates().get(1));
}
/**
* Build PKIXBuilderParameters over target and intermediate certs,
* trusting root, with a custom validation date.
*/
private PKIXBuilderParameters pathLenParams(X509Certificate root,
X509Certificate target, X509Certificate... intermediates)
throws CertificateException, InvalidAlgorithmParameterException,
NoSuchAlgorithmException {
Set<TrustAnchor> anchors = new HashSet<>();
anchors.add(new TrustAnchor(root, null));
Collection<Certificate> certs = new ArrayList<>();
certs.add(target);
for (X509Certificate c : intermediates) {
certs.add(c);
}
CertStore certStore = CertStore.getInstance("Collection",
new CollectionCertStoreParameters(certs));
X509CertSelector selector = new X509CertSelector();
selector.setCertificate(target);
PKIXBuilderParameters params =
new PKIXBuilderParameters(anchors, selector);
params.setRevocationEnabled(false);
params.addCertStore(certStore);
params.setDate(new Date(System.currentTimeMillis()));
return params;
}
/**
* Test that the Java fallback chain builder rejects a chain where a CA
* with BasicConstraints pathLenConstraint 0 issues another CA certificate
* (RFC 5280 section 6.1.4). Only applies to the fallback path, native
* chain building enforces pathLen per the linked wolfSSL version.
*/
@Test
public void testPathLenConstraintRejectsUnauthorizedSubCA()
throws Exception {
Assume.assumeTrue("Only applies when native check_time not supported",
!WolfSSLX509StoreCtx.isStoreCheckTimeSupported());
X509Certificate root = loadCertFromPEM(PATHLEN_ROOT_PEM);
X509Certificate int0 = loadCertFromPEM(PATHLEN0_INT_PEM);
X509Certificate subca = loadCertFromPEM(PATHLEN_SUBCA_PEM);
X509Certificate ee = loadCertFromPEM(PATHLEN_EE_PEM);
PKIXBuilderParameters params = pathLenParams(root, ee, subca, int0);
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider);
try {
cpb.build(params);
fail("Expected CertPathBuilderException, pathLen:0 CA must not " +
"be accepted as issuer of a sub CA");
} catch (CertPathBuilderException e) {
assertNotNull("Exception message should not be null",
e.getMessage());
}
}
/**
* Test that the Java fallback chain builder still accepts a CA with
* pathLenConstraint 0 issuing an end entity directly, proving the pathLen
* check does not over-reject valid chains.
*/
@Test
public void testPathLenZeroAllowsDirectEndEntity()
throws Exception {
Assume.assumeTrue("Only applies when native check_time not supported",
!WolfSSLX509StoreCtx.isStoreCheckTimeSupported());
X509Certificate root = loadCertFromPEM(PATHLEN_ROOT_PEM);
X509Certificate int0 = loadCertFromPEM(PATHLEN0_INT_PEM);
X509Certificate ee = loadCertFromPEM(PATHLEN0_DIRECT_EE_PEM);
PKIXBuilderParameters params = pathLenParams(root, ee, int0);
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider);
CertPathBuilderResult result = cpb.build(params);
assertNotNull("CertPathBuilderResult should not be null", result);
CertPath path = ((PKIXCertPathBuilderResult) result).getCertPath();
assertEquals("Path should contain end entity and intermediate",
2, path.getCertificates().size());
}
/**
* Test that setDate() with a date after cert expiry still fails.
*

View File

@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
import com.wolfssl.wolfcrypt.Dh;
import com.wolfssl.wolfcrypt.Rng;
import com.wolfssl.wolfcrypt.FeatureDetect;
import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.WolfCryptException;
import com.wolfssl.wolfcrypt.Fips;
@ -49,6 +50,45 @@ public class DhTest {
private static Rng rng = new Rng();
private final Object rngLock = new Rng();
/* 2048-bit DH prime p, generator g is 2. */
private static final String DH_P_2048_HEX =
"8E7EC04F98E157D62585624B5283FFD8D33B5EC35F0812FE79227319974A3517" +
"6F858116030A97494618413BB82E87BBCFF26A24DF32CAEEF40BE3FBBC18C709" +
"A167423BEE8C84539B0FADEBD305039AA800C3AA4A4E3C6B775FAD8E11E30FF9" +
"B1E1CA138C9DC9947751A5EE935AC54BB4B045D0CE6BA1AB4D40701D62F4990E" +
"A1CB9783CC3769171C8FFDC1AE7A10EC1C82AF9240B67CC667A00F70F2FE9B3B" +
"4BDABE25EB42FBEC5533E53A4BA5B60B32B7CBF326D42F620757A11C554B91DF" +
"C67B54403807E1A228B858B8D2614BF9D0E08B840F5CA95BA0ADDD641F2481B9" +
"0A7C47583D2F14E9C3A5C80440E74B7AA41BBD71025B9F754F424BF382BB5817";
/* X.509 SubjectPublicKeyInfo for the group above with public value
* Y = g = 2, a valid in range public key. */
private static final String DH_PUB_X509_VALID_HEX =
"308201213082011706092A864886F70D0103013082010802820101008E7EC04F" +
"98E157D62585624B5283FFD8D33B5EC35F0812FE79227319974A35176F858116" +
"030A97494618413BB82E87BBCFF26A24DF32CAEEF40BE3FBBC18C709A167423B" +
"EE8C84539B0FADEBD305039AA800C3AA4A4E3C6B775FAD8E11E30FF9B1E1CA13" +
"8C9DC9947751A5EE935AC54BB4B045D0CE6BA1AB4D40701D62F4990EA1CB9783" +
"CC3769171C8FFDC1AE7A10EC1C82AF9240B67CC667A00F70F2FE9B3B4BDABE25" +
"EB42FBEC5533E53A4BA5B60B32B7CBF326D42F620757A11C554B91DFC67B5440" +
"3807E1A228B858B8D2614BF9D0E08B840F5CA95BA0ADDD641F2481B90A7C4758" +
"3D2F14E9C3A5C80440E74B7AA41BBD71025B9F754F424BF382BB581702010203" +
"0400020102";
/* Same as above but with public value Y = 1, an out of range small
* subgroup value that must be rejected. */
private static final String DH_PUB_X509_SMALLSUBGROUP_HEX =
"308201213082011706092A864886F70D0103013082010802820101008E7EC04F" +
"98E157D62585624B5283FFD8D33B5EC35F0812FE79227319974A35176F858116" +
"030A97494618413BB82E87BBCFF26A24DF32CAEEF40BE3FBBC18C709A167423B" +
"EE8C84539B0FADEBD305039AA800C3AA4A4E3C6B775FAD8E11E30FF9B1E1CA13" +
"8C9DC9947751A5EE935AC54BB4B045D0CE6BA1AB4D40701D62F4990EA1CB9783" +
"CC3769171C8FFDC1AE7A10EC1C82AF9240B67CC667A00F70F2FE9B3B4BDABE25" +
"EB42FBEC5533E53A4BA5B60B32B7CBF326D42F620757A11C554B91DFC67B5440" +
"3807E1A228B858B8D2614BF9D0E08B840F5CA95BA0ADDD641F2481B90A7C4758" +
"3D2F14E9C3A5C80440E74B7AA41BBD71025B9F754F424BF382BB581702010203" +
"0400020101";
@Rule(order = Integer.MIN_VALUE)
public TestRule testWatcher = TimedTestWatcher.create();
@ -119,6 +159,67 @@ public class DhTest {
assertArrayEquals(sharedSecretA, sharedSecretB);
}
/**
* checkPublicKey() must reject an out of range public key value. This
* is the same validation wc_DhAgree applies to a peer public key, so a
* small subgroup value cannot reach the shared secret computation.
*/
@Test
public void checkPublicKeyRejectsOutOfRangeValue() {
Dh dh = new Dh();
dh.setParams(Util.h2b(DH_P_2048_HEX), Util.h2b("02"));
try {
/* Y = 1 is below the valid range 2 <= Y <= p-2 */
dh.checkPublicKey(new byte[] { 0x01 });
fail("checkPublicKey should reject Y = 1");
} catch (WolfCryptException e) {
/* expected */
} finally {
dh.releaseNativeStruct();
}
}
/**
* publicKeyDecodeX509() must accept a valid in range public key.
*/
@Test
public void publicKeyDecodeX509AcceptsValidKey() {
Assume.assumeTrue("WOLFSSL_DH_EXTRA required",
FeatureDetect.DhExtraEnabled());
Dh dh = new Dh();
try {
byte[] der = dh.publicKeyDecodeX509(
Util.h2b(DH_PUB_X509_VALID_HEX));
assertNotNull(der);
} finally {
dh.releaseNativeStruct();
}
}
/**
* publicKeyDecodeX509() must reject a public key whose value is an out
* of range small subgroup element, failing fast at import instead of
* deferring to the shared secret computation.
*/
@Test
public void publicKeyDecodeX509RejectsSmallSubgroupValue() {
Assume.assumeTrue("WOLFSSL_DH_EXTRA required",
FeatureDetect.DhExtraEnabled());
Dh dh = new Dh();
try {
dh.publicKeyDecodeX509(
Util.h2b(DH_PUB_X509_SMALLSUBGROUP_HEX));
fail("publicKeyDecodeX509 should reject Y = 1");
} catch (WolfCryptException e) {
/* expected */
} finally {
dh.releaseNativeStruct();
}
}
@Test
public void bufferOverflowRegressionTest() {
/*

View File

@ -0,0 +1,137 @@
/* PwdbasedTest.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 3 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 com.wolfssl.wolfcrypt.WolfCrypt;
import com.wolfssl.wolfcrypt.Pwdbased;
import com.wolfssl.wolfcrypt.FeatureDetect;
import com.wolfssl.wolfcrypt.Fips;
import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.WolfCryptException;
/**
* Tests for the Pwdbased PBKDF2 and PKCS12 PBKDF JNI wrappers.
*/
public class PwdbasedTest {
@Rule(order = Integer.MIN_VALUE)
public TestRule testWatcher = TimedTestWatcher.create();
@BeforeClass
public static void testSetup() {
System.out.println("JNI Pwdbased Class");
}
/* Password with distinctive pattern, used to detect modification */
private static byte[] makePassword() {
byte[] pass = new byte[32];
for (int i = 0; i < pass.length; i++) {
pass[i] = (byte)(0x41 + ((i * 7) % 26));
}
return pass;
}
/**
* PBKDF2-HMAC-SHA256 known answer test from RFC 7914 Section 11.
*/
@Test
public void testPbkdf2KnownAnswer() {
Assume.assumeTrue("PBKDF2 not compiled in native wolfSSL",
FeatureDetect.Pbkdf2Enabled());
/* RFC 7914 vector uses a 6 byte key and 4 byte salt, below the FIPS
* HMAC key and salt minimums, skip this known answer test in FIPS */
Assume.assumeTrue("RFC 7914 KAT inputs below FIPS minimums",
!Fips.enabled);
byte[] pass = "passwd".getBytes();
byte[] salt = "salt".getBytes();
byte[] expected = Util.h2b(
"55AC046E56E3089FEC1691C22544B605" +
"F94185216DDE0465E68B9D57C20DACBC" +
"49CA9CCCF179B645991664B39D77EF31" +
"7C71B845B1E30BD509112041D3A19783");
byte[] key = Pwdbased.PBKDF2(pass, salt, 1, 64,
WolfCrypt.WC_HASH_TYPE_SHA256);
assertArrayEquals(expected, key);
}
/**
* PBKDF2 must not modify the caller's password array, and repeated
* calls with the same password must derive the same key.
*/
@Test
public void testPbkdf2DoesNotModifyPassword() {
Assume.assumeTrue("PBKDF2 not compiled in native wolfSSL",
FeatureDetect.Pbkdf2Enabled());
byte[] pass = makePassword();
byte[] passCopy = pass.clone();
byte[] salt = new byte[] {1, 2, 3, 4, 5, 6, 7, 8};
byte[] key1 = Pwdbased.PBKDF2(pass, salt, 1000, 32,
WolfCrypt.WC_HASH_TYPE_SHA256);
assertArrayEquals("PBKDF2 modified caller password array",
passCopy, pass);
byte[] key2 = Pwdbased.PBKDF2(pass, salt, 1000, 32,
WolfCrypt.WC_HASH_TYPE_SHA256);
assertArrayEquals("repeated PBKDF2 derived different key",
key1, key2);
}
/**
* PKCS12 PBKDF must not modify the caller's password array.
*/
@Test
public void testPkcs12PbkdfDoesNotModifyPassword() {
byte[] pass = makePassword();
byte[] passCopy = pass.clone();
byte[] salt = new byte[] {1, 2, 3, 4, 5, 6, 7, 8};
byte[] key = null;
try {
key = Pwdbased.PKCS12_PBKDF(pass, salt, 100, 24,
WolfCrypt.WC_HASH_TYPE_SHA256, 1);
} catch (WolfCryptException e) {
Assume.assumeTrue("PKCS12 PBKDF not compiled in native wolfSSL",
e.getError() != WolfCryptError.NOT_COMPILED_IN);
throw e;
}
assertNotNull(key);
assertArrayEquals("PKCS12_PBKDF modified caller password array",
passCopy, pass);
}
}

View File

@ -47,6 +47,7 @@ import org.junit.runners.Suite.SuiteClasses;
Sha512Test.class,
Sha3Test.class,
HmacTest.class,
PwdbasedTest.class,
RngTest.class,
RsaTest.class,
DhTest.class,

View File

@ -180,6 +180,83 @@ public class WolfSSLCertManagerVerifyCallbackTest {
}
}
/**
* Test each CertManager verification invokes its own callback, not
* the most recently registered one from another CertManager.
*/
@Test
public void testVerifyCallbackDispatchPerCertManager() throws Exception {
final AtomicBoolean cm1Invoked = new AtomicBoolean(false);
final AtomicBoolean cm2Invoked = new AtomicBoolean(false);
WolfSSLCertManager cm1 = null;
WolfSSLCertManager cm2 = null;
byte[] caDer = readFile(caCertDer);
byte[] serverDer = readFile(serverCertDer);
try {
cm1 = new WolfSSLCertManager();
cm1.CertManagerLoadCABuffer(caDer, caDer.length,
WolfCrypt.SSL_FILETYPE_ASN1);
cm2 = new WolfSSLCertManager();
cm2.CertManagerLoadCABuffer(caDer, caDer.length,
WolfCrypt.SSL_FILETYPE_ASN1);
/* Register cm2 callback last so it sits at the head of the
* native callback list when cm1 verifies */
cm1.setVerifyCallback(new WolfSSLCertManagerVerifyCallback() {
public int verify(int preverify, int error, int errorDepth) {
cm1Invoked.set(true);
return 1;
}
});
cm2.setVerifyCallback(new WolfSSLCertManagerVerifyCallback() {
public int verify(int preverify, int error, int errorDepth) {
cm2Invoked.set(true);
return 1;
}
});
try {
cm1.CertManagerVerifyBuffer(serverDer, serverDer.length,
WolfCrypt.SSL_FILETYPE_ASN1);
} catch (WolfCryptException e) {
/* Verification result itself is not what we assert here */
}
assertTrue("cm1 verify callback should have been invoked",
cm1Invoked.get());
assertFalse("cm2 verify callback should not have been invoked",
cm2Invoked.get());
/* Verify cm2 verification reaches cm2 callback */
cm1Invoked.set(false);
cm2Invoked.set(false);
try {
cm2.CertManagerVerifyBuffer(serverDer, serverDer.length,
WolfCrypt.SSL_FILETYPE_ASN1);
} catch (WolfCryptException e) {
/* Verification result itself is not what we assert here */
}
assertTrue("cm2 verify callback should have been invoked",
cm2Invoked.get());
assertFalse("cm1 verify callback should not have been invoked",
cm1Invoked.get());
} finally {
if (cm1 != null) {
cm1.free();
}
if (cm2 != null) {
cm2.free();
}
}
}
/**
* Test concurrent certificate verification while callbacks are registered
* and cleared. The native callback list is global across all CertManager