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
# Import keystore certificate page
ssl.import.certificate.keystore.socket.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.title=Import Signed Certificate for Socket-based Communication
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 \
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
/**
* 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.
*
* @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 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).
......
......@@ -49,6 +49,7 @@ import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.X509Extensions;
......@@ -59,7 +60,12 @@ import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
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.jivesoftware.openfire.keystore.CertificateStore;
import org.jivesoftware.openfire.keystore.CertificateStoreConfigException;
......@@ -659,6 +665,9 @@ public class CertificateManager {
if ( pemRepresentation == null || pemRepresentation.trim().isEmpty() ) {
throw new IllegalArgumentException( "Argument 'pemRepresentation' cannot be null or an empty String.");
}
if ( passPhrase == null ) {
passPhrase = "";
}
try ( Reader reader = new StringReader( pemRepresentation.trim() ))
{
final Object object = new PEMParser( reader ).readObject();
......@@ -672,6 +681,25 @@ public class CertificateManager {
final PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build( passPhrase.toCharArray() );
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
{
// Unencrypted key - no password needed
......
......@@ -14,8 +14,8 @@
<% webManager.init(request, response, session, application, out ); %>
<% // Get parameters:
final boolean save = ParamUtils.getParameter( request, "save" ) != null;
final String privateKey = ParamUtils.getParameter(request, "private-key");
final boolean save = ParamUtils.getParameter(request, "save") != null;
final String privateKey = ParamUtils.getParameter(request, "privateKey");
final String passPhrase = ParamUtils.getParameter(request, "passPhrase");
final String certificate = ParamUtils.getParameter(request, "certificate");
final String storePurposeText = ParamUtils.getParameter(request, "connectionType");
......@@ -31,35 +31,24 @@
connectionType = null;
}
pageContext.setAttribute( "connectionType", connectionType );
if (save) {
if (privateKey == null || "".equals(privateKey)) {
if (privateKey == null || privateKey.trim().isEmpty() ) {
errors.put("privateKey", "privateKey");
}
if (certificate == null || "".equals(certificate)) {
if (certificate == null || certificate.trim().isEmpty() ) {
errors.put("certificate", "certificate");
}
if (errors.isEmpty()) {
try {
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
identityStore.installCertificate( alias, privateKey, passPhrase, certificate );
final String alias = identityStore.installCertificate( certificate, privateKey, passPhrase);
// 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;
}
catch (Exception e) {
......@@ -68,18 +57,20 @@
}
}
}
pageContext.setAttribute( "connectionType", connectionType );
pageContext.setAttribute( "errors", errors );
%>
<html>
<head>
<title><fmt:message key="ssl.import.certificate.keystore.${connectionType}.title"/></title>
<head>
<title><fmt:message key="ssl.import.certificate.keystore.boxtitle"/></title>
<meta name="pageID" content="security-certificate-store-management"/>
<meta name="subPageID" content="sidebar-certificate-store-${fn:toLowerCase(connectionType)}-identity-store"/>
</head>
<body>
</head>
<body>
<% pageContext.setAttribute("errors", errors); %>
<c:forEach var="err" items="${errors}">
<c:forEach var="err" items="${errors}">
<admin:infobox type="error">
<c:choose>
<c:when test="${err.key eq 'privateKey'}">
......@@ -105,54 +96,59 @@
</c:otherwise>
</c:choose>
</admin:infobox>
</c:forEach>
</c:forEach>
<p>
<p>
<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>" />
</fmt:message>
</p>
<!-- BEGIN 'Import Private Key and Certificate' -->
<form action="import-keystore-certificate.jsp" method="post" name="f">
<input type="hidden" name="connectionType" value="${connectionType}"/>
<div class="jive-contentBoxHeader">
<fmt:message key="ssl.import.certificate.keystore.boxtitle" />
</div>
<div class="jive-contentBox">
</p>
<!-- BEGIN 'Import Private Key and Certificate' -->
<form action="import-keystore-certificate.jsp?connectionType=${connectionType}" method="post">
<c:set var="title">Private Key</c:set>
<admin:contentBox title="${title}">
<p>Please provide the PEM representation of the private key that should be used to identify Openfire.</p>
<table cellpadding="3" cellspacing="0" border="0">
<tbody>
<tr valign="top">
<td width="1%" nowrap class="c1">
<fmt:message key="ssl.import.certificate.keystore.pass-phrase" />
<label for="passPhrase"><fmt:message key="ssl.import.certificate.keystore.pass-phrase" /></label>
</td>
<td width="99%">
<input type="text" size="30" maxlength="100" name="passPhrase">
<input type="text" size="60" maxlength="200" name="passPhrase" id="passPhrase" value="${not empty param.passPhrase ? param.passPhrase : ''}">
</td>
</tr>
<tr valign="top">
<td width="1%" nowrap class="c1">
<fmt:message key="ssl.import.certificate.keystore.private-key" />
<label for="privateKey"><fmt:message key="ssl.import.certificate.keystore.private-key" /></label>
</td>
<td width="99%">
<textarea name="private-key" cols="60" rows="5" wrap="virtual"></textarea>
<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>
</tr>
</table>
</admin:contentBox>
<c:set var="title">Certificate</c:set>
<admin:contentBox title="${title}">
<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>
<table cellpadding="3" cellspacing="0" border="0">
<tr valign="top">
<td width="1%" nowrap class="c1">
<fmt:message key="ssl.import.certificate.keystore.certificate" />
<label for="certificate"><fmt:message key="ssl.import.certificate.keystore.certificate" /></label>
</td>
<td width="99%">
<textarea name="certificate" cols="60" rows="5" wrap="virtual"></textarea>
<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>
</td>
</tr>
</tbody>
</table>
</div>
</admin:contentBox>
<input type="submit" name="save" value="<fmt:message key="global.save" />">
</form>
<!-- END 'Import Private Key and Certificate' -->
</form>
<!-- END 'Import Private Key and Certificate' -->
</body>
</body>
</html>
......@@ -279,6 +279,7 @@
<c:forEach items="${identities}" var="currentItem" varStatus="stat">
<c:out value="${stat.first ? '' : ','} ${currentItem}"/>
</c:forEach>
(<c:out value="${alias}"/>)
</a>
</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