Unverified Commit 07bebd80 authored by Dave Cridland's avatar Dave Cridland Committed by GitHub

Merge pull request #1031 from guusdk/OF-1495_Add-SANs

OF-1495: Add subject alternative names to certs and CSRs
parents 21639a68 bed4eed5
...@@ -2383,6 +2383,9 @@ ssl.certificates.keystore.restart_server=Certificates were modified so HTTP serv ...@@ -2383,6 +2383,9 @@ ssl.certificates.keystore.restart_server=Certificates were modified so HTTP serv
ssl.certificates.keystore.io_error=Unable to access the certificate key store. The file may be corrupt. ssl.certificates.keystore.io_error=Unable to access the certificate key store. The file may be corrupt.
ssl.certificates.keystore.no_installed=One or more certificates are missing. Click {0}here{1} to generate self-signed \ ssl.certificates.keystore.no_installed=One or more certificates are missing. Click {0}here{1} to generate self-signed \
certificates or {2}here{3} to import a signed certificate and its private key. certificates or {2}here{3} to import a signed certificate and its private key.
ssl.certificates.keystore.no_complete_installed=The installed certificates do not cover all of the identities of this \
server. Click {0}here{1} to replace your certificates with self-signed certificates that do, or {2}here{3} to import \
a signed certificate and its private key.
ssl.certificates.keystore.error_importing-reply=An error occurred while importing the Certificate Authority reply. \ ssl.certificates.keystore.error_importing-reply=An error occurred while importing the Certificate Authority reply. \
Verify that the reply is correct and that it belongs to the correct certificate. Verify that the reply is correct and that it belongs to the correct certificate.
ssl.certificates.keystore.issuer-updated=Issuer information updated successfully. ssl.certificates.keystore.issuer-updated=Issuer information updated successfully.
......
...@@ -13,6 +13,7 @@ import java.io.IOException; ...@@ -13,6 +13,7 @@ import java.io.IOException;
import java.security.*; import java.security.*;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.*; import java.util.*;
...@@ -110,7 +111,7 @@ public class IdentityStore extends CertificateStore ...@@ -110,7 +111,7 @@ public class IdentityStore extends CertificateStore
return pemCSR; return pemCSR;
} }
catch ( IOException | KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | OperatorCreationException e ) catch ( IOException | KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | OperatorCreationException | CertificateParsingException e )
{ {
throw new CertificateStoreConfigException( "Cannot generate CSR for alias '"+ alias +"'", e ); throw new CertificateStoreConfigException( "Cannot generate CSR for alias '"+ alias +"'", e );
} }
...@@ -442,6 +443,69 @@ public class IdentityStore extends CertificateStore ...@@ -442,6 +443,69 @@ public class IdentityStore extends CertificateStore
} }
} }
/**
* Checks if the store contains a certificate of a particular algorithm that contains at least all of the identities
* of this server (which includes the XMPP domain name, but also its hostname, and XMPP addresses of components
* that are currently being hosted).
*
* This method will not distinguish between self-signed and non-self-signed certificates.
*/
public synchronized boolean containsAllIdentityCertificate( String algorithm ) throws CertificateStoreConfigException
{
final Collection<String> dns = CertificateManager.determineSubjectAlternateNameDnsNameValues();
try
{
for ( final String alias : Collections.list( store.aliases() ) )
{
final Set<String> missingDns = new HashSet<>();
final Certificate certificate = store.getCertificate( alias );
if ( !( certificate instanceof X509Certificate ) )
{
continue;
}
if ( !certificate.getPublicKey().getAlgorithm().equalsIgnoreCase( algorithm ) )
{
continue;
}
final List<String> serverIdentities = CertificateManager.getServerIdentities( (X509Certificate) certificate );
// Are all of our DNS names covered?
for ( String dnsId : dns )
{
boolean found = false;
for ( String identity : serverIdentities )
{
if ( !DNSUtil.isNameCoveredByPattern( dnsId, identity ) )
{
found = true;
break;
}
}
if ( !found )
{
Log.info( "Certificate with alias '{}' is missing DNS identity '{}'.", alias, dnsId );
missingDns.add( dnsId );
}
}
if ( missingDns.isEmpty() )
{
return true;
}
}
return false;
}
catch ( KeyStoreException e )
{
throw new CertificateStoreConfigException( "An exception occurred while searching for " + algorithm + " certificates that match the Openfire domain.", e );
}
}
/** /**
* Populates the key store with a self-signed certificate for the domain of this XMPP service. * Populates the key store with a self-signed certificate for the domain of this XMPP service.
*/ */
...@@ -469,6 +533,7 @@ public class IdentityStore extends CertificateStore ...@@ -469,6 +533,7 @@ public class IdentityStore extends CertificateStore
final String name = JiveGlobals.getProperty( "xmpp.domain" ).toLowerCase(); final String name = JiveGlobals.getProperty( "xmpp.domain" ).toLowerCase();
final String alias = name + "_" + algorithm.toLowerCase(); final String alias = name + "_" + algorithm.toLowerCase();
final int validityInDays = 5*365; final int validityInDays = 5*365;
final Set<String> sanDnsNames = CertificateManager.determineSubjectAlternateNameDnsNameValues();
Log.info( "Generating a new private key and corresponding self-signed certificate for domain name '{}', using the {} algorithm (sign-algorithm: {} with a key size of {} bits). Certificate will be valid for {} days.", name, algorithm, signAlgorithm, keySize, validityInDays ); Log.info( "Generating a new private key and corresponding self-signed certificate for domain name '{}', using the {} algorithm (sign-algorithm: {} with a key size of {} bits). Certificate will be valid for {} days.", name, algorithm, signAlgorithm, keySize, validityInDays );
// Generate public and private keys // Generate public and private keys
...@@ -477,7 +542,7 @@ public class IdentityStore extends CertificateStore ...@@ -477,7 +542,7 @@ public class IdentityStore extends CertificateStore
final KeyPair keyPair = generateKeyPair( algorithm.toUpperCase(), keySize ); final KeyPair keyPair = generateKeyPair( algorithm.toUpperCase(), keySize );
// Create X509 certificate with keys and specified domain // Create X509 certificate with keys and specified domain
final X509Certificate cert = CertificateManager.createX509V3Certificate( keyPair, validityInDays, name, name, name, signAlgorithm ); final X509Certificate cert = CertificateManager.createX509V3Certificate( keyPair, validityInDays, name, name, name, signAlgorithm, sanDnsNames );
// Store new certificate and private key in the key store // Store new certificate and private key in the key store
store.setKeyEntry( alias, keyPair.getPrivate(), configuration.getPassword(), new X509Certificate[]{cert} ); store.setKeyEntry( alias, keyPair.getPrivate(), configuration.getPassword(), new X509Certificate[]{cert} );
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
<%@ page import="java.util.HashMap" %> <%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %> <%@ page import="java.util.Map" %>
<%@ page import="org.jivesoftware.openfire.spi.ConnectionType" %> <%@ page import="org.jivesoftware.openfire.spi.ConnectionType" %>
<%@ page import="org.jivesoftware.openfire.keystore.CertificateUtils" %>
<%@ page import="java.util.Set" %>
<%@ taglib uri="admin" prefix="admin" %> <%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
...@@ -114,7 +116,8 @@ ...@@ -114,7 +116,8 @@
int days = 60; int days = 60;
// Regenerate self-sign certs whose subjectDN matches the issuerDN and set the new issuerDN // Regenerate self-sign certs whose subjectDN matches the issuerDN and set the new issuerDN
X509Certificate newCertificate = CertificateManager.createX509V3Certificate(new KeyPair(pubKey, privKey), days, builder, builder, domain, signAlgoritm); final Set<String> sanDnsNames = CertificateManager.determineSubjectAlternateNameDnsNameValues();
X509Certificate newCertificate = CertificateManager.createX509V3Certificate(new KeyPair(pubKey, privKey), days, builder, builder, domain, signAlgoritm, sanDnsNames);
keyStore.setKeyEntry(alias, privKey, identityStore.getConfiguration().getPassword(), new X509Certificate[] { newCertificate }); keyStore.setKeyEntry(alias, privKey, identityStore.getConfiguration().getPassword(), new X509Certificate[] { newCertificate });
} }
} }
......
<%@page import="org.jivesoftware.util.StringUtils"%> <%@page import="org.jivesoftware.util.StringUtils"%>
<%@page import="java.util.LinkedHashMap"%>
<%@page import="java.security.PrivateKey"%> <%@page import="java.security.PrivateKey"%>
<%@page import="org.jivesoftware.util.CertificateManager"%> <%@page import="org.jivesoftware.util.CertificateManager"%>
<%@ page import="org.jivesoftware.util.CookieUtils" %> <%@ page import="org.jivesoftware.util.CookieUtils" %>
...@@ -12,10 +11,7 @@ ...@@ -12,10 +11,7 @@
<%@ page import="org.jivesoftware.openfire.spi.ConnectionType" %> <%@ page import="org.jivesoftware.openfire.spi.ConnectionType" %>
<%@ page import="org.jivesoftware.util.ParamUtils" %> <%@ page import="org.jivesoftware.util.ParamUtils" %>
<%@ page import="java.security.cert.X509Certificate" %> <%@ page import="java.security.cert.X509Certificate" %>
<%@ page import="java.util.Collections" %> <%@ page import="java.util.*" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Set" %>
<%@ taglib uri="admin" prefix="admin" %> <%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
...@@ -28,6 +24,7 @@ ...@@ -28,6 +24,7 @@
<% // Get parameters: <% // Get parameters:
boolean generate = ParamUtils.getBooleanParameter(request, "generate"); boolean generate = ParamUtils.getBooleanParameter(request, "generate");
boolean generateFull = ParamUtils.getBooleanParameter(request, "generateFull");
boolean delete = ParamUtils.getBooleanParameter(request, "delete"); boolean delete = ParamUtils.getBooleanParameter(request, "delete");
boolean importReply = ParamUtils.getBooleanParameter(request, "importReply"); boolean importReply = ParamUtils.getBooleanParameter(request, "importReply");
final String alias = ParamUtils.getParameter( request, "alias" ); final String alias = ParamUtils.getParameter( request, "alias" );
...@@ -37,9 +34,10 @@ ...@@ -37,9 +34,10 @@
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf"); Cookie csrfCookie = CookieUtils.getCookie(request, "csrf");
String csrfParam = ParamUtils.getParameter(request, "csrf"); String csrfParam = ParamUtils.getParameter(request, "csrf");
if (generate | delete | importReply) { if (generate | generateFull | delete | importReply) {
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) { if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) {
generate = false; generate = false;
generateFull = false;
delete = false; delete = false;
importReply = false; importReply = false;
errors.put("csrf", "CSRF Failure!"); errors.put("csrf", "CSRF Failure!");
...@@ -78,6 +76,8 @@ ...@@ -78,6 +76,8 @@
pageContext.setAttribute( "validRSACert", identityStore.containsDomainCertificate( "RSA" ) ); pageContext.setAttribute( "validRSACert", identityStore.containsDomainCertificate( "RSA" ) );
pageContext.setAttribute( "validDSACert", identityStore.containsDomainCertificate( "DSA" ) ); pageContext.setAttribute( "validDSACert", identityStore.containsDomainCertificate( "DSA" ) );
pageContext.setAttribute( "allIDRSACert", identityStore.containsAllIdentityCertificate( "RSA" ) );
pageContext.setAttribute( "allIDDSACert", identityStore.containsAllIdentityCertificate( "DSA" ) );
if ( delete ) if ( delete )
{ {
...@@ -108,7 +108,6 @@ ...@@ -108,7 +108,6 @@
if (generate) { if (generate) {
String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
try { try {
if (errors.containsKey("ioerror") || !identityStore.containsDomainCertificate("DSA")) { if (errors.containsKey("ioerror") || !identityStore.containsDomainCertificate("DSA")) {
identityStore.addSelfSignedDomainCertificate("DSA"); identityStore.addSelfSignedDomainCertificate("DSA");
...@@ -128,6 +127,25 @@ ...@@ -128,6 +127,25 @@
} }
} }
if (generateFull) {
try {
if (!identityStore.containsAllIdentityCertificate("DSA")) {
identityStore.addSelfSignedDomainCertificate("DSA");
}
if (!identityStore.containsAllIdentityCertificate("RSA")) {
identityStore.addSelfSignedDomainCertificate("RSA");
}
// Save new certificates into the key store
identityStore.persist();
// Log the event
webManager.logEvent("generated SSL self-signed certs", null);
response.sendRedirect("security-keystore.jsp?connectionType="+connectionType);
return;
} catch (Exception e) {
e.printStackTrace();
errors.put("generate", e.getMessage());
}
}
if (importReply) { if (importReply) {
String reply = ParamUtils.getParameter(request, "reply"); String reply = ParamUtils.getParameter(request, "reply");
if (alias != null && reply != null && reply.trim().length() > 0) { if (alias != null && reply != null && reply.trim().length() > 0) {
...@@ -187,7 +205,8 @@ ...@@ -187,7 +205,8 @@
</admin:infobox> </admin:infobox>
</c:forEach> </c:forEach>
<c:if test="${not validDSACert or not validRSACert}"> <c:choose>
<c:when test="${not validDSACert or not validRSACert}">
<admin:infobox type="warning"> <admin:infobox type="warning">
<fmt:message key="ssl.certificates.keystore.no_installed"> <fmt:message key="ssl.certificates.keystore.no_installed">
<fmt:param value="<a href='security-keystore.jsp?csrf=${csrf}&generate=true&connectionType=${connectionType}'>"/> <fmt:param value="<a href='security-keystore.jsp?csrf=${csrf}&generate=true&connectionType=${connectionType}'>"/>
...@@ -196,7 +215,19 @@ ...@@ -196,7 +215,19 @@
<fmt:param value="</a>"/> <fmt:param value="</a>"/>
</fmt:message> </fmt:message>
</admin:infobox> </admin:infobox>
</c:if> </c:when>
<c:when test="${not allIDDSACert or not allIDRSACert}">
<admin:infobox type="info">
<fmt:message key="ssl.certificates.keystore.no_complete_installed">
<fmt:param value="<a href='security-keystore.jsp?csrf=${csrf}&generateFull=true&connectionType=${connectionType}'>"/>
<fmt:param value="</a>"/>
<fmt:param value="<a href='import-keystore-certificate.jsp?connectionType=${connectionType}'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</admin:infobox>
</c:when>
</c:choose>
<c:if test="${param.addupdatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.added_updated"/></admin:infobox></c:if> <c:if test="${param.addupdatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.added_updated"/></admin:infobox></c:if>
<c:if test="${param.generatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.generated"/></admin:infobox></c:if> <c:if test="${param.generatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.generated"/></admin:infobox></c:if>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment