Merge pull request #341 from cconlon/pathLenFix

Add pathLen support to`WolfSSLCertificate`/`WolfSSLCertRequest` `addExtension()`
pull/348/head
Ruby Martin 2026-03-18 10:55:50 -06:00 committed by GitHub
commit b635b5baab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 336 additions and 20 deletions

View File

@ -659,7 +659,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1
}
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1set_1object_1boolean
(JNIEnv* jenv, jclass jcl, jlong x509ReqPtr, jint nid, jboolean extValue, jboolean isCritical)
(JNIEnv* jenv, jclass jcl, jlong x509ReqPtr, jint nid, jboolean extValue, jboolean isCritical, jint pathLen)
{
#if !defined(WOLFCRYPT_ONLY) && !defined(NO_CERTS) && defined(OPENSSL_EXTRA)
WOLFSSL_X509* x509 = (WOLFSSL_X509*)(uintptr_t)x509ReqPtr;
@ -672,6 +672,17 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1
return WOLFSSL_FAILURE;
}
/* pathLen >= 0 requires wolfSSL > 5.8.4 which includes fixes so
* wolfSSL_ASN1_OBJECT_dup() copies pathlen and wolfSSL_X509_add_ext()
* correctly sets pathLengthSet for DER encoding. Without these fixes,
* pathLen is silently dropped during cert generation. */
if (pathLen >= 0) {
#if !((LIBWOLFSSL_VERSION_HEX > 0x05008004) || \
defined(WOLFSSL_PR9940_PATCH_APPLIED))
return (jint)NOT_COMPILED_IN;
#endif
}
ext = wolfSSL_X509_EXTENSION_new();
if (ext == NULL) {
ret = WOLFSSL_FAILURE;
@ -699,6 +710,16 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1
}
}
if (ret == WOLFSSL_SUCCESS && pathLen >= 0) {
obj->pathlen = wolfSSL_ASN1_INTEGER_new();
if (obj->pathlen == NULL) {
ret = WOLFSSL_FAILURE;
}
else {
obj->pathlen->length = pathLen;
}
}
if (ret == WOLFSSL_SUCCESS) {
ret = wolfSSL_X509_EXTENSION_set_object(ext, obj);
}
@ -707,7 +728,6 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1
ret = wolfSSL_X509_add_ext(x509, ext, -1);
}
if (obj != NULL) {
wolfSSL_ASN1_OBJECT_free(obj);
}
@ -723,6 +743,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1
(void)nid;
(void)extValue;
(void)isCritical;
(void)pathLen;
return (jint)NOT_COMPILED_IN;
#endif
}

View File

@ -104,10 +104,10 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1
/*
* Class: com_wolfssl_WolfSSLCertRequest
* Method: X509_add_ext_via_set_object_boolean
* Signature: (JIZZ)I
* Signature: (JIZZI)I
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertRequest_X509_1add_1ext_1via_1set_1object_1boolean
(JNIEnv *, jclass, jlong, jint, jboolean, jboolean);
(JNIEnv *, jclass, jlong, jint, jboolean, jboolean, jint);
#ifdef __cplusplus
}

View File

@ -365,7 +365,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1
}
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1set_1object_1boolean
(JNIEnv* jenv, jclass jcl, jlong x509Ptr, jint nid, jboolean extValue, jboolean isCritical)
(JNIEnv* jenv, jclass jcl, jlong x509Ptr, jint nid, jboolean extValue, jboolean isCritical, jint pathLen)
{
#if !defined(WOLFCRYPT_ONLY) && !defined(NO_CERTS) && defined(OPENSSL_EXTRA)
WOLFSSL_X509* x509 = (WOLFSSL_X509*)(uintptr_t)x509Ptr;
@ -378,6 +378,17 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1
return WOLFSSL_FAILURE;
}
/* pathLen >= 0 requires wolfSSL > 5.8.4 which includes fixes so
* wolfSSL_ASN1_OBJECT_dup() copies pathlen and wolfSSL_X509_add_ext()
* correctly sets pathLengthSet for DER encoding. Without these fixes,
* pathLen is silently dropped during cert generation. */
if (pathLen >= 0) {
#if !((LIBWOLFSSL_VERSION_HEX > 0x05008004) || \
defined(WOLFSSL_PR9940_PATCH_APPLIED))
return (jint)NOT_COMPILED_IN;
#endif
}
ext = wolfSSL_X509_EXTENSION_new();
if (ext == NULL) {
ret = WOLFSSL_FAILURE;
@ -405,6 +416,16 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1
}
}
if (ret == WOLFSSL_SUCCESS && pathLen >= 0) {
obj->pathlen = wolfSSL_ASN1_INTEGER_new();
if (obj->pathlen == NULL) {
ret = WOLFSSL_FAILURE;
}
else {
obj->pathlen->length = pathLen;
}
}
if (ret == WOLFSSL_SUCCESS) {
ret = wolfSSL_X509_EXTENSION_set_object(ext, obj);
}
@ -413,7 +434,6 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1
ret = wolfSSL_X509_add_ext(x509, ext, -1);
}
if (obj != NULL) {
wolfSSL_ASN1_OBJECT_free(obj);
}
@ -429,6 +449,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1
(void)nid;
(void)extValue;
(void)isCritical;
(void)pathLen;
return (jint)NOT_COMPILED_IN;
#endif
}

View File

@ -352,10 +352,10 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1
/*
* Class: com_wolfssl_WolfSSLCertificate
* Method: X509_add_ext_via_set_object_boolean
* Signature: (JIZZ)I
* Signature: (JIZZI)I
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1add_1ext_1via_1set_1object_1boolean
(JNIEnv *, jclass, jlong, jint, jboolean, jboolean);
(JNIEnv *, jclass, jlong, jint, jboolean, jboolean, jint);
/*
* Class: com_wolfssl_WolfSSLCertificate

View File

@ -72,7 +72,7 @@ public class WolfSSLCertRequest {
static native int X509_add_ext_via_nconf_nid(long x509Ptr, int nid,
String extValue, boolean isCritical);
static native int X509_add_ext_via_set_object_boolean(long x509Ptr,
int nid, boolean extValue, boolean isCritical);
int nid, boolean extValue, boolean isCritical, int pathLen);
/**
* Create new empty WolfSSLCertRequest object, for use with CSR generation
@ -506,6 +506,36 @@ public class WolfSSLCertRequest {
public void addExtension(int nid, boolean value, boolean isCritical)
throws IllegalStateException, WolfSSLException {
addExtension(nid, value, -1, isCritical);
}
/**
* Add Basic Constraints extension with CA flag and path length constraint
* to a WolfSSLCertRequest.
*
* This method allows setting the Basic Constraints extension with both the
* CA boolean and an optional path length constraint. The path length limits
* the number of intermediate CA certificates that may follow this
* certificate in a valid certification path.
*
* To set Basic Constraints without a path length constraint, use
* {@link #addExtension(int, boolean, boolean)} with
* {@code WolfSSL.NID_basic_constraints} instead.
*
* @param nid NID of extension to add. Must be:
* WolfSSL.NID_basic_constraints
* @param value Boolean value of CA flag (true for CA, false for end entity)
* @param pathLen Maximum number of intermediate CA certificates allowed
* below this CA. Must be >= 0, or -1 to not set a path length
* constraint. Only meaningful when value is true.
* @param isCritical Boolean flag indicating if this extension is critical
*
* @throws IllegalStateException if WolfSSLCertRequest has been freed
* @throws WolfSSLException if invalid arguments or on native JNI error
*/
public void addExtension(int nid, boolean value, int pathLen,
boolean isCritical) throws IllegalStateException, WolfSSLException {
int ret = 0;
confirmObjectIsActive();
@ -514,7 +544,7 @@ public class WolfSSLCertRequest {
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509ReqPtr,
() -> "entered addExtension(nid: " + nid + ", value: " + value +
", isCritical: " + isCritical + ")");
", pathLen: " + pathLen + ", isCritical: " + isCritical + ")");
}
if (nid != WolfSSL.NID_basic_constraints) {
@ -522,12 +552,29 @@ public class WolfSSLCertRequest {
"Unsupported X509v3 extension NID: " + nid);
}
synchronized (x509ReqLock) {
ret = X509_add_ext_via_set_object_boolean(
this.x509ReqPtr, nid, value, isCritical);
if (pathLen < -1) {
throw new WolfSSLException(
"Path length must be >= 0 or -1, got: " + pathLen);
}
if (ret != WolfSSL.SSL_SUCCESS) {
if (!value && pathLen >= 0) {
throw new WolfSSLException(
"pathLen must not be set when isCA is FALSE (RFC 5280), " +
"got pathLen: " + pathLen);
}
synchronized (x509ReqLock) {
ret = X509_add_ext_via_set_object_boolean(this.x509ReqPtr, nid,
value, isCritical, pathLen);
}
if (ret == WolfSSL.NOT_COMPILED_IN) {
throw new WolfSSLException(
"addExtension NOT_COMPILED_IN, pathLen " +
"support requires wolfSSL > 5.8.4 or " +
"PR 9940 patch (ret: " + ret + ")");
}
else if (ret != WolfSSL.SSL_SUCCESS) {
throw new WolfSSLException(
"Error setting extension into native WOLFSSL_X509 " +
"(ret: " + ret + ")");

View File

@ -127,7 +127,7 @@ public class WolfSSLCertificate implements Serializable {
static native int X509_add_ext_via_nconf_nid(long x509Ptr, int nid,
String extValue, boolean isCritical);
static native int X509_add_ext_via_set_object_boolean(long x509Ptr,
int nid, boolean extValue, boolean isCritical);
int nid, boolean extValue, boolean isCritical, int pathLen);
static native int X509_set_notBefore(long x509Ptr, long timeSecs);
static native int X509_set_notAfter(long x509Ptr, long timeSecs);
static native int X509_set_serialNumber(long x509Ptr, byte[] serialBytes);
@ -1195,6 +1195,36 @@ public class WolfSSLCertificate implements Serializable {
public void addExtension(int nid, boolean value, boolean isCritical)
throws IllegalStateException, WolfSSLException {
addExtension(nid, value, -1, isCritical);
}
/**
* Add Basic Constraints extension with CA flag and path length
* constraint to a WOLFSSL_X509.
*
* This method allows setting the Basic Constraints extension with both the
* CA boolean and an optional path length constraint. The path length limits
* the number of intermediate CA certificates that may follow this
* certificate in a valid certification path.
*
* To set Basic Constraints without a path length constraint, use
* {@link #addExtension(int, boolean, boolean)} with
* {@code WolfSSL.NID_basic_constraints} instead.
*
* @param nid NID of extension to add. Must be:
* WolfSSL.NID_basic_constraints
* @param value Boolean value of CA flag (true for CA, false for end entity)
* @param pathLen Maximum number of intermediate CA certificates allowed
* below this CA. Must be &gt;= 0, or -1 to not set a path length
* constraint. Only meaningful when value is true.
* @param isCritical Boolean flag indicating if this extension is critical
*
* @throws IllegalStateException if WolfSSLCertificate has been freed
* @throws WolfSSLException if invalid arguments or on native JNI error.
*/
public void addExtension(int nid, boolean value, int pathLen,
boolean isCritical) throws IllegalStateException, WolfSSLException {
int ret = 0;
confirmObjectIsActive();
@ -1203,7 +1233,8 @@ public class WolfSSLCertificate implements Serializable {
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.x509Ptr,
() -> "entering addExtension(nid: " + nid + ", value: " +
value + ", isCritical: " + isCritical + ")");
value + ", pathLen: " + pathLen + ", isCritical: " +
isCritical + ")");
}
if (nid != WolfSSL.NID_basic_constraints) {
@ -1211,12 +1242,29 @@ public class WolfSSLCertificate implements Serializable {
"Unsupported X509v3 extension NID: " + nid);
}
synchronized (x509Lock) {
ret = X509_add_ext_via_set_object_boolean(
this.x509Ptr, nid, value, isCritical);
if (pathLen < -1) {
throw new WolfSSLException(
"Path length must be >= 0 or -1, got: " + pathLen);
}
if (ret != WolfSSL.SSL_SUCCESS) {
if (!value && pathLen >= 0) {
throw new WolfSSLException(
"pathLen must not be set when isCA is FALSE (RFC 5280), " +
"got pathLen: " + pathLen);
}
synchronized (x509Lock) {
ret = X509_add_ext_via_set_object_boolean(this.x509Ptr, nid, value,
isCritical, pathLen);
}
if (ret == WolfSSL.NOT_COMPILED_IN) {
throw new WolfSSLException(
"addExtension NOT_COMPILED_IN, pathLen " +
"support requires wolfSSL > 5.8.4 or " +
"PR 9940 patch (ret: " + ret + ")");
}
else if (ret != WolfSSL.SSL_SUCCESS) {
throw new WolfSSLException(
"Error setting extension into native WOLFSSL_X509 " +
"(ret: " + ret + ")");

View File

@ -74,6 +74,16 @@ public class WolfSSLCertRequestTest {
cliEccKeyPem = WolfSSLTestCommon.getPath(cliEccKeyPem);
}
private boolean isNotCompiledIn(WolfSSLException e) {
String msg = e.getMessage();
if (msg == null) {
return false;
}
return msg.contains(
Integer.toString(WolfSSL.NOT_COMPILED_IN)) ||
msg.contains("NOT_COMPILED_IN");
}
/* Internal helper method, generate test SubjectName for cert generation */
private WolfSSLX509Name GenerateTestSubjectName() throws WolfSSLException {
@ -185,6 +195,51 @@ public class WolfSSLCertRequestTest {
req.addExtension(WolfSSL.NID_basic_constraints, true, true);
req.addExtension(WolfSSL.NID_basic_constraints, false, true);
/* Test boolean extension with pathLen */
try {
req.addExtension(WolfSSL.NID_basic_constraints, true, 0, true);
} catch (WolfSSLException e) {
if (!isNotCompiledIn(e)) {
throw e;
}
System.out.println("\t\t\t... skipped");
}
try {
req.addExtension(WolfSSL.NID_basic_constraints, true, 3, true);
} catch (WolfSSLException e) {
if (!isNotCompiledIn(e)) {
throw e;
}
System.out.println("\t\t\t... skipped");
}
/* Invalid pathLen (< -1) should throw WolfSSLException */
try {
req.addExtension(WolfSSL.NID_basic_constraints, true, -2, true);
System.out.println("\t\t\t... failed");
fail("Invalid pathLen did not throw exception");
} catch (WolfSSLException e) {
/* expected */
}
/* pathLen with isCA=false should throw (RFC 5280) */
try {
req.addExtension(WolfSSL.NID_basic_constraints, false, 0, true);
System.out.println("\t\t\t... failed");
fail("pathLen with isCA=false did not throw");
} catch (WolfSSLException e) {
/* expected */
}
/* Unsupported NID with pathLen should throw exception */
try {
req.addExtension(123456, true, 0, true);
System.out.println("\t\t\t... failed");
fail("Unsupported NID did not throw exception");
} catch (WolfSSLException e) {
/* expected */
}
/* Adding unsupported NID should throw exception */
try {
req.addExtension(123456, true, false);

View File

@ -897,6 +897,7 @@ public class WolfSSLCertificateTest {
testCertGen_CASigned_UsingFiles();
testCertGen_CASigned_UsingBuffers();
testCertGen_CASigned_UsingJavaClasses();
testCertGen_SelfSigned_WithPathLen();
}
}
@ -960,6 +961,48 @@ public class WolfSSLCertificateTest {
() -> x509.setAuthorityKeyIdEx(issuer),
"setAuthorityKeyIdEx");
/* Basic Constraints with pathLen */
try {
x509.addExtension(WolfSSL.NID_basic_constraints, true, 0, true);
} catch (WolfSSLException e) {
if (!isNotCompiledIn(e)) {
throw e;
}
System.out.println("\t\t... skipped");
}
try {
x509.addExtension(WolfSSL.NID_basic_constraints, true, 3, true);
} catch (WolfSSLException e) {
if (!isNotCompiledIn(e)) {
throw e;
}
System.out.println("\t\t... skipped");
}
/* Invalid pathLen (< -1) should throw WolfSSLException */
try {
x509.addExtension(WolfSSL.NID_basic_constraints, true, -2, true);
fail("Expected WolfSSLException for invalid pathLen");
} catch (WolfSSLException e) {
/* expected */
}
/* pathLen with isCA=false should throw (RFC 5280) */
try {
x509.addExtension(WolfSSL.NID_basic_constraints, false, 0, true);
fail("Expected WolfSSLException for pathLen with isCA=false");
} catch (WolfSSLException e) {
/* expected */
}
/* Unsupported NID with pathLen overload should throw */
try {
x509.addExtension(123456, true, 0, true);
fail("Expected WolfSSLException for unsupported NID");
} catch (WolfSSLException e) {
/* expected */
}
runOrAllowNotCompiled(
() -> x509.addCrlDistPoint("http://crl.example.com/ca.crl", false),
"addCrlDistPoint");
@ -1571,6 +1614,87 @@ public class WolfSSLCertificateTest {
System.out.println("\t... passed");
}
/* Test self-signed CA certificate generation with Basic Constraints
* pathLen set, verifying it round-trips through DER encoding.
* Requires wolfSSL > 5.8.4 or WOLFSSL_PR9940_PATCH_APPLIED. */
private void testCertGen_SelfSigned_WithPathLen()
throws WolfSSLException, WolfSSLJNIException, IOException,
CertificateException, NoSuchAlgorithmException {
System.out.print("\tSelf-signed CA (pathLen)");
WolfSSLCertificate x509 = new WolfSSLCertificate();
assertNotNull(x509);
/* Set notBefore/notAfter dates */
Instant now = Instant.now();
final Date notBefore = Date.from(now);
final Date notAfter = Date.from(now.plus(Duration.ofDays(365)));
x509.setNotBefore(notBefore);
x509.setNotAfter(notAfter);
/* Set serial number */
x509.setSerialNumber(BigInteger.valueOf(99999));
/* Set Subject Name */
WolfSSLX509Name subjectName = GenerateTestSubjectName();
assertNotNull(subjectName);
x509.setSubjectName(subjectName);
/* Set Public Key from generated java.security.PublicKey */
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();
x509.setPublicKey(keyPair.getPublic());
/* Set Extensions */
if (WolfSSL.getLibVersionHex() > 0x05006003) {
x509.addExtension(WolfSSL.NID_key_usage,
"digitalSignature,cRLSign,keyCertSign", true);
x509.addExtension(WolfSSL.NID_ext_key_usage,
"clientAuth,serverAuth,OCSPSigning", false);
}
/* Basic Constraints: CA=true, pathLen=0. Skip test if
* pathLen support not compiled in (older wolfSSL). */
try {
x509.addExtension(WolfSSL.NID_basic_constraints, true, 0, true);
} catch (WolfSSLException e) {
if (isNotCompiledIn(e)) {
System.out.println("\t... skipped");
subjectName.free();
x509.free();
return;
}
throw e;
}
/* Sign cert, self-signed */
x509.signCert(keyPair.getPrivate(), "SHA256");
/* Output to DER */
byte[] derCert = x509.getDer();
assertNotNull(derCert);
assertTrue(derCert.length > 0);
/* Re-load generated cert and verify pathLen */
WolfSSLCertificate reloaded = new WolfSSLCertificate(derCert,
WolfSSL.SSL_FILETYPE_ASN1);
assertNotNull(reloaded);
assertEquals(1, reloaded.isCA());
assertEquals(0, reloaded.getPathLen());
/* Sanity check generated cert buffers */
sanityCheckCertFileBytes(derCert, WolfSSL.SSL_FILETYPE_ASN1);
/* Free native memory */
reloaded.free();
subjectName.free();
x509.free();
System.out.println("\t... passed");
}
/* Utility method if needed for testing, print out cert array to file */
private void writeOutCertFile(byte[] cert, String path)
throws IOException {