Merge pull request #200 from cconlon/certPathBuilderFixes

CertPathBuilder fixes for disabledAlgorithms, date handling, and edge cases
pull/202/head
Ruby Martin 2026-02-20 12:20:12 -07:00 committed by GitHub
commit ae366dd9ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 2119 additions and 234 deletions

View File

@ -7,6 +7,14 @@
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_wolfssl_wolfcrypt_WolfSSLCertManager
* Method: getWOLFSSL_LOAD_FLAG_DATE_ERR_OKAY
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_getWOLFSSL_1LOAD_1FLAG_1DATE_1ERR_1OKAY
(JNIEnv *, jclass);
/*
* Class: com_wolfssl_wolfcrypt_WolfSSLCertManager
* Method: CertManagerNew
@ -39,6 +47,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerLoadCABuffer
(JNIEnv *, jclass, jlong, jbyteArray, jlong, jint);
/*
* Class: com_wolfssl_wolfcrypt_WolfSSLCertManager
* Method: CertManagerLoadCABufferEx
* Signature: (J[BJII)I
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerLoadCABufferEx
(JNIEnv *, jclass, jlong, jbyteArray, jlong, jint, jint);
/*
* Class: com_wolfssl_wolfcrypt_WolfSSLCertManager
* Method: CertManagerUnloadCAs
@ -119,6 +135,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerCheckOCSPResponse
(JNIEnv *, jclass, jlong, jbyteArray, jint, jbyteArray, jint);
/*
* Class: com_wolfssl_wolfcrypt_WolfSSLCertManager
* Method: OcspResponseStatus
* Signature: ([BI)I
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_OcspResponseStatus
(JNIEnv *, jclass, jbyteArray, jint);
/*
* Class: com_wolfssl_wolfcrypt_WolfSSLCertManager
* Method: CertManagerSetVerify

View File

@ -170,6 +170,32 @@ static void removeCallbackCtx(WOLFSSL_CERT_MANAGER* cm)
}
}
/* Extract cert DER bytes at given depth from WOLFSSL_X509_STORE_CTX into
* a new jbyteArray. Returns NULL if cert not available at depth. */
static jbyteArray getCertDerAtDepth(JNIEnv* jenv,
WOLFSSL_X509_STORE_CTX* store, int depth)
{
jbyteArray der = NULL;
WOLFSSL_BUFFER_INFO* certInfo = NULL;
if (store->certs == NULL || depth < 0 || depth >= store->totalCerts) {
return NULL;
}
certInfo = &store->certs[depth];
if (certInfo->buffer == NULL || certInfo->length == 0) {
return NULL;
}
der = (*jenv)->NewByteArray(jenv, (jsize)certInfo->length);
if (der != NULL) {
(*jenv)->SetByteArrayRegion(jenv, der, 0, (jsize)certInfo->length,
(const jbyte*)certInfo->buffer);
}
return der;
}
/* Native verify callback that calls into Java verify callback.
*
* This is registered with wolfSSL_CertManagerSetVerify() and called
@ -185,6 +211,7 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store)
jint result = 0;
JNIEnv* jenv = NULL;
VerifyCallbackCtx* ctx = NULL;
jbyteArray certDer = NULL;
jclass callbackClass = NULL;
jmethodID verifyMethod = NULL;
@ -236,9 +263,15 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store)
error = store->error;
errorDepth = store->error_depth;
/* If available, extract cert DER bytes from store context at errorDepth */
certDer = getCertDerAtDepth(jenv, store, errorDepth);
/* Find verify() method on callback object */
callbackClass = (*jenv)->GetObjectClass(jenv, ctx->callback);
if (callbackClass == NULL) {
if (certDer != NULL) {
(*jenv)->DeleteLocalRef(jenv, certDer);
}
if (needsDetach) {
(*ctx->jvm)->DetachCurrentThread(ctx->jvm);
}
@ -246,8 +279,11 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store)
}
verifyMethod = (*jenv)->GetMethodID(jenv, callbackClass,
"verify", "(III)I");
"verify", "(III[B)I");
if (verifyMethod == NULL) {
if (certDer != NULL) {
(*jenv)->DeleteLocalRef(jenv, certDer);
}
(*jenv)->DeleteLocalRef(jenv, callbackClass);
if (needsDetach) {
(*ctx->jvm)->DetachCurrentThread(ctx->jvm);
@ -255,9 +291,9 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store)
return 0;
}
/* Call Java callback.verify(preverify, error, errorDepth) */
/* Call Java callback.verify(preverify, error, errorDepth, certDer) */
result = (*jenv)->CallIntMethod(jenv, ctx->callback, verifyMethod,
(jint)preverify, (jint)error, (jint)errorDepth);
(jint)preverify, (jint)error, (jint)errorDepth, certDer);
/* Check for Java exceptions, reject on exception */
if ((*jenv)->ExceptionCheck(jenv)) {
@ -273,6 +309,9 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store)
store->error = 0;
}
if (certDer != NULL) {
(*jenv)->DeleteLocalRef(jenv, certDer);
}
(*jenv)->DeleteLocalRef(jenv, callbackClass);
if (needsDetach) {
(*ctx->jvm)->DetachCurrentThread(ctx->jvm);
@ -281,6 +320,15 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store)
return (int)result;
}
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_getWOLFSSL_1LOAD_1FLAG_1DATE_1ERR_1OKAY
(JNIEnv* env, jclass jcl)
{
(void)env;
(void)jcl;
return WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY;
}
JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerNew
(JNIEnv* env, jclass jcl)
{
@ -363,6 +411,31 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager
return (jint)ret;
}
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerLoadCABufferEx
(JNIEnv* env, jclass jcl, jlong cmPtr, jbyteArray in, jlong sz, jint format, jint flags)
{
int ret = 0;
word32 buffSz = 0;
byte* buff = NULL;
WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr;
(void)jcl;
(void)sz;
if (env == NULL || in == NULL) {
return BAD_FUNC_ARG;
}
buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL);
buffSz = (*env)->GetArrayLength(env, in);
ret = wolfSSL_CertManagerLoadCABuffer_ex(cm, buff, buffSz, format, 0,
(word32)flags);
(*env)->ReleaseByteArrayElements(env, in, (jbyte*)buff, JNI_ABORT);
return (jint)ret;
}
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerUnloadCAs
(JNIEnv* env, jclass jcl, jlong cmPtr)
{
@ -715,6 +788,69 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager
#endif
}
/* Get OCSPResponseStatus from raw OCSP response bytes.
*
* Uses wolfSSL_d2i_OCSP_RESPONSE() to parse response and
* wolfSSL_OCSP_response_status() to extract the status.
*
* RFC 6960:
* OCSPResponse ::= SEQUENCE {
* responseStatus OCSPResponseStatus,
* responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
* OCSPResponseStatus ::= ENUMERATED {
* successful (0), malformedRequest (1), internalError (2),
* tryLater (3), sigRequired (5), unauthorized (6) }
*
* Returns OCSPResponseStatus ENUMERATED value (0-6) on success, or negative
* error code on failure (BAD_FUNC_ARG, MEMORY_E, NOT_COMPILED_IN, or -1 on
* parse failure).
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_OcspResponseStatus
(JNIEnv* env, jclass jcl, jbyteArray response, jint responseSz)
{
#if defined(HAVE_OCSP) && defined(OPENSSL_EXTRA)
int ret = -1;
jint arrLen = 0;
byte* respBuf = NULL;
const unsigned char* p = NULL;
OcspResponse* ocspResp = NULL;
(void)jcl;
(void)responseSz;
if (env == NULL || response == NULL) {
return BAD_FUNC_ARG;
}
arrLen = (*env)->GetArrayLength(env, response);
if (arrLen <= 0) {
return BAD_FUNC_ARG;
}
respBuf = (byte*)(*env)->GetByteArrayElements(env, response, NULL);
if (respBuf == NULL) {
return MEMORY_E;
}
p = (const unsigned char*)respBuf;
ocspResp = wolfSSL_d2i_OCSP_RESPONSE(NULL, &p, (int)arrLen);
if (ocspResp != NULL) {
ret = wolfSSL_OCSP_response_status(ocspResp);
wolfSSL_OCSP_RESPONSE_free(ocspResp);
}
(*env)->ReleaseByteArrayElements(env, response, (jbyte*)respBuf, JNI_ABORT);
return (jint)ret;
#else
(void)env;
(void)jcl;
(void)response;
(void)responseSz;
return NOT_COMPILED_IN;
#endif /* HAVE_OCSP && OPENSSL_EXTRA */
}
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerSetVerify
(JNIEnv* env, jclass jcl, jlong cmPtr, jobject callback)
{

View File

@ -113,13 +113,188 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
throw new InvalidAlgorithmParameterException(
"params not of type PKIXBuilderParameters");
}
/* Target constraints must be X509CertSelector if set */
PKIXBuilderParameters pkixParams = (PKIXBuilderParameters)params;
CertSelector selector = pkixParams.getTargetCertConstraints();
if ((selector != null) && !(selector instanceof X509CertSelector)) {
throw new InvalidAlgorithmParameterException(
"Target certificate constraints must be X509CertSelector");
}
}
/**
* Check certificate against disabled algorithms constraints from security
* property jdk.certpath.disabledAlgorithms.
*
* Validates both the signature algorithm and public key algorithm/size
* against the disabled algorithms list.
*
* @param cert certificate to check
*
* @throws CertPathBuilderException if algorithm is disabled or key size is
* too small
*/
private void checkAlgorithmConstraints(X509Certificate cert)
throws CertPathBuilderException {
String sigAlg = null;
PublicKey pubKey = null;
String propertyName = "jdk.certpath.disabledAlgorithms";
if (cert == null) {
throw new CertPathBuilderException(
"X509Certificate is null when checking algorithm constraints");
}
/* Check signature algorithm against disabled list */
sigAlg = cert.getSigAlgName();
if (WolfCryptUtil.isAlgorithmDisabled(sigAlg, propertyName)) {
log("Algorithm constraints check failed on signature algorithm: " +
sigAlg);
throw new CertPathBuilderException(
"Algorithm constraints check failed on signature algorithm: " +
sigAlg);
}
/* Check public key algorithm and size against constraints */
pubKey = cert.getPublicKey();
if (!WolfCryptUtil.isKeyAllowed(pubKey, propertyName)) {
log("Algorithm constraints check failed on public key: " +
pubKey.getAlgorithm());
throw new CertPathBuilderException(
"Algorithm constraints check failed on public key: " +
pubKey.getAlgorithm());
}
}
/**
* Check trust anchor against disabled algorithms constraints from
* security property jdk.certpath.disabledAlgorithms.
*
* Handles both trust anchors with certificates and those with only a
* public key (no cert). Only checks the public key algorithm and size,
* not the signature algorithm, since trust anchors are self-signed and
* their signatures are inherently trusted (match SunJCE behavior).
*
* @param anchor trust anchor to check
*
* @throws CertPathBuilderException if key algorithm is disabled or key
* size is too small
*/
private void checkTrustAnchorConstraints(TrustAnchor anchor)
throws CertPathBuilderException {
PublicKey pubKey = null;
String propertyName = "jdk.certpath.disabledAlgorithms";
if (anchor == null) {
throw new CertPathBuilderException(
"TrustAnchor is null when checking trust anchor constraints");
}
X509Certificate cert = anchor.getTrustedCert();
if (cert != null) {
pubKey = cert.getPublicKey();
}
else {
pubKey = anchor.getCAPublicKey();
}
if (pubKey == null) {
throw new CertPathBuilderException(
"Trust anchor has no public key to check algo constraints");
}
if (!WolfCryptUtil.isKeyAllowed(pubKey, propertyName)) {
log("Algorithm constraints check failed on trust " +
"anchor public key: " + pubKey.getAlgorithm());
throw new CertPathBuilderException(
"Algorithm constraints check failed on trust " +
"anchor public key: " + pubKey.getAlgorithm());
}
}
/**
* Check if certificate uses a disabled algorithm.
*
* Same checks as checkAlgorithmConstraints() but returns boolean
* instead of throwing, for use in filtering intermediate certificates.
*
* @param cert certificate to check
*
* @return true if certificate uses a disabled algorithm, false if allowed
*/
private boolean hasDisabledAlgorithm(X509Certificate cert) {
try {
checkAlgorithmConstraints(cert);
return false;
}
catch (CertPathBuilderException e) {
return true;
}
}
/**
* Check that each certificate in the path was signed by a key that meets
* algorithm constraints. Walks the chain from target toward trust anchor,
* checking each signer's public key against
* jdk.certpath.disabledAlgorithms.
*
* @param path list of certificates (target at index 0)
* @param anchor trust anchor that signed the last cert in path
*
* @throws CertPathBuilderException if a signer key is disabled
*/
private void checkSignerKeyConstraints(
List<X509Certificate> path, TrustAnchor anchor)
throws CertPathBuilderException {
String propertyName = "jdk.certpath.disabledAlgorithms";
for (int i = 0; i < path.size(); i++) {
PublicKey signerKey = null;
if (i < path.size() - 1) {
/* Signer is the next cert in the path */
signerKey = path.get(i + 1).getPublicKey();
}
else {
/* Last cert in path is signed by trust anchor */
X509Certificate anchorCert = anchor.getTrustedCert();
if (anchorCert != null) {
signerKey = anchorCert.getPublicKey();
}
else {
signerKey = anchor.getCAPublicKey();
}
}
if (signerKey != null &&
!WolfCryptUtil.isKeyAllowed(signerKey, propertyName)) {
String certName =
path.get(i).getSubjectX500Principal().getName();
log("Algorithm constraints check failed on signer key for: " +
certName + ", signer key algorithm: " +
signerKey.getAlgorithm());
throw new CertPathBuilderException(
"Algorithm constraints check failed on signer key for: " +
certName);
}
}
}
/**
* Find the target certificate based on the X509CertSelector in params.
*
* Searches through all CertStores for a certificate matching the
* target constraints.
* Searches for a certificate matching the target constraints in the
* following order:
* 1. Directly set in the selector via setCertificate()
* 2. In CertStores attached to the parameters
* 3. Among trust anchor certificates
*
* @param params PKIXBuilderParameters containing target selector
*
@ -136,8 +311,7 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
List<CertStore> certStores = null;
if (params == null) {
throw new CertPathBuilderException(
"PKIXBuilderParameters is null");
throw new CertPathBuilderException("PKIXBuilderParameters is null");
}
selector = params.getTargetCertConstraints();
@ -149,11 +323,13 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
if (!(selector instanceof X509CertSelector)) {
throw new CertPathBuilderException(
"Target certificate constraints must be X509CertSelector");
"Target certificate constraints must be " +
"X509CertSelector");
}
x509Selector = (X509CertSelector)selector;
log("searching for target certificate with selector: " + x509Selector);
log("searching for target certificate with selector: " +
x509Selector);
/* Check if target cert is directly set in selector */
targetCert = x509Selector.getCertificate();
@ -164,32 +340,42 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
/* Search through CertStores for matching certificate */
certStores = params.getCertStores();
if (certStores == null || certStores.isEmpty()) {
throw new CertPathBuilderException(
"No CertStores provided in PKIXBuilderParameters");
if (certStores != null) {
for (CertStore store : certStores) {
try {
Collection<? extends Certificate> certs =
store.getCertificates(x509Selector);
if (certs != null && !certs.isEmpty()) {
targetCert = (X509Certificate)certs.iterator().next();
log("found target certificate in CertStore: " +
targetCert.getSubjectX500Principal().getName());
return targetCert;
}
} catch (CertStoreException e) {
log("error searching CertStore: " + e.getMessage());
/* Continue to next store */
}
}
}
for (CertStore store : certStores) {
try {
Collection<? extends Certificate> certs =
store.getCertificates(x509Selector);
/* Search trust anchor certificates as fallback */
Set<TrustAnchor> anchors = params.getTrustAnchors();
if (anchors != null) {
for (TrustAnchor anchor : anchors) {
X509Certificate anchorCert = anchor.getTrustedCert();
if ((anchorCert != null) && x509Selector.match(anchorCert)) {
if (certs != null && !certs.isEmpty()) {
/* Return first matching certificate */
targetCert = (X509Certificate) certs.iterator().next();
log("found target certificate: " +
targetCert.getSubjectX500Principal().getName());
return targetCert;
log("found target certificate in trust anchors: " +
anchorCert.getSubjectX500Principal().getName());
return anchorCert;
}
} catch (CertStoreException e) {
log("error searching CertStore: " + e.getMessage());
/* Continue to next store */
}
}
throw new CertPathBuilderException(
"Target certificate not found in CertStores");
"Target certificate not found matching selector");
}
/**
@ -545,7 +731,8 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
*
* Searches through provided CertStores for CA certificates
* (basicConstraints >= 0) and returns them as DER-encoded byte arrays.
* Trust anchor certificates are excluded from the results.
* Certificates are excluded if they are trust anchors or use disabled
* algorithms per jdk.certpath.disabledAlgorithms.
*
* @param certStores list of CertStores to search
* @param anchors set of trust anchors to exclude
@ -572,7 +759,8 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
for (Certificate c : certs) {
if (c instanceof X509Certificate) {
X509Certificate x509 = (X509Certificate) c;
/* Skip if it's a trust anchor */
/* Skip if trust anchor */
boolean isTrustAnchor = false;
for (TrustAnchor anchor : anchors) {
X509Certificate ac = anchor.getTrustedCert();
@ -581,11 +769,16 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
break;
}
}
if (!isTrustAnchor) {
intermediatesDer.add(x509.getEncoded());
log("collected intermediate: " +
x509.getSubjectX500Principal().getName());
/* Skip adding if cert uses disabled algorithm */
if (isTrustAnchor || hasDisabledAlgorithm(x509)) {
continue;
}
/* Convert to DER and add to list */
intermediatesDer.add(x509.getEncoded());
log("collected intermediate: " +
x509.getSubjectX500Principal().getName());
}
}
@ -635,34 +828,19 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
try {
storeCtx = new WolfSSLX509StoreCtx();
/* If a custom validation date is specified, we skip date
* validation when adding certificates, then set the custom
* verification time for chain verification.
*
* This allows for testing/use of expired certs if desired,
* or validating against a specific date.
*
* SunJCE only validates expiration dates on chain verification,
* not cert loading, so our default behavior already does some
* extra validation here in the case when a custom date is not
* set. */
/* If a custom validation date is specified, set NO_CHECK_TIME so
* expired certs can be loaded into the store, then set the custom
* verify time. Native code clears NO_CHECK_TIME on the ctx when
* USE_CHECK_TIME is set, so chain verification still validates
* dates against the custom time. */
if (validationDate != null) {
if (!WolfSSLX509StoreCtx.isStoreCheckTimeSupported()) {
throw new CertPathBuilderException(
"PKIXBuilderParameters.setDate() requires " +
"a wolfSSL version that supports X509_STORE " +
"check_time propagation (> 5.8.4)");
}
log("using custom validation date: " + validationDate);
storeCtx.setFlags(
WolfSSLX509StoreCtx.WOLFSSL_NO_CHECK_TIME);
storeCtx.setVerificationTime(
validationDate.getTime() / 1000);
storeCtx.setFlags(WolfSSLX509StoreCtx.WOLFSSL_NO_CHECK_TIME);
storeCtx.setVerificationTime(validationDate.getTime() / 1000);
}
/* Add trust anchors to the store */
for (TrustAnchor anchor : anchors) {
X509Certificate anchorCert = anchor.getTrustedCert();
if (anchorCert != null) {
byte[] anchorDer;
@ -680,9 +858,10 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
}
}
/* Collect all intermediate certificates from CertStores */
List<byte[]> intermediatesDer =
collectIntermediateCertificates(certStores, anchors);
/* Collect all intermediate certificates from CertStores,
* filtering disabled algorithms */
List<byte[]> intermediatesDer = collectIntermediateCertificates(
certStores, anchors);
/* Convert target cert to DER */
byte[] targetDer;
@ -698,8 +877,7 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
byte[][] intermediatesArray = null;
if (!intermediatesDer.isEmpty()) {
int size = intermediatesDer.size();
intermediatesArray =
intermediatesDer.toArray(new byte[size][]);
intermediatesArray = intermediatesDer.toArray(new byte[size][]);
}
/* Build and verify the chain */
@ -734,9 +912,9 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
log("native chain building returned " + fullChain.size() +
" certificate(s)");
/* Chain includes target->intermediates->trust anchor.
* We need to separate the trust anchor from the path.
* The last cert in the chain should match a trust anchor. */
/* Chain includes target->intermediates->trust anchor. Separate
* trust anchor from the path. Last cert in chain should match a
* trust anchor */
if (fullChain.isEmpty()) {
throw new CertPathBuilderException(
"Native chain building returned empty chain");
@ -752,7 +930,7 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
anchorCert.getSubjectX500Principal().equals(
lastCert.getSubjectX500Principal())) {
/* Verify it's the same cert (compare encoded) */
/* Verify same cert (compare encoded) */
try {
if (Arrays.equals(anchorCert.getEncoded(),
lastCert.getEncoded())) {
@ -931,6 +1109,9 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
log("target certificate is a trust anchor, " +
"returning empty path");
/* Check trust anchor public key constraints */
checkTrustAnchorConstraints(anchor);
try {
CertificateFactory cf =
CertificateFactory.getInstance("X.509");
@ -946,11 +1127,34 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
}
}
/* Build and verify path using wolfSSL X509_STORE */
NativeChainResult result = buildAndVerifyPathNative(
targetCert, pkixParams);
path = result.path;
trustAnchor = result.trustAnchor;
/* Check target cert algorithm constraints before building */
checkAlgorithmConstraints(targetCert);
if (pkixParams.getDate() != null &&
!WolfSSLX509StoreCtx.isStoreCheckTimeSupported()) {
/* Native builder can't handle custom dates on this wolfSSL version
* (check_time not supported in X509_STORE). Fall back to Java
* chain building. Date validation deferred to CertPathValidator. */
log("using Java chain building, native check_time not supported");
path = buildPath(targetCert, pkixParams);
trustAnchor = findPathTrustAnchor(path,
pkixParams.getTrustAnchors());
/* Check algorithm constraints for all certs in path (native path
* filters during collectIntermediateCerts) */
for (X509Certificate cert : path) {
checkAlgorithmConstraints(cert);
}
} else {
/* Build and verify path using wolfSSL X509_STORE */
NativeChainResult result =
buildAndVerifyPathNative(targetCert, pkixParams);
path = result.path;
trustAnchor = result.trustAnchor;
}
/* Check that each signer key meets constraints. */
checkSignerKeyConstraints(path, trustAnchor);
try {
/* Convert path to CertPath object */
@ -964,7 +1168,7 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
log("successfully built path with " + path.size() + " certificate(s)");
/* PolicyNode not returned, certificate policies are not supported */
/* PolicyNode not returned, cert policies not supported */
return new PKIXCertPathBuilderResult(certPath, trustAnchor,
null, targetCert.getPublicKey());
}

View File

@ -26,8 +26,6 @@ import java.util.List;
import java.util.Set;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Date;
import java.security.InvalidAlgorithmParameterException;
import java.security.PublicKey;
@ -60,6 +58,9 @@ import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.WolfSSLCertManager;
import com.wolfssl.wolfcrypt.WolfSSLCertManagerVerifyCallback;
import com.wolfssl.wolfcrypt.WolfCryptException;
import java.io.ByteArrayInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateException;
/**
* wolfJCE implementation of CertPathValidator for PKIX (X.509)
@ -83,50 +84,42 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
* Inner class implementing verification callback for date override.
*
* This callback is registered with WolfSSLCertManager when
* PKIXParameters.getDate() returns a non-null override date. It
* intercepts certificate date validation errors and re-validates
* the certificate dates against the override date instead of the
* current system time.
* PKIXParameters.getDate() returns a non-null override date. It intercepts
* certificate date validation errors and re-validates the certificate dates
* against the override date instead of the current system time.
*
* For thread safety, each CertPathValidator creates its own callback
* instance with its own certificate map and override date.
* instance with its own override date.
*/
private class DateOverrideVerifyCallback
implements WolfSSLCertManagerVerifyCallback {
private final Date overrideDate;
private final Map<Integer, X509Certificate> certsByDepth;
private final CertificateFactory certFactory;
/* X509 error codes wolfSSL maps date errors to when
* OPENSSL_COMPATIBLE_DEFAULTS is defined. Values from wolfssl/ssl.h,
* WOLFSSL_X509_V_ERR enum. */
private static final int X509_V_ERR_CERT_NOT_YET_VALID = 9;
private static final int X509_V_ERR_CERT_HAS_EXPIRED = 10;
/**
* Create new DateOverrideVerifyCallback.
*
* @param date Override date to use for validation
* @param certs List of certificates in chain, ordered from
* end-entity (index 0) to root
*
* @throws CertificateException if X.509 CertificateFactory
* is not available
*/
public DateOverrideVerifyCallback(
Date date, List<X509Certificate> certs) {
public DateOverrideVerifyCallback(Date date)
throws CertificateException {
this.overrideDate = date;
this.certsByDepth = new HashMap<>();
/* Map certificates by depth for lookup in callback.
* Depth 0 = end entity cert, increasing depth toward root. */
for (int i = 0; i < certs.size(); i++) {
certsByDepth.put(i, certs.get(i));
}
this.certFactory = CertificateFactory.getInstance("X.509");
}
/**
* Verify callback implementation that overrides date validation.
*
* When a date validation error occurs (ASN_BEFORE_DATE_E or
* ASN_AFTER_DATE_E), this checks if the override date falls
* within the certificate's validity period. If so, the error is
* overridden and verification continues. For all other errors,
* the original preverify result is used.
*
* This callback is called from native JNI.
* Delegates to 4-arg verify with null certDer.
*
* @param preverify 1 if pre-verification passed, 0 if failed
* @param error Error code from verification (0 = no error)
@ -136,68 +129,99 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
*/
@Override
public int verify(int preverify, int error, int errorDepth) {
return verify(preverify, error, errorDepth, null);
}
/**
* Verify callback that enforces override date validation.
*
* This callback is called from native JNI with the DER-encoded
* certificate bytes from the WOLFSSL_X509_STORE_CTX.
*
* Validates the certificate against the override date. For date errors
* where the override date is valid, the error is overridden. Non date
* errors defer to preverify.
*
* Parses the DER-encoded certificate bytes provided by the native
* callback to obtain the certificate validity dates.
*
* Date errors are checked as both raw ASN error codes
* (ASN_BEFORE_DATE_E, ASN_AFTER_DATE_E) and X509 codes
* (X509_V_ERR_CERT_NOT_YET_VALID, X509_V_ERR_CERT_HAS_EXPIRED)
* since wolfSSL maps ASN codes to X509 codes when
* OPENSSL_COMPATIBLE_DEFAULTS is defined.
*
* @param preverify 1 if pre-verification passed, 0 if failed
* @param error Error code from verification (0 = no error)
* @param errorDepth Certificate depth in chain (0 = end entity)
* @param certDer DER-encoded certificate at errorDepth, or
* null if not available
*
* @return 1 to accept certificate, 0 to reject
*/
@Override
public int verify(int preverify, int error, int errorDepth,
byte[] certDer) {
Date notBefore;
Date notAfter;
X509Certificate cert;
X509Certificate cert = null;
/* Get the certificate at this depth */
cert = certsByDepth.get(errorDepth);
if (cert == null) {
/* Reject if cert not found */
log("Date override: cert not found at depth " + errorDepth +
", rejecting");
return 0;
/* Parse cert from DER bytes provided by native callback */
if (certDer != null) {
try {
cert = (X509Certificate)certFactory.generateCertificate(
new ByteArrayInputStream(certDer));
} catch (CertificateException e) {
log("Date override: failed to parse cert DER at depth " +
errorDepth + ": " + e.getMessage());
}
}
/* Get certificate validity dates */
/* If no cert available, defer to native result */
if (cert == null) {
log("Date override: no cert available at depth " + errorDepth +
", returning preverify: " + preverify);
return preverify;
}
/* Get cert validity dates */
notBefore = cert.getNotBefore();
notAfter = cert.getNotAfter();
/* When date override is active, always validate against the
* override date, not system time. */
/* If date error exists but override date is valid, accept */
if (error == WolfCryptError.ASN_BEFORE_DATE_E.getCode() ||
error == WolfCryptError.ASN_AFTER_DATE_E.getCode()) {
/* Override date must be within cert validity */
if (overrideDate.before(notBefore) ||
overrideDate.after(notAfter)) {
log("Date override: override date " + overrideDate +
" outside validity window (notBefore: " + notBefore +
", notAfter: " + notAfter + "), rejecting");
return 0;
}
/* Always check override date against cert validity. Override date
* may be outside cert validity window. */
if (overrideDate.before(notBefore) ||
overrideDate.after(notAfter)) {
log("Date override: override date " + overrideDate +
" within validity, accepting despite date error");
" outside validity window (notBefore: " + notBefore +
", notAfter: " + notAfter + "), rejecting");
return 0;
}
/* Override date is within cert validity window. Override native
* date error if present. For non-date errors, defer to preverify.
*
* Check both raw ASN codes and X509 mapped codes since wolfSSL
* maps ASN to X509 error codes with OPENSSL_COMPATIBLE_DEFAULTS. */
if (error == 0 || preverify == 1) {
return preverify;
}
if (error == WolfCryptError.ASN_BEFORE_DATE_E.getCode() ||
error == WolfCryptError.ASN_AFTER_DATE_E.getCode() ||
error == X509_V_ERR_CERT_NOT_YET_VALID ||
error == X509_V_ERR_CERT_HAS_EXPIRED) {
log("Date override: override date " + overrideDate +
" within validity, accepting despite date error " + error);
return 1;
}
/* No date error, but still need to validate override date.
* If override date is outside cert validity, reject even though
* current system time might be valid. */
if (overrideDate.before(notBefore)) {
log("Date override: override date " + overrideDate +
" before cert validity (notBefore: " + notBefore +
"), rejecting");
return 0;
}
/* Non-date error, defer to preverify */
log("Date override: non-date error " + error + " at depth " +
errorDepth + ", returning preverify: " + preverify);
if (overrideDate.after(notAfter)) {
log("Date override: override date " + overrideDate +
" after cert validity (notAfter: " + notAfter +
"), rejecting");
return 0;
}
/* Override date is valid, accept */
log("Date override: override date " + overrideDate +
" within validity, accepting");
return 1;
return preverify;
}
}
@ -211,7 +235,7 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
/**
* Check CertPathParameters matches our requirements.
* 1. Not null
* 2. Is an instance of PKIXParameters
* 2. Instance of PKIXParameters
*
* @throws InvalidAlgorithmParameterException if null or not an instance
* of PKIXParameters
@ -350,8 +374,8 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
}
/**
* Check certificate against disabled algorithms constraints from
* security property jdk.certpath.disabledAlgorithms.
* Check certificate against disabled algorithms constraints from security
* property jdk.certpath.disabledAlgorithms.
*
* Validates both the signature algorithm and public key algorithm/size
* against the disabled algorithms list.
@ -378,12 +402,10 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
/* Check signature algorithm against disabled list */
sigAlg = cert.getSigAlgName();
if (WolfCryptUtil.isAlgorithmDisabled(sigAlg, propertyName)) {
log("Algorithm constraints check failed on signature " +
"algorithm: " + sigAlg);
log("Algorithm constraints check failed on sig algo: " + sigAlg);
throw new CertPathValidatorException(
"Algorithm constraints check failed on signature " +
"algorithm: " + sigAlg, null, path, certIdx,
BasicReason.ALGORITHM_CONSTRAINED);
"Algorithm constraints check failed on sig algo: " + sigAlg,
null, path, certIdx, BasicReason.ALGORITHM_CONSTRAINED);
}
/* Check public key algorithm and size against constraints */
@ -397,6 +419,53 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
}
}
/**
* Check trust anchor against disabled algorithms constraints from
* security property jdk.certpath.disabledAlgorithms.
*
* Handle both trust anchors with certificates and those with only a
* public key (no cert).
*
* @param anchor trust anchor to check
*
* @throws CertPathValidatorException if key algorithm is disabled or key
* size is too small
*/
private void checkTrustAnchorConstraints(TrustAnchor anchor)
throws CertPathValidatorException {
PublicKey pubKey = null;
String propertyName = "jdk.certpath.disabledAlgorithms";
if (anchor == null) {
throw new CertPathValidatorException(
"TrustAnchor is null when checking trust anchor constraints");
}
X509Certificate cert = anchor.getTrustedCert();
if (cert != null) {
pubKey = cert.getPublicKey();
}
else {
pubKey = anchor.getCAPublicKey();
}
if (pubKey == null) {
throw new CertPathValidatorException(
"Trust anchor has no public key to check against " +
"algo constraints");
}
if (!WolfCryptUtil.isKeyAllowed(pubKey, propertyName)) {
log("Algo constraints check failed on trust anchor public key: " +
pubKey.getAlgorithm());
throw new CertPathValidatorException(
"Algo constraints check failed on trust anchor public key: " +
pubKey.getAlgorithm(), null, null, -1,
BasicReason.ALGORITHM_CONSTRAINED);
}
}
/**
* Check X509Certificate against constraints or settings inside
* PKIXParameters.
@ -423,9 +492,8 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
/* Check target cert constraints, if set in parameters */
checkTargetCertConstraints(cert, certIdx, path, params);
/* Certificate policies are not currently supported by this
* CertPathValidator implementation, throw exceptions when
* user tries to use them. */
/* Certificate policies are not currently supported by this validator,
* throw exceptions when user tries to use them. */
disallowCertPolicyUse(params);
}
@ -520,13 +588,16 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
* Load TrustAnchors from PKIXParameters into WolfSSLCertManager as
* trusted CA certificates.
*
* Trust anchors are always loaded with WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY
* since per RFC 5280 trust anchors should not be date-validated.
*
* @param params PKIXParameters from which to get TrustAnchor Set
* @param cm WolfSSLCertManager to load TrustAnchors into as trusted roots
*
* @throws CertPathValidatorException on failure to load trust anchors
*/
private void loadTrustAnchorsIntoCertManager(
PKIXParameters params, WolfSSLCertManager cm)
private void loadTrustAnchorsIntoCertManager(PKIXParameters params,
WolfSSLCertManager cm)
throws CertPathValidatorException {
Set<TrustAnchor> trustAnchors = null;
@ -554,13 +625,21 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
X509Certificate anchorCert = anchor.getTrustedCert();
if (anchorCert != null) {
try {
cm.CertManagerLoadCA(anchorCert);
cm.CertManagerLoadCA(anchorCert,
WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY);
log("loaded TrustAnchor: " +
anchorCert.getSubjectX500Principal().getName());
} catch (WolfCryptException e) {
throw new CertPathValidatorException(e);
/* In wolfSSL <= 5.8.4 with WOLFSSL_TRUST_PEER_CERT and
* OPENSSL_COMPATIBLE_DEFAULTS, a date error from trusted
* peer loading may overwrite the successful CA load return
* code. The CA is still loaded into the CertManager despite
* the error return, so we just log and continue here. */
log("TrustAnchor load returned error for " +
anchorCert.getSubjectX500Principal().getName() +
", CA may still be loaded: " + e.getMessage());
}
}
}
@ -604,18 +683,21 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
"Failed verification on certificate", e, path, i);
}
/* Verified successfully. If this is a CA and we have more
* certs, load this as trusted (intermediate) */
/* Verified successfully. If this is a CA and we have more certs,
* load this as trusted (intermediate). Use DATE_ERR_OKAY since
* this cert has already been date-validated above. */
if (i > 0 && cert.getBasicConstraints() >= 0) {
try {
cm.CertManagerLoadCA(cert);
cm.CertManagerLoadCA(cert,
WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY);
log("chain [" + i + "] is intermediate, loading as root");
} catch (WolfCryptException e) {
log("chain [" + i + "] is CA, but failed to load as " +
"trusted root, not loading");
/* CA may still be loaded despite error from trusted peer
* loading on older wolfSSL */
log("chain [" + i + "] intermediate load returned error, " +
"CA may still be loaded: " + e.getMessage());
}
}
}
@ -672,13 +754,10 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
overrideDate = params.getDate();
if (overrideDate != null) {
try {
/* Create simple single-cert callback for this verification */
List<X509Certificate> singleCert = new ArrayList<>();
singleCert.add(cert);
DateOverrideVerifyCallback callback =
new DateOverrideVerifyCallback(overrideDate, singleCert);
new DateOverrideVerifyCallback(overrideDate);
cm.setVerifyCallback(callback);
} catch (WolfCryptException e) {
} catch (WolfCryptException | CertificateException e) {
cm.free();
throw new CertPathValidatorException(
"Failed to set date override callback in findTrustAnchor");
@ -710,11 +789,18 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
}
try {
/* Load anchor as CA */
cm.CertManagerLoadCA(anchorCert);
/* Load anchor as CA, skip date validation per RFC 5280 (trust
* anchors not date-validated) */
cm.CertManagerLoadCA(anchorCert,
WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY);
} catch (WolfCryptException e) {
/* error loading CA, skip to next */
continue;
/* In wolfSSL <= 5.8.4 with WOLFSSL_TRUST_PEER_CERT and
* OPENSSL_COMPATIBLE_DEFAULTS, a date error from trusted
* peer loading may overwrite the successful CA load return
* code. The CA is still loaded into the CertManager despite
* the error return, so we just log and continue here. */
log("findTrustAnchor load error, CA may still be loaded: " +
e.getMessage());
}
try {
@ -764,14 +850,14 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
* Check if revocation has been enabled in PKIXParameters, and if so
* find and load any CRLs in params.getCertStores().
*
* When a PKIXRevocationChecker has been registered via
* addCertPathChecker(), that checker handles revocation checking. CRL
* checking in the native CertManager is only enabled if:
* When a PKIXRevocationChecker is registered via addCertPathChecker(),
* that checker handles revocation checking. CRL checking in the native
* CertManager is only enabled if:
* - No PKIXRevocationChecker is present (default CRL behavior), or
* - PKIXRevocationChecker has PREFER_CRLS option set
*
* @param params parameters used to check if revocation is enabled
* and, if so load any CRLs available
* @param params parameters used to check if revocation is enabled and,
* if so load any CRLs available
* @param cm WolfSSLCertManager to load CRLs into
* @param certPath the CertPath being validated (for exception reporting)
* @param certs list of certificates from certPath
@ -780,9 +866,8 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
* @throws CertPathValidatorException if error is encountered during
* revocation checking or CRL loading
*/
private void checkRevocationEnabledAndLoadCRLs(
PKIXParameters params, WolfSSLCertManager cm,
CertPath certPath, List<X509Certificate> certs,
private void checkRevocationEnabledAndLoadCRLs(PKIXParameters params,
WolfSSLCertManager cm, CertPath certPath, List<X509Certificate> certs,
List<PKIXCertPathChecker> pathCheckers)
throws CertPathValidatorException {
@ -810,9 +895,8 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
(WolfCryptPKIXRevocationChecker)checker;
Set<PKIXRevocationChecker.Option> options =
revChecker.getOptions();
if (options != null &&
options.contains(
PKIXRevocationChecker.Option.PREFER_CRLS)) {
if (options != null && options.contains(
PKIXRevocationChecker.Option.PREFER_CRLS)) {
preferCrls = true;
}
break;
@ -827,8 +911,8 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
}
if (params.isRevocationEnabled()) {
log("revocation enabled in PKIXParameters, checking " +
"for CRLs to load");
log("revocation enabled in PKIXParameters, checking for CRLs " +
"to load");
if (!WolfCrypt.CrlEnabled()) {
throw new CertPathValidatorException(
@ -872,8 +956,8 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
certCount++;
} catch (WolfCryptException e) {
/* Log but not hard fail */
log("Warning: failed to load cert from " +
"CertStore: " + e.getMessage());
log("Failed to load cert from CertStore: " +
e.getMessage());
}
}
}
@ -1022,6 +1106,9 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
log("Zero-length cert path, returning trust anchor: " +
anchorCert.getSubjectX500Principal().getName());
/* Check trust anchor public key constraints */
checkTrustAnchorConstraints(anchor);
return new PKIXCertPathValidatorResult(anchor, null,
anchorCert.getPublicKey());
}
@ -1035,10 +1122,9 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
}
try {
/* Get List of Certificate objects in CertPath, sanity check
* that they are X509Certificate instances. This needs to be
* done before date override callback registration since
* callback needs access to certificate list. */
/* Get List of Certificate objects in CertPath, sanity check that
* they are X509Certificate instances. Done before date override
* callback registration since callback needs access to cert list */
certs = new ArrayList<>();
for (Certificate cert : certPath.getCertificates()) {
if (cert instanceof X509Certificate) {
@ -1047,33 +1133,32 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
}
/* Register verify callback to override date validation if
* PKIXParameters specifies an override date */
* PKIXParameters specifies an override date. Callback checks certs
* against the override date. */
if (pkixParams.getDate() != null) {
try {
DateOverrideVerifyCallback callback =
new DateOverrideVerifyCallback(
pkixParams.getDate(), certs);
cm.setVerifyCallback(callback);
cm.setVerifyCallback(new DateOverrideVerifyCallback(
pkixParams.getDate()));
log("Registered date override callback for " +
"validation date: " + pkixParams.getDate());
log("Registered date override callback for validation " +
"date: " + pkixParams.getDate());
} catch (WolfCryptException e) {
} catch (WolfCryptException | CertificateException e) {
throw new CertPathValidatorException(
"Failed to register date override callback: " +
e.getMessage());
}
}
/* Load trust anchors into CertManager from PKIXParameters.
* This must happen before initializing cert path checkers since
* OCSP validation requires trust anchors to verify responses. */
/* Load trust anchors into CertManager from PKIXParameters. Done
* before initializing cert path checkers since OCSP validation
* requires trust anchors to verify responses. */
loadTrustAnchorsIntoCertManager(pkixParams, cm);
/* Initialize all PKIXCertPathCheckers before calling check().
* Store the returned list so we use the same checker instances
* for both init() and check() calls. Pass certs so revocation
* checker can find issuers for OCSP response verification. */
* Store returned list to use same checker instances for init() and
* check(). Pass certs so revocation checker can find issuers
* for OCSP response verification. */
pathCheckers = initCertPathCheckers(pkixParams, cm, certs);
/* Sanity checks on certs from PKIXParameters constraints */
@ -1082,12 +1167,12 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
callCertPathCheckers(certs.get(i), pathCheckers);
}
/* Enable CRL if PKIXParameters.isRevocationEnabled(), load
* any CRLs found in PKIXParameters.getCertStores(). Needs to
* happen after trust anchors are loaded, since native wolfSSL
* will try to find/verify CRL against trusted roots on load.
* Pass pathCheckers so we can skip CRL setup when a
* PKIXRevocationChecker is handling revocation via OCSP. */
/* Enable CRL if PKIXParameters.isRevocationEnabled(), load any
* CRLs found in PKIXParameters.getCertStores(). Done after trust
* anchors loaded, since native wolfSSL will try to find/verify CRL
* against trusted roots on load. Pass pathCheckers so we can skip
* CRL setup when a PKIXRevocationChecker is handling revocation
* via OCSP. */
checkRevocationEnabledAndLoadCRLs(pkixParams, cm, certPath, certs,
pathCheckers);
@ -1096,16 +1181,20 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
/* Cert chain has been verified, find TrustAnchor to return
* in PKIXCertPathValidatorResult */
trustAnchor = findTrustAnchor(
pkixParams, certs.get(certs.size() - 1));
trustAnchor = findTrustAnchor(pkixParams,
certs.get(certs.size() - 1));
/* Check trust anchor public key constraints */
if (trustAnchor != null) {
checkTrustAnchorConstraints(trustAnchor);
}
} finally {
/* Free native WolfSSLCertManager resources */
cm.free();
}
/* PolicyNode not returned, since certificate policies are not
* yet supported */
/* PolicyNode not returned, since certificate policies not supported */
return new PKIXCertPathValidatorResult(trustAnchor, null,
certs.get(0).getPublicKey());
}
@ -1120,9 +1209,9 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
* various checking options before being passed to the validate() method
* via PKIXParameters.addCertPathChecker().
*
* Note: The CertManager will be provided to the checker during
* certificate path validation when the checker's init() method is
* called with the appropriate parameters.
* Note: The CertManager will be provided to the checker during certificate
* path validation when the checker's init() method is called with the
* appropriate parameters.
*
* @return a CertPathChecker object that this implementation uses to
* check the revocation status of certificates.

View File

@ -406,6 +406,36 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker {
}
}
/**
* Get human readable name for OCSPResponseStatus value.
*
* Per RFC 6960, OCSPResponseStatus ::= ENUMERATED {
* successful (0), malformedRequest (1), internalError (2),
* tryLater (3), sigRequired (5), unauthorized (6) }
*
* @param status OCSPResponseStatus value
* @return status name string
*/
private String getOcspResponseStatusName(int status) {
switch (status) {
case 0:
return "SUCCESSFUL";
case 1:
return "MALFORMED_REQUEST";
case 2:
return "INTERNAL_ERROR";
case 3:
return "TRY_LATER";
case 5:
return "SIG_REQUIRED";
case 6:
return "UNAUTHORIZED";
default:
return "UNKNOWN(" + status + ")";
}
}
/**
* Check pre-loaded OCSP response.
*
@ -420,6 +450,7 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker {
private void checkPreloadedOcspResponse(X509Certificate cert)
throws CertPathValidatorException {
int ocspStatus;
byte[] response;
byte[] certDer;
@ -435,14 +466,29 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker {
"CertManager not available for OCSP response checking");
}
/* Check OCSP response status before native verification.
* Non-successful OCSP responses (e.g. UNAUTHORIZED, TRY_LATER) should
* be reported as errors per RFC 6960. Use native wolfSSL to parse the
* response status from raw DER bytes. */
ocspStatus = WolfSSLCertManager.getOcspResponseStatus(response,
response.length);
if (ocspStatus > 0) {
throw new CertPathValidatorException("OCSP response error: " +
getOcspResponseStatusName(ocspStatus));
}
else if (ocspStatus < 0) {
throw new CertPathValidatorException(
"Failed to parse OCSP response status: " + ocspStatus);
}
/* Load issuer cert so OCSP response signature can be verified */
loadIssuerForOcspVerification(cert);
try {
certDer = cert.getEncoded();
certManager.CertManagerCheckOCSPResponse(
response, response.length, certDer, certDer.length);
certManager.CertManagerCheckOCSPResponse(response, response.length,
certDer, certDer.length);
} catch (CertificateEncodingException e) {
throw new CertPathValidatorException(

View File

@ -55,14 +55,22 @@ public class WolfSSLCertManager {
/* lock around native WOLFSSL_CERT_MANAGER pointer use */
private final Object cmLock = new Object();
/** Flag to allow loading certs with date errors */
public static final int WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY =
getWOLFSSL_LOAD_FLAG_DATE_ERR_OKAY();
/* Verification callback, null if not set */
private WolfSSLCertManagerVerifyCallback verifyCallback = null;
private static native int getWOLFSSL_LOAD_FLAG_DATE_ERR_OKAY();
static native long CertManagerNew();
static native void CertManagerFree(long cm);
static native int CertManagerLoadCA(long cm, String f, String d);
static native int CertManagerLoadCABuffer(
long cm, byte[] in, long sz, int format);
static native int CertManagerLoadCABufferEx(
long cm, byte[] in, long sz, int format, int flags);
static native int CertManagerUnloadCAs(long cm);
static native int CertManagerVerifyBuffer(
long cm, byte[] in, long sz, int format);
@ -76,6 +84,7 @@ public class WolfSSLCertManager {
static native int CertManagerCheckOCSP(long cm, byte[] cert, int sz);
static native int CertManagerCheckOCSPResponse(
long cm, byte[] response, int responseSz, byte[] cert, int certSz);
static native int OcspResponseStatus(byte[] response, int responseSz);
static native int CertManagerSetVerify(long cm, Object callback);
static native int CertManagerClearVerify(long cm);
@ -180,8 +189,69 @@ public class WolfSSLCertManager {
synchronized (cmLock) {
try {
/* Throws WolfCryptException on native error */
CertManagerLoadCABuffer(cert.getEncoded(),
cert.getEncoded().length, WolfCrypt.SSL_FILETYPE_ASN1);
byte[] encoded = cert.getEncoded();
CertManagerLoadCABuffer(encoded, encoded.length,
WolfCrypt.SSL_FILETYPE_ASN1);
} catch (CertificateEncodingException e) {
throw new WolfCryptException(e);
}
}
}
/**
* Load CA into CertManager from byte array with extended flags.
*
* @param in byte array holding X.509 certificate to load
* @param sz size of input byte array, bytes
* @param format format of input certificate, either
* WolfCrypt.SSL_FILETYPE_PEM (PEM formatted) or
* WolfCrypt.SSL_FILETYPE_ASN1 (ASN.1/DER).
* @param flags load flags, e.g.
* WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY
*
* @throws IllegalStateException WolfSSLCertManager has been freed
* @throws WolfCryptException on native wolfSSL error
*/
public synchronized void CertManagerLoadCABufferEx(byte[] in, long sz,
int format, int flags)
throws IllegalStateException, WolfCryptException {
int ret = 0;
confirmObjectIsActive();
synchronized (cmLock) {
ret = CertManagerLoadCABufferEx(this.cmPtr, in, sz, format, flags);
if (ret != WolfCrypt.WOLFSSL_SUCCESS) {
throw new WolfCryptException(ret);
}
}
}
/**
* Load CA into CertManager from X509Certificate object with extended flags.
*
* @param cert X509Certificate containing CA cert
* @param flags load flags, e.g.
* WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY
*
* @throws IllegalStateException WolfSSLCertManager has been freed
* @throws WolfCryptException on native wolfSSL error
*/
public synchronized void CertManagerLoadCA(X509Certificate cert, int flags)
throws IllegalStateException, WolfCryptException {
confirmObjectIsActive();
if (cert == null) {
throw new WolfCryptException("Input X509Certificate is null");
}
synchronized (cmLock) {
try {
byte[] encoded = cert.getEncoded();
CertManagerLoadCABufferEx(encoded, encoded.length,
WolfCrypt.SSL_FILETYPE_ASN1, flags);
} catch (CertificateEncodingException e) {
throw new WolfCryptException(e);
}
@ -224,8 +294,8 @@ public class WolfSSLCertManager {
if (cert != null && cert.getBasicConstraints() >= 0) {
/* Will throw WolfCryptException on error */
CertManagerLoadCABuffer(cert.getEncoded(),
cert.getEncoded().length,
byte[] encoded = cert.getEncoded();
CertManagerLoadCABuffer(encoded, encoded.length,
WolfCrypt.SSL_FILETYPE_ASN1);
loadedCerts++;
}
@ -315,8 +385,9 @@ public class WolfSSLCertManager {
synchronized (cmLock) {
try {
/* Throws WolfCryptException on native error */
CertManagerVerifyBuffer(cert.getEncoded(),
cert.getEncoded().length, WolfCrypt.SSL_FILETYPE_ASN1);
byte[] encoded = cert.getEncoded();
CertManagerVerifyBuffer(encoded, encoded.length,
WolfCrypt.SSL_FILETYPE_ASN1);
} catch (CertificateEncodingException e) {
throw new WolfCryptException(e);
}
@ -623,6 +694,38 @@ public class WolfSSLCertManager {
}
}
/**
* Get OCSPResponseStatus from raw OCSP response bytes.
*
* Uses native wolfSSL_d2i_OCSP_RESPONSE() to parse the response and
* wolfSSL_OCSP_response_status() to extract the status value per RFC 6960.
*
* This is a static method that does not require a WolfSSLCertManager
* instance.
*
* @param response raw OCSP response bytes (DER encoded)
* @param responseSz size of OCSP response
*
* @return OCSPResponseStatus value:
* 0 = successful,
* 1 = malformedRequest,
* 2 = internalError,
* 3 = tryLater,
* 5 = sigRequired,
* 6 = unauthorized,
* negative on error (NOT_COMPILED_IN, BAD_FUNC_ARG,
* MEMORY_E, or -1 if response parsing failed)
*/
public static int getOcspResponseStatus(byte[] response, int responseSz) {
if (response == null || responseSz < 0 ||
responseSz > response.length) {
return WolfCryptError.BAD_FUNC_ARG.getCode();
}
return OcspResponseStatus(response, responseSz);
}
/**
* Set verification callback for this CertManager.
*

View File

@ -59,5 +59,29 @@ public interface WolfSSLCertManagerVerifyCallback {
* @return 1 to accept the certificate, 0 to reject
*/
int verify(int preverify, int error, int errorDepth);
/**
* Verify callback with certificate DER bytes from store context.
*
* This overload passes the DER-encoded certificate being verified,
* extracted from the native WOLFSSL_X509_STORE_CTX. Implementations
* can override this method to inspect the actual certificate when it
* is not available through other means (e.g. trust anchors not in a
* pre-built map).
*
* The default implementation delegates to the 3-arg verify() method,
* so existing implementations are not broken.
*
* @param preverify 1 if pre-verification passed, 0 if failed
* @param error Error code from verification (0 indicates no error)
* @param errorDepth Certificate depth in chain (0 = end entity cert)
* @param certDer DER-encoded certificate at errorDepth, or null
* if not available from the store context
* @return 1 to accept the certificate, 0 to reject
*/
default int verify(int preverify, int error, int errorDepth,
byte[] certDer) {
return verify(preverify, error, errorDepth);
}
}

View File

@ -1928,5 +1928,179 @@ public class WolfCryptPKIXCertPathValidatorTest {
e.getMessage().contains("no certificate"));
}
}
/**
* Test that trust anchor public key constraints are enforced when
* validating a normal (non-empty) cert path.
*/
@Test
public void testTrustAnchorKeyConstraintsOnNormalPath()
throws Exception {
String origProperty = null;
CertificateFactory cf = null;
X509Certificate caCert = null;
X509Certificate clientCert = null;
TrustAnchor anchor = null;
Set<TrustAnchor> anchors = null;
PKIXParameters params = null;
CertPath path = null;
CertPathValidator validator = null;
FileInputStream fis = null;
/* Save original security property value */
origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms");
try {
/* Load wolfSSL CA cert (2048-bit RSA key) */
cf = CertificateFactory.getInstance("X.509");
fis = new FileInputStream(caCertDer);
try {
caCert = (X509Certificate)cf.generateCertificate(fis);
} finally {
fis.close();
}
/* Load wolfSSL client cert (2048-bit RSA key) */
fis = new FileInputStream(clientCertDer);
try {
clientCert = (X509Certificate)cf.generateCertificate(fis);
} finally {
fis.close();
}
/* Set minimum RSA key size to 4096 bits. The client
* cert (in the path) will fail first since it is
* checked before the trust anchor. Use a constraint
* that only affects key size to verify trust anchor
* checking also works in normal path flow. */
Security.setProperty("jdk.certpath.disabledAlgorithms",
"RSA keySize < 4096");
/* Setup trust anchor with CA cert */
anchor = new TrustAnchor(caCert, null);
anchors = new HashSet<TrustAnchor>();
anchors.add(anchor);
params = new PKIXParameters(anchors);
params.setRevocationEnabled(false);
/* Create CertPath with client cert */
List<Certificate> certList = new ArrayList<Certificate>();
certList.add(clientCert);
path = cf.generateCertPath(certList);
/* Validate - should fail because key size is too
* small (either path cert or trust anchor) */
validator = CertPathValidator.getInstance("PKIX", "wolfJCE");
try {
validator.validate(path, params);
fail("Expected CertPathValidatorException " +
"for RSA key size constraint");
} catch (CertPathValidatorException cpve) {
assertEquals(
"Expected ALGORITHM_CONSTRAINED",
BasicReason.ALGORITHM_CONSTRAINED, cpve.getReason());
}
} finally {
/* Close any open file streams */
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
/* Ignore close errors */
}
}
/* Restore original security property */
if (origProperty != null) {
Security.setProperty("jdk.certpath.disabledAlgorithms",
origProperty);
}
else {
Security.setProperty("jdk.certpath.disabledAlgorithms", "");
}
}
}
/**
* Test that trust anchor public key constraints are enforced when
* validating a zero-length cert path (trust anchor is the target
* certificate).
*/
@Test
public void testTrustAnchorKeyConstraintsOnZeroLengthPath()
throws Exception {
String origProperty = null;
CertificateFactory cf = null;
X509Certificate caCert = null;
TrustAnchor anchor = null;
Set<TrustAnchor> anchors = null;
PKIXParameters params = null;
CertPath emptyPath = null;
CertPathValidator validator = null;
FileInputStream fis = null;
/* Save original security property value */
origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms");
try {
/* Load CA cert (2048-bit RSA key) */
cf = CertificateFactory.getInstance("X.509");
fis = new FileInputStream(caCertDer);
caCert = (X509Certificate)cf.generateCertificate(fis);
fis.close();
/* Set minimum RSA key size to 4096 bits */
Security.setProperty("jdk.certpath.disabledAlgorithms",
"RSA keySize < 4096");
/* Setup trust anchor with CA cert */
anchor = new TrustAnchor(caCert, null);
anchors = new HashSet<TrustAnchor>();
anchors.add(anchor);
params = new PKIXParameters(anchors);
params.setRevocationEnabled(false);
/* Create zero-length (empty) CertPath */
emptyPath = cf.generateCertPath(new ArrayList<Certificate>());
/* Validate - should fail because trust anchor
* key size is too small */
validator = CertPathValidator.getInstance("PKIX", "wolfJCE");
try {
validator.validate(emptyPath, params);
fail("Expected CertPathValidatorException for trust anchor " +
"RSA key size constraint on zero-length path");
} catch (CertPathValidatorException cpve) {
assertEquals("Expected ALGORITHM_CONSTRAINED",
BasicReason.ALGORITHM_CONSTRAINED, cpve.getReason());
}
} finally {
/* Close any open file streams */
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
/* Ignore close errors */
}
}
/* Restore original security property */
if (origProperty != null) {
Security.setProperty("jdk.certpath.disabledAlgorithms",
origProperty);
}
else {
Security.setProperty("jdk.certpath.disabledAlgorithms", "");
}
}
}
}