Commit 994bb2be authored by Guus der Kinderen's avatar Guus der Kinderen Committed by daryl herzmann

New Certificatemanager plugin (#941)

* OF-1449: Ant build should allow taglibs to be used in plugins.

* First implementation of the Certificate Manager plugin

* Now depend on 4.2.0 (which is in central repo).

* Ensure that the BounceCastle security provider is available.
parent 71e79840
......@@ -1489,6 +1489,7 @@
</fileset>
<fileset dir="${web.dir}">
<include name="WEB-INF/**/*.*"/>
<include name="META-INF/**/*.*"/>
<exclude name="WEB-INF/web.xml"/>
<exclude name="WEB-INF/classes/openfire_init.xml"/>
<exclude name="WEB-INF/tmp/**/*.*"/>
......
......@@ -29,6 +29,7 @@ import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.*;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
......@@ -85,6 +86,11 @@ public class CertificateManager {
static {
if ( Security.getProvider( BouncyCastleProvider.PROVIDER_NAME ) == null )
{
java.security.Security.addProvider( new BouncyCastleProvider() );
}
String serverCertIdentityMapList = JiveGlobals.getProperty("provider.serverCertIdentityMap.classList");
if (serverCertIdentityMapList != null) {
StringTokenizer st = new StringTokenizer(serverCertIdentityMapList, " ,\t\n\r\f");
......
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Certificate Manager Plugin Changelog</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H2 {
font-size : 10pt;
font-weight : bold;
padding-left : 1em;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
</style>
</head>
<body>
<h1>
Certificate Manager Plugin Changelog
</h1>
<p><b>1.0.0</b> -- November 24, 2017</p>
<ul>
<li>Initial release.</li>
</ul>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>org.igniterealtime.openfire.plugins.certificatemanager.CertificateManagerPlugin</class>
<name>Certificate Manager</name>
<description>Adds certificate management features.</description>
<author>Guus der Kinderen</author>
<version>1.0.0</version>
<date>24/11/2017</date>
<minServerVersion>4.0.0</minServerVersion>
<adminconsole>
<tab id="tab-server">
<sidebar id="sidebar-certificates">
<item id="certificate-management"
name="${admin.sidebar.tls.item.certificate-management.name}"
description="${admin.sidebar.tls.item.certificate-management.description}"
url="certificate-management.jsp"/>
</sidebar>
</tab>
</adminconsole>
</plugin>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>plugins</artifactId>
<groupId>org.igniterealtime.openfire</groupId>
<version>4.2.0</version>
</parent>
<groupId>org.igniterealtime.openfire.plugins</groupId>
<artifactId>certificatemanager</artifactId>
<name>Certificate Manager Plugin</name>
<version>1.0.0</version>
<build>
<sourceDirectory>src/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<!-- Compiles the Openfire Admin Console JSP pages. -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jspc-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Certificate Manager Plugin Readme</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H3 {
font-size : 10pt;
font-style: italic;
color: #004444;
}
H2 {
font-size : 10pt;
font-weight : bold;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
#datatable TH {
color : #fff;
background-color : #2A448C;
text-align : left;
}
#datatable TD {
background-color : #FAF6EF;
}
#datatable .name {
background-color : #DCE2F5;
}
</style>
</head>
<body>
<h1>
Certificate Manager Plugin Readme
</h1>
<h2>Overview</h2>
<p>
The Certificate Manager plugin adds functionality to Openfire that relates to administration of its certificate
stores.
</p>
<h2>Installation</h2>
<p>
Copy certificatemanager.jar into the plugins directory of your Openfire installation. The plugin will then be
automatically deployed. To upgrade to a new version, copy the new certificatemanager.jar file over the existing file.
</p>
<h2>Configuration</h2>
<p>
The plugin is configured via the Openfire Admin Console. After installation, a new Admin Console page is available.
The page can be found under in the "Server", "TLS/SSL Certificates" tab. There, this plugin adds a new item on the
side-bar, named "Management".
</p>
<h2>Using the Plugin</h2>
<p>
This plugin allows Openfire to monitor file changes in a particular directory (which, by default, is
<tt>&lt;OPENFIRE_HOME&gt;/resources/security/hotdeploy/</tt>). Whenever a set of files is found that consists of a
PEM-encoded private key and a PEM-encoded certificate chain, this plugin will attempt to install them in the
Openfire identity store.
</p>
<h3>Let's Encrypt / Certbot integration</h3>
<p>
The directory watcher mechanism, described above, is an excellent way to make use of the periodically updated data
that's generated by Let's Encrypts Certbot. Openfire can be configured to monitor the directory in which certbot
places renewed data. This can, however, lead to file-permission issues (the certbot directories are often not
readable by the Openfire process). An alternative solution is to use certbots post-hook to copy the data into a
directory that can be used by Openfire.
</p>
<h2>Attribution</h2>
<p>
The icon used in this plugin is designed by <a href="https://www.flaticon.com/">Flaticon</a>.
</p>
</body>
</html>
global.save_settings=Save Settings
global.csrf.failed=CSRF Error: No changes made, you'll need to retry.
admin.error=Unable to save settings
error.path.exist=That directory cannot be used: it does not exist.
error.path.readable=That directory cannot be used: it cannot be read by Openfire.
error.path.writable=That directory cannot be used: Openfire cannot modify its content (and thus cannot remove old data)
error.path.invalid=That's an invalid directory.
admin.sidebar.tls.item.certificate-management.name=Management
admin.sidebar.tls.item.certificate-management.description=Certificate Management Configuration.
certificate-management.page.title=Certificate Management Configuration
certificate-management.saved_successfully=Configuration Changes Saved.
certificate-management.info=Use the forms below to configure management tasks that operate on Openfires identity stores.
certificate-management.directorywatcher.boxtitle=Directory Watcher (hot-deploy)
certificate-management.directorywatcher.info=New or updated (PEM-encoded) certificate and private key files found in a directory are automatically installed.
certificate-management.directorywatcher.label_enable=Enabled
certificate-management.directorywatcher.label_path=Directory
certificate-management.directorywatcher.label_replace=Remove old identity store content when installing new data.
certificate-management.directorywatcher.label_delete=Delete files from the hot-deploy directory after installation.
/*
* Copyright (C) 2017 Ignite Realtime Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.openfire.plugins.certificatemanager;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import java.io.File;
import java.util.Map;
/**
* The Certificate Manager plugin adds additional administrative functionality to the certificate stores that are used
* by Openfire.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class CertificateManagerPlugin implements Plugin, PropertyEventListener
{
final DirectoryWatcher directoryWatcher = new DirectoryWatcher();
@Override
public void initializePlugin( PluginManager manager, File pluginDirectory )
{
directoryWatcher.start();
PropertyEventDispatcher.addListener( this );
}
@Override
public void destroyPlugin()
{
PropertyEventDispatcher.removeListener( this );
directoryWatcher.stop();
}
protected void applyPropertyChange( String property, Map<String, Object> params )
{
switch ( property )
{
case DirectoryWatcher.PROPERTY_ENABLED: // intended fall-through.
case DirectoryWatcher.PROPERTY_WATCHED_PATH:
directoryWatcher.stop();
directoryWatcher.start();
break;
}
}
@Override
public void propertySet( String property, Map<String, Object> params )
{
applyPropertyChange( property, params );
}
@Override
public void propertyDeleted( String property, Map<String, Object> params )
{
applyPropertyChange( property, params );
}
@Override
public void xmlPropertySet( String property, Map<String, Object> params )
{
applyPropertyChange( property, params );
}
@Override
public void xmlPropertyDeleted( String property, Map<String, Object> params )
{
applyPropertyChange( property, params );
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
</web-app>
<!--
- Copyright (C) 2017 Ignite Realtime Foundation. All rights reserved.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-->
<%@ page errorPage="error.jsp" %>
<%@ page import="org.igniterealtime.openfire.plugins.certificatemanager.DirectoryWatcher" %>
<%@ page import="org.jivesoftware.util.CookieUtils" %>
<%@ page import="org.jivesoftware.util.JiveGlobals" %>
<%@ page import="org.jivesoftware.util.ParamUtils" %>
<%@ page import="org.jivesoftware.util.StringUtils" %>
<%@ page import="java.net.URLDecoder" %>
<%@ page import="java.nio.file.Files" %>
<%@ page import="java.nio.file.InvalidPathException" %>
<%@ page import="java.nio.file.Path" %>
<%@ page import="java.nio.file.Paths" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="admin" prefix="admin" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init(request, response, session, application, out ); %>
<%
Map<String, String> errors = new HashMap<>();
boolean update = request.getParameter("update") != null;
final Cookie csrfCookie = CookieUtils.getCookie( request, "csrf");
String csrfParam = ParamUtils.getParameter( request, "csrf");
if (update) {
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) {
update = false;
errors.put( "csrf", "csrf value is invalid (reload page and try again)." );
}
}
csrfParam = StringUtils.randomString( 15 );
CookieUtils.setCookie(request, response, "csrf", csrfParam, -1);
pageContext.setAttribute("csrf", csrfParam);
if ( update && errors.isEmpty() )
{
final boolean enabled = ParamUtils.getBooleanParameter( request, "directorywatcherEnabled" );
final boolean replace = ParamUtils.getBooleanParameter( request, "directorywatcherReplace" );
final boolean delete = ParamUtils.getBooleanParameter( request, "directorywatcherDelete" );
String path = ParamUtils.getParameter( request, "directorywatcherPath" );
if ( path != null && !path.isEmpty() )
{
path = URLDecoder.decode( path, "UTF-8" );
}
else
{
path = DirectoryWatcher.PROPERTY_WATCHED_PATH_DEFAULT;
}
try
{
final Path dir = Paths.get( path );
if ( !Files.exists( dir ) )
{
errors.put( "path", "does not exist" );
}
else if ( !Files.isReadable( dir ) )
{
errors.put( "path", "not readable" );
}
else if ( delete && !Files.isWritable( dir ) )
{
errors.put( "path", "not writable" );
}
}
catch ( InvalidPathException e )
{
errors.put( "path", "invalid path" );
}
if ( errors.isEmpty() )
{
JiveGlobals.setProperty( DirectoryWatcher.PROPERTY_ENABLED, Boolean.toString( enabled ) );
JiveGlobals.setProperty( DirectoryWatcher.PROPERTY_REPLACE, Boolean.toString( replace ) );
JiveGlobals.setProperty( DirectoryWatcher.PROPERTY_DELETE, Boolean.toString( delete ) );
JiveGlobals.setProperty( DirectoryWatcher.PROPERTY_WATCHED_PATH, path );
response.sendRedirect("certificate-management.jsp?success=true");
return;
}
}
// Read all updated values from the properties.
pageContext.setAttribute( "directorywatcherIsEnabled", JiveGlobals.getBooleanProperty( DirectoryWatcher.PROPERTY_ENABLED, DirectoryWatcher.PROPERTY_ENABLED_DEFAULT ) );
pageContext.setAttribute( "directorywatcherPath", JiveGlobals.getProperty( DirectoryWatcher.PROPERTY_WATCHED_PATH, DirectoryWatcher.PROPERTY_WATCHED_PATH_DEFAULT ) );
pageContext.setAttribute( "directorywatcherIsReplace", JiveGlobals.getBooleanProperty( DirectoryWatcher.PROPERTY_REPLACE, DirectoryWatcher.PROPERTY_REPLACE_DEFAULT ) );
pageContext.setAttribute( "directorywatcherIsDelete", JiveGlobals.getBooleanProperty( DirectoryWatcher.PROPERTY_DELETE, DirectoryWatcher.PROPERTY_DELETE_DEFAULT ) );
pageContext.setAttribute( "errors", errors );
%>
<html>
<head>
<title><fmt:message key="certificate-management.page.title"/></title>
<meta name="pageID" content="certificate-management"/>
</head>
<body>
<c:forEach var="err" items="${errors}">
<admin:infobox type="error">
<c:choose>
<c:when test="${err.key eq 'csrf'}">
<fmt:message key="global.csrf.failed"/>
</c:when>
<c:when test="${err.key eq 'path' and err.value eq 'does not exist'}">
<fmt:message key="error.path.exist"/>
</c:when>
<c:when test="${err.key eq 'path' and err.value eq 'not readable'}">
<fmt:message key="error.path.readable"/>
</c:when>
<c:when test="${err.key eq 'path' and err.value eq 'not writable'}">
<fmt:message key="error.path.writable"/>
</c:when>
<c:when test="${err.key eq 'path' and err.value eq 'invalid path'}">
<fmt:message key="error.path.invalid"/>
</c:when>
<c:otherwise>
<c:if test="${not empty err.value}">
<fmt:message key="admin.error"/>: <c:out value="${err.value}"/>
</c:if>
(<c:out value="${err.key}"/>)
</c:otherwise>
</c:choose>
</admin:infobox>
</c:forEach>
<c:if test="${empty errors and param.success}">
<admin:infoBox type="success">
<fmt:message key="certificate-management.saved_successfully"/>
</admin:infoBox>
</c:if>
<p>
<fmt:message key="certificate-management.info"/>
</p>
<form action="certificate-management.jsp" method="post">
<input type="hidden" name="csrf" value="${csrf}">
<fmt:message key="certificate-management.directorywatcher.boxtitle" var="directorywatcherboxtitle"/>
<admin:contentBox title="${directorywatcherboxtitle}">
<c:set var="escaped">
<c:out value="${directorywatcherPath}"/>
</c:set>
<p><fmt:message key="certificate-management.directorywatcher.info"/></p>
<table cellpadding="3" cellspacing="0" border="0">
<tr valign="middle">
<td colspan="2"><input type="checkbox" name="directorywatcherEnabled" id="directorywatcherEnabled" onclick="applyDisplayable('directorywatcher')" ${directorywatcherIsEnabled ? 'checked' : ''}/><label for="directorywatcherEnabled"><fmt:message key="certificate-management.directorywatcher.label_enable"/></label></td>
</tr>
<tr valign="middle">
<td width="1%" nowrap><label for="directorywatcherPath"><fmt:message key="certificate-management.directorywatcher.label_path"/></label></td>
<td width="99%"><input type="text" size="60" name="directorywatcherPath" id="directorywatcherPath" value="${escaped}"/></td>
</tr>
<%--<tr valign="middle">--%>
<%--<td colspan="2"><input type="checkbox" name="directorywatcherReplace" id="directorywatcherReplace" ${directorywatcherIsReplace ? 'checked' : ''}/><label for="directorywatcherReplace"><fmt:message key="certificate-management.directorywatcher.label_replace"/></label></td>--%>
<%--</tr>--%>
<tr valign="middle">
<td colspan="2"><input type="checkbox" name="directorywatcherDelete" id="directorywatcherDelete" ${directorywatcherIsDelete ? 'checked' : ''}/><label for="directorywatcherDelete"><fmt:message key="certificate-management.directorywatcher.label_delete"/></label></td>
</tr>
</table>
</admin:contentBox>
<input type="submit" name="update" value="<fmt:message key="global.save_settings" />">
</form>
</body>
</html>
......@@ -97,6 +97,7 @@
<module>broadcast</module>
<module>callbackOnOffline</module>
<module>candy</module>
<module>certificateManager</module>
<module>clientControl</module>
<!-- Needs coherence.jar -->
<!--<module>clustering</module>-->
......
......@@ -14,17 +14,18 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
/**
* Unit test to validate the functionality of @{link CertificateManager}.
......@@ -271,4 +272,41 @@ public class CertificateManagerTest
assertTrue( serverIdentities.contains( subjectAltNameXmppAddr ));
assertFalse( serverIdentities.contains( subjectCommonName ) );
}
/**
* Asserts that {@link CertificateManager#parsePrivateKey(InputStream, String)} can parse a password-less private
* key PEM file.
*/
@Test
public void testParsePrivateKey() throws Exception
{
// Setup fixture.
try ( final InputStream stream = getClass().getResourceAsStream( "/privatekey.pem" ) )
{
// Execute system under test.
final PrivateKey result = CertificateManager.parsePrivateKey( stream, "" );
// Verify result.
assertNotNull( result );
}
}
/**
* Asserts that {@link CertificateManager#parseCertificates(InputStream)} can parse a PEM file that contains a
* certificate chain
*/
@Test
public void testParseFullChain() throws Exception
{
// Setup fixture.
try ( final InputStream stream = getClass().getResourceAsStream( "/fullchain.pem" ) )
{
// Execute system under test.
final Collection<X509Certificate> result = CertificateManager.parseCertificates( stream );
// Verify result.
assertNotNull( result );
assertEquals( 2, result.size() );
}
}
}
-----BEGIN CERTIFICATE-----
MIIFHzCCBAegAwIBAgISA0l6n7e5Qg0VzOFCPwfYW/HIMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xNzEwMTkwMzU5MDNaFw0x
ODAxMTcwMzU5MDNaMB0xGzAZBgNVBAMTEmVuY3J5cHQuc3VpdGUud2lraTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMBg80/ajIQPqIvxBJ0+Lx8zvvch
uxllTJ8qN+3aCVraCFKi6xjYRzcXBYKVLdXDZZeVXuv9Iwz5lvWv8WKB5qYHwFTY
ejeRqB2LB1IRWYlafsGRA/cJBiOJcQNt29KCqIgUIJ/3NHe26o5/K69swVD3lSum
4xnZw2mgpMNSuVmKsHNmhZtFuG4ywJQltPCInAVvfwU8VinAtguVJmATdMgPp2e7
68uQWONdfdeBizJcH4TqyM8aE2GEImUWbZfgoVjY6/vGQ5BGmN5FAMacG1FHQFRH
hXguagknPvlWiJOIChBpsqP1zDlqlEGoiRQ9qy2pL+bsW0Vfy+R/EzUKoo0CAwEA
AaOCAiowggImMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUKo+1IPSPbtpOe1R3xV8S
hycj/T8wHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUH
AQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5
cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5
cHQub3JnLzA1BgNVHREELjAsghJlbmNyeXB0LnN1aXRlLndpa2mCFnd3dy5lbmNy
eXB0LnN1aXRlLndpa2kwgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYGCysGAQQB
gt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9y
ZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBvbmx5IGJl
IHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBpbiBhY2Nv
cmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBhdCBodHRw
czovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOC
AQEAOzIti6CUY/pXwVQEzVZ7cV996PigRAliqwMCmRl33uxYzoYhmjs/z0LY6HZz
5X5y57GT3QdH3Ktbkvy7jzAWHl2y/KSmRmOjBt8a9e0GFqZ66RmelBs9NYjjyAZF
yGYsxMzTk43xaUZKcgmKAUMKD0uTAO2ISZMc0UVXM+vkujWsN1LZNRs5xZRqn6/X
RagfLJfPKUpS+wMiLGfJd616p8VTPLoEIQfkd4Bb/Qnak8JgPK0nl4bzXxdyeO/J
GGMpyoh58WVgZPBKMHWClqFAcMXbfou5I6Z+XdDRVmHUw4C1OzODB+6BOppTZLPK
c8zayO5UbwIXZ6uBahaM3rrZDg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA0e9yUwFH5e92JHv2H0NWaHN10HZ+r2Q9TJGvhhIzCyAOCe+A
a6ZNHOjdKgzcXL6sEGdXTYLM9/v9zXWtNGQOZQvXTbVwxYsd3JxcJrz/w4JhbFtJ
A1QyGOWoGVJIMbVGIRXM6bH072uan1s4sziYgrw8UO5BBRBiMg3eux9Zgy3jIwfl
QWUCAa6ygJzRAqfmiQfDh37OVYJ+qAy3HSEqHC317JjnYc7ajZGsL+zDTEfTd0iP
uHWgVSNcW7r7xaZUZSOK64g8JOkouKcDqsAn6DD+YiZBUPzhPVW6ou02tWoyzRbU
rsu8hkqiTUmr8AI5dChy7XscaDj/Um5OHrb3ewIDAQABAoIBAQDI3ECWCnOwHBz9
a11hLBxQc6gVfEpZpZ7A0+MMc/1Cd7j26KAGX2cIcgSpZwSv9/7I8xckojFaU0/S
dPTzW4gqRdoKD7+GQkSf/zpsFgjgFq7/7Zc+CCCmkZT0JTXN2ZLvtNwJ/KpNKLcY
IT9uuoexDkjnZg3vum05Vuv0PVp3loJ+qJsAqCPOajxxLfPxhDpR3wIZ9IdnhIr4
l5KLLxBWqMNwPnoz3B2MV0dB4WwmAS3s7LzSon3dACGZ6/0PGe+x2/7fna3d0/MT
1Cp6rgah56vVhOC9x4vXenIPyhd0Vb1u6fWfjmr9bsxeOw9qnhGf+dW73sl76h8b
K19cK8sBAoGBAP27eDWXnujkDzEKboLweVEdxtyWv42n9nSKb9Y/wiGxIEMaCObZ
Hxlu+I+1IFK1jpHeKOgPPt0zv+j+qBylRHIubJzCqL6roOHnSmIyo068huiqi1xV
s+i77UKf2cwhFufIYQaSyM8TEG15+IAkIPyRR/Ug4CsNXNozYAQzQLEDAoGBANPP
xWhK+kXVxqextntoPnle3pM5r4EaMgO5yglJzOyROu0NvLSeizuyJH7RgwZorpez
iS0/p7QK/a72VLA+qd/QXxFzroIhSP3SDQJY+/xQg2xIB+SDcv/TXM1plT+aeicj
HtNcmWNf6syVGXXZaXOBCrtgFex6Mz2Yg29RpYopAoGAOpizzzSfx0r70yiEkwq0
Ca6lwUWzISS7/vgFkeYRkDRWrghyK9XSn84H6zA6Mb+qGDfFHKqAqxcLZ+R/sHXe
U7wy6xTtYXNDaUg/3mOxYTqasVrJal6EeOgFiBBgfQrbPlYBSkJVy7SsY4v6fqVZ
VNq8UnjwLCEe42lf8Dhh5T8CgYAcxiH0LBNZv1PQXiUDGMM767Zi27hK2osm1Gts
3Zi4HnaZ037qLouNnYK1s7KQAVKOizY8bmRS4hdaWF5i3vBm1kDD9yoixXDMAmgA
BKSevzVLVXG1CmJp/IWj5g7/Z/XmMTAz2GWCCXq1NiASUWmw5jvV/pSb3nHtkTVx
vu61GQKBgGW7ciMhlTKojv/1xrKXCVYm0MYyxxCEdUvfsryrRfIotBwY6tGo6amb
DJwI2feMoUh6hixc2XZ/gEiIJZJUYyyURnAF9hSFAJmOELJPfR9TGHrIgMKsgs7O
dp8YrHWz9N4XdfBoHseYAX78THQbL6fP55LThFSRZt8iMVG4551v
-----END RSA PRIVATE KEY-----
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