浏览代码

refactor self cert generator for testability and maintenance

Jason Rivard 5 年之前
父节点
当前提交
5ec4b508fd

+ 5 - 0
onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java

@@ -72,6 +72,10 @@ public class TomcatOnejarRunner
         }
         catch ( final Exception e )
         {
+            if ( e instanceof InvocationTargetException )
+            {
+                throw new OnejarException( "error generating keystore: " + e.getCause().getMessage() );
+            }
             throw new OnejarException( "error generating keystore: " + e.getMessage() );
         }
 
@@ -160,6 +164,7 @@ public class TomcatOnejarRunner
         connector.setScheme( "https" );
         connector.addUpgradeProtocol( new Http2Protocol() );
         connector.setAttribute( "SSLEnabled", "true" );
+       // connector.setAttribute( "truststoreType", "PKCS12" );
         connector.setAttribute( "keystoreFile", onejarConfig.getKeystoreFile().getAbsolutePath() );
         connector.setAttribute( "keystorePass", onejarConfig.getKeystorePass() );
         connector.setAttribute( "keyAlias", OnejarMain.KEYSTORE_ALIAS );

+ 2 - 238
server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java

@@ -20,23 +20,7 @@
 
 package password.pwm.util.secure;
 
-import org.bouncycastle.asn1.x500.X500NameBuilder;
-import org.bouncycastle.asn1.x500.style.BCStyle;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
-import org.bouncycastle.asn1.x509.Extension;
-import org.bouncycastle.asn1.x509.KeyPurposeId;
-import org.bouncycastle.asn1.x509.KeyUsage;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import password.pwm.AppAttribute;
-import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
 import password.pwm.bean.PrivateKeyCertificate;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
@@ -48,51 +32,22 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
-import password.pwm.util.java.PwmDateFormat;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.self.SelfCertFactory;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.math.BigInteger;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
 import java.security.KeyStore;
 import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Security;
 import java.security.cert.X509Certificate;
-import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.Enumeration;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
 public class HttpsServerCertificateManager
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( HttpsServerCertificateManager.class );
 
-    private static volatile boolean bouncyCastleInitialized;
-
-    private static synchronized void initBouncyCastleProvider( )
-    {
-        if ( !bouncyCastleInitialized )
-        {
-            Security.addProvider( new BouncyCastleProvider() );
-            bouncyCastleInitialized = true;
-        }
-    }
-
-
     public static KeyStore keyStoreForApplication(
             final PwmApplication pwmApplication,
             final PasswordData passwordData,
@@ -151,12 +106,9 @@ public class HttpsServerCertificateManager
     private static KeyStore makeSelfSignedCert( final PwmApplication pwmApplication, final PasswordData password, final String alias )
             throws PwmUnrecoverableException
     {
-        final Configuration configuration = pwmApplication.getConfig();
-
         try
         {
-            final SelfCertGenerator selfCertGenerator = new SelfCertGenerator( configuration );
-            return selfCertGenerator.makeSelfSignedCert( pwmApplication, password, alias );
+            return SelfCertFactory.getExistingCertOrGenerateNewCert( pwmApplication, password, alias );
         }
         catch ( final Exception e )
         {
@@ -164,194 +116,6 @@ public class HttpsServerCertificateManager
         }
     }
 
-    public static class StoredCertData implements Serializable
-    {
-        private final X509Certificate x509Certificate;
-        private String keypairb64;
-
-        public StoredCertData( final X509Certificate x509Certificate, final KeyPair keypair )
-                throws IOException
-        {
-            this.x509Certificate = x509Certificate;
-            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            final ObjectOutputStream oos = new ObjectOutputStream( baos );
-            oos.writeObject( keypair );
-            final byte[] ba = baos.toByteArray();
-            keypairb64 = StringUtil.base64Encode( ba );
-        }
-
-        public X509Certificate getX509Certificate( )
-        {
-            return x509Certificate;
-        }
-
-        public KeyPair getKeypair( )
-                throws IOException, ClassNotFoundException
-        {
-            final byte[] ba = StringUtil.base64Decode( keypairb64 );
-            final ByteArrayInputStream bais = new ByteArrayInputStream( ba );
-            final ObjectInputStream ois = new ObjectInputStream( bais );
-            return ( KeyPair ) ois.readObject();
-        }
-    }
-
-
-    public static class SelfCertGenerator
-    {
-        private final Configuration config;
-
-        public SelfCertGenerator( final Configuration config )
-        {
-            this.config = config;
-        }
-
-        public KeyStore makeSelfSignedCert( final PwmApplication pwmApplication, final PasswordData password, final String alias )
-                throws Exception
-        {
-            final String cnName = makeSubjectName();
-            final KeyStore keyStore = KeyStore.getInstance( "jks" );
-            keyStore.load( null, password.getStringValue().toCharArray() );
-            StoredCertData storedCertData = pwmApplication.readAppAttribute( AppAttribute.HTTPS_SELF_CERT, StoredCertData.class );
-            if ( storedCertData != null )
-            {
-                if ( !cnName.equals( storedCertData.getX509Certificate().getSubjectDN().getName() ) )
-                {
-                    LOGGER.info( () -> "replacing stored self cert, subject name does not match configured site url" );
-                    storedCertData = null;
-                }
-                else if ( storedCertData.getX509Certificate().getNotBefore().after( new Date() ) )
-                {
-                    LOGGER.info( () -> "replacing stored self cert, not-before date is in the future" );
-                    storedCertData = null;
-                }
-                else if ( storedCertData.getX509Certificate().getNotAfter().before( new Date() ) )
-                {
-                    LOGGER.info( () -> "replacing stored self cert, not-after date is in the past" );
-                    storedCertData = null;
-                }
-            }
-
-            if ( storedCertData == null )
-            {
-                storedCertData = makeSelfSignedCert( cnName );
-                pwmApplication.writeAppAttribute( AppAttribute.HTTPS_SELF_CERT, storedCertData );
-            }
-
-            keyStore.setKeyEntry(
-                    alias,
-                    storedCertData.getKeypair().getPrivate(),
-                    password.getStringValue().toCharArray(),
-                    new X509Certificate[]
-                            {
-                                    storedCertData.getX509Certificate(),
-                            }
-            );
-            return keyStore;
-        }
-
-        public String makeSubjectName( )
-                throws Exception
-        {
-            String cnName = PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com";
-            {
-                final String siteURL = config.readSettingAsString( PwmSetting.PWM_SITE_URL );
-                if ( siteURL != null && !siteURL.isEmpty() )
-                {
-                    try
-                    {
-                        final URI uri = new URI( siteURL );
-                        if ( uri.getHost() != null && !uri.getHost().isEmpty() )
-                        {
-                            cnName = uri.getHost();
-                        }
-                    }
-                    catch ( final URISyntaxException e )
-                    {
-                        // disregard
-                    }
-                }
-            }
-            return cnName;
-        }
-
-
-        public StoredCertData makeSelfSignedCert( final String cnName )
-                throws Exception
-        {
-            initBouncyCastleProvider();
-
-            LOGGER.debug( () -> "creating self-signed certificate with cn of " + cnName );
-            final KeyPair keyPair = generateRSAKeyPair( config );
-            final long futureSeconds = Long.parseLong( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_FUTURESECONDS ) );
-            final X509Certificate certificate = generateV3Certificate( keyPair, cnName, futureSeconds );
-            return new StoredCertData( certificate, keyPair );
-        }
-
-
-        public static X509Certificate generateV3Certificate( final KeyPair pair, final String cnValue, final long futureSeconds )
-                throws Exception
-        {
-            final X500NameBuilder subjectName = new X500NameBuilder( BCStyle.INSTANCE );
-            subjectName.addRDN( BCStyle.CN, cnValue );
-
-            final BigInteger serialNumber = makeSerialNumber();
-
-
-            // 2 days in the past
-            final Date notBefore = new Date( System.currentTimeMillis() - TimeUnit.DAYS.toMillis( 2 ) );
-
-            final Date notAfter = new Date( System.currentTimeMillis() + ( futureSeconds * 1000 ) );
-
-            final X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
-                    subjectName.build(),
-                    serialNumber,
-                    notBefore,
-                    notAfter,
-                    subjectName.build(),
-                    pair.getPublic()
-            );
-
-            // false == not a CA
-            final BasicConstraints basic = new BasicConstraints( false );
-
-            // OID, critical, ASN.1 encoded value
-            certGen.addExtension( Extension.basicConstraints, true, basic.getEncoded() );
-
-            // sign and key encipher
-            final KeyUsage keyUsage = new KeyUsage( KeyUsage.digitalSignature | KeyUsage.keyEncipherment );
-
-            // OID, critical, ASN.1 encoded value
-            certGen.addExtension( Extension.keyUsage, true, keyUsage.getEncoded() );
-
-            // server authentication
-            final ExtendedKeyUsage extKeyUsage = new ExtendedKeyUsage( KeyPurposeId.id_kp_serverAuth );
-
-            // OID, critical, ASN.1 encoded value
-            certGen.addExtension( Extension.extendedKeyUsage, true, extKeyUsage.getEncoded() );
-
-            final ContentSigner sigGen = new JcaContentSignerBuilder( "SHA256WithRSAEncryption" ).setProvider( "BC" ).build( pair.getPrivate() );
-
-            return new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate( certGen.build( sigGen ) );
-        }
-
-        private static BigInteger makeSerialNumber()
-        {
-            final PwmDateFormat formatter = PwmDateFormat.newPwmDateFormat( "yyyyMMddhhmmss" );
-            final String serNumStr = formatter.format( Instant.now() );
-            return new BigInteger( serNumStr );
-        }
-
-        static KeyPair generateRSAKeyPair( final Configuration config )
-                throws Exception
-        {
-            final int keySize = Integer.parseInt( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_KEY_SIZE ) );
-            final String keyAlg = config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_ALG );
-            final KeyPairGenerator kpGen = KeyPairGenerator.getInstance( keyAlg, "BC" );
-            kpGen.initialize( keySize, new SecureRandom() );
-            return kpGen.generateKeyPair();
-        }
-    }
-
 
     public enum KeyStoreFormat
     {

+ 151 - 0
server/src/main/java/password/pwm/util/secure/self/SelfCertFactory.java

@@ -0,0 +1,151 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * 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 password.pwm.util.secure.self;
+
+import password.pwm.AppAttribute;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.PasswordData;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.SecureService;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Optional;
+
+public class SelfCertFactory
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( SelfCertFactory.class );
+
+    public static KeyStore getExistingCertOrGenerateNewCert( final PwmApplication pwmApplication, final PasswordData password, final String alias )
+        throws Exception
+    {
+        final Settings settings = Settings.fromConfiguration( pwmApplication.getConfig() );
+
+        final Optional<StoredCertData> existingCert = loadExistingStoredCert( pwmApplication );
+        if ( existingCert.isPresent() )
+        {
+            if ( evaluateExistingStoredCert( existingCert.get(), settings ) )
+            {
+                return storedCertToKeyStore( existingCert.get(), alias, password );
+            }
+        }
+
+        return generateNewCert(
+            settings,
+            pwmApplication.getSecureService(),
+            password,
+            alias );
+    }
+
+    public static KeyStore generateNewCert(
+        final Settings settings,
+        final SecureService secureService,
+        final PasswordData password,
+        final String alias
+    )
+        throws Exception
+    {
+        final SelfCertGenerator selfCertGenerator = new SelfCertGenerator(
+            settings,
+            secureService );
+        final StoredCertData storedCertData = selfCertGenerator.generateNewCertificate( makeSubjectName( settings ) );
+        return storedCertToKeyStore( storedCertData, alias, password );
+    }
+
+    private static Optional<StoredCertData> loadExistingStoredCert( final PwmApplication pwmApplication )
+    {
+        final StoredCertData storedCertData = pwmApplication.readAppAttribute( AppAttribute.HTTPS_SELF_CERT, StoredCertData.class );
+        return Optional.ofNullable( storedCertData );
+    }
+
+    private static boolean evaluateExistingStoredCert( final StoredCertData storedCertData, final Settings settings )
+    {
+        final String cnName = makeSubjectName( settings );
+        if ( !cnName.equals( storedCertData.getX509Certificate().getSubjectDN().getName() ) )
+        {
+            LOGGER.info( () -> "replacing stored self cert, subject name does not match configured site url" );
+            return false;
+        }
+        else if ( storedCertData.getX509Certificate().getNotBefore().after( new Date() ) )
+        {
+            LOGGER.info( () -> "replacing stored self cert, not-before date is in the future" );
+            return false;
+        }
+        else if ( storedCertData.getX509Certificate().getNotAfter().before( new Date() ) )
+        {
+            LOGGER.info( () -> "replacing stored self cert, not-after date is in the past" );
+            return false;
+        }
+
+        return true;
+    }
+
+    private static String makeSubjectName( final Settings settings )
+    {
+        String cnName = PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com";
+        {
+            final String siteURL = settings.getSiteUrl();
+            if ( !StringUtil.isEmpty( siteURL ) )
+            {
+                try
+                {
+                    final URI uri = new URI( siteURL );
+                    if ( uri.getHost() != null && !uri.getHost().isEmpty() )
+                    {
+                        cnName = uri.getHost();
+                    }
+                }
+                catch ( final URISyntaxException e )
+                {
+                    // disregard
+                }
+            }
+        }
+        return cnName;
+    }
+
+    static KeyStore storedCertToKeyStore( final StoredCertData storedCertData, final String alias, final PasswordData password )
+        throws KeyStoreException, IOException, ClassNotFoundException, PwmUnrecoverableException, CertificateException, NoSuchAlgorithmException
+    {
+        final KeyStore keyStore = KeyStore.getInstance( "jks" );
+        keyStore.load( null, password.getStringValue().toCharArray() );
+        keyStore.setKeyEntry(
+            alias,
+            storedCertData.getKeypair().getPrivate(),
+            password.getStringValue().toCharArray(),
+            new X509Certificate[]
+                {
+                    storedCertData.getX509Certificate(),
+                    }
+        );
+        return keyStore;
+    }
+}

+ 159 - 0
server/src/main/java/password/pwm/util/secure/self/SelfCertGenerator.java

@@ -0,0 +1,159 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * 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 password.pwm.util.secure.self;
+
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import password.pwm.util.java.PwmDateFormat;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.SecureService;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+class SelfCertGenerator
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( SelfCertGenerator.class );
+
+    private static volatile boolean bouncyCastleInitialized;
+
+    private final Settings settings;
+    private final SecureService secureService;
+
+    SelfCertGenerator( final Settings settings,  final SecureService secureService )
+    {
+        this.secureService = secureService;
+        this.settings = settings;
+    }
+
+    StoredCertData generateNewCertificate( final String cnName )
+        throws Exception
+    {
+        initBouncyCastleProvider();
+
+        LOGGER.debug( () -> "creating self-signed certificate with cn of " + cnName );
+        final KeyPair keyPair = generateRSAKeyPair( );
+        final X509Certificate certificate = generateV3Certificate( keyPair, cnName );
+        return new StoredCertData( certificate, keyPair );
+    }
+
+
+    private X509Certificate generateV3Certificate( final KeyPair pair, final String cnValue )
+        throws Exception
+    {
+        final X500NameBuilder subjectName = new X500NameBuilder( BCStyle.INSTANCE );
+        subjectName.addRDN( BCStyle.CN, cnValue );
+
+        final BigInteger serialNumber = makeSerialNumber();
+
+        // 2 days in the past
+        final Date notBefore = new Date( System.currentTimeMillis() - TimeUnit.DAYS.toMillis( 2 ) );
+
+        final long futureSeconds = settings.getFutureSeconds();
+        final Date notAfter = new Date( System.currentTimeMillis() + ( futureSeconds * 1000 ) );
+
+        final X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
+            subjectName.build(),
+            serialNumber,
+            notBefore,
+            notAfter,
+            subjectName.build(),
+            pair.getPublic()
+        );
+
+        // false == not a CA
+        final BasicConstraints basic = new BasicConstraints( false );
+
+        // OID, critical, ASN.1 encoded value
+        certGen.addExtension( Extension.basicConstraints, true, basic.getEncoded() );
+
+        // add subject alternate name
+        /*
+        {
+            final ASN1Encodable[] subjectAlternativeNames = new ASN1Encodable[]
+                {
+                    new GeneralName( GeneralName.dNSName, cnValue ),
+                    };
+            final DERSequence subjectAlternativeNamesExtension = new DERSequence( subjectAlternativeNames );
+            certGen.addExtension( Extension.subjectAlternativeName, false, subjectAlternativeNamesExtension );
+        }
+        */
+
+
+        // sign and key encipher
+        final KeyUsage keyUsage = new KeyUsage( KeyUsage.digitalSignature | KeyUsage.keyEncipherment );
+
+        // OID, critical, ASN.1 encoded value
+        certGen.addExtension( Extension.keyUsage, true, keyUsage.getEncoded() );
+
+        // server authentication
+        final ExtendedKeyUsage extKeyUsage = new ExtendedKeyUsage( KeyPurposeId.id_kp_serverAuth );
+
+        // OID, critical, ASN.1 encoded value
+        certGen.addExtension( Extension.extendedKeyUsage, true, extKeyUsage.getEncoded() );
+
+        final ContentSigner sigGen = new JcaContentSignerBuilder( "SHA256WithRSAEncryption" ).setProvider( "BC" ).build( pair.getPrivate() );
+
+        return new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate( certGen.build( sigGen ) );
+    }
+
+    private BigInteger makeSerialNumber()
+    {
+        final PwmDateFormat formatter = PwmDateFormat.newPwmDateFormat( "yyyyMMddhhmmss" );
+        final String serNumStr = formatter.format( Instant.now() );
+        return new BigInteger( serNumStr );
+    }
+
+    private KeyPair generateRSAKeyPair( )
+        throws Exception
+    {
+        final KeyPairGenerator kpGen = KeyPairGenerator.getInstance( settings.getKeyAlg(), "BC" );
+        kpGen.initialize( settings.getKeySize(), secureService == null ? new SecureRandom() : secureService.pwmRandom() );
+        return kpGen.generateKeyPair();
+    }
+
+    private static synchronized void initBouncyCastleProvider( )
+    {
+        if ( !bouncyCastleInitialized )
+        {
+            Security.addProvider( new BouncyCastleProvider() );
+            bouncyCastleInitialized = true;
+        }
+    }
+}

+ 56 - 0
server/src/main/java/password/pwm/util/secure/self/Settings.java

@@ -0,0 +1,56 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * 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 password.pwm.util.secure.self;
+
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.AppProperty;
+import password.pwm.PwmConstants;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.util.java.TimeDuration;
+
+@Value
+@Builder
+public class Settings
+{
+    @Builder.Default
+    private int keySize = 1024;
+
+    @Builder.Default
+    private String keyAlg = "RSA";
+
+    @Builder.Default
+    private long futureSeconds = TimeDuration.of( 30, TimeDuration.Unit.DAYS ).as( TimeDuration.Unit.SECONDS );
+
+    @Builder.Default
+    private String siteUrl = "http://" + PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com";
+
+    public static Settings fromConfiguration ( final Configuration config )
+    {
+        return Settings.builder()
+            .keySize( Integer.parseInt( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_KEY_SIZE )  ) )
+            .keyAlg( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_ALG ) )
+            .futureSeconds( Long.parseLong( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_FUTURESECONDS ) ) )
+            .siteUrl( config.readSettingAsString( PwmSetting.PWM_SITE_URL ) )
+            .build();
+    }
+}

+ 63 - 0
server/src/main/java/password/pwm/util/secure/self/StoredCertData.java

@@ -0,0 +1,63 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * 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 password.pwm.util.secure.self;
+
+import password.pwm.util.java.StringUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+public class StoredCertData implements Serializable
+{
+    private final X509Certificate x509Certificate;
+    private String keypairb64;
+
+    public StoredCertData( final X509Certificate x509Certificate, final KeyPair keypair )
+        throws IOException
+    {
+        this.x509Certificate = x509Certificate;
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final ObjectOutputStream oos = new ObjectOutputStream( baos );
+        oos.writeObject( keypair );
+        final byte[] ba = baos.toByteArray();
+        keypairb64 = StringUtil.base64Encode( ba );
+    }
+
+    public X509Certificate getX509Certificate( )
+    {
+        return x509Certificate;
+    }
+
+    public KeyPair getKeypair( )
+        throws IOException, ClassNotFoundException
+    {
+        final byte[] ba = StringUtil.base64Decode( keypairb64 );
+        final ByteArrayInputStream bais = new ByteArrayInputStream( ba );
+        final ObjectInputStream ois = new ObjectInputStream( bais );
+        return ( KeyPair ) ois.readObject();
+    }
+}

+ 0 - 57
server/src/test/java/password/pwm/tests/MakeSelfSignedCertTest.java

@@ -1,57 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2019 The PWM Project
- *
- * 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 password.pwm.tests;
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.junit.Assert;
-import org.junit.Test;
-import password.pwm.util.secure.HttpsServerCertificateManager;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.cert.X509Certificate;
-import java.util.concurrent.TimeUnit;
-
-public class MakeSelfSignedCertTest
-{
-    private static final Provider BC_PROVIDER = new BouncyCastleProvider();
-
-    @Test
-    public void testSelfSignedCert() throws Exception
-    {
-        Security.addProvider( BC_PROVIDER );
-
-        final KeyPairGenerator kpGen = KeyPairGenerator.getInstance( "RSA", "BC" );
-        kpGen.initialize( 2048, new SecureRandom() );
-        final KeyPair keyPair = kpGen.generateKeyPair();
-
-
-        final String cnName = "test.myname.com";
-        final long futureSeconds = ( TimeUnit.DAYS.toMillis( 2 * 365 ) ) / 1000;
-
-        final X509Certificate storedCertData = HttpsServerCertificateManager.SelfCertGenerator.generateV3Certificate( keyPair, cnName, futureSeconds );
-        Assert.assertNotNull( storedCertData );
-        Assert.assertEquals( storedCertData.getSubjectDN().getName(), storedCertData.getIssuerDN().getName() );
-    }
-}

+ 43 - 0
server/src/test/java/password/pwm/util/secure/self/SelfCertGeneratorTest.java

@@ -0,0 +1,43 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * 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 password.pwm.util.secure.self;
+
+import org.junit.Assert;
+import org.junit.Test;
+import password.pwm.PwmConstants;
+import password.pwm.util.PasswordData;
+
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+public class SelfCertGeneratorTest
+{
+
+    @Test
+    public void doSelfCertGeneratorTest() throws Exception
+    {
+        final KeyStore keyStore = SelfCertFactory.generateNewCert( Settings.builder().build(), null, new PasswordData( "password" ), "alias" );
+        final Certificate certificate = keyStore.getCertificate( "alias" );
+        final String subjectDN = ( ( X509Certificate) certificate ).getSubjectDN().getName();
+        Assert.assertEquals( "CN=" + PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com", subjectDN );
+    }
+}