Commit 3147d63c authored by Guus der Kinderen's avatar Guus der Kinderen

OF-1495: Notify user of missing Subject Alternative Name values.

Depending on what components (plugins) are loaded, the certificate that is used to identify the local
domain should have additional values in its Subject Alternative Name extension.

This commit adds an information header in the TLS admin console pages when Openfire is serving components
that are not included in its certificate.
parent 6a0b9f59
......@@ -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.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.
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. \
Verify that the reply is correct and that it belongs to the correct certificate.
ssl.certificates.keystore.issuer-updated=Issuer information updated successfully.
......
......@@ -429,6 +429,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.
*/
......@@ -457,7 +520,6 @@ public class IdentityStore extends CertificateStore
final String alias = name + "_" + algorithm.toLowerCase();
final int validityInDays = 5*365;
final Set<String> sanDnsNames = CertificateManager.determineSubjectAlternateNameDnsNameValues();
final Set<String> sanXmppAddrs = CertificateManager.determineSubjectAlternateNameXmppAddrValues();
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
......@@ -466,7 +528,7 @@ public class IdentityStore extends CertificateStore
final KeyPair keyPair = generateKeyPair( algorithm.toUpperCase(), keySize );
// Create X509 certificate with keys and specified domain
final X509Certificate cert = CertificateManager.createX509V3Certificate( keyPair, validityInDays, name, name, name, signAlgorithm, sanDnsNames, sanXmppAddrs );
final X509Certificate cert = CertificateManager.createX509V3Certificate( keyPair, validityInDays, name, name, name, signAlgorithm, sanDnsNames );
// Store new certificate and private key in the key store
store.setKeyEntry( alias, keyPair.getPrivate(), configuration.getPassword(), new X509Certificate[]{cert} );
......
......@@ -447,12 +447,12 @@ public class CertificateManager {
String subjectCommonName, String domain,
String signAlgoritm)
throws GeneralSecurityException, IOException {
return createX509V3Certificate( kp, days, issuerCommonName, subjectCommonName, domain, signAlgoritm, null, null );
return createX509V3Certificate( kp, days, issuerCommonName, subjectCommonName, domain, signAlgoritm, null );
}
public static synchronized X509Certificate createX509V3Certificate(KeyPair kp, int days, String issuerCommonName,
String subjectCommonName, String domain,
String signAlgoritm, Set<String> sanDnsNames, Set<String> sanXmppAddrs)
String signAlgoritm, Set<String> sanDnsNames)
throws GeneralSecurityException, IOException {
// subjectDN
......@@ -463,7 +463,7 @@ public class CertificateManager {
X500NameBuilder issuerBuilder = new X500NameBuilder();
issuerBuilder.addRDN(BCStyle.CN, issuerCommonName);
return createX509V3Certificate(kp, days, issuerBuilder, subjectBuilder, domain, signAlgoritm, sanDnsNames, sanXmppAddrs);
return createX509V3Certificate(kp, days, issuerBuilder, subjectBuilder, domain, signAlgoritm, sanDnsNames);
}
/**
......@@ -482,11 +482,11 @@ public class CertificateManager {
public static synchronized X509Certificate createX509V3Certificate(KeyPair kp, int days, X500NameBuilder issuerBuilder,
X500NameBuilder subjectBuilder, String domain, String signAlgoritm ) throws GeneralSecurityException, IOException
{
return createX509V3Certificate( kp, days, issuerBuilder, subjectBuilder, domain, signAlgoritm, null, null );
return createX509V3Certificate( kp, days, issuerBuilder, subjectBuilder, domain, signAlgoritm, null );
}
public static synchronized X509Certificate createX509V3Certificate(KeyPair kp, int days, X500NameBuilder issuerBuilder,
X500NameBuilder subjectBuilder, String domain, String signAlgoritm, Set<String> sanDnsNames, Set<String> sanXmppAddrs ) throws GeneralSecurityException, IOException {
X500NameBuilder subjectBuilder, String domain, String signAlgoritm, Set<String> sanDnsNames ) throws GeneralSecurityException, IOException {
PublicKey pubKey = kp.getPublic();
PrivateKey privKey = kp.getPrivate();
......@@ -510,7 +510,7 @@ public class CertificateManager {
);
// add subjectAlternativeName extension that includes all relevant names.
final GeneralNames subjectAlternativeNames = getSubjectAlternativeNames( sanDnsNames, sanXmppAddrs );
final GeneralNames subjectAlternativeNames = getSubjectAlternativeNames( sanDnsNames );
final boolean critical = subjectDN.getRDNs().length == 0;
certBuilder.addExtension(Extension.subjectAlternativeName, critical, subjectAlternativeNames);
......@@ -543,7 +543,7 @@ public class CertificateManager {
}
}
protected static GeneralNames getSubjectAlternativeNames( Set<String> sanDnsNames, Set<String> sanXmppAddrs )
protected static GeneralNames getSubjectAlternativeNames( Set<String> sanDnsNames )
{
final ASN1EncodableVector subjectAlternativeNames = new ASN1EncodableVector();
if ( sanDnsNames != null )
......@@ -556,24 +556,6 @@ public class CertificateManager {
}
}
if ( sanXmppAddrs != null )
{
for ( final String xmppAddrValue : sanXmppAddrs )
{
subjectAlternativeNames.add(
new DERTaggedObject( false,
GeneralName.otherName,
new DERSequence(
new ASN1Encodable[] {
new ASN1ObjectIdentifier( "1.3.6.1.5.5.7.8.5" ),
new DERTaggedObject( true, GeneralName.otherName, new DERUTF8String( xmppAddrValue ) )
}
)
)
);
}
}
return GeneralNames.getInstance(
new DERSequence( subjectAlternativeNames )
);
......@@ -589,25 +571,12 @@ public class CertificateManager {
{
final HashSet<String> result = new HashSet<>();
// The fully qualified domain name of the server
result.add( XMPPServer.getInstance().getServerInfo().getHostname() );
return result;
}
/**
* Finds all values that aught to be added as a Subject Alternate Name of the dnsName type to a certificate that
* identifies this XMPP server.
*
* @return A set of names, possibly empty, never null.
*/
public static Set<String> determineSubjectAlternateNameXmppAddrValues()
{
final HashSet<String> result = new HashSet<>();
// Add the XMPP domain name itself.
result.add( XMPPServer.getInstance().getServerInfo().getXMPPDomain() );
// The fully qualified domain name of the server
result.add( XMPPServer.getInstance().getServerInfo().getHostname() );
if ( XMPPServer.getInstance().getIQDiscoItemsHandler() != null ) // When we're not in setup any longer...
{
// Add the name of each of the domain level item nodes as reported by service discovery.
......@@ -616,6 +585,7 @@ public class CertificateManager {
result.add( item.getJID().toBareJID() );
}
}
return result;
}
}
......@@ -117,8 +117,7 @@
// Regenerate self-sign certs whose subjectDN matches the issuerDN and set the new issuerDN
final Set<String> sanDnsNames = CertificateManager.determineSubjectAlternateNameDnsNameValues();
final Set<String> sanXmppAddrs = CertificateManager.determineSubjectAlternateNameXmppAddrValues();
X509Certificate newCertificate = CertificateManager.createX509V3Certificate(new KeyPair(pubKey, privKey), days, builder, builder, domain, signAlgoritm, sanDnsNames, sanXmppAddrs);
X509Certificate newCertificate = CertificateManager.createX509V3Certificate(new KeyPair(pubKey, privKey), days, builder, builder, domain, signAlgoritm, sanDnsNames);
keyStore.setKeyEntry(alias, privKey, identityStore.getConfiguration().getPassword(), new X509Certificate[] { newCertificate });
}
}
......
<%@page import="org.jivesoftware.util.StringUtils"%>
<%@page import="java.util.LinkedHashMap"%>
<%@page import="java.security.PrivateKey"%>
<%@page import="org.jivesoftware.util.CertificateManager"%>
<%@ page import="org.jivesoftware.util.CookieUtils" %>
......@@ -12,10 +11,7 @@
<%@ page import="org.jivesoftware.openfire.spi.ConnectionType" %>
<%@ page import="org.jivesoftware.util.ParamUtils" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Set" %>
<%@ page import="java.util.*" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
......@@ -28,6 +24,7 @@
<% // Get parameters:
boolean generate = ParamUtils.getBooleanParameter(request, "generate");
boolean generateFull = ParamUtils.getBooleanParameter(request, "generateFull");
boolean delete = ParamUtils.getBooleanParameter(request, "delete");
boolean importReply = ParamUtils.getBooleanParameter(request, "importReply");
final String alias = ParamUtils.getParameter( request, "alias" );
......@@ -37,9 +34,10 @@
Cookie csrfCookie = CookieUtils.getCookie(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)) {
generate = false;
generateFull = false;
delete = false;
importReply = false;
errors.put("csrf", "CSRF Failure!");
......@@ -78,6 +76,8 @@
pageContext.setAttribute( "validRSACert", identityStore.containsDomainCertificate( "RSA" ) );
pageContext.setAttribute( "validDSACert", identityStore.containsDomainCertificate( "DSA" ) );
pageContext.setAttribute( "allIDRSACert", identityStore.containsAllIdentityCertificate( "RSA" ) );
pageContext.setAttribute( "allIDDSACert", identityStore.containsAllIdentityCertificate( "DSA" ) );
if ( delete )
{
......@@ -108,7 +108,6 @@
if (generate) {
String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
try {
if (errors.containsKey("ioerror") || !identityStore.containsDomainCertificate("DSA")) {
identityStore.addSelfSignedDomainCertificate("DSA");
......@@ -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) {
String reply = ParamUtils.getParameter(request, "reply");
if (alias != null && reply != null && reply.trim().length() > 0) {
......@@ -187,16 +205,29 @@
</admin:infobox>
</c:forEach>
<c:if test="${not validDSACert or not validRSACert}">
<admin:infobox type="warning">
<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>"/>
<fmt:param value="<a href='import-keystore-certificate.jsp?connectionType=${connectionType}'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</admin:infobox>
</c:if>
<c:choose>
<c:when test="${not validDSACert or not validRSACert}">
<admin:infobox type="warning">
<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>"/>
<fmt:param value="<a href='import-keystore-certificate.jsp?connectionType=${connectionType}'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</admin:infobox>
</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.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