Commit 5a52d003 authored by Guus der Kinderen's avatar Guus der Kinderen

Restoring 'import private key & certificate' functionality.

parent 75494821
...@@ -3106,9 +3106,7 @@ mediaproxy.summary.stopbutton = Stop Active Sessions ...@@ -3106,9 +3106,7 @@ mediaproxy.summary.stopbutton = Stop Active Sessions
# Import keystore certificate page # Import keystore certificate page
ssl.import.certificate.keystore.socket.title=Import Signed Certificate for Socket-based Communication ssl.import.certificate.keystore.title=Import Signed Certificate for Socket-based Communication
ssl.import.certificate.keystore.bosh.title=Import Signed Certificate for BOSH-based Communication
ssl.import.certificate.keystore.administrative.title=Import Signed Certificate for Administrative Purposes
ssl.import.certificate.keystore.info=Use the form below to import a private key and certificate that was provided by a \ ssl.import.certificate.keystore.info=Use the form below to import a private key and certificate that was provided by a \
Certificate Authority. Make sure that root certificates of the CA signing the certificate are present in the \ Certificate Authority. Make sure that root certificates of the CA signing the certificate are present in the \
truststore. Otherwise you will need to manually import them using the "keytool" command line tool. If you are \ truststore. Otherwise you will need to manually import them using the "keytool" command line tool. If you are \
......
...@@ -211,8 +211,46 @@ public class IdentityStore extends CertificateStore ...@@ -211,8 +211,46 @@ public class IdentityStore extends CertificateStore
/** /**
* Imports a certificate and the private key that was used to generate the certificate. * Imports a certificate and the private key that was used to generate the certificate.
* *
* This method will import the certificate and key in the store using a unique alias. This alias is returned.
*
* This method will fail when the provided certificate does not match the domain of this XMPP service. * This method will fail when the provided certificate does not match the domain of this XMPP service.
* *
* @param pemCertificates a PEM representation of the certificate or certificate chain (cannot be null or empty).
* @param pemPrivateKey a PEM representation of the private key (cannot be null or empty).
* @param passPhrase optional pass phrase (must be present if the private key is encrypted).
* @return The alias that was used (never null).
*/
public String installCertificate( String pemCertificates, String pemPrivateKey, String passPhrase ) throws CertificateStoreConfigException
{
// Generate a unique alias.
final String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
int index = 1;
String alias = domain + "_" + index;
try
{
while ( store.containsAlias( alias ) )
{
index = index + 1;
alias = domain + "_" + index;
}
}
catch ( KeyStoreException e )
{
throw new CertificateStoreConfigException( "Unable to install a certificate into an identity store.", e );
}
// Perform the installation using the generated alias.
installCertificate( alias, pemCertificates, pemPrivateKey, passPhrase );
return alias;
}
/**
* Imports a certificate and the private key that was used to generate the certificate.
*
* This method will fail when the provided certificate does not match the domain of this XMPP service, or when the
* provided alias refers to an existing entry.
*
* @param alias the name (key) under which the certificate is to be stored in the store (cannot be null or empty). * @param alias the name (key) under which the certificate is to be stored in the store (cannot be null or empty).
* @param pemCertificates a PEM representation of the certificate or certificate chain (cannot be null or empty). * @param pemCertificates a PEM representation of the certificate or certificate chain (cannot be null or empty).
* @param pemPrivateKey a PEM representation of the private key (cannot be null or empty). * @param pemPrivateKey a PEM representation of the private key (cannot be null or empty).
......
...@@ -49,6 +49,7 @@ import org.bouncycastle.asn1.DEROutputStream; ...@@ -49,6 +49,7 @@ import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String; import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.asn1.x509.X509Extensions;
...@@ -59,7 +60,12 @@ import org.bouncycastle.openssl.PEMDecryptorProvider; ...@@ -59,7 +60,12 @@ import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.jivesoftware.openfire.keystore.CertificateStore; import org.jivesoftware.openfire.keystore.CertificateStore;
import org.jivesoftware.openfire.keystore.CertificateStoreConfigException; import org.jivesoftware.openfire.keystore.CertificateStoreConfigException;
...@@ -659,6 +665,9 @@ public class CertificateManager { ...@@ -659,6 +665,9 @@ public class CertificateManager {
if ( pemRepresentation == null || pemRepresentation.trim().isEmpty() ) { if ( pemRepresentation == null || pemRepresentation.trim().isEmpty() ) {
throw new IllegalArgumentException( "Argument 'pemRepresentation' cannot be null or an empty String."); throw new IllegalArgumentException( "Argument 'pemRepresentation' cannot be null or an empty String.");
} }
if ( passPhrase == null ) {
passPhrase = "";
}
try ( Reader reader = new StringReader( pemRepresentation.trim() )) try ( Reader reader = new StringReader( pemRepresentation.trim() ))
{ {
final Object object = new PEMParser( reader ).readObject(); final Object object = new PEMParser( reader ).readObject();
...@@ -672,6 +681,25 @@ public class CertificateManager { ...@@ -672,6 +681,25 @@ public class CertificateManager {
final PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build( passPhrase.toCharArray() ); final PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build( passPhrase.toCharArray() );
kp = converter.getKeyPair( ( (PEMEncryptedKeyPair) object ).decryptKeyPair( decProv ) ); kp = converter.getKeyPair( ( (PEMEncryptedKeyPair) object ).decryptKeyPair( decProv ) );
} }
else if ( object instanceof PKCS8EncryptedPrivateKeyInfo )
{
// Encrypted key - we will use provided password
try
{
final PKCS8EncryptedPrivateKeyInfo encryptedInfo = (PKCS8EncryptedPrivateKeyInfo) object;
final InputDecryptorProvider provider = new JceOpenSSLPKCS8DecryptorProviderBuilder().build( passPhrase.toCharArray() );
final PrivateKeyInfo privateKeyInfo = encryptedInfo.decryptPrivateKeyInfo( provider );
return converter.getPrivateKey( privateKeyInfo );
}
catch ( PKCSException | OperatorCreationException e )
{
throw new IOException( "Unable to decrypt private key.", e );
}
}
else if ( object instanceof PrivateKeyInfo )
{
return converter.getPrivateKey( (PrivateKeyInfo) object );
}
else else
{ {
// Unencrypted key - no password needed // Unencrypted key - no password needed
......
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
<% webManager.init(request, response, session, application, out ); %> <% webManager.init(request, response, session, application, out ); %>
<% // Get parameters: <% // Get parameters:
final boolean save = ParamUtils.getParameter( request, "save" ) != null; final boolean save = ParamUtils.getParameter(request, "save") != null;
final String privateKey = ParamUtils.getParameter(request, "private-key"); final String privateKey = ParamUtils.getParameter(request, "privateKey");
final String passPhrase = ParamUtils.getParameter(request, "passPhrase"); final String passPhrase = ParamUtils.getParameter(request, "passPhrase");
final String certificate = ParamUtils.getParameter(request, "certificate"); final String certificate = ParamUtils.getParameter(request, "certificate");
final String storePurposeText = ParamUtils.getParameter(request, "connectionType"); final String storePurposeText = ParamUtils.getParameter(request, "connectionType");
...@@ -31,35 +31,24 @@ ...@@ -31,35 +31,24 @@
connectionType = null; connectionType = null;
} }
pageContext.setAttribute( "connectionType", connectionType );
if (save) { if (save) {
if (privateKey == null || "".equals(privateKey)) { if (privateKey == null || privateKey.trim().isEmpty() ) {
errors.put("privateKey", "privateKey"); errors.put("privateKey", "privateKey");
} }
if (certificate == null || "".equals(certificate)) { if (certificate == null || certificate.trim().isEmpty() ) {
errors.put("certificate", "certificate"); errors.put("certificate", "certificate");
} }
if (errors.isEmpty()) { if (errors.isEmpty()) {
try { try {
final IdentityStore identityStore = XMPPServer.getInstance().getCertificateStoreManager().getIdentityStore( connectionType ); final IdentityStore identityStore = XMPPServer.getInstance().getCertificateStoreManager().getIdentityStore( connectionType );
// Create an alias for the signed certificate
String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
int index = 1;
String alias = domain + "_" + index;
while ( identityStore.getStore().containsAlias( alias )) {
index = index + 1;
alias = domain + "_" + index;
}
// Import certificate // Import certificate
identityStore.installCertificate( alias, privateKey, passPhrase, certificate ); final String alias = identityStore.installCertificate( certificate, privateKey, passPhrase);
// Log the event // Log the event
webManager.logEvent("imported SSL certificate in identity store "+ storePurposeText, "alias = "+alias); webManager.logEvent("imported SSL certificate in identity store "+ connectionType, "alias = "+alias);
response.sendRedirect("security-keystore.jsp?connectionType="+storePurposeText); response.sendRedirect("security-keystore.jsp?connectionType="+connectionType+"&addupdatesuccess=true");
return; return;
} }
catch (Exception e) { catch (Exception e) {
...@@ -68,91 +57,98 @@ ...@@ -68,91 +57,98 @@
} }
} }
} }
pageContext.setAttribute( "connectionType", connectionType );
pageContext.setAttribute( "errors", errors );
%> %>
<html> <html>
<head> <head>
<title><fmt:message key="ssl.import.certificate.keystore.${connectionType}.title"/></title> <title><fmt:message key="ssl.import.certificate.keystore.boxtitle"/></title>
<meta name="pageID" content="security-certificate-store-management"/> <meta name="pageID" content="security-certificate-store-management"/>
<meta name="subPageID" content="sidebar-certificate-store-${fn:toLowerCase(connectionType)}-identity-store"/> <meta name="subPageID" content="sidebar-certificate-store-${fn:toLowerCase(connectionType)}-identity-store"/>
</head> </head>
<body> <body>
<% pageContext.setAttribute("errors", errors); %> <c:forEach var="err" items="${errors}">
<c:forEach var="err" items="${errors}"> <admin:infobox type="error">
<admin:infobox type="error"> <c:choose>
<c:choose> <c:when test="${err.key eq 'privateKey'}">
<c:when test="${err.key eq 'privateKey'}"> <fmt:message key="ssl.import.certificate.keystore.error.private-key"/>
<fmt:message key="ssl.import.certificate.keystore.error.private-key"/> </c:when>
</c:when>
<c:when test="${err.key eq 'certificate'}">
<c:when test="${err.key eq 'certificate'}"> <fmt:message key="ssl.import.certificate.keystore.error.certificate"/>
<fmt:message key="ssl.import.certificate.keystore.error.certificate"/> </c:when>
</c:when>
<c:when test="${err.key eq 'import'}">
<c:when test="${err.key eq 'import'}"> <fmt:message key="ssl.import.certificate.keystore.error.import"/>
<fmt:message key="ssl.import.certificate.keystore.error.import"/> <c:if test="${not empty err.value}">
<c:if test="${not empty err.value}"> <fmt:message key="admin.error"/>: <c:out value="${err.value}"/>
<fmt:message key="admin.error"/>: <c:out value="${err.value}"/> </c:if>
</c:if> </c:when>
</c:when>
<c:otherwise>
<c:otherwise> <c:if test="${not empty err.value}">
<c:if test="${not empty err.value}"> <fmt:message key="admin.error"/>: <c:out value="${err.value}"/>
<fmt:message key="admin.error"/>: <c:out value="${err.value}"/> </c:if>
</c:if> (<c:out value="${err.key}"/>)
(<c:out value="${err.key}"/>) </c:otherwise>
</c:otherwise> </c:choose>
</c:choose> </admin:infobox>
</admin:infobox> </c:forEach>
</c:forEach>
<p>
<p> <fmt:message key="ssl.import.certificate.keystore.info">
<fmt:message key="ssl.import.certificate.keystore.info"> <fmt:param value="<a href='http://java.sun.com/javase/downloads/index.jsp'>" />
<fmt:param value="<a href='http://java.sun.com/javase/downloads/index.jsp'>" /> <fmt:param value="</a>" />
<fmt:param value="</a>" /> </fmt:message>
</fmt:message> </p>
</p>
<!-- BEGIN 'Import Private Key and Certificate' -->
<!-- BEGIN 'Import Private Key and Certificate' --> <form action="import-keystore-certificate.jsp?connectionType=${connectionType}" method="post">
<form action="import-keystore-certificate.jsp" method="post" name="f">
<input type="hidden" name="connectionType" value="${connectionType}"/> <c:set var="title">Private Key</c:set>
<div class="jive-contentBoxHeader"> <admin:contentBox title="${title}">
<fmt:message key="ssl.import.certificate.keystore.boxtitle" /> <p>Please provide the PEM representation of the private key that should be used to identify Openfire.</p>
</div> <table cellpadding="3" cellspacing="0" border="0">
<div class="jive-contentBox"> <tr valign="top">
<table cellpadding="3" cellspacing="0" border="0"> <td width="1%" nowrap class="c1">
<tbody> <label for="passPhrase"><fmt:message key="ssl.import.certificate.keystore.pass-phrase" /></label>
<tr valign="top"> </td>
<td width="1%" nowrap class="c1"> <td width="99%">
<fmt:message key="ssl.import.certificate.keystore.pass-phrase" /> <input type="text" size="60" maxlength="200" name="passPhrase" id="passPhrase" value="${not empty param.passPhrase ? param.passPhrase : ''}">
</td> </td>
<td width="99%"> </tr>
<input type="text" size="30" maxlength="100" name="passPhrase"> <tr valign="top">
</td> <td width="1%" nowrap class="c1">
</tr> <label for="privateKey"><fmt:message key="ssl.import.certificate.keystore.private-key" /></label>
<tr valign="top"> </td>
<td width="1%" nowrap class="c1"> <td width="99%">
<fmt:message key="ssl.import.certificate.keystore.private-key" /> <textarea name="privateKey" id="privateKey" cols="80" rows="15" wrap="virtual"><c:if test="${not empty param.privateKey}"><c:out value="${param.privateKey}"/></c:if></textarea>
</td> </td>
<td width="99%"> </tr>
<textarea name="private-key" cols="60" rows="5" wrap="virtual"></textarea> </table>
</td> </admin:contentBox>
</tr>
<tr valign="top"> <c:set var="title">Certificate</c:set>
<td width="1%" nowrap class="c1"> <admin:contentBox title="${title}">
<fmt:message key="ssl.import.certificate.keystore.certificate" /> <p>Please provide the PEM representation of the certificate chain that represents the identity of Openfire. Note that the certificate chain must be based on the private key provided above.</p>
</td> <table cellpadding="3" cellspacing="0" border="0">
<td width="99%"> <tr valign="top">
<textarea name="certificate" cols="60" rows="5" wrap="virtual"></textarea> <td width="1%" nowrap class="c1">
</td> <label for="certificate"><fmt:message key="ssl.import.certificate.keystore.certificate" /></label>
</tr> </td>
</tbody> <td width="99%">
</table> <textarea name="certificate" id="certificate" cols="80" rows="15" wrap="virtual"><c:if test="${not empty param.certificate}"><c:out value="${param.certificate}"/></c:if></textarea>
</div> </td>
<input type="submit" name="save" value="<fmt:message key="global.save" />"> </tr>
</form> </table>
<!-- END 'Import Private Key and Certificate' --> </admin:contentBox>
</body> <input type="submit" name="save" value="<fmt:message key="global.save" />">
</form>
<!-- END 'Import Private Key and Certificate' -->
</body>
</html> </html>
...@@ -279,6 +279,7 @@ ...@@ -279,6 +279,7 @@
<c:forEach items="${identities}" var="currentItem" varStatus="stat"> <c:forEach items="${identities}" var="currentItem" varStatus="stat">
<c:out value="${stat.first ? '' : ','} ${currentItem}"/> <c:out value="${stat.first ? '' : ','} ${currentItem}"/>
</c:forEach> </c:forEach>
(<c:out value="${alias}"/>)
</a> </a>
</td> </td>
<td> <td>
......
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