Merge pull request #245 from cconlon/fenrirJuly23
Fenrir correctness and robustness fixespull/250/head
commit
90c50b850b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
33
jni/jni_dh.c
33
jni/jni_dh.c
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
|||
Sha512Test.class,
|
||||
Sha3Test.class,
|
||||
HmacTest.class,
|
||||
PwdbasedTest.class,
|
||||
RngTest.class,
|
||||
RsaTest.class,
|
||||
DhTest.class,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue