Browse Source

persist generated https server self-signed cert in localdb

jrivard 9 năm trước cách đây
mục cha
commit
e70660ee3f

+ 3 - 0
pwm/servlet/src/password/pwm/AppProperty.java

@@ -31,6 +31,8 @@ import java.util.ResourceBundle;
  */
 public enum AppProperty {
 
+    APPLICATION_FILELOCK_FILENAME                   ("application.fileLock.filename"),
+    APPLICATION_FILELOCK_WAIT_SECONDS               ("application.fileLock.waitSeconds"),
     AUDIT_EVENTS_EMAILFROM                          ("audit.events.emailFrom"),
     AUDIT_VAULT_MAX_RECORDS                         ("audit.vault.maxRecords"),
     BACKUP_LOCATION                                 ("backup.path"),
@@ -201,6 +203,7 @@ public enum AppProperty {
     SECURITY_STRIP_INLINE_JAVASCRIPT                ("security.html.stripInlineJavascript"),
     SECURITY_HTTP_STRIP_HEADER_REGEX                ("security.http.stripHeaderRegex"),
     SECURITY_HTTP_PROMISCUOUS_ENABLE                ("security.http.promiscuousEnable"),
+    SECURITY_HTTPSSERVER_SELF_FUTURESECONDS         ("security.httpsServer.selfCert.futureSeconds"),
     SECURITY_RESPONSES_HASH_ITERATIONS              ("security.responses.hashIterations"),
     SECURITY_INPUT_TRIM                             ("security.input.trim"),
     SECURITY_INPUT_PASSWORD_TRIM                    ("security.input.password.trim"),

+ 3 - 0
pwm/servlet/src/password/pwm/AppProperty.properties

@@ -23,6 +23,8 @@
 # Default application values.  This is not an end user modifiable file.  Values
 # can be overridden in the configuration.
 
+application.fileLock.filename=applicationPath.lock
+application.fileLock.waitSeconds=120
 audit.events.emailFrom=Audit Event Notification <@DefaultEmailFromAddress@>
 audit.vault.maxRecords=100000000
 backup.path=backup
@@ -184,6 +186,7 @@ recaptcha.validateUrl=https://www.google.com/recaptcha/api/siteverify
 security.html.stripInlineJavascript=false
 security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
 security.http.promiscuousEnable=false
+security.httpsServer.selfCert.futureSeconds=63113904
 security.responses.hashIterations=100000
 security.input.trim=true
 security.input.password.trim=false

+ 13 - 1
pwm/servlet/src/password/pwm/PwmApplication.java

@@ -103,6 +103,7 @@ public class PwmApplication {
         LOCALDB_IMPORT_STATUS("localDB.import.status"),
         WORDLIST_METADATA("wordlist.metadata"),
         SEEDLIST_METADATA("seedlist.metadata"),
+        HTTPS_SELF_CERT("https.selfCert"),
 
         ;
 
@@ -185,6 +186,10 @@ public class PwmApplication {
             }
         }
 
+        // get file lock
+        pwmEnvironment.waitForFileLock();;
+
+
         LOGGER.info("initializing, application mode=" + getApplicationMode()
                         + ", applicationPath=" + (pwmEnvironment.getApplicationPath() == null ? "null" : pwmEnvironment.getApplicationPath().getAbsolutePath())
                         + ", pwmEnvironment.getConfig()File=" + (pwmEnvironment.getConfigurationFile() == null ? "null" : pwmEnvironment.getConfigurationFile().getAbsolutePath())
@@ -337,7 +342,7 @@ public class PwmApplication {
     public List<PwmService> getPwmServices() {
         final List<PwmService> pwmServices = new ArrayList<>();
         pwmServices.add(this.localDBLogger);
-        pwmServices.addAll(this.pwmServiceManager.getPwmServices());
+        pwmServices.addAll(this.pwmServiceManager.getRunningServices());
         pwmServices.remove(null);
         return Collections.unmodifiableList(pwmServices);
     }
@@ -529,6 +534,8 @@ public class PwmApplication {
             localDB = null;
         }
 
+        pwmEnvironment.releaseFileLock();
+
         LOGGER.info(PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " closed for bidness, cya!");
     }
 
@@ -628,6 +635,11 @@ public class PwmApplication {
             }
         } catch (Exception e) {
             LOGGER.error("error retrieving key '" + appAttribute.getKey() + "' installation date from localDB: " + e.getMessage());
+            try {
+                localDB.remove(LocalDB.DB.PWM_META, appAttribute.getKey());
+            } catch (Exception e2) {
+                LOGGER.error("error removing bogus appAttribute value for key " + appAttribute.getKey() + ", error: " + localDB);
+            }
         }
     }
 }

+ 97 - 8
pwm/servlet/src/password/pwm/PwmEnvironment.java

@@ -27,16 +27,24 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
+import password.pwm.util.Helper;
 import password.pwm.util.JsonUtil;
+import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.io.Serializable;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 public class PwmEnvironment implements Serializable {
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmEnvironment.class);
 
+    // data elements
     private final PwmApplication.MODE applicationMode;
     private final Configuration config;
     private final File applicationPath;
@@ -45,9 +53,12 @@ public class PwmEnvironment implements Serializable {
     private final ContextManager contextManager;
     private final Collection<ApplicationFlag> flags;
 
+    private final FileLocker fileLocker;
+
     public enum ApplicationFlag {
         Appliance,
         ManageHttps,
+        NoFileLock,
 
         ;
 
@@ -118,6 +129,9 @@ public class PwmEnvironment implements Serializable {
         this.configurationFile = configurationFile;
         this.contextManager = contextManager;
         this.flags = flags == null ? Collections.<ApplicationFlag>emptySet() : Collections.unmodifiableSet(new HashSet<>(flags));
+
+        this.fileLocker = new FileLocker();
+
         verify();
     }
 
@@ -243,8 +257,6 @@ public class PwmEnvironment implements Serializable {
                     + "  This happens when an applicationPath was previously configured, but is not now being specified."
                     + "  An explicit applicationPath parameter must be specified, or the file can be removed if the applicationPath should be changed to the default /WEB-INF directory.";
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg));
-        } else {
-            LOGGER.trace("marker file " + infoFile.getAbsolutePath() + " does not exist (this is usually a good thing, this file should not exist in a configured applicationPath");
         }
 
     }
@@ -311,7 +323,6 @@ public class PwmEnvironment implements Serializable {
             }
             return returnFlags;
         }
-
     }
 
 
@@ -373,11 +384,6 @@ public class PwmEnvironment implements Serializable {
             return this;
         }
 
-        public Builder setApplicationPath(File applicationPath) {
-            this.applicationPath = applicationPath;
-            return this;
-        }
-
         public PwmEnvironment createPwmEnvironment() {
             return new PwmEnvironment(
                     applicationMode,
@@ -390,4 +396,87 @@ public class PwmEnvironment implements Serializable {
             );
         }
     }
+
+    public void attemptFileLock() {
+        fileLocker.attemptFileLock();
+    }
+
+    public void releaseFileLock()
+    {
+        fileLocker.releaseFileLock();
+    }
+
+    public boolean isFileLocked() {
+        return fileLocker.isLocked();
+    }
+
+    public void waitForFileLock() throws PwmUnrecoverableException {
+        final int maxWaitSeconds = Integer.parseInt(getConfig().readAppProperty(AppProperty.APPLICATION_FILELOCK_WAIT_SECONDS));
+        final Date startTime = new Date();
+        final int attemptInterval = 5021; //ms
+
+        while (!this.isFileLocked() && TimeDuration.fromCurrent(startTime).isShorterThan(maxWaitSeconds, TimeUnit.SECONDS)) {
+            attemptFileLock();
+
+            if (!isFileLocked()) {
+                LOGGER.debug("can't establish application file lock after "
+                        + TimeDuration.fromCurrent(startTime).asCompactString()
+                        + ", will retry;");
+                Helper.pause(attemptInterval);
+            }
+        }
+
+        if (!isFileLocked()) {
+            final String errorMsg = "unable to obtain application path file lock";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_STARTUP_ERROR,errorMsg);
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+    }
+
+    private class FileLocker {
+        private FileLock lock;
+        private final File lockfile;
+
+        public FileLocker() {
+            final String lockfileName = config.readAppProperty(AppProperty.APPLICATION_FILELOCK_FILENAME);
+            lockfile = new File(getApplicationPath(), lockfileName);
+        }
+
+        private boolean lockingAllowed() {
+            return !isInternalRuntimeInstance() && !getFlags().contains(ApplicationFlag.NoFileLock);
+        }
+
+        public boolean isLocked() {
+            return !lockingAllowed() || lock != null && lock.isValid();
+        }
+
+        public void attemptFileLock() {
+            if (lockingAllowed() && !isLocked()) {
+                try {
+                    final RandomAccessFile file = new RandomAccessFile(lockfile, "rw");
+                    final FileChannel f = file.getChannel();
+                    lock = f.tryLock();
+                    if (lock != null) {
+                        LOGGER.debug("obtained file lock on file " + lockfile.getAbsolutePath());
+                    } else {
+                        LOGGER.debug("unable to obtain file lock on file " + lockfile.getAbsolutePath());
+                    }
+                } catch (Exception e) {
+                    LOGGER.error("unable to obtain file lock on file " + lockfile.getAbsolutePath() + " due to error: " + e.getMessage());
+                }
+            }
+        }
+
+        public void releaseFileLock() {
+            if (lock != null && lock.isValid()) {
+                try {
+                    lock.release();
+                } catch (IOException e) {
+                    LOGGER.error("error releasing file lock: " + e.getMessage());
+                }
+                lock = null;
+                LOGGER.debug("released file lock on file " + lockfile.getAbsolutePath());
+            }
+        }
+    }
 }

+ 8 - 3
pwm/servlet/src/password/pwm/config/function/HttpsCertParseFunction.java

@@ -22,6 +22,8 @@
 
 package password.pwm.config.function;
 
+import password.pwm.PwmApplication;
+import password.pwm.PwmEnvironment;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigurationImpl;
@@ -29,7 +31,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
-import password.pwm.util.secure.HttpsKeyStoreCreator;
+import password.pwm.util.secure.HttpsServerCertificateManager;
 
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -42,7 +44,10 @@ public class HttpsCertParseFunction extends AbstractUriCertImportFunction {
     public String provideFunction(PwmRequest pwmRequest, StoredConfigurationImpl storedConfiguration, PwmSetting setting, String profile)
             throws PwmUnrecoverableException
     {
-        return keyStoreToStringOutput(HttpsKeyStoreCreator.configToKeystore(new Configuration(storedConfiguration)));
+        final PwmEnvironment pwmEnvironment = pwmRequest.getPwmApplication().getPwmEnvironment().makeRuntimeInstance(new Configuration(storedConfiguration));
+        final PwmApplication tempApplication = new PwmApplication(pwmEnvironment);
+        final HttpsServerCertificateManager httpsCertificateManager = new HttpsServerCertificateManager(tempApplication);
+        return keyStoreToStringOutput(httpsCertificateManager.configToKeystore());
     }
 
     @Override
@@ -57,7 +62,7 @@ public class HttpsCertParseFunction extends AbstractUriCertImportFunction {
             for (final Enumeration<String> aliases = keyStore.aliases(); aliases.hasMoreElements(); ) {
                 final String alias = aliases.nextElement();
                 final X509Certificate certificate = (X509Certificate)keyStore.getCertificate(alias);
-                sb.append("--- Certificate alias \"" + alias + "\" ---\n");
+                sb.append("--- Certificate alias \"").append(alias).append("\" ---\n");
                 sb.append(certificate.toString());
                 if (aliases.hasMoreElements()) {
                     sb.append("\n\n");

+ 3 - 1
pwm/servlet/src/password/pwm/config/stored/ConfigurationReader.java

@@ -22,6 +22,7 @@
 
 package password.pwm.config.stored;
 
+import org.apache.commons.io.FileUtils;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -120,7 +121,8 @@ public class ConfigurationReader {
 
         final InputStream theFileData;
         try {
-            theFileData = new FileInputStream(configFile);
+            final byte[] contents = FileUtils.readFileToByteArray(configFile);
+            theFileData = new ByteArrayInputStream(contents);
         } catch (Exception e) {
             final String errorMsg = "unable to read configuration file: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});

+ 19 - 8
pwm/servlet/src/password/pwm/http/servlet/resource/ResourceServletService.java

@@ -30,6 +30,7 @@ import password.pwm.health.HealthRecord;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.stats.EventRateMeter;
 import password.pwm.util.Percent;
+import password.pwm.util.logging.PwmLogger;
 
 import java.math.BigDecimal;
 import java.util.Collections;
@@ -38,11 +39,14 @@ import java.util.List;
 import java.util.Map;
 
 public class ResourceServletService implements PwmService {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(ResourceServletService.class);
+
 
     private ResourceServletConfiguration resourceServletConfiguration;
     private Map<CacheKey, CacheEntry> cacheMap;
     private EventRateMeter.MovingAverage cacheHitRatio = new EventRateMeter.MovingAverage(60 * 60 * 1000);
     private String resourceNonce;
+    private STATUS status = STATUS.NEW;
 
     public String getResourceNonce() {
         return resourceNonce;
@@ -86,18 +90,25 @@ public class ResourceServletService implements PwmService {
 
     @Override
     public STATUS status() {
-        return STATUS.OPEN;
+        return status;
     }
 
     @Override
     public void init(PwmApplication pwmApplication) throws PwmException {
-        this.resourceServletConfiguration = new ResourceServletConfiguration(pwmApplication);
-
-        cacheMap = new ConcurrentLinkedHashMap.Builder<CacheKey, CacheEntry>()
-                .maximumWeightedCapacity(resourceServletConfiguration.getMaxCacheItems())
-                .build();
-
-        resourceNonce = makeResourcePathNonce(pwmApplication);
+        status = STATUS.OPENING;
+        try {
+            this.resourceServletConfiguration = new ResourceServletConfiguration(pwmApplication);
+
+            cacheMap = new ConcurrentLinkedHashMap.Builder<CacheKey, CacheEntry>()
+                    .maximumWeightedCapacity(resourceServletConfiguration.getMaxCacheItems())
+                    .build();
+
+            resourceNonce = makeResourcePathNonce(pwmApplication);
+            status = STATUS.OPEN;
+        } catch (Exception e) {
+            LOGGER.error("error during initialization, will remain closed; error: " + e.getMessage());
+            status = STATUS.CLOSED;
+        }
     }
 
     @Override

+ 75 - 41
pwm/servlet/src/password/pwm/svc/PwmServiceManager.java

@@ -35,49 +35,85 @@ public class PwmServiceManager {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmServiceManager.class);
 
-    private static final List<Class<? extends PwmService>> PWM_SERVICE_CLASSES  = Collections.unmodifiableList(Arrays.asList(
-            SecureService.class,
-            LdapConnectionService.class,
-            DatabaseAccessorImpl.class,
-            SharedHistoryManager.class,
-            HealthMonitor.class,
-            AuditService.class,
-            StatisticsManager.class,
-            WordlistManager.class,
-            SeedlistManager.class,
-            EmailQueueManager.class,
-            SmsQueueManager.class,
-            UrlShortenerService.class,
-            TokenService.class,
-            VersionChecker.class,
-            IntruderManager.class,
-            ReportService.class,
-            CrService.class,
-            OtpService.class,
-            CacheService.class,
-            ResourceServletService.class,
-            SessionTrackService.class
-    ));
 
     private final PwmApplication pwmApplication;
-    private final Map<Class<? extends PwmService>,PwmService> pwmServices = new HashMap<>();
+    private final Map<Class<? extends PwmService>, PwmService> runningServices = new HashMap<>();
     private boolean initialized;
 
+    public enum PwmServiceClassEnum {
+        SecureService(          SecureService.class,             true),
+        LdapConnectionService(  LdapConnectionService.class,     true),
+        DatabaseAccessorImpl(   DatabaseAccessorImpl.class,      true),
+        SharedHistoryManager(   SharedHistoryManager.class,      false),
+        HealthMonitor(          HealthMonitor.class,             false),
+        AuditService(           AuditService.class,              false),
+        StatisticsManager(      StatisticsManager.class,         false),
+        WordlistManager(        WordlistManager.class,           false),
+        SeedlistManager(        SeedlistManager.class,           false),
+        EmailQueueManager(      EmailQueueManager.class,         false),
+        SmsQueueManager(        SmsQueueManager.class,           false),
+        UrlShortenerService(    UrlShortenerService.class,       false),
+        TokenService(           TokenService.class,              false),
+        VersionChecker(         VersionChecker.class,            false),
+        IntruderManager(        IntruderManager.class,           false),
+        ReportService(          ReportService.class,             true),
+        CrService(              CrService.class,                 true),
+        OtpService(             OtpService.class,                false),
+        CacheService(           CacheService.class,              true),
+        ResourceServletService( ResourceServletService.class,    false),
+        SessionTrackService(    SessionTrackService.class,       false),
+
+        ;
+
+        private final Class<? extends PwmService> clazz;
+        private final boolean internalRuntime;
+
+        PwmServiceClassEnum(Class<? extends PwmService> clazz, final boolean internalRuntime) {
+            this.clazz = clazz;
+            this.internalRuntime = internalRuntime;
+        }
+
+        public boolean isInternalRuntime() {
+            return internalRuntime;
+        }
+
+        static List<Class<? extends PwmService>> allClasses() {
+            final List<Class<? extends PwmService>> pwmServiceClasses = new ArrayList<>();
+            for (final PwmServiceClassEnum enumClass : values()) {
+                pwmServiceClasses.add(enumClass.getPwmServiceClass());
+            }
+            return Collections.unmodifiableList(pwmServiceClasses);
+        }
+
+        public Class<? extends PwmService> getPwmServiceClass() {
+            return clazz;
+        }
+    }
+
     public PwmServiceManager(PwmApplication pwmApplication) {
         this.pwmApplication = pwmApplication;
     }
 
     public PwmService getService(final Class<? extends PwmService> serviceClass) {
-        return pwmServices.get(serviceClass);
+        return runningServices.get(serviceClass);
     }
 
-
     public void initAllServices()
             throws PwmUnrecoverableException
     {
-        for (final Class<? extends PwmService> serviceClass : PWM_SERVICE_CLASSES) {
-            final PwmService newServiceInstance = initService(serviceClass);
-            pwmServices.put(serviceClass,newServiceInstance);
+
+        final boolean internalRuntimeInstance = pwmApplication.getPwmEnvironment().isInternalRuntimeInstance();
+
+        for (final PwmServiceClassEnum serviceClassEnum : PwmServiceClassEnum.values()) {
+            boolean startService = true;
+            if (internalRuntimeInstance && !serviceClassEnum.isInternalRuntime()) {
+                startService = false;
+            }
+            if (startService) {
+                final Class<? extends PwmService> serviceClass = serviceClassEnum.getPwmServiceClass();
+                final PwmService newServiceInstance = initService(serviceClass);
+                runningServices.put(serviceClass, newServiceInstance);
+            }
         }
         initialized = true;
     }
@@ -90,11 +126,11 @@ public class PwmServiceManager {
         final String serviceName = serviceClass.getName();
         try {
             final Object newInstance = serviceClass.newInstance();
-            newServiceInstance = (PwmService)newInstance;
+            newServiceInstance = (PwmService) newInstance;
         } catch (Exception e) {
             final String errorMsg = "unexpected error instantiating service class '" + serviceName + "', error: " + e.toString();
-            LOGGER.fatal(errorMsg,e);
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR,errorMsg));
+            LOGGER.fatal(errorMsg, e);
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg));
         }
 
         try {
@@ -110,22 +146,21 @@ public class PwmServiceManager {
                 errorMsg += ", cause: " + e.getCause();
             }
             LOGGER.fatal(errorMsg);
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR,errorMsg));
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg));
         }
         return newServiceInstance;
     }
 
-
     public void shutdownAllServices()
     {
         if (!initialized) {
             return;
         }
 
-        final List<Class<? extends PwmService>> reverseServiceList = new ArrayList<>(PWM_SERVICE_CLASSES);
+        final List<Class<? extends PwmService>> reverseServiceList = new ArrayList<>(PwmServiceClassEnum.allClasses());
         Collections.reverse(reverseServiceList);
         for (final Class<? extends PwmService> serviceClass : reverseServiceList) {
-            if (pwmServices.containsKey(serviceClass)) {
+            if (runningServices.containsKey(serviceClass)) {
                 shutDownService(serviceClass);
             }
         }
@@ -135,17 +170,16 @@ public class PwmServiceManager {
     private void shutDownService(final Class<? extends PwmService> serviceClass)
     {
         LOGGER.trace("closing service " + serviceClass.getName());
-        final PwmService loopService = pwmServices.get(serviceClass);
+        final PwmService loopService = runningServices.get(serviceClass);
         LOGGER.trace("successfully closed service " + serviceClass.getName());
         try {
             loopService.close();
         } catch (Exception e) {
-            LOGGER.error("error closing " + loopService.getClass().getSimpleName() + ": " + e.getMessage(),e);
+            LOGGER.error("error closing " + loopService.getClass().getSimpleName() + ": " + e.getMessage(), e);
         }
     }
 
-    public List<PwmService> getPwmServices() {
-        return Collections.unmodifiableList(new ArrayList<PwmService>(this.pwmServices.values()));
+    public List<PwmService> getRunningServices() {
+        return Collections.unmodifiableList(new ArrayList<>(this.runningServices.values()));
     }
-
 }

+ 1 - 3
pwm/servlet/src/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -35,7 +35,6 @@ import password.pwm.health.HealthTopic;
 import password.pwm.http.ContextManager;
 import password.pwm.svc.PwmService;
 import password.pwm.util.Helper;
-import password.pwm.util.JsonUtil;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
@@ -282,8 +281,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService {
     }
 
     void writeMetadata(final StoredWordlistDataBean metadataBean) {
-        final String jsonValue = JsonUtil.serialize(metadataBean);
-        pwmApplication.writeAppAttribute(getMetaDataAppAttribute(),jsonValue);
+        pwmApplication.writeAppAttribute(getMetaDataAppAttribute(),metadataBean);
     }
 
     @Override

+ 4 - 0
pwm/servlet/src/password/pwm/util/TimeDuration.java

@@ -269,6 +269,10 @@ public class TimeDuration implements Comparable, Serializable {
         return this.isLongerThan(timeUnit.toMillis(duration));
     }
 
+    public boolean isShorterThan(final long duration, final TimeUnit timeUnit) {
+        return this.isShorterThan(timeUnit.toMillis(duration));
+    }
+
     public long getSeconds() {
         return getTimeDetail().seconds;
     }

+ 6 - 8
pwm/servlet/src/password/pwm/util/cli/ExportHttpsKeyStoreCommand.java

@@ -22,9 +22,8 @@
 
 package password.pwm.util.cli;
 
-import password.pwm.config.Configuration;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.secure.HttpsKeyStoreCreator;
+import password.pwm.util.secure.HttpsServerCertificateManager;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -52,18 +51,17 @@ public class ExportHttpsKeyStoreCommand extends AbstractCliCommand {
             password = promptForPassword();
         }
 
-        final Configuration config = cliEnvironment.getConfig();
-
+        final HttpsServerCertificateManager httpsCertificateManager = new HttpsServerCertificateManager(this.cliEnvironment.pwmApplication);
         KeyStore keyStore = null;
         try {
-            keyStore = HttpsKeyStoreCreator.configToKeystore(config);
+            keyStore = httpsCertificateManager.configToKeystore();
             out("output configured https certificates");
         } catch (PwmUnrecoverableException e) {
             out("unable to load configured https certificate: " + e.getMessage());
         }
 
         if (keyStore == null) {
-            keyStore = HttpsKeyStoreCreator.makeSelfSignedCert(config, password);
+            keyStore = httpsCertificateManager.makeSelfSignedCert(password);
             out("output self-signed certificate");
         }
 
@@ -99,8 +97,8 @@ public class ExportHttpsKeyStoreCommand extends AbstractCliCommand {
         cliParameters.options = Arrays.asList(CliParameters.REQUIRED_NEW_OUTPUT_FILE, passwordValueOption);
 
         cliParameters.needsLocalDB = false;
-        cliParameters.needsPwmApplication = false;
-        cliParameters.readOnly = true;
+        cliParameters.needsPwmApplication = true;
+        cliParameters.readOnly = false;
 
         return cliParameters;
     }

+ 23 - 9
pwm/servlet/src/password/pwm/util/cli/MainClass.java

@@ -147,7 +147,7 @@ public class MainClass {
         final LocalDB localDB;
 
         if (parameters.needsPwmApplication) {
-            pwmApplication = loadPwmApplication(applicationPath, config, configurationFile, parameters.readOnly);
+            pwmApplication = loadPwmApplication(applicationPath, MAIN_OPTIONS.applicationFlags, config, configurationFile, parameters.readOnly);
             localDB = pwmApplication.getLocalDB();
         } else if (parameters.needsLocalDB) {
             pwmApplication = null;
@@ -298,6 +298,7 @@ public class MainClass {
     static String[] parseMainCommandLineOptions(String[] args) {
         final String OPT_DEBUG_LEVEL = "-debugLevel";
         final String OPT_APP_PATH = "-applicationPath";
+        final String OPT_APP_FLAGS= "-applicationFlags";
         final String OPT_FORCE = "-force";
 
         if (args == null || args.length < 1) {
@@ -332,6 +333,15 @@ public class MainClass {
                     }
                 } else if (arg.equals(OPT_FORCE)) {
                     MAIN_OPTIONS.forceFlag = true;
+                } else if (arg.startsWith(OPT_APP_FLAGS)){
+                    if (arg.length() < OPT_APP_FLAGS.length() + 2) {
+                        out(OPT_APP_FLAGS + " option must include value (example: -" + OPT_APP_FLAGS + "=Flag1,Flag2");
+                        System.exit(-1);
+                    } else {
+                        final String flagStr = arg.substring(OPT_APP_PATH.length() + 1, arg.length());
+                        MAIN_OPTIONS.applicationFlags = PwmEnvironment.ParseHelper.parseApplicationFlagValueParameter(flagStr);
+                    }
+                    outputArgs.add(arg);
                 } else {
                     outputArgs.add(arg);
                 }
@@ -385,15 +395,23 @@ public class MainClass {
         return reader;
     }
 
-    static PwmApplication loadPwmApplication(final File applicationPath, final Configuration config, final File configurationFile, final boolean readonly)
+    static PwmApplication loadPwmApplication(
+            final File applicationPath,
+            final Collection<PwmEnvironment.ApplicationFlag> flags,
+            final Configuration config,
+            final File configurationFile,
+            final boolean readonly
+    )
             throws LocalDBException, PwmUnrecoverableException
     {
         final PwmApplication.MODE mode = readonly ? PwmApplication.MODE.READ_ONLY : PwmApplication.MODE.RUNNING;
+        final Collection<PwmEnvironment.ApplicationFlag> applicationFlags = flags == null
+                ? PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem(null)
+                : flags;
         final PwmEnvironment pwmEnvironment = new PwmEnvironment.Builder(config, applicationPath)
                 .setApplicationMode(mode)
-                .setInternalRuntimeInstance(true)
                 .setConfigurationFile(configurationFile)
-                .setFlags(PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem(null))
+                .setFlags(applicationFlags)
                 .createPwmEnvironment();
         final PwmApplication pwmApplication = new PwmApplication(pwmEnvironment);
         final PwmApplication.MODE runningMode = pwmApplication.getApplicationMode();
@@ -418,6 +436,7 @@ public class MainClass {
         private PwmLogLevel pwmLogLevel = null;
         private File applicationPath = null;
         private boolean forceFlag = false;
+        private Collection<PwmEnvironment.ApplicationFlag> applicationFlags;
 
         public PwmLogLevel getPwmLogLevel() {
             return pwmLogLevel;
@@ -432,11 +451,6 @@ public class MainClass {
         }
     }
 
-    private static void exitWithError(final String msg) {
-        out(msg);
-        System.exit(-1);
-    }
-
     private static File figureApplicationPath(final MainOptions mainOptions) throws IOException, PwmUnrecoverableException {
         final File applicationPath;
         if (mainOptions != null && mainOptions.applicationPath != null) {

+ 0 - 153
pwm/servlet/src/password/pwm/util/secure/HttpsKeyStoreCreator.java

@@ -1,153 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://code.google.com/p/pwm/
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2015 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-package password.pwm.util.secure;
-
-import org.bouncycastle.asn1.x509.*;
-import org.bouncycastle.jce.X509Principal;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.x509.X509V3CertificateGenerator;
-import password.pwm.PwmConstants;
-import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
-import password.pwm.config.value.FileValue;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.PasswordData;
-import password.pwm.util.logging.PwmLogger;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.*;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-public class HttpsKeyStoreCreator {
-    private static PwmLogger LOGGER = PwmLogger.forClass(HttpsKeyStoreCreator.class);
-
-    private static final Provider BC_PROVIDER = new BouncyCastleProvider();
-
-    private HttpsKeyStoreCreator() {
-    }
-
-    public static KeyStore configToKeystore(final Configuration configuration) throws PwmUnrecoverableException {
-        final PasswordData keystorePassword = configuration.readSettingAsPassword(PwmSetting.HTTPS_CERT_PASSWORD);
-        if (keystorePassword == null || keystorePassword.getStringValue().isEmpty()) {
-            final String errorMsg = "https keystore password is not configured";
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[]{errorMsg}));
-        }
-
-        Map<FileValue.FileInformation, FileValue.FileContent> files = configuration.readSettingAsFile(PwmSetting.HTTPS_CERT_PKCS12);
-        if (files == null || files.isEmpty()) {
-            final String errorMsg = "https keystore pkcs12 file is not present";
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[]{errorMsg}));
-        }
-
-        final FileValue.FileInformation fileInformation = files.keySet().iterator().next();
-        final FileValue.FileContent fileContent = files.get(fileInformation);
-
-        final KeyStore keyStore;
-        try {
-            final InputStream stream = new ByteArrayInputStream(fileContent.getContents());
-            keyStore = KeyStore.getInstance("pkcs12", "SunJSSE");
-            keyStore.load(stream, keystorePassword.getStringValue().toCharArray());
-
-            if (!keyStore.containsAlias("https")) {
-                final String errorMsg = "pkcs12 store does not does not contain a certificate with \"https\" alias";
-                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[]{errorMsg}));
-            }
-            return keyStore;
-        } catch (IOException | NoSuchAlgorithmException | CertificateException | NoSuchProviderException | KeyStoreException e) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, "error parsing pkcs12 file: " + e.getMessage()));
-        }
-    }
-
-
-    public static KeyStore makeSelfSignedCert(final Configuration configuration, final String password) throws Exception {
-        String cnName = PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com";
-        {
-            final String siteURL = configuration.readSettingAsString(PwmSetting.PWM_SITE_URL);
-            if (siteURL != null && !siteURL.isEmpty()) {
-                try {
-                    URI uri = new URI(siteURL);
-                    if (uri.getHost() != null && !uri.getHost().isEmpty()) {
-                        cnName = uri.getHost();
-                    }
-                } catch (URISyntaxException e) {
-                    // disregard
-                }
-            }
-        }
-        return makeSelfSignedCert(cnName, password);
-    }
-
-
-    public static KeyStore makeSelfSignedCert(final String cnName, final String password) throws Exception {
-        Security.addProvider(BC_PROVIDER);
-
-        LOGGER.debug("creating self-signed certificate with cn of " + cnName);
-        final KeyStore keyStore = KeyStore.getInstance("jks");
-        keyStore.load(null, password.toCharArray());
-        final KeyPair keyPair = generateRSAKeyPair();
-        final X509Certificate certificate = generateV3Certificate(keyPair, cnName);
-        keyStore.setKeyEntry("https", keyPair.getPrivate(), password.toCharArray(), new X509Certificate[]{certificate});
-        return keyStore;
-    }
-
-
-    static X509Certificate generateV3Certificate(final KeyPair pair, final String cnValue)
-            throws Exception {
-
-        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
-
-        final X509Principal x509Principal = new X509Principal("CN=" + cnValue);
-        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
-        certGen.setIssuerDN(x509Principal);
-        certGen.setNotBefore(new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365 * 10)));
-        certGen.setNotAfter(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365 * 10)));
-        certGen.setSubjectDN(x509Principal);
-        certGen.setPublicKey(pair.getPublic());
-        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
-
-        certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
-        certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
-                | KeyUsage.keyEncipherment));
-        certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(
-                KeyPurposeId.id_kp_serverAuth));
-
-        return certGen.generateX509Certificate(pair.getPrivate(), "BC");
-    }
-
-    static KeyPair generateRSAKeyPair() throws Exception {
-        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
-        kpGen.initialize(1024, new SecureRandom());
-        return kpGen.generateKeyPair();
-    }
-}

+ 250 - 0
pwm/servlet/src/password/pwm/util/secure/HttpsServerCertificateManager.java

@@ -0,0 +1,250 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://code.google.com/p/pwm/
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2015 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.util.secure;
+
+import org.bouncycastle.asn1.x509.*;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.value.FileValue;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.PasswordData;
+import password.pwm.util.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class HttpsServerCertificateManager {
+    private static PwmLogger LOGGER = PwmLogger.forClass(HttpsServerCertificateManager.class);
+
+    private static final Provider BC_PROVIDER = new BouncyCastleProvider();
+    private static final String KEYSTORE_ALIAS = "https";
+
+    private final PwmApplication pwmApplication;
+
+    public HttpsServerCertificateManager(final PwmApplication pwmApplication) {
+        this.pwmApplication = pwmApplication;
+    }
+
+    public KeyStore configToKeystore() throws PwmUnrecoverableException {
+        final Configuration configuration = pwmApplication.getConfig();
+        final PasswordData keystorePassword = configuration.readSettingAsPassword(PwmSetting.HTTPS_CERT_PASSWORD);
+        if (keystorePassword == null || keystorePassword.getStringValue().isEmpty()) {
+            final String errorMsg = "https keystore password is not configured";
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[]{errorMsg}));
+        }
+
+        Map<FileValue.FileInformation, FileValue.FileContent> files = configuration.readSettingAsFile(PwmSetting.HTTPS_CERT_PKCS12);
+        if (files == null || files.isEmpty()) {
+            final String errorMsg = "https keystore pkcs12 file is not present";
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[]{errorMsg}));
+        }
+
+        final FileValue.FileInformation fileInformation = files.keySet().iterator().next();
+        final FileValue.FileContent fileContent = files.get(fileInformation);
+
+        final KeyStore keyStore;
+        try {
+            final InputStream stream = new ByteArrayInputStream(fileContent.getContents());
+            keyStore = KeyStore.getInstance("pkcs12", "SunJSSE");
+            keyStore.load(stream, keystorePassword.getStringValue().toCharArray());
+
+            if (!keyStore.containsAlias(KEYSTORE_ALIAS)) {
+                final String errorMsg = "pkcs12 store does not does not contain a certificate with \"" + KEYSTORE_ALIAS + "\" alias";
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[]{errorMsg}));
+            }
+            return keyStore;
+        } catch (IOException | NoSuchAlgorithmException | CertificateException | NoSuchProviderException | KeyStoreException e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, "error parsing pkcs12 file: " + e.getMessage()));
+        }
+    }
+
+    public KeyStore makeSelfSignedCert(final String password)
+            throws PwmUnrecoverableException
+    {
+        final Configuration configuration = pwmApplication.getConfig();
+
+        try {
+            final SelfCertGenerator selfCertGenerator = new SelfCertGenerator(configuration);
+            return selfCertGenerator.makeSelfSignedCert(pwmApplication, password);
+        } catch (Exception e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_CERTIFICATE_ERROR,"unable to generate self signed certificate: " + e.getMessage()));
+        }
+    }
+
+    public static class StoredCertData implements Serializable {
+        private X509Certificate x509Certificate;
+        private String keypairb64;
+
+        public StoredCertData(X509Certificate x509Certificate, 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();
+        }
+    }
+
+
+    private static class SelfCertGenerator {
+        private final Configuration config;
+
+        public SelfCertGenerator(Configuration config) {
+            this.config = config;
+        }
+
+        public KeyStore makeSelfSignedCert(final PwmApplication pwmApplication, final String password)
+                throws Exception
+        {
+            final String cnName = makeSubjectName();
+            final KeyStore keyStore = KeyStore.getInstance("jks");
+            keyStore.load(null, password.toCharArray());
+            StoredCertData storedCertData = pwmApplication.readAppAttribute(PwmApplication.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(password);
+                pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.HTTPS_SELF_CERT, storedCertData);
+            }
+
+            keyStore.setKeyEntry(
+                    KEYSTORE_ALIAS,
+                    storedCertData.getKeypair().getPrivate(),
+                    password.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 {
+                        URI uri = new URI(siteURL);
+                        if (uri.getHost() != null && !uri.getHost().isEmpty()) {
+                            cnName = uri.getHost();
+                        }
+                    } catch (URISyntaxException e) {
+                        // disregard
+                    }
+                }
+            }
+            return cnName;
+        }
+
+
+        public StoredCertData makeSelfSignedCert(final String cnName)
+                throws Exception
+        {
+            Security.addProvider(BC_PROVIDER);
+
+            LOGGER.debug("creating self-signed certificate with cn of " + cnName);
+            final KeyPair keyPair = generateRSAKeyPair();
+            final X509Certificate certificate = generateV3Certificate(keyPair, cnName, config);
+            return new StoredCertData(certificate, keyPair);
+        }
+
+
+        static X509Certificate generateV3Certificate(final KeyPair pair, final String cnValue, final Configuration config)
+                throws Exception
+        {
+
+            X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
+
+            final X509Principal x509Principal = new X509Principal("CN=" + cnValue);
+            certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+            certGen.setIssuerDN(x509Principal);
+            certGen.setNotBefore(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)));
+            {
+                final Long futureSeconds = Long.parseLong(config.readAppProperty(AppProperty.SECURITY_HTTPSSERVER_SELF_FUTURESECONDS));
+                certGen.setNotAfter(new Date(System.currentTimeMillis() + futureSeconds));
+            }
+            certGen.setSubjectDN(x509Principal);
+            certGen.setPublicKey(pair.getPublic());
+            certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+            certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
+            certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
+                    | KeyUsage.keyEncipherment));
+            certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(
+                    KeyPurposeId.id_kp_serverAuth));
+
+            return certGen.generateX509Certificate(pair.getPrivate(), "BC");
+        }
+
+        static KeyPair generateRSAKeyPair()
+                throws Exception
+        {
+            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+            kpGen.initialize(1024, new SecureRandom());
+            return kpGen.generateKeyPair();
+        }
+    }
+}