Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OF-2134: Add option to enable certificate revocation checks #2610

Merged
merged 6 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions distribution/src/bin/openfire.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
guusdk marked this conversation as resolved.
Show resolved Hide resolved

# 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"

Expand Down
2 changes: 2 additions & 0 deletions distribution/src/security/java.security
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Permit client-driven OCSP (has no effect unless revocation checking is also enabled)
ocsp.enable=true
172 changes: 169 additions & 3 deletions documentation/ssl-guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h2>Introduction</h2>
<ul>
<li><a href="#background">Background</a></li>
<li><a href="#tools">Oracle Java Development Kit security tools</a></li>
<li><a href="#revocation">Certificate Revocation Configuration</a></li>
<li><a href="#misc">Other options</a></li>
</ul>
</nav>
Expand Down Expand Up @@ -82,7 +83,7 @@ <h2>Background</h2>
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
Expand Down Expand Up @@ -182,7 +183,7 @@ <h3>2. Create a self-signed server certificate</h3>
to <b>complete with your server's name when asked for your first and last name</b>.
After you have entered all the required information, keytool will ask you to
verify the information and set a key password.
<b>You must use the same key password as the store password.</b> By default
<b>You must use the same key password as the store password.</b> By default,
you get this by simply hitting 'enter' when prompted for a key password.
</p>
<p>
Expand Down Expand Up @@ -297,13 +298,178 @@ <h3>7. Configure Openfire</h3>

</section>


<section id="revocation">
<h2>Certificate Revocation</h2>

<p>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:</p>

<ol>
<li>As a server when:
<ul>
<li>Accepting client connections (C2S)</li>
<li>Accepting incoming server-to-server (S2S) connections</li>
</ul>
</li>
<li>As a client when:
<ul>
<li>Initiating outbound server-to-server (S2S) connections</li>
</ul>
</li>
</ol>

<h3>Overview of Revocation Checking Methods</h3>

<p>Openfire supports three methods for checking certificate revocation status:</p>

<ol>
<li><strong>OCSP Stapling</strong>: The server attaches ("staples") the OCSP response to its certificate
during the TLS handshake.
<ul>
<li>Most efficient method</li>
<li>Reduces load on OCSP responders</li>
<li>Supported in both client and server roles</li>
</ul>
</li>

<li><strong>Client-driven OCSP</strong>: Direct OCSP responder queries to verify certificate status.
<ul>
<li>Real-time verification</li>
<li>Higher network overhead</li>
<li>Increased latency during TLS handshake</li>
</ul>
</li>

<li><strong>Certificate Revocation Lists (CRL)</strong>: Downloadable lists of revoked certificates.
<ul>
<li>Periodic updates</li>
<li>Can be cached locally</li>
<li>Larger bandwidth requirements</li>
<li>Can be used as a fallback method</li>
</ul>
</li>
</ol>

<h3>Configuring Revocation Checking</h3>

<p>To enable certificate revocation checking:</p>

<ol>
<li>Go to the Openfire admin console.</li>
<li>Navigate to "Server / Server Settings / Server to Server" or "Server / Server Settings / Client Connections".</li>
<li>In the "Certificate chain checking" section, locate the option labelled "Verify that certificates have not been revoked (by checking Certificate Revocation Lists and OCSP)".</li>
<li>Enable the option to verify certificates against Certificate Revocation Lists (CRL) and through Online Certificate Status Protocol (OCSP).</li>
</ol>

<p>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.</p>

<h4>Fallback behavior when Openfire is the Client (S2S Connections)</h4>

<p>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:</p>
<ol>
<li>Check OCSP stapled response (if available)</li>
<li>Attempt client-driven OCSP query if no stapled response is present</li>
<li>Check CRL (if OCSP is unavailable)</li>
<li>Fail the connection if all methods fail</li>
</ol>

<h4>OCSP Stapling</h4>

<p>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:</p>

<ul>
<li>The certificate includes an OCSP responder URL in its Authority Info Access (AIA) extension.</li>
<li>The specified OCSP responder returns a valid (non-error) response.</li>
</ul>

<p>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.</p>

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

<p>
The following configuration options allow you to customise OCSP stapling behavior:
</p>
<table class="general">
<thead>
<tr>
<th>Property</th>
<th>Description</th>
<th>Openfire Default Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>jdk.tls.server.enableStatusRequestExtension</code></td>
<td>Enables the server-side support for OCSP stapling.</td>
<td>True</td>
</tr>
<tr>
<td><code>jdk.tls.stapling.responseTimeout</code></td>
<td>
<p>Controls the maximum amount of time the server will use to obtain OCSP responses, whether from the cache or by contacting an OCSP responder.</p>
<p>The responses that are already received will be sent in a <code>CertificateStatus</code> message, if applicable based on the type of stapling being done.</p>
</td>
<td>5000 (integer value in milliseconds)</td>
</tr>
<tr>
<td><code>jdk.tls.stapling.cacheSize</code></td>
<td>
<p>Controls the maximum cache size in entries.</p>
<p>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.</p>
</td>
<td>256 objects</td>
</tr>
<tr>
<td><code>jdk.tls.stapling.cacheLifetime</code></td>
<td>
<p>Controls the maximum life of a cached response.</p>
<p>It is possible for responses to have shorter lifetimes than the value set with this property if the response has a <strong>nextUpdate</strong> 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 <strong>nextUpdate</strong> value and cache lifetimes are disabled, then the response will not be cached.</p>
</td>
<td>3600 seconds (1 hour)</td>
</tr>
<tr>
<td><code>jdk.tls.stapling.responderURI</code></td>
<td>
<p>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.</p>
<p>It will not override the Authority Info Access extension value unless the <code>jdk.tls.stapling.responderOverride</code> property is set.</p>
</td>
<td>Not set</td>
</tr>
<tr>
<td><code>jdk.tls.stapling.responderOverride</code></td>
<td>
<p>Enables a URI provided through the <code>jdk.tls.stapling.responderURI</code> property to override any AIA extension value.</p>
</td>
<td>False</td>
</tr>
<tr>
<td><code>jdk.tls.stapling.ignoreExtensions</code></td>
<td>
<p>Disables the forwarding of OCSP extensions specified in the <code>status_request</code> or <code>status_request_v2</code> TLS extensions.</p>
</td>
<td>False</td>
</tr>
</tbody>
</table>

</section>

<section id="misc">

<h2>Other options</h2>
<p>
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 <a href="https://www.igniterealtime.org/projects/openfire/plugin-archive.jsp?plugin=certificatemanager">Certificate Manager</a> 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.
</p>

Expand Down
5 changes: 5 additions & 0 deletions documentation/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ fieldset {
right: .5em;
}

table.general {
margin-top: 3em;
margin-left: 3em;
border : 1px #ccc solid;
}

table.dbtable {
margin-top: 3em;
Expand Down
2 changes: 2 additions & 0 deletions i18n/src/main/resources/openfire_i18n.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1635,10 +1635,12 @@ connection.advanced.settings.clientauth.label_disabled=<b>Disabled</b> - Peer ce
connection.advanced.settings.clientauth.label_wanted=<b>Wanted</b> - Peer certificates are verified, but only when they are presented by the peer.
connection.advanced.settings.clientauth.label_needed=<b>Needed</b> - 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 <code>ocsp.enable=false</code> 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.
connection.advanced.settings.certchain.label_validity=Verify that the certificate is currently valid (based on the &#39;notBefore&#39; and &#39;notAfter&#39; 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 &#39;hello&#39;) 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
*
* @author Guus der Kinderen, [email protected]
*/
// 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 );
Expand All @@ -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<X509Certificate> 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<X509Certificate> trusted = new HashSet<>();
Expand All @@ -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
Expand Down Expand Up @@ -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() );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -67,7 +68,22 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> 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();
guusdk marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
Loading
Loading