From 8d8afffc855657c41ae01d4372df5d09b9fe8f09 Mon Sep 17 00:00:00 2001 From: Matthew Vivian Date: Tue, 19 Nov 2024 10:27:47 +0000 Subject: [PATCH 1/6] feat: Add option to enable certificate revocation checks When enabled, certificates will be verified against Certificate Revocation Lists (CRL) and through Online Certificate Status Protocol (OCSP) to ensure they have not been revoked. --- .../main/resources/openfire_i18n.properties | 1 + .../keystore/OpenfireX509TrustManager.java | 16 ++++--- .../openfire/spi/ConnectionConfiguration.java | 18 ++++++- .../openfire/spi/ConnectionListener.java | 47 +++++++++++++++++++ .../spi/EncryptionArtifactFactory.java | 10 ++-- .../webapp/connection-settings-advanced.jsp | 7 +++ .../keystore/CheckChainTrustedTest.java | 20 +++++--- .../OpenfireX509TrustManagerTest.java | 2 +- .../LocalOutgoingServerSessionTest.java | 2 +- 9 files changed, 101 insertions(+), 22 deletions(-) diff --git a/i18n/src/main/resources/openfire_i18n.properties b/i18n/src/main/resources/openfire_i18n.properties index 7017cf18ff..19834d609c 100644 --- a/i18n/src/main/resources/openfire_i18n.properties +++ b/i18n/src/main/resources/openfire_i18n.properties @@ -1639,6 +1639,7 @@ connection.advanced.settings.certchain.boxtitle=Certificate chain checking connection.advanced.settings.certchain.info=These options configure some aspects of the verification/validation of the certificates that are presented by peers while setting up encrypted connections. connection.advanced.settings.certchain.label_selfsigned=Allow peer certificates to be self-signed. connection.advanced.settings.certchain.label_validity=Verify that the certificate is currently valid (based on the 'notBefore' and 'notAfter' values of the certificate). +connection.advanced.settings.certchain.label_revocation=Verify that certificates have not been revoked (by checking Certificate Revocation Lists and OCSP) connection.advanced.settings.protocols.boxtitle=Encryption Protocols connection.advanced.settings.protocols.info=These are all encryption protocols that this instance of Openfire supports. Those with a checked box are enabled, and can be used to establish an encrypted connection. Deselecting all values will cause a default to be restored. connection.advanced.settings.protocols.sslv2hello.info=When setting up a new encrypted connection some encryption protocols allow you to have part of the handshake (the 'hello') encapsulated in an SSLv2 format. The SSLv2Hello option below controls this encapsulation. When enabled, incoming data may use the SSLv2 handshake format (but SSLv2 itself will never be allowed). When disabled, all incoming data must conform to the SSLv3/TLSv1 handshake format. All outgoing data (which applies to outbound server-to-server connections) will always conform to the SSLv3/TLSv1 format irrespective of this setting. diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManager.java b/xmppserver/src/main/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManager.java index c5eacdfa59..cdf65a41f6 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManager.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManager.java @@ -30,8 +30,6 @@ * * @author Guus der Kinderen, guus.der.kinderen@gmail.com */ -// TODO re-enable optional OCSP checking. -// TODO re-enable CRL checking. public class OpenfireX509TrustManager implements X509TrustManager { private static final Logger Log = LoggerFactory.getLogger( OpenfireX509TrustManager.class ); @@ -55,16 +53,22 @@ public class OpenfireX509TrustManager implements X509TrustManager */ private final boolean checkValidity; + /** + * A boolean that indicates if this trust manager will check revocation status of certificates. + */ + private final boolean checkRevocation; + /** * The set of trusted issuers from the trust store. Note that these certificates are not validated. It is assumed * that this set can be long-lived. Time-based validation should occur close to the actual usage / invocation. */ protected final Set trustedIssuers; - public OpenfireX509TrustManager( KeyStore trustStore, boolean acceptSelfSigned, boolean checkValidity ) throws NoSuchAlgorithmException, KeyStoreException + public OpenfireX509TrustManager( KeyStore trustStore, boolean acceptSelfSigned, boolean checkValidity, boolean checkRevocation ) throws NoSuchAlgorithmException, KeyStoreException { this.acceptSelfSigned = acceptSelfSigned; this.checkValidity = checkValidity; + this.checkRevocation = checkRevocation; // Retrieve all trusted certificates from the store, but don't validate them just yet! final Set trusted = new HashSet<>(); @@ -85,7 +89,7 @@ public OpenfireX509TrustManager( KeyStore trustStore, boolean acceptSelfSigned, trustedIssuers = Collections.unmodifiableSet( trusted ); - Log.debug( "Constructed trust manager. Number of trusted issuers: {}, accepts self-signed: {}, checks validity: {}", trustedIssuers.size(), acceptSelfSigned, checkValidity ); + Log.debug( "Constructed trust manager. Number of trusted issuers: {}, accepts self-signed: {}, checks validity: {}, checks revocation: {}", trustedIssuers.size(), acceptSelfSigned, checkValidity, checkRevocation ); } @Override @@ -253,8 +257,8 @@ protected CertPath checkChainTrusted( CertSelector selector, X509Certificate... // entire chain should now be in the store. parameters.addCertStore( certificates ); - // When true, validation will fail if no CRLs are provided! - parameters.setRevocationEnabled( false ); + // When true, validation will fail if no OCSP staple, OCSP response, or CRLs, are provided + parameters.setRevocationEnabled(checkRevocation); Log.debug( "Validating chain with {} certificates, using {} trust anchors.", chain.length, trustAnchors.size() ); diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java index a5188f70e0..9984b1a69a 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java @@ -43,6 +43,7 @@ public class ConnectionConfiguration private final CertificateStoreConfiguration trustStoreConfiguration; private final boolean acceptSelfSignedCertificates; private final boolean verifyCertificateValidity; + private final boolean verifyCertificateRevocation; private final boolean strictCertificateValidation; private final Set encryptionProtocols; private final Set encryptionCipherSuites; @@ -64,14 +65,15 @@ public class ConnectionConfiguration * @param identityStoreConfiguration the certificates the server identify as * @param trustStoreConfiguration the certificates the server trusts * @param acceptSelfSignedCertificates {@code true} to accept self-signed certificates, otherwise {@code false} - * @param verifyCertificateValidity {@code true} to accept self-signed certificates, otherwise {@code false} + * @param verifyCertificateValidity {@code true} to verify validity of certificates (based on their 'notBefore' and 'notAfter' property values), otherwise {@code false} + * @param verifyCertificateRevocation {@code true} to check certificate revocation status, otherwise {@code false} * @param encryptionProtocols the set of protocols supported * @param encryptionCipherSuites the set of ciphers supported * @param compressionPolicy the compression policy * @param strictCertificateValidation {@code true} to abort connections if certificate validation fails, otherwise {@code false} */ // TODO input validation - public ConnectionConfiguration( ConnectionType type, boolean enabled, int maxThreadPoolSize, int maxBufferSize, Connection.ClientAuth clientAuth, InetAddress bindAddress, int port, Connection.TLSPolicy tlsPolicy, CertificateStoreConfiguration identityStoreConfiguration, CertificateStoreConfiguration trustStoreConfiguration, boolean acceptSelfSignedCertificates, boolean verifyCertificateValidity, Set encryptionProtocols, Set encryptionCipherSuites, Connection.CompressionPolicy compressionPolicy, boolean strictCertificateValidation ) + public ConnectionConfiguration( ConnectionType type, boolean enabled, int maxThreadPoolSize, int maxBufferSize, Connection.ClientAuth clientAuth, InetAddress bindAddress, int port, Connection.TLSPolicy tlsPolicy, CertificateStoreConfiguration identityStoreConfiguration, CertificateStoreConfiguration trustStoreConfiguration, boolean acceptSelfSignedCertificates, boolean verifyCertificateValidity, boolean verifyCertificateRevocation, Set encryptionProtocols, Set encryptionCipherSuites, Connection.CompressionPolicy compressionPolicy, boolean strictCertificateValidation ) { if ( maxThreadPoolSize <= 0 ) { throw new IllegalArgumentException( "Argument 'maxThreadPoolSize' must be equal to or greater than one." ); @@ -92,6 +94,7 @@ public ConnectionConfiguration( ConnectionType type, boolean enabled, int maxThr this.trustStoreConfiguration = trustStoreConfiguration; this.acceptSelfSignedCertificates = acceptSelfSignedCertificates; this.verifyCertificateValidity = verifyCertificateValidity; + this.verifyCertificateRevocation = verifyCertificateRevocation; this.encryptionProtocols = Collections.unmodifiableSet( encryptionProtocols ); this.encryptionCipherSuites = Collections.unmodifiableSet( encryptionCipherSuites ); this.compressionPolicy = compressionPolicy; @@ -173,6 +176,17 @@ public boolean isVerifyCertificateValidity() return verifyCertificateValidity; } + /** + * A boolean that indicates if the revocation status of certificates is checked when they are used to establish an + * encrypted connection. + * + * @return true when the revocation status of certificates is checked, otherwise false. + */ + public boolean isVerifyCertificateRevocation() + { + return verifyCertificateRevocation; + } + /** * A collection of protocol names that can be used for encryption of connections. * diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java index 0dffd1528e..ada215029a 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java @@ -289,6 +289,7 @@ public ConnectionConfiguration generateConnectionConfiguration() trustStoreConfiguration, acceptSelfSignedCertificates(), verifyCertificateValidity(), + verifyCertificateRevocation(), getEncryptionProtocols(), getEncryptionCipherSuites(), getCompressionPolicy(), @@ -803,6 +804,52 @@ public void setVerifyCertificateValidity( boolean verify ) restart(); } + /** + * Returns whether certificate revocation checking is enabled. + * When enabled, certificates will be verified against Certificate Revocation Lists (CRL) + * and through Online Certificate Status Protocol (OCSP) to ensure they have not been revoked. + * + * @return true if certificate revocation checking is enabled, false otherwise + */ + public boolean verifyCertificateRevocation() + { + final String propertyName = type.getPrefix() + "certificate.verify.revocation"; + final boolean defaultValue = true; + + if ( type.getFallback() == null ) + { + return JiveGlobals.getBooleanProperty( propertyName, defaultValue ); + } + else + { + return JiveGlobals.getBooleanProperty( propertyName, getConnectionListener( type.getFallback() ).verifyCertificateRevocation() ); + } + } + + /** + * Sets whether certificate revocation checking should be enabled. + * When enabled, certificates will be verified against Certificate Revocation Lists (CRL) + * and through Online Certificate Status Protocol (OCSP) to ensure they have not been revoked. + * + * @param verify true to enable certificate revocation checking, false to disable it + */ + public void setVerifyCertificateRevocation( boolean verify ) + { + final boolean oldValue = verifyCertificateRevocation(); + + // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value). + JiveGlobals.setProperty( type.getPrefix() + "certificate.verify.revocation", Boolean.toString( verify ) ); + + if ( oldValue == verify ) + { + Log.debug( "Ignoring certificate revocation verification configuration change request (to '{}'): listener already in this state.", verify ); + return; + } + + Log.debug( "Changing certificate revocation verification configuration from '{}' to '{}'.", oldValue, verify ); + restart(); + } + /** * A collection of protocol names that can be used for encryption of connections. * diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/EncryptionArtifactFactory.java b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/EncryptionArtifactFactory.java index a070de30b1..b9247c43b1 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/EncryptionArtifactFactory.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/EncryptionArtifactFactory.java @@ -152,15 +152,15 @@ public synchronized TrustManager[] getTrustManagers() throws KeyStoreException, try { - Log.debug( "Attempting to instantiate '{}' using the three-argument constructor that is properietary to Openfire.", trustManagerClass ); - final Constructor constructor = trustManagerClass.getConstructor( KeyStore.class, Boolean.TYPE, Boolean.TYPE); - final TrustManager trustManager = constructor.newInstance( configuration.getTrustStore().getStore(), configuration.isAcceptSelfSignedCertificates(), configuration.isVerifyCertificateValidity() ); + Log.debug( "Attempting to instantiate '{}' using the four-argument constructor that is proprietary to Openfire.", trustManagerClass ); + final Constructor constructor = trustManagerClass.getConstructor( KeyStore.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE ); + final TrustManager trustManager = constructor.newInstance( configuration.getTrustStore().getStore(), configuration.isAcceptSelfSignedCertificates(), configuration.isVerifyCertificateValidity(), configuration.isVerifyCertificateRevocation() ); Log.debug( "Successfully instantiated '{}'.", trustManagerClass ); return new TrustManager[] { trustManager }; } catch ( Exception e ) { - Log.debug( "Unable to instantiate '{}' using the three-argument constructor that is properietary to Openfire. Trying to use a no-arg constructor instead...", trustManagerClass ); + Log.debug( "Unable to instantiate '{}' using the four-argument constructor that is proprietary to Openfire. Trying to use a no-arg constructor instead...", trustManagerClass ); try { final TrustManager trustManager = trustManagerClass.newInstance(); @@ -171,7 +171,7 @@ public synchronized TrustManager[] getTrustManagers() throws KeyStoreException, catch ( InstantiationException | IllegalAccessException ex ) { Log.warn( "Unable to instantiate an instance of the configured Trust Manager implementation '{}'. Using {} instead.", trustManagerClass, OpenfireX509TrustManager.class, ex ); - return new TrustManager[] { new OpenfireX509TrustManager( configuration.getTrustStore().getStore(), configuration.isAcceptSelfSignedCertificates(), configuration.isVerifyCertificateValidity() )}; + return new TrustManager[] { new OpenfireX509TrustManager( configuration.getTrustStore().getStore(), configuration.isAcceptSelfSignedCertificates(), configuration.isVerifyCertificateValidity(), configuration.isVerifyCertificateRevocation() )}; } } } diff --git a/xmppserver/src/main/webapp/connection-settings-advanced.jsp b/xmppserver/src/main/webapp/connection-settings-advanced.jsp index add878df30..3b396ea5b3 100644 --- a/xmppserver/src/main/webapp/connection-settings-advanced.jsp +++ b/xmppserver/src/main/webapp/connection-settings-advanced.jsp @@ -105,6 +105,7 @@ final int listenerMaxThreads = ParamUtils.getIntParameter( request, "maxThreads", configuration.getMaxThreadPoolSize() ); final boolean acceptSelfSignedCertificates = ParamUtils.getBooleanParameter( request, "accept-self-signed-certificates" ); final boolean verifyCertificateValidity = ParamUtils.getBooleanParameter( request, "verify-certificate-validity" ); + final boolean verifyCertificateRevocation = ParamUtils.getBooleanParameter( request, "verify-certificate-revocation" ); final boolean strictCertificateValidation = ParamUtils.getBooleanParameter( request, "strict-certificate-validation" ); @@ -122,6 +123,7 @@ listener.setEncryptionCipherSuites( cipherSuites ); listener.setAcceptSelfSignedCertificates( acceptSelfSignedCertificates ); listener.setVerifyCertificateValidity( verifyCertificateValidity ); + listener.setVerifyCertificateRevocation( verifyCertificateRevocation ); listener.setStrictCertificateValidation( strictCertificateValidation ); // Log the event @@ -411,6 +413,11 @@ + + + + + diff --git a/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/CheckChainTrustedTest.java b/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/CheckChainTrustedTest.java index e8f8322436..080a0b4cad 100644 --- a/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/CheckChainTrustedTest.java +++ b/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/CheckChainTrustedTest.java @@ -39,14 +39,14 @@ public class CheckChainTrustedTest * iterable returned here (its values are passed to the constructor of this class). This allows us to execute the * same set of tests against a different configuration of the system under test. */ - @Parameterized.Parameters(name = "acceptSelfSignedCertificates={0},checkValidity={1}" ) + @Parameterized.Parameters(name = "acceptSelfSignedCertificates={0},checkValidity={1},checkRevocation={2}") public static Iterable constructorArguments() { final List constructorArguments = new ArrayList<>(); - constructorArguments.add( new Object[] { false, true } ); // acceptSelfSignedCertificates = false, check validity = true - constructorArguments.add( new Object[] { false, false } ); // acceptSelfSignedCertificates = false, check validity = false - constructorArguments.add( new Object[] { true, true } ); // acceptSelfSignedCertificates = true, check validity = true - constructorArguments.add( new Object[] { true, false } ); // acceptSelfSignedCertificates = true, check validity = false + constructorArguments.add( new Object[] { false, true, false } ); // acceptSelfSignedCertificates = false, check validity = true + constructorArguments.add( new Object[] { false, false, false } ); // acceptSelfSignedCertificates = false, check validity = false + constructorArguments.add( new Object[] { true, true, false } ); // acceptSelfSignedCertificates = true, check validity = true + constructorArguments.add( new Object[] { true, false, false } ); // acceptSelfSignedCertificates = true, check validity = false return constructorArguments; } @@ -60,6 +60,11 @@ public static Iterable constructorArguments() */ private final boolean checkValidity; + /** + * Configuration for the system under test: does or does not check certificate revocation. + */ + private final boolean checkRevocation; + /** * The keystore that contains the certificates used by the system under test (refreshed before every test invocation). */ @@ -93,10 +98,11 @@ public static Iterable constructorArguments() */ private OpenfireX509TrustManager trustManager; - public CheckChainTrustedTest( boolean acceptSelfSigned, boolean checkValidity ) + public CheckChainTrustedTest( boolean acceptSelfSigned, boolean checkValidity, boolean checkRevocation ) { this.acceptSelfSigned = acceptSelfSigned; this.checkValidity = checkValidity; + this.checkRevocation = checkRevocation; } @BeforeClass @@ -124,7 +130,7 @@ public void createFixture() throws Exception trustStore.setCertificateEntry( getLast( expiredRootChain ).getSubjectDN().getName(), getLast( expiredRootChain ) ); // Reset the system under test before each test. - trustManager = new OpenfireX509TrustManager( trustStore, acceptSelfSigned, checkValidity ); + trustManager = new OpenfireX509TrustManager( trustStore, acceptSelfSigned, checkValidity, checkRevocation); } /** diff --git a/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManagerTest.java b/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManagerTest.java index a09fe496be..5e69d5da56 100644 --- a/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManagerTest.java +++ b/xmppserver/src/test/java/org/jivesoftware/openfire/keystore/OpenfireX509TrustManagerTest.java @@ -87,7 +87,7 @@ public void createFixture() throws Exception } // Create the Trust Manager that is subject of these tests. - systemUnderTest = new OpenfireX509TrustManager( trustStore, false, true ); + systemUnderTest = new OpenfireX509TrustManager( trustStore, false, true, false); } /** diff --git a/xmppserver/src/test/java/org/jivesoftware/openfire/session/LocalOutgoingServerSessionTest.java b/xmppserver/src/test/java/org/jivesoftware/openfire/session/LocalOutgoingServerSessionTest.java index 5c4483dbc1..d31df46b39 100644 --- a/xmppserver/src/test/java/org/jivesoftware/openfire/session/LocalOutgoingServerSessionTest.java +++ b/xmppserver/src/test/java/org/jivesoftware/openfire/session/LocalOutgoingServerSessionTest.java @@ -176,7 +176,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable final Connection.TLSPolicy tlsPolicy = Connection.TLSPolicy.valueOf(JiveGlobals.getProperty(ConnectionSettings.Server.TLS_POLICY, Connection.TLSPolicy.optional.toString())); final Set suites = Set.of("TLS_AES_256_GCM_SHA384","TLS_AES_128_GCM_SHA256","TLS_CHACHA20_POLY1305_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_DHE_DSS_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_DSS_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_DHE_DSS_WITH_AES_256_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_DSS_WITH_AES_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_DSS_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_DSS_WITH_AES_128_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"); final Set protocols = Set.of("TLSv1.3", "TLSv1.2"); - return new ConnectionConfiguration(ConnectionType.SOCKET_S2S, true, 10, -1, Connection.ClientAuth.wanted, null, 9999, tlsPolicy, identityStoreConfig, trustStoreConfig, true, true, protocols, suites, Connection.CompressionPolicy.optional, true ); + return new ConnectionConfiguration(ConnectionType.SOCKET_S2S, true, 10, -1, Connection.ClientAuth.wanted, null, 9999, tlsPolicy, identityStoreConfig, trustStoreConfig, true, true, false, protocols, suites, Connection.CompressionPolicy.optional, true ); } } From ca563c93999fd699ca0b6434b85a4bd07fedeb13 Mon Sep 17 00:00:00 2001 From: Matthew Vivian Date: Tue, 19 Nov 2024 10:27:47 +0000 Subject: [PATCH 2/6] fix: Use existing default for revocation checking --- .../java/org/jivesoftware/openfire/spi/ConnectionListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java index ada215029a..4e6c2199df 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionListener.java @@ -814,7 +814,7 @@ public void setVerifyCertificateValidity( boolean verify ) public boolean verifyCertificateRevocation() { final String propertyName = type.getPrefix() + "certificate.verify.revocation"; - final boolean defaultValue = true; + final boolean defaultValue = false; if ( type.getFallback() == null ) { From cd751b1e55afe2a5848f2558f9bbaa78dc0cf934 Mon Sep 17 00:00:00 2001 From: Matthew Vivian Date: Tue, 19 Nov 2024 13:01:36 +0000 Subject: [PATCH 3/6] feat: Configure OCSP - Permit client-driven OCSP (has no effect unless revocation checking is also enabled) by adding property to java.security settings. - Enable OCSP stapling by specifying jdk.tls.server.enableStatusRequestExtension=true Java system property. With this default configuration: - as a client: Openfire will behave in the same way as it did prior to this commit. - as a server: Openfire will staple OCSP responses when presenting its certificate if the certificate is configured with an OCSP responder and Openfire receives a response from the listed responder, otherwise the certificate will be presented with no OCSP response (the default behaviour prior to this commit). For further configuration options see: https://docs.oracle.com/en/java/javase/17/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-527BAE97-3B78-4390-A479-623BD998C4EE --- distribution/src/bin/openfire.sh | 5 +++++ distribution/src/security/java.security | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 distribution/src/security/java.security diff --git a/distribution/src/bin/openfire.sh b/distribution/src/bin/openfire.sh index 4d365b5a3d..1953f5a65e 100644 --- a/distribution/src/bin/openfire.sh +++ b/distribution/src/bin/openfire.sh @@ -138,6 +138,11 @@ case $arguments in esac done +# Java security config +OPENFIRE_OPTS="${OPENFIRE_OPTS} -Djava.security.properties=${OPENFIRE_HOME}/resources/security/java.security" + +# Enable OCSP Stapling +OPENFIRE_OPTS="${OPENFIRE_OPTS} -Djdk.tls.server.enableStatusRequestExtension=true" JAVACMD="${JAVACMD} -Dlog4j.configurationFile=${OPENFIRE_LIB}/log4j2.xml -Dlog4j2.formatMsgNoLookups=true -Djdk.tls.ephemeralDHKeySize=matched -Djsse.SSLEngine.acceptLargeFragments=true -Djava.net.preferIPv6Addresses=system" diff --git a/distribution/src/security/java.security b/distribution/src/security/java.security new file mode 100644 index 0000000000..a7cb6b7a44 --- /dev/null +++ b/distribution/src/security/java.security @@ -0,0 +1,2 @@ +# Permit client-driven OCSP (has no effect unless revocation checking is also enabled) +ocsp.enable=true From 42de8352c8992073cce2f494afed23e625bffee5 Mon Sep 17 00:00:00 2001 From: Matthew Vivian Date: Wed, 20 Nov 2024 14:06:40 +0000 Subject: [PATCH 4/6] docs: Certificate revocation documentation --- documentation/ssl-guide.html | 172 ++++++++++++++++++++++++++++++++++- documentation/style.css | 5 + 2 files changed, 174 insertions(+), 3 deletions(-) diff --git a/documentation/ssl-guide.html b/documentation/ssl-guide.html index cab0e7bc8a..db7e4903e2 100644 --- a/documentation/ssl-guide.html +++ b/documentation/ssl-guide.html @@ -46,6 +46,7 @@

Introduction

@@ -82,7 +83,7 @@

Background

If you only require light security, are deploying for internal use on trusted networks, etc. you can use "self-signed" certificates. Self-signed certificates encrypts the communication channel between - client and server. However the client must verify the legitimacy of the + client and server. However, the client must verify the legitimacy of the self-signed certificate through some other channel. The most common client reaction to a self-signed certificate is to ask the user whether to trust the certificate, or to silently trust the certificate is @@ -182,7 +183,7 @@

2. Create a self-signed server certificate

to complete with your server's name when asked for your first and last name. After you have entered all the required information, keytool will ask you to verify the information and set a key password. - You must use the same key password as the store password. By default + You must use the same key password as the store password. By default, you get this by simply hitting 'enter' when prompted for a key password.

@@ -297,13 +298,178 @@

7. Configure Openfire

+ +
+

Certificate Revocation

+ +

This section covers the configuration of certificate revocation checking in Openfire, including OCSP + (Online Certificate Status Protocol) and CRL (Certificate Revocation List) mechanisms. This applies to + both roles that Openfire can assume in TLS connections:

+ +
    +
  1. As a server when: +
      +
    • Accepting client connections (C2S)
    • +
    • Accepting incoming server-to-server (S2S) connections
    • +
    +
  2. +
  3. As a client when: +
      +
    • Initiating outbound server-to-server (S2S) connections
    • +
    +
  4. +
+ +

Overview of Revocation Checking Methods

+ +

Openfire supports three methods for checking certificate revocation status:

+ +
    +
  1. OCSP Stapling: The server attaches ("staples") the OCSP response to its certificate + during the TLS handshake. +
      +
    • Most efficient method
    • +
    • Reduces load on OCSP responders
    • +
    • Supported in both client and server roles
    • +
    +
  2. + +
  3. Client-driven OCSP: Direct OCSP responder queries to verify certificate status. +
      +
    • Real-time verification
    • +
    • Higher network overhead
    • +
    • Increased latency during TLS handshake
    • +
    +
  4. + +
  5. Certificate Revocation Lists (CRL): Downloadable lists of revoked certificates. +
      +
    • Periodic updates
    • +
    • Can be cached locally
    • +
    • Larger bandwidth requirements
    • +
    • Can be used as a fallback method
    • +
    +
  6. +
+ +

Configuring Revocation Checking

+ +

To enable certificate revocation checking:

+ +
    +
  1. Go to the Openfire admin console.
  2. +
  3. Navigate to "Server / Server Settings / Server to Server" or "Server / Server Settings / Client Connections".
  4. +
  5. In the "Certificate chain checking" section, locate the option labelled "Verify that certificates have not been revoked (by checking Certificate Revocation Lists and OCSP)".
  6. +
  7. Enable the option to verify certificates against Certificate Revocation Lists (CRL) and through Online Certificate Status Protocol (OCSP).
  8. +
+ +

When this option is enabled, Openfire will check the revocation status of certificates used in server-to-server + (S2S) and client-to-server (C2S) connections to ensure they have not been revoked.

+ +

Fallback behavior when Openfire is the Client (S2S Connections)

+ +

When revocation checking is enabled, Openfire employs a multistep process to verify certificate validity + using both OCSP and CRLs. When Openfire acts as a client during the TLS handshake and receives certificates + from a server, it performs the following revocation checking process:

+
    +
  1. Check OCSP stapled response (if available)
  2. +
  3. Attempt client-driven OCSP query if no stapled response is present
  4. +
  5. Check CRL (if OCSP is unavailable)
  6. +
  7. Fail the connection if all methods fail
  8. +
+ +

OCSP Stapling

+ +

Openfire, when operating as a TLS server and presenting its own certificate, will attempt to staple OCSP + responses when both of these conditions are met:

+ +
    +
  • The certificate includes an OCSP responder URL in its Authority Info Access (AIA) extension.
  • +
  • The specified OCSP responder returns a valid (non-error) response.
  • +
+ +

If an OCSP response cannot be obtained, Openfire will present the certificate without an OCSP staple. + OCSP stapling improves performance by eliminating the need for clients to make separate requests to + verify certificate revocation status.

+ +

OCSP stapling is enabled by default. If you need to disable it for any reason, you can set the Java + system property jdk.tls.server.enableStatusRequestExtension to false.

+ +

+ The following configuration options allow you to customise OCSP stapling behavior: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyDescriptionOpenfire Default Value
jdk.tls.server.enableStatusRequestExtensionEnables the server-side support for OCSP stapling.True
jdk.tls.stapling.responseTimeout +

Controls the maximum amount of time the server will use to obtain OCSP responses, whether from the cache or by contacting an OCSP responder.

+

The responses that are already received will be sent in a CertificateStatus message, if applicable based on the type of stapling being done.

+
5000 (integer value in milliseconds)
jdk.tls.stapling.cacheSize +

Controls the maximum cache size in entries.

+

If the cache is full and a new response needs to be cached, then the least recently used cache entry will be replaced with the new one. A value of zero or less for this property means that the cache will have no upper bound on the number of responses it can contain.

+
256 objects
jdk.tls.stapling.cacheLifetime +

Controls the maximum life of a cached response.

+

It is possible for responses to have shorter lifetimes than the value set with this property if the response has a nextUpdate field that expires sooner than the cache lifetime. A value of zero or less for this property disables the cache lifetime. If an object has no nextUpdate value and cache lifetimes are disabled, then the response will not be cached.

+
3600 seconds (1 hour)
jdk.tls.stapling.responderURI +

Enables the administrator to set a default URI in the event that certificates used for TLS do not have the Authority Info Access (AIA) extension.

+

It will not override the Authority Info Access extension value unless the jdk.tls.stapling.responderOverride property is set.

+
Not set
jdk.tls.stapling.responderOverride +

Enables a URI provided through the jdk.tls.stapling.responderURI property to override any AIA extension value.

+
False
jdk.tls.stapling.ignoreExtensions +

Disables the forwarding of OCSP extensions specified in the status_request or status_request_v2 TLS extensions.

+
False
+ +
+

Other options

You can also use OpenSSL to create new private keys and generate certificate requests for your CA to issue new certificates. Also, check out the new Certificate Manager plugin, - which allows to setup a hotdeploy directory for new certificates deployment, which in turn combined with Let's Encrypt certbot + which allows to set up a hotdeploy directory for new certificates deployment, which in turn combined with Let's Encrypt certbot allows dynamic certificates renewal without administrator intervention.

diff --git a/documentation/style.css b/documentation/style.css index f7030e020e..78d7be49b1 100644 --- a/documentation/style.css +++ b/documentation/style.css @@ -338,6 +338,11 @@ fieldset { right: .5em; } +table.general { + margin-top: 3em; + margin-left: 3em; + border : 1px #ccc solid; +} table.dbtable { margin-top: 3em; From 662c569bddaacdd4734cd5c06eb5e3cc70b0582a Mon Sep 17 00:00:00 2001 From: Matthew Vivian Date: Fri, 22 Nov 2024 11:13:31 +0000 Subject: [PATCH 5/6] fix: Don't send error stanza for TLS handshake failures Prior to this change, if the TLS handshake failed (e.g. if certificate validation did not succeed), an error stanza would be returned to the TLS client with the misleading message "An error occurred in XMPP Decoder". --- .../openfire/nio/NettyXMPPDecoder.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyXMPPDecoder.java b/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyXMPPDecoder.java index f18c586005..77835e2e66 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyXMPPDecoder.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyXMPPDecoder.java @@ -19,11 +19,12 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.util.CharsetUtil; +import io.netty.handler.codec.DecoderException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.StreamError; +import javax.net.ssl.SSLHandshakeException; import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -67,7 +68,22 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { final NettyConnection connection = ctx.channel().attr(CONNECTION).get(); + + if (isSslHandshakeError(cause)) { + connection.close(); + return; + } + Log.warn("Error occurred while decoding XMPP stanza, closing connection: {}", connection, cause); connection.close(new StreamError(StreamError.Condition.internal_server_error, "An error occurred in XMPP Decoder"), cause instanceof IOException); } + + private boolean isSslHandshakeError(Throwable t) { + // Unwrap DecoderException to check for potential SSLHandshakeException + if (t instanceof DecoderException) { + t = t.getCause(); + } + + return (t instanceof SSLHandshakeException); + } } From a2e3af55e3e500c413ae763fdbe3a860a9129dee Mon Sep 17 00:00:00 2001 From: Matthew Vivian Date: Fri, 22 Nov 2024 11:18:01 +0000 Subject: [PATCH 6/6] feat: Add notice when revocation is enabled but client-driven OCSP is not If Openfire is configured to do revocation checking, but Java is configured to not support client-driven OCSP checking, we now inform the user. --- i18n/src/main/resources/openfire_i18n.properties | 1 + .../openfire/spi/ConnectionConfiguration.java | 16 ++++++++++++++++ .../main/webapp/connection-settings-advanced.jsp | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/i18n/src/main/resources/openfire_i18n.properties b/i18n/src/main/resources/openfire_i18n.properties index 19834d609c..8ba35a229c 100644 --- a/i18n/src/main/resources/openfire_i18n.properties +++ b/i18n/src/main/resources/openfire_i18n.properties @@ -1635,6 +1635,7 @@ connection.advanced.settings.clientauth.label_disabled=Disabled - Peer ce connection.advanced.settings.clientauth.label_wanted=Wanted - Peer certificates are verified, but only when they are presented by the peer. connection.advanced.settings.clientauth.label_needed=Needed - A connection cannot be established if the peer does not present a valid certificate. connection.advanced.settings.clientauth.label_strict_cert_validation=If attempting to validate a certificate fails, the connection is closed and not attempted via dialback authentication. +connection.advanced.settings.certchain.ocsp.warning=Your server is configured with the Java security property ocsp.enable=false which disables client-driven OCSP certificate revocation checking. While OCSP stapling validation and CRL checking remain active, Openfire will not perform direct OCSP requests to verify certificate status. connection.advanced.settings.certchain.boxtitle=Certificate chain checking connection.advanced.settings.certchain.info=These options configure some aspects of the verification/validation of the certificates that are presented by peers while setting up encrypted connections. connection.advanced.settings.certchain.label_selfsigned=Allow peer certificates to be self-signed. diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java index 9984b1a69a..24bcf49272 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/spi/ConnectionConfiguration.java @@ -20,6 +20,7 @@ import org.jivesoftware.openfire.keystore.*; import java.net.InetAddress; +import java.security.Security; import java.util.*; /** @@ -50,6 +51,7 @@ public class ConnectionConfiguration private final Connection.CompressionPolicy compressionPolicy; // derived + private final boolean isOcspEnabled; private final IdentityStore identityStore; private final TrustStore trustStore; @@ -100,6 +102,7 @@ public ConnectionConfiguration( ConnectionType type, boolean enabled, int maxThr this.compressionPolicy = compressionPolicy; this.strictCertificateValidation = strictCertificateValidation; + this.isOcspEnabled = Boolean.parseBoolean(Security.getProperty("ocsp.enable")); final CertificateStoreManager certificateStoreManager = XMPPServer.getInstance().getCertificateStoreManager(); this.identityStore = certificateStoreManager.getIdentityStore( type ); this.trustStore = certificateStoreManager.getTrustStore( type ); @@ -229,6 +232,19 @@ public TrustStore getTrustStore() return trustStore; } + /** + * Indicates if client-driven Online Certificate Status Protocol (OCSP) is enabled. + * + * This is a prerequisite to enable client-driven OCSP, it has no effect unless revocation + * checking is also enabled. + * + * @return true if client-driven OCSP is enabled, otherwise false. + */ + public boolean isOcspEnabled() + { + return isOcspEnabled; + } + public boolean isEnabled() { return enabled; diff --git a/xmppserver/src/main/webapp/connection-settings-advanced.jsp b/xmppserver/src/main/webapp/connection-settings-advanced.jsp index 3b396ea5b3..1641d49d1e 100644 --- a/xmppserver/src/main/webapp/connection-settings-advanced.jsp +++ b/xmppserver/src/main/webapp/connection-settings-advanced.jsp @@ -310,6 +310,12 @@ + + + + + +