diff --git a/native/com_wolfssl_WolfSSL.h b/native/com_wolfssl_WolfSSL.h index c6d6741f..27a33d67 100644 --- a/native/com_wolfssl_WolfSSL.h +++ b/native/com_wolfssl_WolfSSL.h @@ -187,6 +187,8 @@ extern "C" { #define com_wolfssl_WolfSSL_CACHE_MATCH_ERROR -280L #undef com_wolfssl_WolfSSL_MAX_RECORD_SIZE #define com_wolfssl_WolfSSL_MAX_RECORD_SIZE 16384L +#undef com_wolfssl_WolfSSL_EXTERNAL_SERIAL_SIZE +#define com_wolfssl_WolfSSL_EXTERNAL_SERIAL_SIZE 32L #undef com_wolfssl_WolfSSL_TLS_RECORD_HEADER_LEN #define com_wolfssl_WolfSSL_TLS_RECORD_HEADER_LEN 5L #undef com_wolfssl_WolfSSL_TLS_RECORD_LEN_HI_OFF diff --git a/native/com_wolfssl_WolfSSLCRL.c b/native/com_wolfssl_WolfSSLCRL.c index f5780225..986ed5e5 100644 --- a/native/com_wolfssl_WolfSSLCRL.c +++ b/native/com_wolfssl_WolfSSLCRL.c @@ -44,6 +44,13 @@ #define WOLFSSL_JNI_CRL_GEN_ENABLED #endif +/* WOLFSSL_X509_REVOKED.revocationDate was added in 5.9.0 */ +#if defined(WOLFSSL_JNI_CRL_GEN_ENABLED) && \ + ((LIBWOLFSSL_VERSION_HEX > 0x05008004) || \ + defined(WOLFSSL_PR9839_PATCH_APPLIED)) +#define WOLFSSL_JNI_CRL_REVDATE_ENABLED +#endif + JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLCRL_X509_1CRL_1new (JNIEnv* jenv, jclass jcl) { @@ -260,15 +267,16 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCRL_X509_1CRL_1add_1revoked WOLFSSL_X509_CRL* crl = (WOLFSSL_X509_CRL*)(uintptr_t)crlPtr; WOLFSSL_X509_REVOKED revoked; WOLFSSL_ASN1_INTEGER* serialInt = NULL; +#ifdef WOLFSSL_JNI_CRL_REVDATE_ENABLED + WOLFSSL_ASN1_TIME revTime; + byte* revDateBuf = NULL; + int revDateSz = 0; +#endif byte* serialBuf = NULL; int serialSz = 0; int ret = WOLFSSL_SUCCESS; (void)jcl; - /* Note: date is not currently used until WOLFSSL_X509_REVOKED adds it. */ - (void)revDate; - (void)dateFmt; - if (jenv == NULL || crl == NULL || serial == NULL) { return WOLFSSL_FAILURE; } @@ -291,10 +299,46 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCRL_X509_1CRL_1add_1revoked serialInt->isDynamic = 0; serialInt->type = 0; revoked.serialNumber = serialInt; - ret = wolfSSL_X509_CRL_add_revoked(crl, &revoked); + + #ifdef WOLFSSL_JNI_CRL_REVDATE_ENABLED + /* Set caller-supplied revocation date if present, otherwise + * wolfSSL fills in the current time. revDate holds the ASN.1 + * time value bytes, dateFmt is ASN_UTC_TIME or + * ASN_GENERALIZED_TIME tag. */ + if (revDate != NULL) { + revDateBuf = (byte*)(*jenv)->GetByteArrayElements(jenv, + revDate, NULL); + revDateSz = (*jenv)->GetArrayLength(jenv, revDate); + if (revDateBuf == NULL || revDateSz <= 0 || + (size_t)revDateSz > sizeof(revTime.data)) { + ret = WOLFSSL_FAILURE; + } + else { + XMEMSET(&revTime, 0, sizeof(revTime)); + XMEMCPY(revTime.data, revDateBuf, (size_t)revDateSz); + revTime.length = revDateSz; + revTime.type = (int)dateFmt; + revoked.revocationDate = &revTime; + } + } + #else + /* No revocationDate support, wolfSSL records the current time. */ + (void)revDate; + (void)dateFmt; + #endif + + if (ret == WOLFSSL_SUCCESS) { + ret = wolfSSL_X509_CRL_add_revoked(crl, &revoked); + } } } +#ifdef WOLFSSL_JNI_CRL_REVDATE_ENABLED + if (revDateBuf != NULL) { + (*jenv)->ReleaseByteArrayElements(jenv, revDate, + (jbyte*)revDateBuf, JNI_ABORT); + } +#endif if (serialBuf != NULL) { (*jenv)->ReleaseByteArrayElements(jenv, serial, (jbyte*)serialBuf, JNI_ABORT); @@ -325,13 +369,20 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCRL_X509_1CRL_1add_1revoked_1cert int certSz = 0; int ret = WOLFSSL_SUCCESS; (void)jcl; - (void)revDate; (void)dateFmt; if (jenv == NULL || crl == NULL || certDer == NULL) { return WOLFSSL_FAILURE; } + /* Reject non-null date rather than ignoring it, since + * wolfSSL_X509_CRL_add_revoked_cert() always records the current time. + * Caller should use addRevoked() with an explicit serial number to set a + * specific revocation date. */ + if (revDate != NULL) { + return BAD_FUNC_ARG; + } + certBuf = (byte*)(*jenv)->GetByteArrayElements(jenv, certDer, NULL); certSz = (*jenv)->GetArrayLength(jenv, certDer); if (certBuf == NULL || certSz == 0) { diff --git a/native/com_wolfssl_WolfSSLContext.c b/native/com_wolfssl_WolfSSLContext.c index b0a3faf4..75f5f7f7 100644 --- a/native/com_wolfssl_WolfSSLContext.c +++ b/native/com_wolfssl_WolfSSLContext.c @@ -804,7 +804,7 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) { JNIEnv* jenv; jint vmret = 0; - jint retval = -1; + jint retval = 0; int needsDetach = 0; jclass excClass = NULL; jclass verifyClass = NULL; @@ -828,11 +828,11 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL); #endif if (vmret) { - return -101; /* failed to attach JNIEnv to thread */ + return 0; /* failed to attach JNIEnv to thread */ } needsDetach = 1; } else if (vmret != JNI_OK) { - return -102; /* unable to get JNIEnv from JavaVM */ + return 0; /* unable to get JNIEnv from JavaVM */ } /* find exception class */ @@ -843,7 +843,7 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) freeNativeVerifyCbLocalRefs(jenv, excClass, verifyClass, verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return -103; + return 0; } /* Locate the per-context verify callback jobject via @@ -896,7 +896,7 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return -104; + return 0; } verifyMethod = (*jenv)->GetMethodID(jenv, verifyClass, @@ -913,7 +913,7 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return -105; + return 0; } retval = (*jenv)->CallIntMethod(jenv, verifyCbObj, @@ -927,7 +927,7 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return -106; + return 0; } } else { @@ -941,14 +941,19 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) freeNativeVerifyCbLocalRefs(jenv, excClass, verifyClass, verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return -1; + return 0; } freeNativeVerifyCbLocalRefs(jenv, excClass, verifyClass, verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return retval; + /* Accept only on an explicit callback success (1), reject otherwise. */ + if (retval == 1) { + return 1; + } + + return 0; } JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLContext_setOptions diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index d17383c3..322723ef 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -107,7 +107,7 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) { JNIEnv* jenv; jint vmret = 0; - jint retval = -1; + jint retval = 0; int needsDetach = 0; jobjectRefType refcheck; SSLAppData* appData; /* WOLFSSL app data, stored verify cb obj */ @@ -128,11 +128,11 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL); #endif if (vmret) { - return -101; /* failed to attach JNIEnv to thread */ + return 0; /* failed to attach JNIEnv to thread */ } needsDetach = 1; } else if (vmret != JNI_OK) { - return -102; /* unable to get JNIEnv from JavaVM */ + return 0; /* unable to get JNIEnv from JavaVM */ } /* get app data to retrieve stored Java jobject callback object */ @@ -143,7 +143,7 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) if (needsDetach) { (*g_vm)->DetachCurrentThread(g_vm); } - return -105; + return 0; } /* Promote stored global callback ref to a local ref under g_verifyCbMutex @@ -161,7 +161,7 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) if (needsDetach) { (*g_vm)->DetachCurrentThread(g_vm); } - return -106; + return 0; } /* valid ref check: non-zero type covers local/global/weak, and verifyCbObj @@ -180,7 +180,7 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) if (needsDetach) { (*g_vm)->DetachCurrentThread(g_vm); } - return -107; + return 0; } retval = (*jenv)->CallIntMethod(jenv, verifyCbObj, @@ -194,7 +194,7 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) (*jenv)->DeleteLocalRef(jenv, verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return -109; + return 0; } } else { @@ -208,7 +208,7 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) (*jenv)->DeleteLocalRef(jenv, verifyCbObj); if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return -1; + return 0; } (*jenv)->DeleteLocalRef(jenv, verifyCbObj); @@ -216,7 +216,12 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) if (needsDetach) (*g_vm)->DetachCurrentThread(g_vm); - return retval; + /* Accept only on an explicit callback success (1), reject otherwise. */ + if (retval == 1) { + return 1; + } + + return 0; } #ifndef USE_WINDOWS_API @@ -4957,13 +4962,19 @@ JNIEXPORT jstring JNICALL Java_com_wolfssl_WolfSSLSession_getPskIdentityHint { #ifndef NO_PSK WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; + const char* hint = NULL; (void)obj; if (jenv == NULL || ssl == NULL) { return NULL; } - return (*jenv)->NewStringUTF(jenv, wolfSSL_get_psk_identity_hint(ssl)); + hint = wolfSSL_get_psk_identity_hint(ssl); + if (hint == NULL) { + return NULL; + } + + return (*jenv)->NewStringUTF(jenv, hint); #else (void)jenv; (void)obj; @@ -4977,13 +4988,19 @@ JNIEXPORT jstring JNICALL Java_com_wolfssl_WolfSSLSession_getPskIdentity { #ifndef NO_PSK WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; + const char* identity = NULL; (void)obj; if (jenv == NULL || ssl == NULL) { return NULL; } - return (*jenv)->NewStringUTF(jenv, wolfSSL_get_psk_identity(ssl)); + identity = wolfSSL_get_psk_identity(ssl); + if (identity == NULL) { + return NULL; + } + + return (*jenv)->NewStringUTF(jenv, identity); #else (void)jenv; (void)obj; diff --git a/src/java/com/wolfssl/WolfSSL.java b/src/java/com/wolfssl/WolfSSL.java index 050230f3..4e8e126a 100644 --- a/src/java/com/wolfssl/WolfSSL.java +++ b/src/java/com/wolfssl/WolfSSL.java @@ -357,6 +357,10 @@ public enum TLS_VERSION { /** Maximum SSL record size (16KB) as defined by the protocol. */ public static final int MAX_RECORD_SIZE = 16384; + /** Maximum X.509 serial number size in bytes. Matches + * EXTERNAL_SERIAL_SIZE in wolfssl/wolfcrypt/asn.h. */ + public static final int EXTERNAL_SERIAL_SIZE = 32; + /** TLS record header is: type(1) + version(2) + length(2) */ public static final int TLS_RECORD_HEADER_LEN = 5; /** TLS record header length high byte offset */ diff --git a/src/java/com/wolfssl/WolfSSLCRL.java b/src/java/com/wolfssl/WolfSSLCRL.java index 66f40739..a3c19401 100644 --- a/src/java/com/wolfssl/WolfSSLCRL.java +++ b/src/java/com/wolfssl/WolfSSLCRL.java @@ -418,7 +418,7 @@ public int addRevoked(byte[] serialNumber, Date revocationDate) { int dateFmt = 0; if (revocationDate != null) { Asn1TimeData timeData = buildAsn1TimeData(revocationDate); - revDateBytes = timeData.paddedData; + revDateBytes = Arrays.copyOf(timeData.paddedData, timeData.length); dateFmt = timeData.type; } @@ -436,14 +436,20 @@ public int addRevoked(byte[] serialNumber, Date revocationDate) { * Add revoked certificate entry to CRL from DER-encoded certificate. * * @param certDer DER-encoded certificate to add as revoked - * @param revocationDate Date when certificate was revoked, or null + * @param revocationDate Date when certificate was revoked, or null to + * record the current time * * @return native wolfSSL return code * - * @throws IllegalStateException if WolfSSLCRL has been freed. - * @throws IllegalArgumentException if certDer is null or empty. + * @throws IllegalStateException if WolfSSLCRL has been freed, or if + * the certificate serial number cannot be read. + * @throws IllegalArgumentException if certDer is null or empty, or if + * revocationDate is non-null and certDer cannot be parsed. */ public int addRevokedCert(byte[] certDer, Date revocationDate) { + + WolfSSLCertificate cert = null; + if (certDer == null || certDer.length == 0) { throw new IllegalArgumentException( "Certificate DER is null or empty"); @@ -451,21 +457,27 @@ public int addRevokedCert(byte[] certDer, Date revocationDate) { confirmObjectIsActive(); - byte[] revDateBytes = null; - int dateFmt = 0; if (revocationDate != null) { - Asn1TimeData timeData = buildAsn1TimeData(revocationDate); - revDateBytes = timeData.paddedData; - dateFmt = timeData.type; + try { + cert = new WolfSSLCertificate(certDer); + return addRevokedBySerial(cert, revocationDate); + } + catch (WolfSSLException ex) { + throw new IllegalArgumentException( + "Failed to parse certificate DER", ex); + } + finally { + if (cert != null) { + cert.free(); + } + } } synchronized (crlLock) { WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, WolfSSLDebug.INFO, this.crlPtr, - () -> "entered addRevokedCert(der: " + certDer.length + - ", revocationDate: " + revocationDate + ")"); - return X509_CRL_add_revoked_cert(this.crlPtr, certDer, - revDateBytes, dateFmt); + () -> "entered addRevokedCert(der: " + certDer.length + ")"); + return X509_CRL_add_revoked_cert(this.crlPtr, certDer, null, 0); } } @@ -473,15 +485,18 @@ public int addRevokedCert(byte[] certDer, Date revocationDate) { * Add revoked certificate entry to CRL from WolfSSLCertificate object. * * @param cert WolfSSLCertificate object to add as revoked - * @param revocationDate Date when certificate was revoked, or null + * @param revocationDate Date when certificate was revoked, or null to + * record the current time * * @return native wolfSSL return code * - * @throws IllegalStateException if WolfSSLCRL has been freed or if - * certificate DER encoding fails. + * @throws IllegalStateException if WolfSSLCRL has been freed, if + * certificate DER encoding fails, or if the certificate serial + * number cannot be read. * @throws IllegalArgumentException if cert is null. */ public int addRevokedCert(WolfSSLCertificate cert, Date revocationDate) { + if (cert == null) { throw new IllegalArgumentException("Certificate is null"); } @@ -495,6 +510,10 @@ public int addRevokedCert(WolfSSLCertificate cert, Date revocationDate) { revocationDate + ")"); } + if (revocationDate != null) { + return addRevokedBySerial(cert, revocationDate); + } + byte[] certDer = null; try { certDer = cert.getDer(); @@ -508,7 +527,22 @@ public int addRevokedCert(WolfSSLCertificate cert, Date revocationDate) { throw new IllegalStateException("Certificate DER is empty"); } - return addRevokedCert(certDer, revocationDate); + return addRevokedCert(certDer, null); + } + + /** + * Revoke by a certificate's serial number so an explicit date is honored. + * Backs the addRevokedCert() date paths. */ + private int addRevokedBySerial(WolfSSLCertificate cert, + Date revocationDate) { + + byte[] serial = cert.getSerialBytes(); + if (serial == null || serial.length == 0) { + throw new IllegalStateException( + "Failed to read certificate serial number"); + } + + return addRevoked(serial, revocationDate); } /** diff --git a/src/java/com/wolfssl/WolfSSLCertificate.java b/src/java/com/wolfssl/WolfSSLCertificate.java index da2d9863..53186961 100644 --- a/src/java/com/wolfssl/WolfSSLCertificate.java +++ b/src/java/com/wolfssl/WolfSSLCertificate.java @@ -1577,7 +1577,7 @@ public byte[] getTbs() throws IllegalStateException { */ public BigInteger getSerial() throws IllegalStateException { - byte[] out = new byte[32]; + byte[] out = new byte[WolfSSL.EXTERNAL_SERIAL_SIZE]; int sz; confirmObjectIsActive(); @@ -1597,6 +1597,35 @@ public BigInteger getSerial() throws IllegalStateException { } } + /** + * Return an X.509 serial number as the raw bytes. + * + * @return serial number bytes, or null if not available + * + * @throws IllegalStateException if WolfSSLCertificate has been freed. + */ + byte[] getSerialBytes() throws IllegalStateException { + + int sz; + byte[] out = new byte[WolfSSL.EXTERNAL_SERIAL_SIZE]; + + confirmObjectIsActive(); + + synchronized (x509Lock) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.x509Ptr, + () -> "entering getSerialBytes()"); + + sz = X509_get_serial_number(this.x509Ptr, out); + } + + if (sz <= 0) { + return null; + } + + return Arrays.copyOf(out, sz); + } + /** * Get X.509 validity notBefore date * diff --git a/src/java/com/wolfssl/WolfSSLVerifyCallback.java b/src/java/com/wolfssl/WolfSSLVerifyCallback.java index fd247b27..2c00f7e5 100644 --- a/src/java/com/wolfssl/WolfSSLVerifyCallback.java +++ b/src/java/com/wolfssl/WolfSSLVerifyCallback.java @@ -27,7 +27,7 @@ * callback class to be used by wolfSSL during the handshake process. *

* After implementing this interface, it should be passed as a parameter - * to the {@link WolfSSLContext#setVerify(long, int, WolfSSLVerifyCallback) + * to the {@link WolfSSLContext#setVerify(int, WolfSSLVerifyCallback) * WolfSSLContext.setVerify()} method to be registered with the native wolfSSL * library. * @@ -45,10 +45,9 @@ public interface WolfSSLVerifyCallback { * already passed. 0 if failed, 1 if passed. * @param x509StorePtr pointer to the context used for certificate * chain verification. - * @return 0 if the verification process should - * stop immediately with an error. 1 if - * the verification process should continue with the - * rest of the handshake. + * @return 1 to accept and continue the handshake. + * Any other return value or exception thrown is + * treated as verification failure and aborts handshake. */ public int verifyCallback(int preverify_ok, long x509StorePtr); diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java b/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java index c375af7c..52d62d1d 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java @@ -698,7 +698,7 @@ private static class WolfSSLPrincipal implements Principal { private String[] DNs = { "/emailAddress=", "/CN=", "/OU=", "/O=", "/L=", "/ST=", "/C="}; - /* replace the wolfSSL version of the tag. Returns replacement + /* Replace the wolfSSL version of the tag. Returns replacement * on success. */ private String getReplace(String in) { if (in.equals("/emailAddress=")) { @@ -725,53 +725,51 @@ private String getReplace(String in) { return null; } - /* check if the string starts with an expected tag. - * returns index into DNs of tag when found */ - private int containsDN(String in) { - int i; - for (i = 0; i < DNs.length; i++) { - if (in.startsWith(DNs[i])) - return i; + /* Map "TAG=value" component to its JSSE spelling, leaving + * unrecognized tags unchanged. */ + private String mapComponent(String component) { + for (int i = 0; i < DNs.length; i++) { + /* DNs entries carry a leading '/', drop it to compare */ + String tag = DNs[i].substring(1); + if (component.startsWith(tag)) { + String replace = getReplace(DNs[i]); + if (replace != null) { + return replace.concat( + component.substring(tag.length())); + } + } } - return -1; + return component; } - /* convert name from having "/DN=" format to "DN= ," format + /* Convert name from having "/DN=" format to "DN= ," format * returns the new reformatted string on success */ private String reformatList(String in) { - String[] ret; - int i, j; - String tmp = in; - ArrayList list = new ArrayList(); - if (in == null) { return null; } - ret = in.split("/"); - - while (tmp.length() > 3) { - for (i = tmp.length() - 3; i >= 0; i--) { - if ((j = containsDN(in.substring(i))) >= 0) { - String current = tmp.substring(i, tmp.length()); - current = current.replaceAll(DNs[j], - getReplace(DNs[j])); - list.add(current); - tmp = tmp.substring(0, i); - break; - } + /* Split before each '/' followed by a "TAG=" attribute type, + * then reverse the RDN order. wolfSSL does not escape '/', so + * '/' inside a value followed by "TAG=" will split too. + * Unrecognized RDN types pass through unchanged. */ + String[] parts = in.split("/(?=[A-Za-z0-9.]+=)"); + ArrayList list = new ArrayList(); + for (int i = 0; i < parts.length; i++) { + if (!parts[i].isEmpty()) { + list.add(mapComponent(parts[i])); } } - ret = list.toArray(new String[list.size()]); - tmp = ""; - for (i = 0; i < ret.length - 1; i++) { - tmp = tmp.concat(ret[i]); - tmp = tmp.concat(", "); + StringBuilder sb = new StringBuilder(); + for (int i = list.size() - 1; i >= 0; i--) { + sb.append(list.get(i)); + if (i > 0) { + sb.append(", "); + } } - tmp = tmp.concat(ret[i]); - return tmp; + return sb.toString(); } private WolfSSLPrincipal(String in) { diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java index e334ac59..2f0e4419 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java @@ -49,6 +49,7 @@ import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; +import java.util.Base64; import java.util.Date; import java.util.Set; import java.util.List; @@ -116,6 +117,62 @@ public void setupCommonX509Objects() throws Exception { subjectDN = x509.getSubjectDN(); } + /* Self-signed cert whose subject and issuer use DC (domainComponent) + * RDNs, an attribute type outside the reformat tag table. + * DN: DC=com, DC=example, CN=test.example.com */ + private static final String DC_RDN_CERT_DER = + "MIIDczCCAlugAwIBAgIUJvqgr93mm9aonQB+pyzrjyu7TBowDQYJKoZIhvcN" + + "AQELBQAwSTETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmSJomT8ixkARkW" + + "B2V4YW1wbGUxGTAXBgNVBAMMEHRlc3QuZXhhbXBsZS5jb20wHhcNMjYwODI0" + + "MjA0NjUyWhcNMzYwODIxMjA0NjUyWjBJMRMwEQYKCZImiZPyLGQBGRYDY29t" + + "MRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UEAwwQdGVzdC5leGFt" + + "cGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAo9F5Q" + + "xsEOHMFK0sNiHi5HUscCQWtFxA5AlH9mqfgSAYbEpm9YmHbvgBH2Mvc5vEZY" + + "hB1CRQT7pipS4iXUG+7cnz7M8YyKUuh5TtQTIARaPu2ZpTHOxQ2QMip/eHsI" + + "l8IggiVZZuJcnXHj1nrB8pcx4wreXyi+7r7INViffNV61bGOes6ftIH/sptD" + + "Sw0CXQUf1KfiM1rXaXw8cezYmmJWFoie0R6tbrMEfTRAKb1j4IEqWa3e7Krq" + + "+bZEmiw5kfP2vlEW2w+TylNWOm7uQu742VAJnIqgZ3nnVPxqd/5Ef5E8sMmI" + + "Xy/F+rhEFxZtmAiDnBflmPypXSp0w5c01P0CAwEAAaNTMFEwHQYDVR0OBBYE" + + "FPHoxpJFtgM3/ntZjdyu3Zy2CtSnMB8GA1UdIwQYMBaAFPHoxpJFtgM3/ntZ" + + "jdyu3Zy2CtSnMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB" + + "AJvFrnuTfU9MNTRCJGM3JAyex5RK3g5/SOs6uBjM8A1oM5NzTuZrjQzFWMtd" + + "srS8HMIoatKOxGYJUU1OHy4JpfF6Rfv38+q6QuugemIGmDjBEqmO100ZoYgF" + + "ppujEABELIyMpvqp48e6U8uo1mRoK8BWp4KeZiPo7jzi32HoDkeu+ZUNvn1G" + + "0DwgDiYl5ailSofBKplprpI77hx11fypekatWqsfR01Q/0OEkSsj/gO50em1" + + "VAMUYB1v8WXAivovfkUjx+J8xaund6Tx595jTHMOpQMckM/4C2ecvmixEKrF" + + "uz8gim9WCP+Pe6WZI948Kia+btaslCf58Y6/qrgJTJE="; + + @Test + public void testGetSubjectIssuerDNUnrecognizedRdnTerminates() + throws Exception { + + Assume.assumeTrue(WolfSSL.RsaEnabled()); + + byte[] der = Base64.getDecoder().decode(DC_RDN_CERT_DER); + final WolfSSLX509 cert = new WolfSSLX509(der); + + /* Reversed DN, matching getSubjectX500Principal() output. */ + final String expected = "CN=test.example.com, DC=example, DC=com"; + final String[] result = new String[2]; + + /* Run in a worker thread so a non-terminating reformat shows up as + * a still-alive thread instead of hanging the whole test run. */ + Thread worker = new Thread(new Runnable() { + public void run() { + result[0] = cert.getSubjectDN().getName(); + result[1] = cert.getIssuerDN().getName(); + } + }); + worker.setDaemon(true); + worker.start(); + worker.join(5000); + + assertFalse("getSubjectDN/getIssuerDN did not terminate on an " + + "unrecognized RDN type", worker.isAlive()); + assertEquals(expected, result[0]); + assertEquals(expected, result[1]); + } + @Test public void testServerParsing() { try { diff --git a/src/test/com/wolfssl/test/WolfSSLCRLTest.java b/src/test/com/wolfssl/test/WolfSSLCRLTest.java index 3e1f12fd..c43d0379 100644 --- a/src/test/com/wolfssl/test/WolfSSLCRLTest.java +++ b/src/test/com/wolfssl/test/WolfSSLCRLTest.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.IOException; +import java.io.ByteArrayInputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.file.Files; @@ -42,6 +43,12 @@ import java.security.cert.CertificateException; import java.util.Date; import java.util.Calendar; +import java.util.TimeZone; +import java.math.BigInteger; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.security.cert.X509CRLEntry; +import java.security.cert.X509Certificate; import com.wolfssl.WolfSSL; import com.wolfssl.WolfSSLX509Name; @@ -344,31 +351,110 @@ public void testAddRevoked() } @Test - public void testAddRevokedCert_ByteArray() - throws WolfSSLException, WolfSSLJNIException, IOException, - CertificateException { + public void testAddRevokedHonorsRevocationDate() + throws Exception { Assume.assumeTrue(WolfSSL.CrlGenerationEnabled()); WolfSSLCRL crl = new WolfSSLCRL(); assertNotNull(crl); - /* Load certificate from PEM file and convert to DER */ + crl.setVersion(1); + WolfSSLX509Name issuerName = GenerateTestIssuerName(); + crl.setIssuerName(issuerName); + crl.setLastUpdate(new Date()); + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, 30); + crl.setNextUpdate(cal.getTime()); + + /* Fixed past revocation date, distinct from the current time the + * discarded-date path would otherwise record. */ + Calendar revCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + revCal.clear(); + revCal.set(2020, Calendar.JANUARY, 2, 3, 4, 5); + Date revDate = revCal.getTime(); + + byte[] serial = new byte[] { 0x11, 0x22, 0x33, 0x44 }; + int ret = crl.addRevoked(serial, revDate); + assertTrue("addRevoked should succeed", ret >= 0); + + /* Sign so the entry is DER-encoded, then read the date back. */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + int signRet = crl.sign(kpg.generateKeyPair().getPrivate(), "SHA256"); + assertTrue("CRL sign should succeed", signRet >= 0); + byte[] der = crl.getDer(); + assertNotNull("CRL DER should not be null", der); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509CRL parsed = (X509CRL)cf.generateCRL(new ByteArrayInputStream(der)); + X509CRLEntry entry = parsed.getRevokedCertificate( + new BigInteger(1, serial)); + assertNotNull("revoked entry should be present", entry); + assertEquals("revocation date must match the value passed in", + revDate, entry.getRevocationDate()); + + issuerName.free(); + crl.free(); + } + + /* Check a signed CRL revokes the certificate, on the expected date when + * one is given. The JDK parses both, as an independent check. */ + private static void assertCrlRevokesCert(byte[] crlDer, byte[] certDer, + Date expectedDate) throws Exception { + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate)cf.generateCertificate( + new ByteArrayInputStream(certDer)); + X509CRL parsed = (X509CRL)cf.generateCRL( + new ByteArrayInputStream(crlDer)); + X509CRLEntry entry = parsed.getRevokedCertificate( + cert.getSerialNumber()); + assertNotNull("entry for the certificate serial should be present", + entry); + if (expectedDate != null) { + assertEquals("revocation date must match the value passed in", + expectedDate, entry.getRevocationDate()); + } + } + + /* Fixed past date, distinct from the current time recorded by default. */ + private static Date fixedRevocationDate() { + + Calendar revCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + revCal.clear(); + revCal.set(2020, Calendar.JANUARY, 2, 3, 4, 5); + + return revCal.getTime(); + } + + @Test + public void testAddRevokedCert_ByteArray() throws Exception { + + Assume.assumeTrue(WolfSSL.CrlGenerationEnabled()); + + WolfSSLCRL crl = new WolfSSLCRL(); + assertNotNull(crl); + + crl.setVersion(1); + WolfSSLX509Name issuerName = GenerateTestIssuerName(); + crl.setIssuerName(issuerName); + crl.setLastUpdate(new Date()); + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, 30); + crl.setNextUpdate(cal.getTime()); + + /* Load certificates from PEM and convert to DER */ WolfSSLCertificate cert = new WolfSSLCertificate(clientCertPem, WolfSSL.SSL_FILETYPE_PEM); assertNotNull(cert); byte[] certDer = cert.getDer(); assertNotNull(certDer); assertTrue(certDer.length > 0); - - /* Add revoked certificate by DER */ - Date revDate = new Date(); - int ret = crl.addRevokedCert(certDer, revDate); - assertTrue("addRevokedCert should succeed", ret >= 0); - - /* Add revoked certificate without revocation date */ - ret = crl.addRevokedCert(certDer, null); - assertTrue("addRevokedCert should succeed", ret >= 0); + WolfSSLCertificate caCert = new WolfSSLCertificate(caCertPem, + WolfSSL.SSL_FILETYPE_PEM); + byte[] caDer = caCert.getDer(); + assertNotNull(caDer); /* Test null certificate DER */ try { @@ -386,34 +472,61 @@ public void testAddRevokedCert_ByteArray() /* expected */ } + /* Test unparseable certificate DER with a date */ + try { + crl.addRevokedCert(new byte[] { 0x01, 0x02, 0x03 }, new Date()); + fail("unparseable certificate DER should throw exception"); + } catch (IllegalArgumentException e) { + /* expected */ + } + + /* Revoke with an explicit date, and a second cert with none */ + Date revDate = fixedRevocationDate(); + int ret = crl.addRevokedCert(certDer, revDate); + assertTrue("addRevokedCert with date should succeed", ret >= 0); + ret = crl.addRevokedCert(caDer, null); + assertTrue("addRevokedCert should succeed", ret >= 0); + + /* Sign, then check both entries and the requested date */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + int signRet = crl.sign(kpg.generateKeyPair().getPrivate(), "SHA256"); + assertTrue("CRL sign should succeed", signRet >= 0); + byte[] crlDer = crl.getDer(); + assertNotNull(crlDer); + + assertCrlRevokesCert(crlDer, certDer, revDate); + assertCrlRevokesCert(crlDer, caDer, null); + + cert.free(); + caCert.free(); + issuerName.free(); crl.free(); } @Test - public void testAddRevokedCert_WolfSSLCertificate() - throws WolfSSLException, WolfSSLJNIException, IOException, - CertificateException { + public void testAddRevokedCert_WolfSSLCertificate() throws Exception { Assume.assumeTrue(WolfSSL.CrlGenerationEnabled()); WolfSSLCRL crl = new WolfSSLCRL(); assertNotNull(crl); - /* Load certificate */ + crl.setVersion(1); + WolfSSLX509Name issuerName = GenerateTestIssuerName(); + crl.setIssuerName(issuerName); + crl.setLastUpdate(new Date()); + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, 30); + crl.setNextUpdate(cal.getTime()); + + /* Load certificates */ WolfSSLCertificate cert = new WolfSSLCertificate(clientCertPem, WolfSSL.SSL_FILETYPE_PEM); assertNotNull(cert); - - /* Add revoked certificate */ - Date revDate = new Date(); - int ret = crl.addRevokedCert(cert, revDate); - assertTrue("addRevokedCert should succeed", ret >= 0); - - /* Add revoked certificate without revocation date */ - WolfSSLCertificate cert2 = new WolfSSLCertificate(clientCertPem, + WolfSSLCertificate caCert = new WolfSSLCertificate(caCertPem, WolfSSL.SSL_FILETYPE_PEM); - ret = crl.addRevokedCert(cert2, null); - assertTrue("addRevokedCert should succeed", ret >= 0); + assertNotNull(caCert); /* Test null certificate */ try { @@ -423,9 +536,28 @@ public void testAddRevokedCert_WolfSSLCertificate() /* expected */ } + /* Revoke with an explicit date, and a second cert with none */ + Date revDate = fixedRevocationDate(); + int ret = crl.addRevokedCert(cert, revDate); + assertTrue("addRevokedCert with date should succeed", ret >= 0); + ret = crl.addRevokedCert(caCert, null); + assertTrue("addRevokedCert should succeed", ret >= 0); + + /* Sign, then check both entries and the requested date */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + int signRet = crl.sign(kpg.generateKeyPair().getPrivate(), "SHA256"); + assertTrue("CRL sign should succeed", signRet >= 0); + byte[] crlDer = crl.getDer(); + assertNotNull(crlDer); + + assertCrlRevokesCert(crlDer, cert.getDer(), revDate); + assertCrlRevokesCert(crlDer, caCert.getDer(), null); + /* Free native memory */ cert.free(); - cert2.free(); + caCert.free(); + issuerName.free(); crl.free(); } @@ -847,8 +979,7 @@ public void testGenCRL_UsingCertificates() /* Add revoked certificates using WolfSSLCertificate objects */ WolfSSLCertificate cert1 = new WolfSSLCertificate(clientCertPem, WolfSSL.SSL_FILETYPE_PEM); - Date revDate1 = new Date(); - crl.addRevokedCert(cert1, revDate1); + crl.addRevokedCert(cert1, null); /* Sign CRL with RSA key */ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); diff --git a/src/test/com/wolfssl/test/WolfSSLSessionTest.java b/src/test/com/wolfssl/test/WolfSSLSessionTest.java index 2cb4eb73..fcad33ab 100644 --- a/src/test/com/wolfssl/test/WolfSSLSessionTest.java +++ b/src/test/com/wolfssl/test/WolfSSLSessionTest.java @@ -721,6 +721,202 @@ public Void call() throws Exception { } } + /* Accept one TLS connection on a background thread. A server-side + * handshake failure is expected once the client rejects the cert. */ + private Future runOneShotTlsServer(final ServerSocket srvSocket, + final WolfSSLContext srvCtx, ExecutorService es) { + return es.submit(new Callable() { + @Override + public Void call() { + Socket server = null; + WolfSSLSession srvSes = null; + try { + server = srvSocket.accept(); + server.setSoTimeout(10000); + srvSes = new WolfSSLSession(srvCtx); + srvSes.setFd(server); + int ret, err; + do { + ret = srvSes.accept(); + err = srvSes.getError(ret); + } while (ret != WolfSSL.SSL_SUCCESS && + (err == WolfSSL.SSL_ERROR_WANT_READ || + err == WolfSSL.SSL_ERROR_WANT_WRITE)); + } catch (Exception e) { + /* expected once the client rejects the certificate */ + } finally { + if (srvSes != null) { + try { + srvSes.freeSSL(); + } catch (Exception e) { } + } + if (server != null) { + try { + server.close(); + } catch (Exception e) { } + } + } + return null; + } + }); + } + + /* Client handshake against a one-shot server, trusting no CA so the + * server cert cannot verify, with the verify callback on the context + * or session. Assert the handshake fails closed. */ + private void assertHandshakeFailsWithVerifyCallback( + WolfSSLVerifyCallback cb, boolean registerOnCtx) throws Exception { + + ServerSocket srvSocket = null; + WolfSSLContext srvCtx = null; + WolfSSLContext cliCtx = null; + ExecutorService es = null; + Future srvFuture = null; + Socket cliSock = null; + WolfSSLSession ssl = null; + + /* Initialize wolfSSL so the verify callback ex_data slot is + * allocated before setVerify() is called. */ + new WolfSSL(); + + try { + srvSocket = new ServerSocket(0); + srvSocket.setSoTimeout(10000); + int port = srvSocket.getLocalPort(); + + srvCtx = createAndSetupWolfSSLContext( + srvCert, srvKey, WolfSSL.SSL_FILETYPE_PEM, cliCert, + WolfSSL.SSLv23_ServerMethod()); + + cliCtx = new WolfSSLContext(WolfSSL.SSLv23_ClientMethod()); + if (registerOnCtx) { + cliCtx.setVerify(WolfSSL.SSL_VERIFY_PEER, cb); + } + + es = Executors.newSingleThreadExecutor(); + srvFuture = runOneShotTlsServer(srvSocket, srvCtx, es); + + cliSock = new Socket("localhost", port); + cliSock.setSoTimeout(10000); + ssl = new WolfSSLSession(cliCtx); + if (!registerOnCtx) { + ssl.setVerify(WolfSSL.SSL_VERIFY_PEER, cb); + } + + int ret = ssl.setFd(cliSock); + assertEquals(WolfSSL.SSL_SUCCESS, ret); + + int err; + do { + ret = ssl.connect(); + err = ssl.getError(ret); + } while (ret != WolfSSL.SSL_SUCCESS && + (err == WolfSSL.SSL_ERROR_WANT_READ || + err == WolfSSL.SSL_ERROR_WANT_WRITE)); + + assertTrue("handshake must fail closed when the verify callback " + + "throws or returns non-success on an untrusted certificate", + ret != WolfSSL.SSL_SUCCESS); + } finally { + if (ssl != null) { + try { + ssl.freeSSL(); + } catch (Exception e) { + /* ignore on purpose */ + } + } + if (cliSock != null) { + try { + cliSock.close(); + } catch (Exception e) { + /* ignore on purpose */ + } + } + if (srvFuture != null) { + try { + srvFuture.get(10, TimeUnit.SECONDS); + } catch (Exception e) { + /* ignore on purpose */ + } + } + if (es != null) { + es.shutdown(); + } + if (srvSocket != null) { + try { + srvSocket.close(); + } catch (Exception e) { + /* ignore on purpose */ + } + } + if (srvCtx != null) { + try { + srvCtx.free(); + } catch (Exception e) { + /* ignore on purpose */ + } + } + if (cliCtx != null) { + try { + cliCtx.free(); + } catch (Exception e) { + /* ignore on purpose */ + } + } + } + } + + @Test + public void test_WolfSSLSession_verifyCallbackExceptionFailsClosed() + throws Exception { + /* Throwing callback on the context must fail the handshake closed. */ + assertHandshakeFailsWithVerifyCallback(new WolfSSLVerifyCallback() { + @Override + public int verifyCallback(int preverify_ok, long storePtr) { + throw new RuntimeException("verify callback failure"); + } + }, true); + } + + @Test + public void test_WolfSSLSession_sslVerifyCallbackExceptionFailsClosed() + throws Exception { + /* Throwing callback on the session (the wolfJSSE path) must fail + * closed. */ + assertHandshakeFailsWithVerifyCallback(new WolfSSLVerifyCallback() { + @Override + public int verifyCallback(int preverify_ok, long storePtr) { + throw new RuntimeException("verify callback failure"); + } + }, false); + } + + @Test + public void test_WolfSSLSession_verifyCallbackNegativeReturnFailsClosed() + throws Exception { + /* Negative return on the context must fail closed, exercising the + * return normalization rather than the exception path. */ + assertHandshakeFailsWithVerifyCallback(new WolfSSLVerifyCallback() { + @Override + public int verifyCallback(int preverify_ok, long storePtr) { + return -1; + } + }, true); + } + + @Test + public void test_WolfSSLSession_sslVerifyCallbackNegativeReturnFailsClosed() + throws Exception { + /* Negative return on the session must fail closed, exercising the + * return normalization rather than the exception path. */ + assertHandshakeFailsWithVerifyCallback(new WolfSSLVerifyCallback() { + @Override + public int verifyCallback(int preverify_ok, long storePtr) { + return -1; + } + }, false); + } + @Test public void test_WolfSSLSession_getDhKeySizeBeforeHandshakeAndAfterFree() throws WolfSSLJNIException, WolfSSLException {