This is an automated email from the ASF dual-hosted git repository.
manikumar pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git
The following commit(s) were added to refs/heads/trunk by this push:
new f46db86d34f KAFKA-15273: Log common name of expired client
certificates (#14130)
f46db86d34f is described below
commit f46db86d34f9e5fe1b0d7604306a5108a89c113e
Author: Eike Thaden <[email protected]>
AuthorDate: Fri Sep 15 17:36:37 2023 +0200
KAFKA-15273: Log common name of expired client certificates (#14130)
This contribution extends the TrustManager created by the
DefaultSSLEngineFactory class with code that checks for any invalid certificate
whether it is just expired but valid otherwise. If this is the case, it
extracts the common name and logs it. Apart from that, the trust manager will
behave exactly as the default one.
Extensive unit tests are included in this pull request for ensuring that
the modified trust manager will behave exactly as the default one, except for
logging expired certificates common name. The test code generates several
certificate chains with valid, invalid and expired end certificates, root CAs
and even intermediate CAs.
This contribution is my original work and I license the work to the project
under the project's open source license.
Reviewers: Manikumar Reddy <[email protected]>, Rajini Sivaram
<[email protected]>
---
.../org/apache/kafka/common/config/SslConfigs.java | 7 +-
.../ssl/CommonNameLoggingSslEngineFactory.java | 36 ++
...ommonNameLoggingTrustManagerFactoryWrapper.java | 424 +++++++++++++++
.../security/ssl/DefaultSslEngineFactory.java | 18 +-
.../ssl/CommonNameLoggingSslEngineFactoryTest.java | 25 +
...nNameLoggingTrustManagerFactoryWrapperTest.java | 595 +++++++++++++++++++++
.../security/ssl/DefaultSslEngineFactoryTest.java | 8 +-
.../java/org/apache/kafka/test/TestSslUtils.java | 125 ++++-
clients/src/test/resources/log4j.properties | 2 +
9 files changed, 1229 insertions(+), 11 deletions(-)
diff --git
a/clients/src/main/java/org/apache/kafka/common/config/SslConfigs.java
b/clients/src/main/java/org/apache/kafka/common/config/SslConfigs.java
index 38b849be835..5c3bbcdfb96 100644
--- a/clients/src/main/java/org/apache/kafka/common/config/SslConfigs.java
+++ b/clients/src/main/java/org/apache/kafka/common/config/SslConfigs.java
@@ -128,7 +128,12 @@ public class SslConfigs {
public static final String SSL_SECURE_RANDOM_IMPLEMENTATION_DOC = "The
SecureRandom PRNG implementation to use for SSL cryptography operations. ";
public static final String SSL_ENGINE_FACTORY_CLASS_CONFIG =
"ssl.engine.factory.class";
- public static final String SSL_ENGINE_FACTORY_CLASS_DOC = "The class of
type org.apache.kafka.common.security.auth.SslEngineFactory to provide
SSLEngine objects. Default value is
org.apache.kafka.common.security.ssl.DefaultSslEngineFactory";
+ public static final String SSL_ENGINE_FACTORY_CLASS_DOC = "The class of
type org.apache.kafka.common.security.auth.SslEngineFactory to provide
SSLEngine objects. "
+ + "Default value is
org.apache.kafka.common.security.ssl.DefaultSslEngineFactory. "
+ + "Alternatively, setting this to
org.apache.kafka.common.security.ssl.CommonNameLoggingSslEngineFactory will log
the common name of expired SSL certificates used by clients to authenticate at
any of the brokers with log level "
+ + LogLevelConfig.INFO_LOG_LEVEL + ". "
+ + "Note that this will cause a tiny delay during establishment of new
connections from mTLS clients to brokers due to the extra code for examining
the certificate chain provided by the client. "
+ + "Note further that the implementation uses a custom truststore based
on the standard Java truststore and thus might be considered a security risk
due to not being as mature as the standard one.";
public static void addClientSslSupport(ConfigDef config) {
config.define(SslConfigs.SSL_PROTOCOL_CONFIG, ConfigDef.Type.STRING,
SslConfigs.DEFAULT_SSL_PROTOCOL, ConfigDef.Importance.MEDIUM,
SslConfigs.SSL_PROTOCOL_DOC)
diff --git
a/clients/src/main/java/org/apache/kafka/common/security/ssl/CommonNameLoggingSslEngineFactory.java
b/clients/src/main/java/org/apache/kafka/common/security/ssl/CommonNameLoggingSslEngineFactory.java
new file mode 100644
index 00000000000..f683feadf09
--- /dev/null
+++
b/clients/src/main/java/org/apache/kafka/common/security/ssl/CommonNameLoggingSslEngineFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.common.security.ssl;
+
+
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.KeyStoreException;
+
+import javax.net.ssl.TrustManager;
+
+public final class CommonNameLoggingSslEngineFactory extends
DefaultSslEngineFactory {
+
+ @Override
+ protected TrustManager[] getTrustManagers(SecurityStore truststore, String
tmfAlgorithm) throws NoSuchAlgorithmException, KeyStoreException {
+ CommonNameLoggingTrustManagerFactoryWrapper tmf =
CommonNameLoggingTrustManagerFactoryWrapper.getInstance(tmfAlgorithm);
+ KeyStore ts = truststore == null ? null : truststore.get();
+ tmf.init(ts);
+ return tmf.getTrustManagers();
+ }
+
+}
diff --git
a/clients/src/main/java/org/apache/kafka/common/security/ssl/CommonNameLoggingTrustManagerFactoryWrapper.java
b/clients/src/main/java/org/apache/kafka/common/security/ssl/CommonNameLoggingTrustManagerFactoryWrapper.java
new file mode 100644
index 00000000000..75998858281
--- /dev/null
+++
b/clients/src/main/java/org/apache/kafka/common/security/ssl/CommonNameLoggingTrustManagerFactoryWrapper.java
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.common.security.ssl;
+
+import org.apache.kafka.common.KafkaException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+/**
+ * A wrapper around the original trust manager factory for creating common
name logging trust managers.
+ * These trust managers log the common name of an expired but otherwise valid
(client) certificate before rejecting the connection attempt.
+ * This allows to identify misconfigured clients in complex network
environments, where the IP address is not sufficient.
+ */
+class CommonNameLoggingTrustManagerFactoryWrapper {
+
+ private static final Logger log =
LoggerFactory.getLogger(CommonNameLoggingTrustManagerFactoryWrapper.class);
+
+ private TrustManagerFactory origTmf;
+
+ /**
+ * Create a wrapped trust manager factory
+ * @param kmfAlgorithm the algorithm
+ * @return A wrapped trust manager factory
+ * @throws NoSuchAlgorithmException
+ */
+ protected CommonNameLoggingTrustManagerFactoryWrapper(String kmfAlgorithm)
throws NoSuchAlgorithmException {
+ this.origTmf = TrustManagerFactory.getInstance(kmfAlgorithm);
+ }
+ /**
+ * Factory for creating a wrapped trust manager factory
+ * @param kmfAlgorithm the algorithm
+ * @return A wrapped trust manager factory
+ * @throws NoSuchAlgorithmException
+ */
+ public static CommonNameLoggingTrustManagerFactoryWrapper
getInstance(String kmfAlgorithm) throws NoSuchAlgorithmException {
+ return new CommonNameLoggingTrustManagerFactoryWrapper(kmfAlgorithm);
+ }
+
+ public TrustManagerFactory getOriginalTrustManagerFactory() {
+ return this.origTmf;
+ }
+
+ public String getAlgorithm() {
+ return this.origTmf.getAlgorithm();
+ }
+
+ public void init(KeyStore ts) throws KeyStoreException {
+ this.origTmf.init(ts);
+ }
+
+ public TrustManager[] getTrustManagers() {
+ TrustManager[] origTrustManagers = this.origTmf.getTrustManagers();
+ TrustManager[] wrappedTrustManagers = new
TrustManager[origTrustManagers.length];
+ for (int i = 0; i < origTrustManagers.length; i++) {
+ TrustManager tm = origTrustManagers[i];
+ if (tm instanceof X509TrustManager) {
+ // Wrap only X509 trust managers
+ wrappedTrustManagers[i] = new
CommonNameLoggingTrustManager((X509TrustManager) tm, 2000);
+ } else {
+ wrappedTrustManagers[i] = tm;
+ }
+ }
+ return wrappedTrustManagers;
+ }
+ /**
+ * A trust manager which logs the common name of an expired but otherwise
valid (client) certificate before rejecting the connection attempt.
+ * This allows to identify misconfigured clients in complex network
environments, where the IP address is not sufficient.
+ * this class wraps a standard trust manager and delegates almost all
requests to it, except for cases where an invalid certificate is reported by the
+ * standard trust manager. In this cases this manager checks whether the
provided certificate is invalid only due to being expired and logs the common
+ * name if that is the case. This trust manager will always return the
results of the wrapped standard trust manager, i.e. return if the certificate
is valid
+ * or rethrow the original exception if it is not.
+ */
+ static class CommonNameLoggingTrustManager implements X509TrustManager {
+
+ final private X509TrustManager origTm;
+ final int nrOfRememberedBadCerts;
+ final private LinkedHashMap<ByteBuffer, String>
previouslyRejectedClientCertChains;
+
+ public CommonNameLoggingTrustManager(X509TrustManager
originalTrustManager, int nrOfRememberedBadCerts) {
+ this.origTm = originalTrustManager;
+ this.nrOfRememberedBadCerts = nrOfRememberedBadCerts;
+ // Restrict maximal size of the LinkedHashMap to avoid security
attacks causing OOM
+ this.previouslyRejectedClientCertChains = new
LinkedHashMap<ByteBuffer, String>() {
+ @Override
+ protected boolean removeEldestEntry(final
Map.Entry<ByteBuffer, String> eldest) {
+ return size() > nrOfRememberedBadCerts;
+ }
+ };
+ }
+
+ public X509TrustManager getOriginalTrustManager() {
+ return this.origTm;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String
authType)
+ throws CertificateException {
+ CertificateException origException = null;
+ ByteBuffer chainDigest = calcDigestForCertificateChain(chain);
+ if (chainDigest != null) {
+ String errorMessage =
this.previouslyRejectedClientCertChains.get(chainDigest);
+ if (errorMessage != null) {
+ // Reinsert the digest, to remember that this is the most
recent digest we've seen
+ addRejectedClientCertChains(chainDigest, errorMessage,
true);
+ // Then throw with the original error Message
+ throw new CertificateException(errorMessage);
+ }
+ }
+ try {
+ this.origTm.checkClientTrusted(chain, authType);
+ // If the last line did not throw, the chain is valid
(including that none of the certificates is expired)
+ } catch (CertificateException e) {
+ origException = e;
+ try {
+ X509Certificate[] wrappedChain =
sortChainAnWrapEndCertificate(chain);
+ this.origTm.checkClientTrusted(wrappedChain, authType);
+ // No exception occurred this time. The certificate is
either not yet valid or already expired
+ Date now = new Date();
+ // Check if the certificate was valid in the past
+ if (wrappedChain[0].getNotBefore().before(now)) {
+ String commonName =
wrappedChain[0].getSubjectX500Principal().toString();
+ String notValidAfter =
wrappedChain[0].getNotAfter().toString();
+ log.info("Certificate with common name \"" +
commonName + "\" expired on " + notValidAfter);
+ // The end certificate is expired and thus will never
become valid anymore, as long as the trust store is not changed
+ addRejectedClientCertChains(chainDigest,
origException.getMessage(), false);
+ }
+
+ } catch (CertificateException innerException) {
+ // Ignore this exception as we throw the original one below
+ // Even with disabled date check, this cert chain is
invalid: Remember the chain and fail faster next time we see it
+ addRejectedClientCertChains(chainDigest,
origException.getMessage(), false);
+ }
+ }
+ if (origException != null) {
+ throw origException;
+ }
+ }
+
+ public static ByteBuffer
calcDigestForCertificateChain(X509Certificate[] chain) throws
CertificateEncodingException {
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+ for (X509Certificate cert: chain) {
+ md.update(cert.getEncoded());
+ }
+ return ByteBuffer.wrap(md.digest());
+ }
+
+ private void addRejectedClientCertChains(ByteBuffer chainDigest,
String errorMessage, boolean removeIfExisting) {
+ if (removeIfExisting) {
+ this.previouslyRejectedClientCertChains.remove(chainDigest);
+ }
+ this.previouslyRejectedClientCertChains.put(chainDigest,
errorMessage);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String
authType)
+ throws CertificateException {
+ this.origTm.checkServerTrusted(chain, authType);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return this.origTm.getAcceptedIssuers();
+ }
+
+ /**
+ * This method sorts the certificate chain from end to root
certificate and wraps the end certificate to make it "never-expireing"
+ * @param origChain The original (unsorted) certificate chain
+ * @return The sorted and wrapped certificate chain
+ * @throws CertificateException
+ * @throws NoSuchAlgorithmException
+ */
+ public static X509Certificate[]
sortChainAnWrapEndCertificate(X509Certificate[] origChain) throws
CertificateException {
+ if (origChain == null || origChain.length < 1) {
+ throw new CertificateException("Certificate chain is null or
empty");
+ }
+ // Find the end certificate by looking at all issuers and find the
one not referred to by any other certificate
+ // There might be multiple end certificates if the chain is
invalid!
+ // Create a map from principal to certificate
+ HashMap<X500Principal, X509Certificate> principalToCertMap = new
HashMap<>();
+ // First, create a map from principal of issuer (!) to certificate
for easily finding the right certificates
+ HashMap<X500Principal, X509Certificate>
issuedbyPrincipalToCertificatesMap = new HashMap<>();
+ for (X509Certificate cert: origChain) {
+ X500Principal principal = cert.getSubjectX500Principal();
+ X500Principal issuerPrincipal = cert.getIssuerX500Principal();
+ if (issuerPrincipal.equals(principal)) {
+ // self-signed certificate in chain! This should not happen
+ boolean isCA = cert.getBasicConstraints() >= 0;
+ if (!isCA) {
+ throw new CertificateException("Self-signed
certificate in chain that is not a CA!");
+ }
+ }
+ issuedbyPrincipalToCertificatesMap.put(issuerPrincipal, cert);
+ principalToCertMap.put(principal, cert);
+ }
+ // Thus, expect certificate chain to be broken, e.g. containing
multiple enbd certificates
+ HashSet<X509Certificate> endCertificates = new HashSet<>();
+ for (X509Certificate cert: origChain) {
+ X500Principal subjectPrincipal =
cert.getSubjectX500Principal();
+ if
(!issuedbyPrincipalToCertificatesMap.containsKey(subjectPrincipal)) {
+ // We found a certificate which is not an issuer of
another certificate. We consider it to be an end certificate
+ endCertificates.add(cert);
+ }
+ }
+ // There should be exactly one end certificate, otherwise we don't
know which one to wrap
+ if (endCertificates.size() != 1) {
+ throw new CertificateException("Multiple end certificates in
chain");
+ }
+ X509Certificate endCertificate = endCertificates.iterator().next();
+ X509Certificate[] wrappedChain = new
X509Certificate[origChain.length];
+ // Add the wrapped certificate as first element in the new
certificate chain array
+ wrappedChain[0] = new NeverExpiringX509Certificate(endCertificate);
+ // Add all other (potential) certificates in order of dependencies
(result will be sorted from end certificate to last intermediate/root
certificate)
+ for (int i = 1; i < origChain.length; i++) {
+ X500Principal siblingCertificateIssuer = wrappedChain[i -
1].getIssuerX500Principal();
+ if (principalToCertMap.containsKey(siblingCertificateIssuer)) {
+ wrappedChain[i] =
principalToCertMap.get(siblingCertificateIssuer);
+ } else {
+ throw new CertificateException("Certificate chain contains
certificates not belonging to the chain");
+ }
+ }
+ return wrappedChain;
+ }
+ }
+
+ static class NeverExpiringX509Certificate extends X509Certificate {
+
+ private X509Certificate origCertificate;
+
+ public NeverExpiringX509Certificate(X509Certificate origCertificate) {
+ this.origCertificate = origCertificate;
+ if (this.origCertificate == null) {
+ throw new KafkaException("No X509 certificate provided in
constructor NeverExpiringX509Certificate");
+ }
+ }
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return this.origCertificate.getCriticalExtensionOIDs();
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return this.origCertificate.getExtensionValue(oid);
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return this.origCertificate.getNonCriticalExtensionOIDs();
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return this.origCertificate.hasUnsupportedCriticalExtension();
+ }
+
+ @Override
+ public void checkValidity()
+ throws CertificateExpiredException,
CertificateNotYetValidException {
+ Date now = new Date();
+ // Do nothing for certificates which are not valid anymore now
+ if (this.origCertificate.getNotAfter().before(now)) {
+ return;
+ }
+ // Check validity as usual
+ this.origCertificate.checkValidity();
+ }
+
+ @Override
+ public void checkValidity(Date date)
+ throws CertificateExpiredException,
CertificateNotYetValidException {
+ // We do not check validity at all.
+ return;
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ return this.origCertificate.getBasicConstraints();
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return this.origCertificate.getIssuerDN();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return this.origCertificate.getIssuerUniqueID();
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return this.origCertificate.getKeyUsage();
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return this.origCertificate.getNotAfter();
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return this.origCertificate.getNotBefore();
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return this.origCertificate.getSerialNumber();
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return this.origCertificate.getSigAlgName();
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return this.origCertificate.getSigAlgOID();
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return this.origCertificate.getSigAlgParams();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return this.origCertificate.getSignature();
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return this.origCertificate.getSubjectDN();
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return this.origCertificate.getSubjectUniqueID();
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return this.origCertificate.getTBSCertificate();
+ }
+
+ @Override
+ public int getVersion() {
+ return this.origCertificate.getVersion();
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return this.origCertificate.getEncoded();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return this.origCertificate.getPublicKey();
+ }
+
+ @Override
+ public String toString() {
+ return this.origCertificate.toString();
+ }
+
+ @Override
+ public void verify(PublicKey publicKey) throws CertificateException,
NoSuchAlgorithmException,
+ InvalidKeyException, NoSuchProviderException,
SignatureException {
+ this.origCertificate.verify(publicKey);
+ }
+
+ @Override
+ public void verify(PublicKey publicKey, String sigProvider)
+ throws CertificateException, NoSuchAlgorithmException,
InvalidKeyException,
+ NoSuchProviderException, SignatureException {
+ this.origCertificate.verify(publicKey, sigProvider);
+ }
+ }
+}
diff --git
a/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java
b/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java
index 0175139fea3..9aacf1af1e0 100644
---
a/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java
+++
b/clients/src/main/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactory.java
@@ -35,6 +35,8 @@ import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.KeyStoreException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
@@ -68,8 +70,9 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.TrustManager;
-public final class DefaultSslEngineFactory implements SslEngineFactory {
+public class DefaultSslEngineFactory implements SslEngineFactory {
private static final Logger log =
LoggerFactory.getLogger(DefaultSslEngineFactory.class);
public static final String PEM_TYPE = "PEM";
@@ -255,11 +258,9 @@ public final class DefaultSslEngineFactory implements
SslEngineFactory {
}
String tmfAlgorithm = this.tmfAlgorithm != null ?
this.tmfAlgorithm : TrustManagerFactory.getDefaultAlgorithm();
- TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
- KeyStore ts = truststore == null ? null : truststore.get();
- tmf.init(ts);
+ TrustManager[] trustManagers = getTrustManagers(truststore,
tmfAlgorithm);
- sslContext.init(keyManagers, tmf.getTrustManagers(),
this.secureRandomImplementation);
+ sslContext.init(keyManagers, trustManagers,
this.secureRandomImplementation);
log.debug("Created SSL context with keystore {}, truststore {},
provider {}.",
keystore, truststore, sslContext.getProvider().getName());
return sslContext;
@@ -268,6 +269,13 @@ public final class DefaultSslEngineFactory implements
SslEngineFactory {
}
}
+ protected TrustManager[] getTrustManagers(SecurityStore truststore, String
tmfAlgorithm) throws NoSuchAlgorithmException, KeyStoreException {
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
+ KeyStore ts = truststore == null ? null : truststore.get();
+ tmf.init(ts);
+ return tmf.getTrustManagers();
+ }
+
// Visibility to override for testing
protected SecurityStore createKeystore(String type, String path, Password
password, Password keyPassword, Password privateKey, Password certificateChain)
{
if (privateKey != null) {
diff --git
a/clients/src/test/java/org/apache/kafka/common/security/ssl/CommonNameLoggingSslEngineFactoryTest.java
b/clients/src/test/java/org/apache/kafka/common/security/ssl/CommonNameLoggingSslEngineFactoryTest.java
new file mode 100644
index 00000000000..c69e99661f0
--- /dev/null
+++
b/clients/src/test/java/org/apache/kafka/common/security/ssl/CommonNameLoggingSslEngineFactoryTest.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.common.security.ssl;
+
+public class CommonNameLoggingSslEngineFactoryTest extends
DefaultSslEngineFactoryTest {
+
+ @Override
+ protected DefaultSslEngineFactory sslEngineFactory() {
+ return new CommonNameLoggingSslEngineFactory();
+ }
+}
diff --git
a/clients/src/test/java/org/apache/kafka/common/security/ssl/CommonNameLoggingTrustManagerFactoryWrapperTest.java
b/clients/src/test/java/org/apache/kafka/common/security/ssl/CommonNameLoggingTrustManagerFactoryWrapperTest.java
new file mode 100644
index 00000000000..8757dfee60b
--- /dev/null
+++
b/clients/src/test/java/org/apache/kafka/common/security/ssl/CommonNameLoggingTrustManagerFactoryWrapperTest.java
@@ -0,0 +1,595 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.common.security.ssl;
+
+import
org.apache.kafka.common.security.ssl.CommonNameLoggingTrustManagerFactoryWrapper.CommonNameLoggingTrustManager;
+import
org.apache.kafka.common.security.ssl.CommonNameLoggingTrustManagerFactoryWrapper.NeverExpiringX509Certificate;
+import org.apache.kafka.common.utils.LogCaptureAppender;
+import org.apache.kafka.test.TestSslUtils;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+
+import java.nio.ByteBuffer;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.List;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@TestInstance(Lifecycle.PER_CLASS)
+public class CommonNameLoggingTrustManagerFactoryWrapperTest {
+
+ private X509Certificate[] chainWithValidEndCertificate;
+ private X509Certificate[] chainWithExpiredEndCertificate;
+ private X509Certificate[] chainWithInvalidEndCertificate;
+ private X509Certificate[] chainWithMultipleEndCertificates;
+ private X509Certificate[] chainWithValidAndInvalidEndCertificates;
+
+ @BeforeAll
+ public void setUpOnce() throws CertificateException,
NoSuchAlgorithmException {
+ chainWithValidEndCertificate = generateKeyChainIncludingCA(false,
false, true, false);
+ chainWithExpiredEndCertificate = generateKeyChainIncludingCA(true,
false, true, false);
+ chainWithInvalidEndCertificate = generateKeyChainIncludingCA(false,
false, false, false);
+ chainWithMultipleEndCertificates = generateKeyChainIncludingCA(false,
true, false, true);
+ chainWithValidAndInvalidEndCertificates =
generateKeyChainIncludingCA(false, true, true, false);
+ }
+
+ @Test
+ void testNeverExpiringX509Certificate() throws Exception {
+ final KeyPair keyPair = TestSslUtils.generateKeyPair("RSA");
+ final String dn = "CN=Test, L=London, C=GB";
+ // Create and initialize data structures
+ int nrOfCerts = 5;
+ X509Certificate[] testCerts = new X509Certificate[nrOfCerts];
+ PublicKey[] signedWith = new PublicKey[nrOfCerts];
+ boolean[] expectValidEndCert = new boolean[nrOfCerts];
+ final int days = 1;
+ // Generate valid certificate
+ testCerts[0] = TestSslUtils.generateCertificate(dn, keyPair, days,
"SHA512withRSA");
+ // Self-signed
+ signedWith[0] = testCerts[0].getPublicKey();
+ expectValidEndCert[0] = true;
+ // Generate expired, but valid certificate
+ testCerts[1] = TestSslUtils.generateCertificate(dn, keyPair, -days,
"SHA512withRSA");
+ // Self-signed
+ signedWith[1] = testCerts[1].getPublicKey();
+ expectValidEndCert[1] = true;
+ // Use existing real certificate chain, where the end certificate (the
first on
+ // in the
+ // chain) is valid
+ testCerts[2] = chainWithValidEndCertificate[0];
+ // The end certificate must be signed by the intermediate CA public key
+ signedWith[2] = chainWithValidEndCertificate[1].getPublicKey();
+ expectValidEndCert[2] = true;
+ // Use existing real certificate chain, where the end certificate (the
first on
+ // in the
+ // chain) is expired
+ testCerts[3] = chainWithExpiredEndCertificate[0];
+ // The end certificate must be signed by the intermediate CA public key
+ signedWith[3] = chainWithExpiredEndCertificate[1].getPublicKey();
+ expectValidEndCert[3] = true;
+ // Test with invalid certificate
+ testCerts[4] = chainWithInvalidEndCertificate[0];
+ // Check whether this certificate is signed by the intermediate
certificate in
+ // our chain (it is not)
+ signedWith[4] = chainWithInvalidEndCertificate[1].getPublicKey();
+ expectValidEndCert[4] = false;
+
+ for (int i = 0; i < testCerts.length; i++) {
+ X509Certificate cert = testCerts[i];
+ final NeverExpiringX509Certificate wrappedCert = new
NeverExpiringX509Certificate(
+ cert);
+ // All results must be identically for original as well as wrapped
certificate
+ // class
+ assertEquals(cert.getCriticalExtensionOIDs(),
wrappedCert.getCriticalExtensionOIDs());
+ final String testOid = "2.5.29.14"; // Should not be in test
certificate
+ assertEquals(cert.getExtensionValue(testOid),
wrappedCert.getExtensionValue(testOid));
+ assertEquals(cert.getNonCriticalExtensionOIDs(),
+ wrappedCert.getNonCriticalExtensionOIDs());
+ assertEquals(cert.hasUnsupportedCriticalExtension(),
+ wrappedCert.hasUnsupportedCriticalExtension());
+ // We have just generated a valid test certificate, it should
still be valid now
+ assertEquals(cert.getBasicConstraints(),
wrappedCert.getBasicConstraints());
+ assertEquals(cert.getIssuerDN(), wrappedCert.getIssuerDN());
+ assertEquals(cert.getIssuerUniqueID(),
wrappedCert.getIssuerUniqueID());
+ assertEquals(cert.getKeyUsage(), wrappedCert.getKeyUsage());
+ assertEquals(cert.getNotAfter(), wrappedCert.getNotAfter());
+ assertEquals(cert.getNotBefore(), wrappedCert.getNotBefore());
+ assertEquals(cert.getSerialNumber(),
wrappedCert.getSerialNumber());
+ assertEquals(cert.getSigAlgName(), wrappedCert.getSigAlgName());
+ assertEquals(cert.getSigAlgOID(), wrappedCert.getSigAlgOID());
+ assertArrayEquals(cert.getSigAlgParams(),
wrappedCert.getSigAlgParams());
+ assertArrayEquals(cert.getSignature(), wrappedCert.getSignature());
+ assertEquals(cert.getSubjectDN(), wrappedCert.getSubjectDN());
+ assertEquals(cert.getSubjectUniqueID(),
wrappedCert.getSubjectUniqueID());
+ assertArrayEquals(cert.getTBSCertificate(),
wrappedCert.getTBSCertificate());
+ assertEquals(cert.getVersion(), wrappedCert.getVersion());
+ assertArrayEquals(cert.getEncoded(), wrappedCert.getEncoded());
+ assertEquals(cert.getPublicKey(), wrappedCert.getPublicKey());
+ assertEquals(cert.toString(), wrappedCert.toString());
+ final PublicKey signingKey = signedWith[i];
+ if (expectValidEndCert[i]) {
+ assertDoesNotThrow(() -> cert.verify(signingKey));
+ assertDoesNotThrow(() -> wrappedCert.verify(signingKey));
+ } else {
+ Exception origException =
assertThrows(SignatureException.class, () -> cert.verify(signingKey));
+ Exception testException =
assertThrows(SignatureException.class, () -> wrappedCert.verify(signingKey));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ }
+ // Test timing now, starting with "now"
+ Date dateNow = new Date();
+ if (cert.getNotBefore().before(dateNow) &&
cert.getNotAfter().after(dateNow)) {
+ assertDoesNotThrow(() -> cert.checkValidity());
+ } else {
+ assertThrows(CertificateException.class, () ->
cert.checkValidity());
+ }
+ // The wrappedCert must never throw due to being expired
+ assertDoesNotThrow(() -> wrappedCert.checkValidity());
+ if (cert.getNotBefore().before(dateNow) &&
cert.getNotAfter().after(dateNow)) {
+ assertDoesNotThrow(() -> cert.checkValidity(dateNow));
+ } else {
+ assertThrows(CertificateException.class, () ->
cert.checkValidity(dateNow));
+ }
+ // wrapped cert must not throw even if it is expired
+ assertDoesNotThrow(() -> wrappedCert.checkValidity(dateNow));
+ // Test with (days/2) before now.
+ Date dateRecentPast = new Date(System.currentTimeMillis() - days *
12 * 60 * 60 * 1000);
+ if (cert.getNotBefore().before(dateRecentPast)
+ && cert.getNotAfter().after(dateRecentPast)) {
+ assertDoesNotThrow(() -> cert.checkValidity(dateRecentPast));
+ assertDoesNotThrow(() ->
wrappedCert.checkValidity(dateRecentPast));
+ } else {
+ // Cert not valid yet
+ Exception origException =
assertThrows(CertificateException.class,
+ () -> cert.checkValidity(dateRecentPast));
+ // The wrappend certificate class does not check dates at all
+ assertDoesNotThrow(() ->
wrappedCert.checkValidity(dateRecentPast));
+ }
+ // Test with (days+1) before now. Both certificates were not yet
valid, thus
+ // both checks
+ // must throw
+ Date datePast = new Date(System.currentTimeMillis() - (days + 2) *
24 * 60 * 60 * 1000);
+ assertThrows(CertificateException.class, () ->
cert.checkValidity(datePast));
+ // The wrappend certificate class does not check dates at all
+ assertDoesNotThrow(() -> wrappedCert.checkValidity(datePast));
+ // Test with "days+2" after now.
+ // Cert is not valid anymore. The original class must throw
+ Date dateFuture = new Date(System.currentTimeMillis() + (days + 2)
* 24 * 60 * 60 * 1000);
+ assertThrows(CertificateException.class, () ->
cert.checkValidity(dateFuture));
+ // This checks the only deviation in behavior of the
+ // NeverExpiringX509Certificate
+ // compared to the standard Certificate:
+ // The NeverExpiringX509Certificate will report any expired
certificate as still
+ // valid
+ assertDoesNotThrow(() -> wrappedCert.checkValidity(dateFuture));
+ }
+ }
+
+ private static X509TrustManager getX509TrustManager(TrustManagerFactory
tmf) throws Exception {
+ for (TrustManager trustManager : tmf.getTrustManagers()) {
+ if (trustManager instanceof X509TrustManager) {
+ return (X509TrustManager) trustManager;
+ }
+ }
+ throw new Exception("Unable to find X509TrustManager");
+ }
+
+ @Test
+ public void testCommonNameLoggingTrustManagerFactoryWrapper() throws
Exception {
+ // We need to construct a trust store for testing
+ X509Certificate caCert = chainWithValidEndCertificate[2];
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry("CA", caCert);
+
+ String kmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory origTmFactory =
TrustManagerFactory.getInstance(kmfAlgorithm);
+ origTmFactory.init(trustStore);
+ TrustManager[] origTrustManagers = origTmFactory.getTrustManagers();
+ // Create wrapped trust manager factory
+ CommonNameLoggingTrustManagerFactoryWrapper testTmFactory =
CommonNameLoggingTrustManagerFactoryWrapper.getInstance(kmfAlgorithm);
+ testTmFactory.init(trustStore);
+ TrustManager[] wrappendTrustManagers =
testTmFactory.getTrustManagers();
+ // Number of trust managers must be equal (usually "1")
+ assertEquals(origTrustManagers.length, wrappendTrustManagers.length);
+ // Algorithms must be equal
+ assertEquals(origTmFactory.getAlgorithm(),
testTmFactory.getAlgorithm());
+ // Compare trust managers. Only for X509 there must be a difference
+ for (int i = 0; i < origTrustManagers.length; i++) {
+ TrustManager origTrustManager = origTrustManagers[i];
+ TrustManager testTrustManager = wrappendTrustManagers[i];
+ if (origTrustManager instanceof X509TrustManager) {
+ assertInstanceOf(CommonNameLoggingTrustManager.class,
testTrustManager);
+ CommonNameLoggingTrustManager commonNameLoggingTrustManager =
(CommonNameLoggingTrustManager) testTrustManager;
+ // Two different instances of X509TrustManager wouldn't be
considered equal. Thus we at least check that their classes are equal
+ assertEquals(origTrustManager.getClass(),
commonNameLoggingTrustManager.getOriginalTrustManager().getClass());
+ } else {
+ // Two different instances of X509TrustManager wouldn't be
considered equal. Thus we at least check that their classes are equal
+ assertEquals(origTrustManager.getClass(),
testTrustManager.getClass());
+ }
+ }
+ }
+
+ @Test
+ public void testCommonNameLoggingTrustManagerValidChain() throws Exception
{
+
+ X509Certificate endCert = chainWithValidEndCertificate[0];
+ X509Certificate intermediateCert = chainWithValidEndCertificate[1];
+ X509Certificate caCert = chainWithValidEndCertificate[2];
+ X509Certificate[] chainWithoutCa = new X509Certificate[] {endCert,
intermediateCert};
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry("CA", caCert);
+
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(trustStore);
+ final X509TrustManager origTrustManager = getX509TrustManager(tmf);
+
+ try (final LogCaptureAppender appender =
LogCaptureAppender.createAndRegister(CommonNameLoggingSslEngineFactory.class)) {
+ int nrOfInitialMessagges = appender.getMessages().size();
+ CommonNameLoggingTrustManager testTrustManager = new
CommonNameLoggingTrustManager(origTrustManager, 2);
+ // Check client certificate first
+ assertEquals(testTrustManager.getOriginalTrustManager(),
origTrustManager);
+ assertDoesNotThrow(() ->
origTrustManager.checkClientTrusted(chainWithoutCa, "RSA"));
+ assertDoesNotThrow(() ->
testTrustManager.checkClientTrusted(chainWithoutCa, "RSA"));
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+ // Check the same client certificate again. Expect the exact same
behavior as before
+ assertEquals(testTrustManager.getOriginalTrustManager(),
origTrustManager);
+ assertDoesNotThrow(() ->
origTrustManager.checkClientTrusted(chainWithoutCa, "RSA"));
+ assertDoesNotThrow(() ->
testTrustManager.checkClientTrusted(chainWithoutCa, "RSA"));
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+ // Check server certificate (no changes here)
+ assertDoesNotThrow(() ->
origTrustManager.checkServerTrusted(chainWithoutCa, "RSA"));
+ assertDoesNotThrow(() ->
testTrustManager.checkServerTrusted(chainWithoutCa, "RSA"));
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+ assertArrayEquals(origTrustManager.getAcceptedIssuers(),
testTrustManager.getAcceptedIssuers());
+ }
+ }
+
+ @Test
+ public void testCommonNameLoggingTrustManagerValidChainWithCA() throws
Exception {
+
+ X509Certificate endCert = chainWithValidEndCertificate[0];
+ X509Certificate intermediateCert = chainWithValidEndCertificate[1];
+ X509Certificate caCert = chainWithValidEndCertificate[2];
+ X509Certificate[] chainWitCa = new X509Certificate[] {endCert,
intermediateCert, caCert};
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry("CA", caCert);
+
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(trustStore);
+ final X509TrustManager origTrustManager = getX509TrustManager(tmf);
+
+ try (final LogCaptureAppender appender =
LogCaptureAppender.createAndRegister(CommonNameLoggingSslEngineFactory.class)) {
+ int nrOfInitialMessagges = appender.getMessages().size();
+ CommonNameLoggingTrustManager testTrustManager = new
CommonNameLoggingTrustManager(origTrustManager, 2);
+ assertEquals(testTrustManager.getOriginalTrustManager(),
origTrustManager);
+ assertDoesNotThrow(() ->
origTrustManager.checkClientTrusted(chainWitCa, "RSA"));
+ assertDoesNotThrow(() ->
testTrustManager.checkClientTrusted(chainWitCa, "RSA"));
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+ assertDoesNotThrow(() ->
origTrustManager.checkServerTrusted(chainWitCa, "RSA"));
+ assertDoesNotThrow(() ->
testTrustManager.checkServerTrusted(chainWitCa, "RSA"));
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+ assertArrayEquals(origTrustManager.getAcceptedIssuers(),
testTrustManager.getAcceptedIssuers());
+ }
+ }
+
+
+ @Test
+ public void testCommonNameLoggingTrustManagerWithInvalidEndCert() throws
Exception {
+ X509Certificate endCert = chainWithInvalidEndCertificate[0];
+ X509Certificate intermediateCert = chainWithInvalidEndCertificate[1];
+ X509Certificate caCert = chainWithInvalidEndCertificate[2];
+ X509Certificate[] chainWithoutCa = new X509Certificate[] {endCert,
intermediateCert};
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry("CA", caCert);
+
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(trustStore);
+ final X509TrustManager origTrustManager = getX509TrustManager(tmf);
+
+ try (final LogCaptureAppender appender =
LogCaptureAppender.createAndRegister(CommonNameLoggingSslEngineFactory.class)) {
+ int nrOfInitialMessagges = appender.getMessages().size();
+ CommonNameLoggingTrustManager testTrustManager = new
CommonNameLoggingTrustManager(origTrustManager, 2);
+ // Check client certificate
+ assertEquals(testTrustManager.getOriginalTrustManager(),
origTrustManager);
+ Exception origException = assertThrows(CertificateException.class,
+ () -> origTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ Exception testException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+ // Check the client certificate again, expecting the exact same
result
+ assertEquals(testTrustManager.getOriginalTrustManager(),
origTrustManager);
+ origException = assertThrows(CertificateException.class,
+ () -> origTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ testException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+
+ // Check server certificate
+ origException = assertThrows(CertificateException.class,
+ () -> origTrustManager.checkServerTrusted(chainWithoutCa,
"RSA"));
+ testException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkServerTrusted(chainWithoutCa,
"RSA"));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ assertEquals(nrOfInitialMessagges, appender.getMessages().size());
+ assertArrayEquals(origTrustManager.getAcceptedIssuers(),
testTrustManager.getAcceptedIssuers());
+ }
+ }
+
+ @Test
+ public void testCommonNameLoggingTrustManagerWithExpiredEndCert() throws
Exception {
+ X509Certificate endCert = chainWithExpiredEndCertificate[0];
+ X509Certificate intermediateCert = chainWithExpiredEndCertificate[1];
+ X509Certificate caCert = chainWithExpiredEndCertificate[2];
+ X509Certificate[] chainWithoutCa = new X509Certificate[] {endCert,
intermediateCert};
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry("CA", caCert);
+
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(trustStore);
+ final X509TrustManager origTrustManager = getX509TrustManager(tmf);
+
+ try (final LogCaptureAppender appender =
LogCaptureAppender.createAndRegister(CommonNameLoggingTrustManagerFactoryWrapper.class))
{
+ int nrOfInitialMessagges = appender.getMessages().size();
+
+ CommonNameLoggingTrustManager testTrustManager = new
CommonNameLoggingTrustManager(origTrustManager, 2);
+ assertEquals(origTrustManager,
testTrustManager.getOriginalTrustManager());
+ // Call original method, then method of wrapped trust manager and
compare result
+ Exception origException = assertThrows(CertificateException.class,
+ () -> origTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ Exception testException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ // Check that there is exactly one new message
+ List<String> logMessages = appender.getMessages();
+ assertEquals(nrOfInitialMessagges + 1, logMessages.size());
+ assertEquals("Certificate with common name \"" +
endCert.getSubjectX500Principal() +
+ "\" expired on " + endCert.getNotAfter(),
logMessages.get(logMessages.size() - 1));
+ // Call original method, then method of wrapped trust manager and
compare result
+ origException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkServerTrusted(chainWithoutCa,
"RSA"));
+ testException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkServerTrusted(chainWithoutCa,
"RSA"));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ // Check that there are no new messages
+ assertEquals(nrOfInitialMessagges + 1,
appender.getMessages().size());
+ assertArrayEquals(origTrustManager.getAcceptedIssuers(),
testTrustManager.getAcceptedIssuers());
+ }
+ }
+
+ @Test
+ public void testCommonNameLoggingTrustManagerWithExpiredEndCertWithCA()
throws Exception {
+ X509Certificate endCert = chainWithExpiredEndCertificate[0];
+ X509Certificate intermediateCert = chainWithExpiredEndCertificate[1];
+ X509Certificate caCert = chainWithExpiredEndCertificate[2];
+ X509Certificate[] chainWithoutCa = new X509Certificate[] {endCert,
intermediateCert, caCert};
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry("CA", caCert);
+
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(trustStore);
+ final X509TrustManager origTrustManager = getX509TrustManager(tmf);
+
+ try (final LogCaptureAppender appender =
LogCaptureAppender.createAndRegister(CommonNameLoggingTrustManagerFactoryWrapper.class))
{
+ int nrOfInitialMessagges = appender.getMessages().size();
+
+ CommonNameLoggingTrustManager testTrustManager = new
CommonNameLoggingTrustManager(origTrustManager, 2);
+ assertEquals(origTrustManager,
testTrustManager.getOriginalTrustManager());
+ // Call original method, then method of wrapped trust manager and
compare result
+ Exception origException = assertThrows(CertificateException.class,
+ () -> origTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ Exception testException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkClientTrusted(chainWithoutCa,
"RSA"));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ // Check that there is exactly one new message
+ List<String> logMessages = appender.getMessages();
+ assertEquals(nrOfInitialMessagges + 1, logMessages.size());
+ assertEquals("Certificate with common name \"" +
endCert.getSubjectX500Principal() +
+ "\" expired on " + endCert.getNotAfter(),
logMessages.get(logMessages.size() - 1));
+ // Note: As there are multiple SSLContext created within Kafka,
the message may be logged multiple times
+
+ // Check validation of server certificates, then method of wrapped
trust manager and compare result
+ origException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkServerTrusted(chainWithoutCa,
"RSA"));
+ testException = assertThrows(CertificateException.class,
+ () -> testTrustManager.checkServerTrusted(chainWithoutCa,
"RSA"));
+ assertEquals(origException.getMessage(),
testException.getMessage());
+ // Check that there are no new messages
+ assertEquals(nrOfInitialMessagges + 1,
appender.getMessages().size());
+ assertArrayEquals(origTrustManager.getAcceptedIssuers(),
testTrustManager.getAcceptedIssuers());
+ }
+ }
+
+ @Test
+ public void
testCommonNameLoggingTrustManagerMixValidAndInvalidCertificates() throws
Exception {
+ // Setup certificate chain with expired end certificate
+ X509Certificate endCertValid =
chainWithValidAndInvalidEndCertificates[0];
+ X509Certificate endCertInvalid =
chainWithValidAndInvalidEndCertificates[1];
+ X509Certificate intermediateCert =
chainWithValidAndInvalidEndCertificates[2];
+ X509Certificate caCert = chainWithValidAndInvalidEndCertificates[3];
+ X509Certificate[] validChainWithoutCa = new X509Certificate[]
{endCertValid, intermediateCert};
+ X509Certificate[] invalidChainWithoutCa = new X509Certificate[]
{endCertInvalid, intermediateCert};
+ // Setup certificate chain with valid end certificate
+
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry("CA", caCert);
+
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(trustStore);
+ final X509TrustManager origTrustManager = getX509TrustManager(tmf);
+
+ try (final LogCaptureAppender appender =
LogCaptureAppender.createAndRegister(CommonNameLoggingSslEngineFactory.class)) {
+
+ CommonNameLoggingTrustManager testTrustManager = new
CommonNameLoggingTrustManager(origTrustManager, 2);
+
+ // Call with valid certificate
+ //assertDoesNotThrow(() ->
testTrustManager.checkClientTrusted(validChainWithoutCa, "RSA"));
+ // Call with invalid certificate
+ assertThrows(CertificateException.class,
+ () ->
testTrustManager.checkClientTrusted(invalidChainWithoutCa, "RSA"));
+ // Call with valid certificate again
+ //assertDoesNotThrow(() ->
testTrustManager.checkClientTrusted(validChainWithoutCa, "RSA"));
+ // Call with invalid certificate
+ assertThrows(CertificateException.class,
+ () ->
testTrustManager.checkClientTrusted(invalidChainWithoutCa, "RSA"));
+ // Call with valid certificate again
+ //assertDoesNotThrow(() ->
testTrustManager.checkClientTrusted(validChainWithoutCa, "RSA"));
+ }
+ }
+
+ @Test
+ public void testSortChainAnWrapEndCertificate() {
+ // Calling method with null or empty chain is expected to throw
+ assertThrows(CertificateException.class,
+ () ->
CommonNameLoggingTrustManager.sortChainAnWrapEndCertificate(null));
+ assertThrows(CertificateException.class,
+ () ->
CommonNameLoggingTrustManager.sortChainAnWrapEndCertificate(new
X509Certificate[0]));
+
+ X509Certificate endCert = chainWithExpiredEndCertificate[0];
+ X509Certificate intermediateCert = chainWithExpiredEndCertificate[1];
+ X509Certificate caCert = chainWithExpiredEndCertificate[2];
+
+ // Check that a chain with just one certificate works
+ X509Certificate[] chainWithEndCert = new X509Certificate[] {endCert};
+ X509Certificate[] sortedChain = assertDoesNotThrow(
+ () ->
CommonNameLoggingTrustManager.sortChainAnWrapEndCertificate(chainWithEndCert));
+ assertEquals(endCert.getSubjectX500Principal(),
sortedChain[0].getSubjectX500Principal());
+ // Check that the order is unchanged for an already sorted certificate
chain
+ // (starting with end certificate)
+ X509Certificate[] chainWithoutCaInOrder = new X509Certificate[]
{endCert, intermediateCert};
+ sortedChain = assertDoesNotThrow(
+ () ->
CommonNameLoggingTrustManager.sortChainAnWrapEndCertificate(chainWithoutCaInOrder));
+ assertEquals(endCert.getSubjectX500Principal(),
sortedChain[0].getSubjectX500Principal());
+ assertEquals(intermediateCert.getSubjectX500Principal(),
sortedChain[1].getSubjectX500Principal());
+ // Check that the order is changed for an unsorted certificate chain
such that
+ // it starts with end certificate
+ X509Certificate[] chainWithoutCaOutOfOrder = new X509Certificate[]
{intermediateCert, endCert};
+ sortedChain = assertDoesNotThrow(
+ () ->
CommonNameLoggingTrustManager.sortChainAnWrapEndCertificate(chainWithoutCaOutOfOrder));
+ assertEquals(endCert.getSubjectX500Principal(),
sortedChain[0].getSubjectX500Principal());
+ assertEquals(intermediateCert.getSubjectX500Principal(),
sortedChain[1].getSubjectX500Principal());
+
+ X509Certificate[] chainWithCaOutOfOrder = new X509Certificate[]
{caCert, intermediateCert, endCert};
+ sortedChain = assertDoesNotThrow(
+ () ->
CommonNameLoggingTrustManager.sortChainAnWrapEndCertificate(chainWithCaOutOfOrder));
+ assertEquals(endCert.getSubjectX500Principal(),
sortedChain[0].getSubjectX500Principal());
+ assertEquals(intermediateCert.getSubjectX500Principal(),
sortedChain[1].getSubjectX500Principal());
+ assertEquals(caCert.getSubjectX500Principal(),
sortedChain[2].getSubjectX500Principal());
+ }
+
+ @Test
+ public void testSortChainWithMultipleEndCertificate() {
+ assertThrows(CertificateException.class,
+ () ->
CommonNameLoggingTrustManager.sortChainAnWrapEndCertificate(chainWithMultipleEndCertificates));
+ }
+
+ @Test
+ public void testCalcDigestForCertificateChain() {
+ ByteBuffer digestForValidChain =
+ assertDoesNotThrow(() ->
CommonNameLoggingTrustManager.calcDigestForCertificateChain(chainWithValidEndCertificate));
+ ByteBuffer digestForValidChainAgain =
+ assertDoesNotThrow(() ->
CommonNameLoggingTrustManager.calcDigestForCertificateChain(chainWithValidEndCertificate));
+ assertEquals(digestForValidChain, digestForValidChainAgain);
+ ByteBuffer digestForInvalidChain =
+ assertDoesNotThrow(() ->
CommonNameLoggingTrustManager.calcDigestForCertificateChain(chainWithInvalidEndCertificate));
+ assertNotEquals(digestForValidChain, digestForInvalidChain);
+ ByteBuffer digestForExpiredChain =
+ assertDoesNotThrow(() ->
CommonNameLoggingTrustManager.calcDigestForCertificateChain(chainWithExpiredEndCertificate));
+ assertNotEquals(digestForValidChain, digestForExpiredChain);
+ assertNotEquals(digestForInvalidChain, digestForExpiredChain);
+ }
+
+ /**
+ * This helper method generates a valid key chain with one end entity
+ * (client/server cert), one intermediate certificate authority and one
+ * root certificate authority (self-signed)
+ *
+ * @return
+ * @throws CertificateException
+ * @throws NoSuchAlgorithmException
+ */
+ private X509Certificate[] generateKeyChainIncludingCA(boolean expired,
boolean multipleEndCert, boolean endCert0Valid, boolean endCert1Valid)
+ throws CertificateException, NoSuchAlgorithmException {
+ // For testing, we might create another end certificate
+ int nrOfCerts = multipleEndCert ? 4 : 3;
+ KeyPair[] keyPairs = new KeyPair[nrOfCerts];
+ for (int i = 0; i < nrOfCerts; i++) {
+ keyPairs[i] = TestSslUtils.generateKeyPair("RSA");
+ }
+ X509Certificate[] certs = new X509Certificate[nrOfCerts];
+ int endCertDaysValidBeforeNow = 1;
+ // If using 0 or a negative value, the generated certificate will be
expired
+ int endCertDaysValidAfterNow = expired ? 0 : 1;
+ // Generate root CA
+ int caIndex = nrOfCerts - 1;
+ certs[caIndex] = TestSslUtils.generateSignedCertificate("CN=CA",
keyPairs[caIndex], 365,
+ 365, null, null, "SHA512withRSA", true, false, false);
+ int intermediateCertIndex = caIndex - 1;
+ certs[intermediateCertIndex] =
TestSslUtils.generateSignedCertificate("CN=Intermediate CA",
+ keyPairs[intermediateCertIndex], 365, 365,
certs[caIndex].getSubjectX500Principal().getName(), keyPairs[caIndex],
+ "SHA512withRSA", true, false, false);
+ for (int currIndex = intermediateCertIndex - 1; currIndex >= 0;
currIndex--) {
+ // When generating multiple end certificates,
+ boolean endCertValid = (currIndex == 0) ? endCert0Valid :
endCert1Valid;
+ if (endCertValid) {
+ // Generate a valid end certificate, i.e. one that is signed
by our intermediate
+ // CA
+ certs[currIndex] =
TestSslUtils.generateSignedCertificate("CN=kafka", keyPairs[currIndex],
+ endCertDaysValidBeforeNow, endCertDaysValidAfterNow,
+
certs[intermediateCertIndex].getSubjectX500Principal().getName(),
keyPairs[intermediateCertIndex], "SHA512withRSA", false, true, true);
+ } else {
+ // Generate an invalid end certificate, by creating a
self-signed one.
+ certs[currIndex] =
TestSslUtils.generateSignedCertificate("C=GB, L=London, CN=kafka",
keyPairs[currIndex],
+ endCertDaysValidBeforeNow, endCertDaysValidAfterNow,
+ null, null, "SHA512withRSA", false, true, true);
+ }
+ }
+ return certs;
+ }
+}
+
diff --git
a/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java
b/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java
index fc3726ac59a..dfc3291ab62 100644
---
a/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java
+++
b/clients/src/test/java/org/apache/kafka/common/security/ssl/DefaultSslEngineFactoryTest.java
@@ -194,15 +194,19 @@ public class DefaultSslEngineFactoryTest {
private static final Password KEY_PASSWORD = new Password("key-password");
- private DefaultSslEngineFactory factory = new DefaultSslEngineFactory();
+ private DefaultSslEngineFactory factory = sslEngineFactory();
Map<String, Object> configs = new HashMap<>();
@BeforeEach
public void setUp() {
- factory = new DefaultSslEngineFactory();
+ factory = sslEngineFactory();
configs.put(SslConfigs.SSL_PROTOCOL_CONFIG, "TLSv1.2");
}
+ protected DefaultSslEngineFactory sslEngineFactory() {
+ return new DefaultSslEngineFactory();
+ }
+
@Test
public void testPemTrustStoreConfigWithOneCert() throws Exception {
configs.put(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG,
pemAsConfigValue(CA1));
diff --git a/clients/src/test/java/org/apache/kafka/test/TestSslUtils.java
b/clients/src/test/java/org/apache/kafka/test/TestSslUtils.java
index 675e6563f87..6b7c16b0335 100644
--- a/clients/src/test/java/org/apache/kafka/test/TestSslUtils.java
+++ b/clients/src/test/java/org/apache/kafka/test/TestSslUtils.java
@@ -21,6 +21,7 @@ import org.apache.kafka.common.config.types.Password;
import org.apache.kafka.common.network.Mode;
import org.apache.kafka.common.security.auth.SslEngineFactory;
import org.apache.kafka.common.security.ssl.DefaultSslEngineFactory;
+import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERT61String;
@@ -30,9 +31,11 @@ import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
@@ -106,7 +109,7 @@ public class TestSslUtils {
*
* @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
* @param pair the KeyPair
- * @param days how many days from now the Certificate is valid for
+ * @param days how many days from now the Certificate is valid for, or -
for negative values - how many days before now
* @param algorithm the signing algorithm, eg "SHA1withRSA"
* @return the self-signed certificate
* @throws CertificateException thrown if a security error or an IO error
occurred.
@@ -117,6 +120,28 @@ public class TestSslUtils {
return new CertificateBuilder(days, algorithm).generate(dn, pair);
}
+ /**
+ * Generate a signed certificate. Self-signed, if no issuer and
parentKeyPair are supplied
+ *
+ * @param dn The distinguished name of this certificate
+ * @param keyPair A key pair
+ * @param daysBeforeNow how many days before now the Certificate is valid
for
+ * @param daysAfterNow how many days from now the Certificate is valid for
+ * @param issuer The issuer who signs the certificate. Leave null if you
want to generate a root
+ * CA.
+ * @param parentKeyPair The key pair of the issuer. Leave null if you want
to generate a root
+ * CA.
+ * @param algorithm the signing algorithm, eg "SHA1withRSA"
+ * @return the signed certificate
+ * @throws CertificateException
+ */
+ public static X509Certificate generateSignedCertificate(String dn, KeyPair
keyPair,
+ int daysBeforeNow, int daysAfterNow, String issuer, KeyPair
parentKeyPair,
+ String algorithm, boolean isCA, boolean isServerCert, boolean
isClientCert) throws CertificateException {
+ return new CertificateBuilder(0,
algorithm).generateSignedCertificate(dn, keyPair,
+ daysBeforeNow, daysAfterNow, issuer, parentKeyPair, isCA,
isServerCert, isClientCert);
+ }
+
public static KeyPair generateKeyPair(String algorithm) throws
NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
keyGen.initialize(algorithm.equals("EC") ? 256 : 2048);
@@ -415,8 +440,10 @@ public class TestSslUtils {
else
throw new IllegalArgumentException("Unsupported algorithm
" + keyAlgorithm);
ContentSigner sigGen =
signerBuilder.build(privateKeyAsymKeyParam);
- Date from = new Date();
- Date to = new Date(from.getTime() + days * 86400000L);
+ // Negative numbers for "days" can be used to generate expired
certificates
+ Date now = new Date();
+ Date from = (days >= 0) ? now : new Date(now.getTime() + days
* 86400000L);
+ Date to = (days >= 0) ? new Date(now.getTime() + days *
86400000L) : now;
BigInteger sn = new BigInteger(64, new SecureRandom());
X509v3CertificateBuilder v3CertGen = new
X509v3CertificateBuilder(dn, sn, from, to, dn, subPubKeyInfo);
@@ -430,6 +457,98 @@ public class TestSslUtils {
throw new CertificateException(e);
}
}
+
+ /**
+ * @param dn The distinguished name to use
+ * @param keyPair A key pair to use
+ * @param daysBeforeNow how many days before now the Certificate is
valid for
+ * @param daysAfterNow how many days from now the Certificate is valid
for
+ * @param issuer The issuer name. if null, "dn" is used
+ * @param parentKeyPair The parent key pair used to sign this
certificate. If null, create
+ * self-signed certificate authority (CA)
+ * @return A (self-) signed certificate
+ * @throws CertificateException
+ */
+ public X509Certificate generateSignedCertificate(String dn, KeyPair
keyPair,
+ int daysBeforeNow, int daysAfterNow, String issuer, KeyPair
parentKeyPair, boolean isCA, boolean isServerCert, boolean isClientCert)
+ throws CertificateException {
+ X500Name issuerOrDn = (issuer != null) ? new X500Name(issuer) :
new X500Name(dn);
+ return generateSignedCertificate(new X500Name(dn), keyPair,
daysBeforeNow, daysAfterNow,
+ issuerOrDn, parentKeyPair, isCA, isServerCert,
isClientCert);
+ }
+
+ /**
+ *
+ * @param dn The distinguished name to use
+ * @param keyPair A key pair to use
+ * @param daysBeforeNow how many days before now the Certificate is
valid for
+ * @param daysAfterNow how many days from now the Certificate is valid
for
+ * @param issuer The issuer name. if null, "dn" is used
+ * @param parentKeyPair The parent key pair used to sign this
certificate. If null, create
+ * self-signed certificate authority (CA)
+ * @return A (self-) signed certificate
+ * @throws CertificateException
+ */
+ public X509Certificate generateSignedCertificate(X500Name dn, KeyPair
keyPair,
+ int daysBeforeNow, int daysAfterNow, X500Name issuer, KeyPair
parentKeyPair, boolean isCA, boolean isServerCert, boolean isClientCert)
+ throws CertificateException {
+ try {
+ Security.addProvider(new BouncyCastleProvider());
+ AlgorithmIdentifier sigAlgId =
+ new
DefaultSignatureAlgorithmIdentifierFinder().find(algorithm);
+ AlgorithmIdentifier digAlgId =
+ new
DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
+ // Create self-signed certificate if no parentKeyPair has been
specified, otherwise
+ // sign with private key of parentKeyPair
+ KeyPair signingKeyPair = (parentKeyPair != null) ?
parentKeyPair : keyPair;
+ AsymmetricKeyParameter privateKeyAsymKeyParam =
+
PrivateKeyFactory.createKey(signingKeyPair.getPrivate().getEncoded());
+ SubjectPublicKeyInfo subPubKeyInfo =
+
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
+ BcContentSignerBuilder signerBuilder;
+ String keyAlgorithm = keyPair.getPublic().getAlgorithm();
+ if (keyAlgorithm.equals("RSA"))
+ signerBuilder = new BcRSAContentSignerBuilder(sigAlgId,
digAlgId);
+ else if (keyAlgorithm.equals("DSA"))
+ signerBuilder = new BcDSAContentSignerBuilder(sigAlgId,
digAlgId);
+ else if (keyAlgorithm.equals("EC"))
+ signerBuilder = new BcECContentSignerBuilder(sigAlgId,
digAlgId);
+ else
+ throw new IllegalArgumentException("Unsupported algorithm
" + keyAlgorithm);
+ ContentSigner sigGen =
signerBuilder.build(privateKeyAsymKeyParam);
+ // Negative numbers for "days" can be used to generate expired
certificates
+ Date now = new Date();
+ Date from = new Date(now.getTime() - daysBeforeNow *
86400000L);
+ Date to = new Date(now.getTime() + daysAfterNow * 86400000L);
+ BigInteger sn = new BigInteger(64, new SecureRandom());
+ X500Name issuerOrDn = (issuer != null) ? issuer : dn;
+ X509v3CertificateBuilder v3CertGen =
+ new X509v3CertificateBuilder(issuerOrDn, sn, from, to,
dn, subPubKeyInfo);
+ if (isCA) {
+ v3CertGen.addExtension(Extension.basicConstraints, true,
new BasicConstraints(isCA));
+ }
+ if (isServerCert || isClientCert) {
+ ASN1EncodableVector purposes = new ASN1EncodableVector();
+ if (isServerCert) {
+ purposes.add(KeyPurposeId.id_kp_serverAuth);
+ }
+ if (isClientCert) {
+ purposes.add(KeyPurposeId.id_kp_clientAuth);
+ }
+ v3CertGen.addExtension(Extension.extendedKeyUsage, false,
new DERSequence(purposes));
+ }
+ if (subjectAltName != null) {
+ v3CertGen.addExtension(Extension.subjectAlternativeName,
false, subjectAltName);
+ }
+ X509CertificateHolder certificateHolder =
v3CertGen.build(sigGen);
+ return new JcaX509CertificateConverter().setProvider("BC")
+ .getCertificate(certificateHolder);
+ } catch (CertificateException ce) {
+ throw ce;
+ } catch (Exception e) {
+ throw new CertificateException(e);
+ }
+ }
}
public static class SslConfigsBuilder {
diff --git a/clients/src/test/resources/log4j.properties
b/clients/src/test/resources/log4j.properties
index b1d5b7f2b40..0992580eca1 100644
--- a/clients/src/test/resources/log4j.properties
+++ b/clients/src/test/resources/log4j.properties
@@ -19,3 +19,5 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c:%L)%n
log4j.logger.org.apache.kafka=ERROR
+# We are testing for a particular INFO log message in
CommonNameLoggingTrustManagerFactoryWrapper
+log4j.logger.org.apache.kafka.common.security.ssl.CommonNameLoggingTrustManagerFactoryWrapper=INFO