Prechádzať zdrojové kódy

certificate management refactoring and bugfixes

Jason Rivard 3 rokov pred
rodič
commit
c15acf8c01
28 zmenil súbory, kde vykonal 536 pridanie a 289 odobranie
  1. 3 0
      server/src/main/java/password/pwm/PwmConstants.java
  2. 25 16
      server/src/main/java/password/pwm/config/DomainConfig.java
  3. 10 7
      server/src/main/java/password/pwm/config/value/AbstractValue.java
  4. 27 28
      server/src/main/java/password/pwm/config/value/ActionValue.java
  5. 4 3
      server/src/main/java/password/pwm/config/value/PrivateKeyValue.java
  6. 8 28
      server/src/main/java/password/pwm/config/value/X509CertificateValue.java
  7. 15 5
      server/src/main/java/password/pwm/error/PwmInternalException.java
  8. 1 9
      server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java
  9. 2 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/ActionCertViewerFunction.java
  10. 2 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/RemoteWebServiceCertViewerFunction.java
  11. 2 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/X509CertViewerFunction.java
  12. 2 11
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java
  13. 3 13
      server/src/main/java/password/pwm/svc/wordlist/WordlistConfiguration.java
  14. 2 1
      server/src/main/java/password/pwm/util/cli/commands/LdapSchemaExtendCommand.java
  15. 1 1
      server/src/main/java/password/pwm/util/secure/CertificateReadingTrustManager.java
  16. 1 1
      server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java
  17. 21 0
      server/src/main/java/password/pwm/util/secure/PwmHashAlgorithm.java
  18. 15 30
      server/src/main/java/password/pwm/util/secure/SecureEngine.java
  19. 96 0
      server/src/main/java/password/pwm/util/secure/X509CertDataParser.java
  20. 111 0
      server/src/main/java/password/pwm/util/secure/X509CertInfo.java
  21. 37 115
      server/src/main/java/password/pwm/util/secure/X509Utils.java
  22. 102 0
      server/src/test/java/password/pwm/http/ServletTest.java
  23. 2 1
      server/src/test/java/password/pwm/http/servlet/ControlledPwmServletTest.java
  24. 2 1
      server/src/test/java/password/pwm/ws/server/rest/RestServletTest.java
  25. 13 11
      webapp/src/main/webapp/public/resources/js/configeditor-settings-action.js
  26. 26 3
      webapp/src/main/webapp/public/resources/js/configeditor-settings-remotewebservices.js
  27. 2 1
      webapp/src/main/webapp/public/resources/js/configeditor-settings.js
  28. 1 1
      webapp/src/main/webapp/public/resources/js/configeditor.js

+ 3 - 0
server/src/main/java/password/pwm/PwmConstants.java

@@ -88,6 +88,9 @@ public abstract class PwmConstants
 
     public static final int XML_OUTPUT_LINE_WRAP_LENGTH = 120;
 
+    public static final Package PWM_BASE_PACKAGE = ClassLoader.getSystemClassLoader()
+            .getDefinedPackage( "password.pwm" );
+
     public static final String LDAP_AD_PASSWORD_POLICY_CONTROL_ASN = "1.2.840.113556.1.4.2066";
     public static final String PROFILE_ID_ALL = "all";
     public static final String PROFILE_ID_DEFAULT = "default";

+ 25 - 16
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -21,6 +21,7 @@
 package password.pwm.config;
 
 import password.pwm.AppProperty;
+import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
@@ -43,6 +44,7 @@ import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.FileValue;
+import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.value.data.NamedSecretData;
@@ -57,9 +59,8 @@ import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
-import password.pwm.util.secure.SecureEngine;
 
-import java.io.StringWriter;
+import java.security.MessageDigest;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -114,7 +115,7 @@ public class DomainConfig implements SettingReader
                 ) ) );
 
         this.ldapProfiles = makeLdapProfileMap( this );
-        this.domainSecurityKey = makeDomainSecurityKey( this );
+        this.domainSecurityKey = makeDomainSecurityKey( appConfig, domainID );
     }
 
     public AppConfig getAppConfig()
@@ -412,23 +413,16 @@ public class DomainConfig implements SettingReader
                         Map.Entry::getValue ) ) );
     }
 
-    private static PwmSecurityKey makeDomainSecurityKey( final DomainConfig domainConfig )
-            throws PwmInternalException
+    private static PwmSecurityKey makeDomainSecurityKey(
+            final AppConfig appConfig,
+            final DomainID domainID
+    )
     {
         try
         {
-            final StringWriter keyData = new StringWriter();
-            keyData.append( domainConfig.getDomainID().stringValue() );
-            CollectionUtil.iteratorToStream( domainConfig.getStoredConfiguration().keys() )
-                    .filter( key -> Objects.equals( key.getDomainID(), domainConfig.getDomainID() ) )
-                    .sorted()
-                    .map( s -> domainConfig.getStoredConfiguration().readStoredValue( s ) )
-                    .flatMap( Optional::stream )
-                    .forEach( value -> keyData.append( value.valueHash() ) );
-
-            final String hashedData = SecureEngine.hash( keyData.toString(), PwmHashAlgorithm.SHA512 );
+            final String hashedData = valueHash( appConfig.getStoredConfiguration(), domainID );
             final PwmSecurityKey domainKey = new PwmSecurityKey( hashedData );
-            return domainConfig.getAppConfig().getSecurityKey().add( domainKey );
+            return appConfig.getSecurityKey().add( domainKey );
         }
         catch ( final PwmUnrecoverableException e )
         {
@@ -436,4 +430,19 @@ public class DomainConfig implements SettingReader
         }
     }
 
+
+    private static String valueHash( final StoredConfiguration storedConfiguration, final DomainID domainID )
+    {
+        final MessageDigest messageDigest = PwmHashAlgorithm.SHA512.newMessageDigest();
+        messageDigest.update( domainID.stringValue().getBytes( PwmConstants.DEFAULT_CHARSET ) );
+
+        CollectionUtil.iteratorToStream( storedConfiguration.keys() )
+                .filter( key -> Objects.equals( key.getDomainID(), domainID ) )
+                .map( storedConfiguration::readStoredValue )
+                .flatMap( Optional::stream )
+                .map( StoredValue::valueHash )
+                .forEach( s -> messageDigest.update( s.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
+
+        return JavaHelper.binaryArrayToHex( messageDigest.digest() );
+    }
 }

+ 10 - 7
server/src/main/java/password/pwm/config/value/AbstractValue.java

@@ -28,17 +28,18 @@ import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.ImmutableByteArray;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.json.JsonProvider;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
-import password.pwm.util.secure.SecureEngine;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.Serializable;
+import java.security.DigestOutputStream;
 import java.util.List;
 import java.util.Locale;
 
@@ -121,15 +122,17 @@ public abstract class AbstractValue implements StoredValue
             final List<XmlElement> xmlValues = storedValue.toXmlValues( StoredConfigXmlConstants.XML_ELEMENT_VALUE, xmlOutputProcessData );
             final XmlDocument document = XmlChai.getFactory().newDocument( "root" );
             document.getRootElement().attachElement( xmlValues );
-            final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-            XmlChai.getFactory().output( document, byteArrayOutputStream );
-            final byte[] bytesToHash = byteArrayOutputStream.toByteArray();
-            return SecureEngine.hash( bytesToHash, PwmHashAlgorithm.SHA512 );
 
+            final DigestOutputStream digestOutputStream = new DigestOutputStream(
+                    OutputStream.nullOutputStream(),
+                    PwmHashAlgorithm.SHA512.newMessageDigest() );
+            XmlChai.getFactory().output( document, digestOutputStream );
+            return JavaHelper.binaryArrayToHex( digestOutputStream.getMessageDigest().digest() );
         }
-        catch ( final IOException | PwmUnrecoverableException e )
+        catch ( final IOException e )
         {
             throw new IllegalStateException( e );
         }
     }
+
 }

+ 27 - 28
server/src/main/java/password/pwm/config/value/ActionValue.java

@@ -28,6 +28,7 @@ import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.config.value.data.ActionConfiguration;
+import password.pwm.error.PwmInternalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.MiscUtil;
@@ -35,6 +36,7 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.X509Utils;
 
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -157,30 +159,28 @@ public class ActionValue extends AbstractValue implements StoredValue
                         : webAction.getSuccessStatus();
 
                 // decrypt pw
+                Optional<String> decodedValue = Optional.empty();
                 try
                 {
-                    final Optional<String> decodedValue = StoredValueEncoder.decode(
+                    decodedValue = StoredValueEncoder.decode(
                             webAction.getPassword(),
                             StoredValueEncoder.Mode.ENCODED,
                             pwmSecurityKey );
-                    decodedValue.ifPresent( s ->
-                    {
-                        clonedWebActions.add( webAction.toBuilder()
-                                .password( s )
-                                .successStatus( successStatus )
-                                .build() );
-                    } );
                 }
                 catch ( final PwmOperationalException e )
                 {
                     LOGGER.warn( () -> "error decoding stored pw value on setting '" + pwmSetting.getKey() + "': " + e.getMessage() );
                 }
+
+                final String passwordValue = decodedValue.orElse( "" );
+                clonedWebActions.add( webAction.toBuilder()
+                        .password( passwordValue )
+                        .successStatus( successStatus )
+                        .build() );
             }
 
             return Optional.of( value.toBuilder().webActions( clonedWebActions ).build() );
         }
-
-
     }
 
     @Override
@@ -209,31 +209,26 @@ public class ActionValue extends AbstractValue implements StoredValue
     )
     {
         final List<ActionConfiguration.WebAction> clonedWebActions = new ArrayList<>( webActions.size() );
+
         for ( final ActionConfiguration.WebAction webAction : webActions )
         {
-            if ( StringUtil.notEmpty( webAction.getPassword() ) )
+            try
             {
-                try
-                {
-                    final String encodedValue = StoredValueEncoder.encode(
-                            webAction.getPassword(),
-                            xmlOutputProcessData.getStoredValueEncoderMode(),
-                            xmlOutputProcessData.getPwmSecurityKey() );
-                    clonedWebActions.add( webAction.toBuilder()
-                            .password( encodedValue )
-                            .build() );
-                }
-                catch ( final PwmOperationalException e )
-                {
-                    LOGGER.warn( () -> "error encoding stored pw value: " + e.getMessage() );
-                }
+                final String encodedValue = StringUtil.isEmpty( webAction.getPassword() )
+                        ? ""
+                        : StoredValueEncoder.encode( webAction.getPassword(),
+                                xmlOutputProcessData.getStoredValueEncoderMode(),
+                                xmlOutputProcessData.getPwmSecurityKey() );
+                clonedWebActions.add( webAction.toBuilder()
+                        .password( encodedValue )
+                        .build() );
             }
-            else
+            catch ( final PwmOperationalException e )
             {
-                clonedWebActions.add( webAction.toBuilder().build() );
+                throw new PwmInternalException( "error encoding stored pw value: " + e.getMessage() );
             }
         }
-        return clonedWebActions;
+        return Collections.unmodifiableList( clonedWebActions );
     }
 
     @Override
@@ -323,6 +318,10 @@ public class ActionValue extends AbstractValue implements StoredValue
                 sb.append( "\n   WebServiceAction: " );
                 sb.append( "\n    method=" ).append( webAction.getMethod() );
                 sb.append( "\n    url=" ).append( webAction.getUrl() );
+                if ( !CollectionUtil.isEmpty( webAction.getCertificates() ) )
+                {
+                    sb.append( "\n  certs=" ).append( X509Utils.makeDebugTexts( webAction.getCertificates() ) );
+                }
                 sb.append( "\n    headers=" ).append( JsonFactory.get().serializeMap( webAction.getHeaders() ) );
                 sb.append( "\n    username=" ).append( webAction.getUsername() );
                 sb.append( "\n    password=" ).append(

+ 4 - 3
server/src/main/java/password/pwm/config/value/PrivateKeyValue.java

@@ -31,6 +31,7 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.X509CertInfo;
 import password.pwm.util.secure.X509Utils;
 
 import java.io.Serializable;
@@ -211,8 +212,8 @@ public class PrivateKeyValue extends AbstractValue
     {
         if ( privateKeyCertificate != null )
         {
-            return "PrivateKeyCertificate: key=" + JsonFactory.get().serializeMap( X509Utils.makeDebugInfoMap( privateKeyCertificate.getKey() ) )
-                    + ", certificates=" + JsonFactory.get().serializeCollection( X509Utils.makeDebugInfoMap( privateKeyCertificate.getCertificates() ) );
+            return "PrivateKeyCertificate: key=" + JsonFactory.get().serializeMap( X509CertInfo.makeDebugInfoMap( privateKeyCertificate.getKey() ) )
+                    + ", certificates=" + JsonFactory.get().serializeCollection( X509CertInfo.makeDebugInfoMap( privateKeyCertificate.getCertificates() ) );
         }
         return "";
     }
@@ -230,7 +231,7 @@ public class PrivateKeyValue extends AbstractValue
                 }
                 : null;
         final Map<String, Object> returnMap = new LinkedHashMap<>();
-        returnMap.put( "certificates", X509Utils.makeDebugInfoMap( privateKeyCertificate.getCertificates(), flags ) );
+        returnMap.put( "certificates", X509CertInfo.makeDebugInfoMap( privateKeyCertificate.getCertificates(), flags ) );
         final Map<String, Object> privateKeyInfo = new LinkedHashMap<>();
         privateKeyInfo.put( "algorithm", privateKeyCertificate.getKey().getAlgorithm() );
         privateKeyInfo.put( "format", privateKeyCertificate.getKey().getFormat() );

+ 8 - 28
server/src/main/java/password/pwm/config/value/X509CertificateValue.java

@@ -26,19 +26,15 @@ import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.XmlOutputProcessData;
-import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
-import password.pwm.util.secure.SecureEngine;
+import password.pwm.util.secure.X509CertInfo;
 import password.pwm.util.secure.X509Utils;
 
-import java.io.ByteArrayInputStream;
 import java.io.Serializable;
-import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -146,23 +142,11 @@ public class X509CertificateValue extends AbstractValue implements StoredValue
         final int counter = 0;
         for ( final X509Certificate cert : certs.get() )
         {
-            sb.append( "Certificate " ).append( counter ).append( '\n' );
-            sb.append( " Subject: " ).append( cert.getSubjectDN().toString() ).append( '\n' );
-            sb.append( " Serial: " ).append( X509Utils.hexSerial( cert ) ).append( '\n' );
-            sb.append( " Issuer: " ).append( cert.getIssuerDN().toString() ).append( '\n' );
-            sb.append( " IssueDate: " ).append( StringUtil.toIsoDate( cert.getNotBefore().toInstant() ) ).append( '\n' );
-            sb.append( " ExpireDate: " ).append( StringUtil.toIsoDate( cert.getNotAfter().toInstant() ) ).append( '\n' );
-            try
+            sb.append( "Certificate " + counter + "\n" );
+            X509CertInfo.makeDebugInfoMap( cert ).forEach( ( key, value ) ->
             {
-                sb.append( " MD5 Hash: " ).append( SecureEngine.hash( new ByteArrayInputStream( cert.getEncoded() ),
-                        PwmHashAlgorithm.MD5 ) ).append( '\n' );
-                sb.append( " SHA1 Hash: " ).append( SecureEngine.hash( new ByteArrayInputStream( cert.getEncoded() ),
-                        PwmHashAlgorithm.SHA1 ) ).append( '\n' );
-            }
-            catch ( final PwmUnrecoverableException | CertificateEncodingException e )
-            {
-                LOGGER.warn( () -> "error generating hash for certificate: " + e.getMessage() );
-            }
+                sb.append( " " ).append( key ).append( ": " ).append( value ).append( "\n" );
+            } );
         }
         return sb.toString();
     }
@@ -180,14 +164,10 @@ public class X509CertificateValue extends AbstractValue implements StoredValue
             return Collections.emptyList();
         }
 
-        final X509Utils.DebugInfoFlag[] flags = includeDetail
-                ? new X509Utils.DebugInfoFlag[]
-                {
-                        X509Utils.DebugInfoFlag.IncludeCertificateDetail,
-                }
-                : null;
+        return certs.get().stream()
+                .map( cert -> X509CertInfo.makeDebugInfoMap( cert, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
+                .collect( Collectors.toUnmodifiableList() );
 
-        return certs.get().stream().map( cert -> X509Utils.makeDebugInfoMap( cert, flags ) ).collect( Collectors.toUnmodifiableList() );
     }
 
 }

+ 15 - 5
server/src/main/java/password/pwm/error/PwmInternalException.java

@@ -22,14 +22,29 @@ package password.pwm.error;
 
 public class PwmInternalException extends RuntimeException
 {
+    protected final ErrorInformation errorInformation;
+
+    public PwmInternalException( final ErrorInformation error )
+    {
+        this.errorInformation = error == null ? new ErrorInformation( PwmError.ERROR_INTERNAL ) : error;
+    }
+
+    public PwmInternalException( final Throwable cause )
+    {
+        super( cause );
+        this.errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "cause: " + cause.getMessage() );
+    }
+
     public PwmInternalException( final String message, final Throwable cause )
     {
         super( message, cause );
+        this.errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, message + ", cause: " + cause.getMessage() );
     }
 
     public PwmInternalException( final String message )
     {
         super( message );
+        this.errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, message );
     }
 
     public static PwmInternalException fromPwmException( final String message, final Exception pwmException )
@@ -41,9 +56,4 @@ public class PwmInternalException extends RuntimeException
     {
         return new PwmInternalException( pwmException );
     }
-
-    public PwmInternalException( final Throwable cause )
-    {
-        super( cause );
-    }
 }

+ 1 - 9
server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java

@@ -202,15 +202,7 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
             stackTraceText = errorStack.toString();
         }
 
-        String stackTraceHash = "hash";
-        try
-        {
-            stackTraceHash = SecureEngine.hash( stackTraceText, PwmHashAlgorithm.SHA1 );
-        }
-        catch ( final PwmUnrecoverableException e1 )
-        {
-            /* */
-        }
+        final String stackTraceHash = SecureEngine.hash( stackTraceText, PwmHashAlgorithm.SHA1 );
         final String errorMsg = "unexpected error processing request: " + JavaHelper.readHostileExceptionMessage( e ) + " [" + stackTraceHash + "]";
 
         LOGGER.error( pwmRequest, () -> errorMsg, e );

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/function/ActionCertViewerFunction.java

@@ -26,6 +26,7 @@ import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.http.PwmRequest;
 import password.pwm.util.json.JsonFactory;
+import password.pwm.util.secure.X509CertInfo;
 import password.pwm.util.secure.X509Utils;
 
 import java.io.Serializable;
@@ -71,7 +72,7 @@ public class ActionCertViewerFunction implements SettingUIFunction
         final ActionConfiguration.WebAction webAction = actionConfiguration.getWebActions().get( webActionIter );
 
         return webAction.getCertificates().stream()
-                .map( cert -> X509Utils.makeDebugInfoMap( cert, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
+                .map( cert -> X509CertInfo.makeDebugInfoMap( cert, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
                 .collect( Collectors.toUnmodifiableList() );
     }
 }

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/function/RemoteWebServiceCertViewerFunction.java

@@ -26,6 +26,7 @@ import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 import password.pwm.http.PwmRequest;
 import password.pwm.util.json.JsonFactory;
+import password.pwm.util.secure.X509CertInfo;
 import password.pwm.util.secure.X509Utils;
 
 import java.io.Serializable;
@@ -68,7 +69,7 @@ public class RemoteWebServiceCertViewerFunction implements SettingUIFunction
         final RemoteWebServiceConfiguration remoteWebServiceConfiguration = values.get( iteration );
 
         return remoteWebServiceConfiguration.getCertificates().stream()
-                .map( cert -> X509Utils.makeDebugInfoMap( cert, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
+                .map( cert -> X509CertInfo.makeDebugInfoMap( cert, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
                 .collect( Collectors.toUnmodifiableList() );
     }
 }

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/function/X509CertViewerFunction.java

@@ -25,6 +25,7 @@ import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.http.PwmRequest;
+import password.pwm.util.secure.X509CertInfo;
 import password.pwm.util.secure.X509Utils;
 
 import java.io.Serializable;
@@ -62,7 +63,7 @@ public class X509CertViewerFunction implements SettingUIFunction
         final List<X509Certificate> values = ( ( X509CertificateValue ) storedValue ).asX509Certificates();
 
         return values.stream()
-                .map( cert -> X509Utils.makeDebugInfoMap( cert, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
+                .map( cert -> X509CertInfo.makeDebugInfoMap( cert, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
                 .collect( Collectors.toUnmodifiableList() );
     }
 }

+ 2 - 11
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java

@@ -47,7 +47,6 @@ import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
 import java.io.Serializable;
-import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.util.ArrayList;
@@ -175,23 +174,15 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
             final String profileId,
             final X509Certificate certificate
     )
-            throws PwmUnrecoverableException
     {
         final CertificateDebugDataItem.CertificateDebugDataItemBuilder builder = CertificateDebugDataItem.builder();
         builder.menuLocation( setting.toMenuLocationDebug( profileId, PwmConstants.DEFAULT_LOCALE ) );
-        builder.subject( certificate.getSubjectDN().toString() );
+        builder.subject( certificate.getSubjectX500Principal().getName() );
         builder.serial( certificate.getSerialNumber().toString() );
         builder.algorithm( certificate.getSigAlgName() );
         builder.issueDate( certificate.getNotBefore().toInstant() );
         builder.expirationDate( certificate.getNotAfter().toInstant() );
-        try
-        {
-            builder.detail( X509Utils.makeDetailText( certificate ) );
-        }
-        catch ( final CertificateEncodingException e )
-        {
-            LOGGER.error( () -> "unexpected error parsing certificate detail text: " + e.getMessage() );
-        }
+        builder.detail( X509Utils.makeDetailText( certificate ) );
         return builder.build();
     }
 

+ 3 - 13
server/src/main/java/password/pwm/svc/wordlist/WordlistConfiguration.java

@@ -28,13 +28,12 @@ import password.pwm.AppAttribute;
 import password.pwm.AppProperty;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
-import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.MiscUtil;
-import password.pwm.util.json.JsonFactory;
 import password.pwm.util.java.LazySupplier;
+import password.pwm.util.java.MiscUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
+import password.pwm.util.json.JsonFactory;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.SecureEngine;
@@ -165,16 +164,7 @@ public class WordlistConfiguration implements Serializable
 
     @Getter( AccessLevel.PRIVATE )
     private final transient Supplier<String> configHash = new LazySupplier<>( () ->
-    {
-        try
-        {
-            return SecureEngine.hash( JsonFactory.get().serialize( WordlistConfiguration.this ), HASH_ALGORITHM );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            throw new IllegalStateException( "unexpected error generating wordlist-config hash: " + e.getMessage() );
-        }
-    } );
+            SecureEngine.hash( JsonFactory.get().serialize( WordlistConfiguration.this ), HASH_ALGORITHM ) );
 
     public boolean isAutoImportUrlConfigured()
     {

+ 2 - 1
server/src/main/java/password/pwm/util/cli/commands/LdapSchemaExtendCommand.java

@@ -32,6 +32,7 @@ import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.json.JsonProvider;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.secure.PwmTrustManager;
+import password.pwm.util.secure.X509CertInfo;
 import password.pwm.util.secure.X509Utils;
 
 import javax.net.ssl.X509TrustManager;
@@ -128,7 +129,7 @@ public class LdapSchemaExtendCommand extends AbstractCliCommand
         {
             out( "ldaps certificates from: " + url );
             final List<X509Certificate> certificateList = X509Utils.readRemoteCertificates( new URI ( url ), cliEnvironment.getConfig() );
-            out( JsonFactory.get().serializeCollection( X509Utils.makeDebugInfoMap( certificateList ), JsonProvider.Flag.PrettyPrint ) );
+            out( JsonFactory.get().serializeCollection( X509CertInfo.makeDebugInfoMap( certificateList ), JsonProvider.Flag.PrettyPrint ) );
             return certificateList;
         }
         return Collections.emptyList();

+ 1 - 1
server/src/main/java/password/pwm/util/secure/CertificateReadingTrustManager.java

@@ -117,7 +117,7 @@ public class CertificateReadingTrustManager implements X509TrustManager
         }
 
         LOGGER.debug( () -> "ServerCertReader: read self-signed certificates from remote server: "
-                + JsonFactory.get().serialize( new ArrayList<>( X509Utils.makeDebugInfoMap( certificates ) ) ) );
+                + JsonFactory.get().serialize( new ArrayList<>( X509CertInfo.makeDebugInfoMap( certificates ) ) ) );
         return Collections.unmodifiableList( certificates );
     }
 }

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

@@ -170,7 +170,7 @@ public class HttpsServerCertificateManager
             final PrivateKey key = entry.getPrivateKey();
             final List<X509Certificate> certificates = Arrays.asList( ( X509Certificate[] ) entry.getCertificateChain() );
 
-            LOGGER.debug( () -> "importing certificate chain: " + JsonFactory.get().serializeCollection( X509Utils.makeDebugInfoMap( certificates ) ) );
+            LOGGER.debug( () -> "importing certificate chain: " + JsonFactory.get().serializeCollection( X509CertInfo.makeDebugInfoMap( certificates ) ) );
             privateKeyCertificate = new PrivateKeyCertificate( certificates, key );
         }
         catch ( final Exception e )

+ 21 - 0
server/src/main/java/password/pwm/util/secure/PwmHashAlgorithm.java

@@ -20,6 +20,13 @@
 
 package password.pwm.util.secure;
 
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmInternalException;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
 public enum PwmHashAlgorithm
 {
     MD5( "MD5", 32 ),
@@ -46,4 +53,18 @@ public enum PwmHashAlgorithm
     {
         return hexValueLength;
     }
+
+    public MessageDigest newMessageDigest()
+    {
+        try
+        {
+            return MessageDigest.getInstance( getAlgName() );
+        }
+        catch ( final NoSuchAlgorithmException e )
+        {
+            final String errorMsg = "missing hash algorithm: " + e.getMessage();
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg );
+            throw new PwmInternalException( errorInformation );
+        }
+    }
 }

+ 15 - 30
server/src/main/java/password/pwm/util/secure/SecureEngine.java

@@ -24,6 +24,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import password.pwm.PwmConstants;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
+import password.pwm.error.PwmInternalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
@@ -313,12 +314,8 @@ public class SecureEngine
             final String input,
             final PwmHashAlgorithm algorithm
     )
-            throws PwmUnrecoverableException
     {
-        if ( input == null || input.length() < 1 )
-        {
-            return null;
-        }
+        Objects.requireNonNull( input );
         return hash( new ByteArrayInputStream( input.getBytes( PwmConstants.DEFAULT_CHARSET ) ), algorithm );
     }
 
@@ -326,7 +323,6 @@ public class SecureEngine
             final InputStream is,
             final PwmHashAlgorithm algorithm
     )
-            throws PwmUnrecoverableException
     {
         return JavaHelper.binaryArrayToHex( computeHashToBytes( is, algorithm ) );
     }
@@ -336,8 +332,8 @@ public class SecureEngine
             final PwmSecurityKey pwmSecurityKey,
             final String input
     )
-            throws PwmUnrecoverableException
     {
+        Objects.requireNonNull( input );
         return JavaHelper.binaryArrayToHex( computeHmacToBytes( hmacAlgorithm, pwmSecurityKey, input.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
     }
 
@@ -346,8 +342,8 @@ public class SecureEngine
             final PwmSecurityKey pwmSecurityKey,
             final String input
     )
-            throws PwmUnrecoverableException
     {
+        Objects.requireNonNull( input );
         return JavaHelper.binaryArrayToHex( computeHmacToBytes( hmacAlgorithm, pwmSecurityKey, input.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
     }
 
@@ -356,21 +352,19 @@ public class SecureEngine
             final PwmSecurityKey pwmSecurityKey,
             final byte[] input
     )
-            throws PwmUnrecoverableException
     {
         try
         {
-
             final Mac mac = Mac.getInstance( hmacAlgorithm.getAlgorithmName() );
             final SecretKey secretKey = pwmSecurityKey.getKey( hmacAlgorithm.getKeyType() );
             mac.init( secretKey );
             return mac.doFinal( input );
         }
-        catch ( final GeneralSecurityException e )
+        catch ( final GeneralSecurityException | PwmUnrecoverableException e )
         {
             final String errorMsg = "error during hmac operation: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg );
-            throw new PwmUnrecoverableException( errorInformation );
+            throw new PwmInternalException( errorInformation );
         }
     }
 
@@ -379,22 +373,13 @@ public class SecureEngine
             final InputStream is,
             final PwmHashAlgorithm algorithm
     )
-            throws PwmUnrecoverableException
     {
+        Objects.requireNonNull( is );
+        Objects.requireNonNull( algorithm );
 
         final InputStream bis = is instanceof BufferedInputStream ? is : new BufferedInputStream( is );
 
-        final MessageDigest messageDigest;
-        try
-        {
-            messageDigest = MessageDigest.getInstance( algorithm.getAlgName() );
-        }
-        catch ( final NoSuchAlgorithmException e )
-        {
-            final String errorMsg = "missing hash algorithm: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg );
-            throw new PwmUnrecoverableException( errorInformation );
-        }
+        final MessageDigest messageDigest = algorithm.newMessageDigest();
 
         try
         {
@@ -417,7 +402,7 @@ public class SecureEngine
         {
             final String errorMsg = "unexpected error during hash operation: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg );
-            throw new PwmUnrecoverableException( errorInformation );
+            throw new PwmInternalException( errorInformation );
         }
     }
 
@@ -449,11 +434,11 @@ public class SecureEngine
         NonceGenerator( final int fixedComponentLength, final int counterComponentLength )
         {
             this.fixedComponentLength = fixedComponentLength;
-            value = new byte[ fixedComponentLength + counterComponentLength ];
+            value = new byte[fixedComponentLength + counterComponentLength];
             PwmRandom.getInstance().nextBytes( value );
         }
 
-        public synchronized byte[] nextValue( )
+        public synchronized byte[] nextValue()
         {
             lock.lock();
             try
@@ -469,9 +454,9 @@ public class SecureEngine
 
         private void increment( final int index )
         {
-            if ( value[ index ] == Byte.MAX_VALUE )
+            if ( value[index] == Byte.MAX_VALUE )
             {
-                value[ index ] = 0;
+                value[index] = 0;
                 if ( index > fixedComponentLength )
                 {
                     increment( index - 1 );
@@ -479,7 +464,7 @@ public class SecureEngine
             }
             else
             {
-                value[ index ]++;
+                value[index]++;
             }
         }
     }

+ 96 - 0
server/src/main/java/password/pwm/util/secure/X509CertDataParser.java

@@ -0,0 +1,96 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 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;
+
+import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.security.PublicKey;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+class X509CertDataParser
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( X509CertDataParser.class );
+
+    static Optional<String> readCertSubject( final X509Certificate cert )
+    {
+        if ( cert.getSubjectX500Principal() != null )
+        {
+            return Optional.of( cert.getSubjectX500Principal().getName() );
+        }
+        return Optional.empty();
+    }
+
+    static Optional<String> readCertIssuer( final X509Certificate cert )
+    {
+        if ( cert.getIssuerX500Principal() != null )
+        {
+            return Optional.of( cert.getIssuerX500Principal().getName() );
+        }
+        return Optional.empty();
+    }
+
+    static Optional<String> readCertPublicKeyInfo( final X509Certificate cert )
+    {
+        if ( cert.getPublicKey() != null )
+        {
+            final PublicKey publicKey = cert.getPublicKey();
+            final String publicKeyInfo = publicKey.getFormat() + " " + publicKey.getAlgorithm();
+            return Optional.of( publicKeyInfo );
+        }
+
+        return Optional.empty();
+    }
+
+    static Optional<String> readCertSubjectAlternativeNames( final X509Certificate cert )
+    {
+
+        try
+        {
+            if ( !CollectionUtil.isEmpty( cert.getSubjectAlternativeNames() ) )
+            {
+                final String sans = cert.getSubjectAlternativeNames().stream()
+                        .map( Object::toString )
+                        .collect( Collectors.joining( "," ) );
+
+                return Optional.of( sans );
+            }
+        }
+        catch ( final CertificateParsingException e )
+        {
+            LOGGER.trace( () -> "error while examining subject alternate names for certificate: " + e.getMessage() );
+        }
+
+        return Optional.empty();
+    }
+
+    static boolean certIsSigningKey( final X509Certificate certificate )
+    {
+        final int keyCertSignBitPosition = 5;
+        final boolean[] keyUsages = certificate.getKeyUsage();
+        return keyUsages != null
+                && keyUsages.length > keyCertSignBitPosition - 1
+                && keyUsages[keyCertSignBitPosition];
+    }
+}

+ 111 - 0
server/src/main/java/password/pwm/util/secure/X509CertInfo.java

@@ -0,0 +1,111 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 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;
+
+import password.pwm.PwmConstants;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public enum X509CertInfo
+{
+    subject,
+    subjectAlternateNames,
+    publicKeyType,
+    serial,
+    issuer,
+    issueDate,
+    expireDate,
+    md5Hash,
+    sha1Hash,
+    sha256Hash,
+    sha512Hash,
+    isKeySigning,
+    detail,;
+
+    public static List<Map<String, String>> makeDebugInfoMap( final List<X509Certificate> certificates, final X509Utils.DebugInfoFlag... flags )
+    {
+        final List<Map<String, String>> returnList = new ArrayList<>();
+        if ( certificates != null )
+        {
+            for ( final X509Certificate cert : certificates )
+            {
+                returnList.add( makeDebugInfoMap( cert, flags ) );
+            }
+        }
+        return Collections.unmodifiableList( returnList );
+    }
+
+    public static Map<String, String> makeDebugInfoMap( final X509Certificate cert, final X509Utils.DebugInfoFlag... flags )
+    {
+        if ( JavaHelper.enumArrayContainsValue( flags, X509Utils.DebugInfoFlag.IncludeCertificateDetail ) )
+        {
+            final Map<X509CertInfo, String> infoMap = new EnumMap<>( X509CertInfo.class );
+            infoMap.putAll( makeDebugInfoMapImpl( cert ) );
+            infoMap.put( detail, X509Utils.makeDetailText( cert ) );
+            return CollectionUtil.enumMapToStringMap( infoMap );
+        }
+
+        return CollectionUtil.enumMapToStringMap( makeDebugInfoMapImpl( cert ) );
+    }
+
+    static Map<X509CertInfo, String> makeDebugInfoMapImpl( final X509Certificate cert  )
+    {
+
+        final Map<X509CertInfo, String> returnMap = new EnumMap<>( X509CertInfo.class );
+
+        returnMap.put( serial, X509Utils.hexSerial( cert ) );
+        returnMap.put( issueDate, StringUtil.toIsoDate( cert.getNotBefore().toInstant() ) );
+        returnMap.put( expireDate, StringUtil.toIsoDate( cert.getNotAfter().toInstant() ) );
+        returnMap.put( md5Hash, X509Utils.hash( cert, PwmHashAlgorithm.MD5 ) );
+        returnMap.put( sha1Hash, X509Utils.hash( cert, PwmHashAlgorithm.SHA1 ) );
+        returnMap.put( sha256Hash, X509Utils.hash( cert, PwmHashAlgorithm.SHA256 ) );
+        returnMap.put( sha512Hash, X509Utils.hash( cert, PwmHashAlgorithm.SHA512 ) );
+        returnMap.put( isKeySigning, LocaleHelper.valueBoolean( PwmConstants.DEFAULT_LOCALE, X509CertDataParser.certIsSigningKey( cert ) ) );
+
+        X509CertDataParser.readCertSubject( cert ).ifPresent( s -> returnMap.put( subject, s ) );
+        X509CertDataParser.readCertIssuer( cert ).ifPresent( s -> returnMap.put( issuer, s ) );
+
+        X509CertDataParser.readCertPublicKeyInfo( cert ).ifPresent( s -> returnMap.put( publicKeyType, s ) );
+
+        X509CertDataParser.readCertSubjectAlternativeNames( cert ).ifPresent( s -> returnMap.put( subjectAlternateNames, s ) );
+
+        return Collections.unmodifiableMap( returnMap );
+    }
+
+    public static Map<String, String> makeDebugInfoMap( final PrivateKey key )
+    {
+        final Map<String, String> returnMap = new LinkedHashMap<>();
+        returnMap.put( X509Utils.KeyDebugInfoKey.algorithm.toString(), key.getAlgorithm() );
+        returnMap.put( X509Utils.KeyDebugInfoKey.format.toString(), key.getFormat() );
+        return returnMap;
+    }
+}

+ 37 - 115
server/src/main/java/password/pwm/util/secure/X509Utils.java

@@ -28,6 +28,7 @@ import password.pwm.config.option.CertificateMatchingMode;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
+import password.pwm.error.PwmInternalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
@@ -56,7 +57,6 @@ import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
 import java.security.SecureRandom;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
@@ -65,10 +65,8 @@ import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -82,11 +80,27 @@ public class X509Utils
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( X509Utils.class );
 
-    private X509Utils()
+    public enum ReadCertificateFlag
+    {
+        ReadOnlyRootCA
+    }
+
+    public enum DebugInfoFlag
     {
+        IncludeCertificateDetail
     }
 
 
+    enum KeyDebugInfoKey
+    {
+        algorithm,
+        format,
+    }
+
+    private X509Utils()
+    {
+    }
+
     public static List<X509Certificate> readRemoteCertificates(
             final URI uri,
             final AppConfig appConfig
@@ -269,33 +283,31 @@ public class X509Utils
     }
 
     public static String makeDetailText( final X509Certificate x509Certificate )
-            throws CertificateEncodingException, PwmUnrecoverableException
     {
-        return x509Certificate.toString()
-                + "\nMD5: " + hash( x509Certificate, PwmHashAlgorithm.MD5 )
-                + "\nSHA1: " + hash( x509Certificate, PwmHashAlgorithm.SHA1 )
-                + "\nSHA2-256: " + hash( x509Certificate, PwmHashAlgorithm.SHA256 )
-                + "\nSHA2-512: " + hash( x509Certificate, PwmHashAlgorithm.SHA512 )
-                + "\n:IsRootCA: " + certIsRootCA( x509Certificate );
+        final StringBuilder sb = new StringBuilder();
+        X509CertInfo.makeDebugInfoMapImpl( x509Certificate ).forEach( ( key, value ) ->
+        {
+            sb.append( key );
+            sb.append( ": " );
+            sb.append( value );
+            sb.append( "\n" );
+        } );
+
+        sb.append( x509Certificate );
+        sb.append( "\n" );
+        return sb.toString();
     }
 
     public static String makeDebugTexts( final List<X509Certificate> x509Certificates )
     {
-        final StringBuilder sb = new StringBuilder();
-        sb.append( "Certificates: " );
-        for ( final X509Certificate x509Certificate : x509Certificates )
-        {
-            sb.append( '[' );
-            sb.append( makeDebugText( x509Certificate ) );
-            sb.append( ']' );
-
-        }
-        return sb.toString();
+        return x509Certificates.stream()
+                .map( x509Certificate -> '[' + makeDebugText( x509Certificate ) + ']' )
+                .collect( Collectors.joining( "", "Certificates: ", "" ) );
     }
 
     public static String makeDebugText( final X509Certificate x509Certificate )
     {
-        return "subject=" + x509Certificate.getSubjectDN().getName() + ", serial=" + x509Certificate.getSerialNumber();
+        return "subject=" + X509CertDataParser.readCertSubject( x509Certificate ) + ", serial=" + x509Certificate.getSerialNumber();
     }
 
     public static List<X509Certificate> certificatesFromBase64s( final Collection<String> b64certificates )
@@ -363,80 +375,6 @@ public class X509Utils
         }
     }
 
-    enum CertDebugInfoKey
-    {
-        subject,
-        serial,
-        issuer,
-        issueDate,
-        expireDate,
-        md5Hash,
-        sha1Hash,
-        sha512Hash,
-        detail,
-    }
-
-    public enum ReadCertificateFlag
-    {
-        ReadOnlyRootCA
-    }
-
-    public enum DebugInfoFlag
-    {
-        IncludeCertificateDetail
-    }
-
-    public static List<Map<String, String>> makeDebugInfoMap( final List<X509Certificate> certificates, final DebugInfoFlag... flags )
-    {
-        final List<Map<String, String>> returnList = new ArrayList<>();
-        if ( certificates != null )
-        {
-            for ( final X509Certificate cert : certificates )
-            {
-                returnList.add( makeDebugInfoMap( cert, flags ) );
-            }
-        }
-        return returnList;
-    }
-
-    public static Map<String, String> makeDebugInfoMap( final X509Certificate cert, final DebugInfoFlag... flags )
-    {
-        final Map<String, String> returnMap = new LinkedHashMap<>();
-        returnMap.put( CertDebugInfoKey.subject.toString(), cert.getSubjectDN().toString() );
-        returnMap.put( CertDebugInfoKey.serial.toString(), X509Utils.hexSerial( cert ) );
-        returnMap.put( CertDebugInfoKey.issuer.toString(), cert.getIssuerDN().toString() );
-        returnMap.put( CertDebugInfoKey.issueDate.toString(), StringUtil.toIsoDate( cert.getNotBefore().toInstant() ) );
-        returnMap.put( CertDebugInfoKey.expireDate.toString(), StringUtil.toIsoDate( cert.getNotAfter().toInstant() ) );
-        try
-        {
-            returnMap.put( CertDebugInfoKey.md5Hash.toString(), hash( cert, PwmHashAlgorithm.MD5 ) );
-            returnMap.put( CertDebugInfoKey.sha1Hash.toString(), hash( cert, PwmHashAlgorithm.SHA1 ) );
-            returnMap.put( CertDebugInfoKey.sha512Hash.toString(), hash( cert, PwmHashAlgorithm.SHA512 ) );
-            if ( JavaHelper.enumArrayContainsValue( flags, DebugInfoFlag.IncludeCertificateDetail ) )
-            {
-                returnMap.put( CertDebugInfoKey.detail.toString(), X509Utils.makeDetailText( cert ) );
-            }
-        }
-        catch ( final PwmUnrecoverableException | CertificateEncodingException e )
-        {
-            LOGGER.warn( () -> "error generating hash for certificate: " + e.getMessage() );
-        }
-        return returnMap;
-    }
-
-    enum KeyDebugInfoKey
-    {
-        algorithm,
-        format,
-    }
-
-    public static Map<String, String> makeDebugInfoMap( final PrivateKey key )
-    {
-        final Map<String, String> returnMap = new LinkedHashMap<>();
-        returnMap.put( KeyDebugInfoKey.algorithm.toString(), key.getAlgorithm() );
-        returnMap.put( KeyDebugInfoKey.format.toString(), key.getFormat() );
-        return returnMap;
-    }
 
     public static X509Certificate certificateFromBase64( final String b64encodedStr )
             throws CertificateException, IOException
@@ -462,7 +400,7 @@ public class X509Utils
 
         for ( final X509Certificate certificate : certificates )
         {
-            if ( certIsRootCA( certificate ) )
+            if ( X509CertDataParser.certIsSigningKey( certificate ) )
             {
                 returnList.add( certificate );
             }
@@ -476,21 +414,6 @@ public class X509Utils
         return Optional.empty();
     }
 
-    private static boolean certIsRootCA( final X509Certificate certificate )
-    {
-        final int keyCertSignBitPosition = 5;
-        final boolean[] keyUsages = certificate.getKeyUsage();
-        if ( keyUsages != null && keyUsages.length > keyCertSignBitPosition - 1 )
-        {
-            if ( keyUsages[keyCertSignBitPosition] )
-            {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
     public static TrustManager[] getDefaultJavaTrustManager( final AppConfig appConfig )
             throws PwmUnrecoverableException
     {
@@ -509,7 +432,6 @@ public class X509Utils
     }
 
     public static String hash( final X509Certificate certificate, final PwmHashAlgorithm pwmHashAlgorithm )
-            throws PwmUnrecoverableException
     {
         try
         {
@@ -517,7 +439,8 @@ public class X509Utils
         }
         catch ( final CertificateEncodingException e )
         {
-            throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unexpected error encoding certificate: " + e.getMessage() );
+            throw new PwmInternalException( new ErrorInformation( PwmError.ERROR_INTERNAL,
+                    "unexpected error encoding certificate: " + e.getMessage() ) );
         }
     }
 
@@ -548,5 +471,4 @@ public class X509Utils
         }
         return Collections.unmodifiableSet( resultCertificates );
     }
-
 }

+ 102 - 0
server/src/test/java/password/pwm/http/ServletTest.java

@@ -0,0 +1,102 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 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.http;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.reflections.Reflections;
+import org.reflections.scanners.Scanners;
+import org.reflections.util.ClasspathHelper;
+import org.reflections.util.ConfigurationBuilder;
+import password.pwm.PwmConstants;
+import password.pwm.util.java.StringUtil;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ServletTest
+{
+    @Test
+    public void testDuplicateServletNames()
+    {
+        final var seenServletNames = new HashSet<String>();
+
+        final var servletClasses = getServletClasses();
+
+        for ( final Class<? extends HttpServlet> httpServletClass : servletClasses )
+        {
+            final var webServletAnnotation = httpServletClass.getAnnotation( WebServlet.class );
+            if ( webServletAnnotation != null )
+            {
+                final var name = webServletAnnotation.name();
+                if ( !StringUtil.isEmpty( name ) )
+                {
+                    if ( StringUtil.caseIgnoreContains( seenServletNames, name ) )
+                    {
+                        Assert.fail( httpServletClass.getName() + " servlet class name duplicate (case ignore) detected: " + name );
+                    }
+                    seenServletNames.add( name );
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testDuplicatePatternsNames()
+    {
+        final var seenPatterns = new HashSet<String>();
+
+        final var servletClasses = getServletClasses();
+
+        for ( final Class<? extends HttpServlet> httpServletClass : servletClasses )
+        {
+            final WebServlet webServletAnnotation = httpServletClass.getAnnotation( WebServlet.class );
+            if ( webServletAnnotation != null )
+            {
+                final var names = webServletAnnotation.urlPatterns();
+                for ( final var name : names )
+                {
+                    if ( !StringUtil.isEmpty( name ) )
+                    {
+                        if ( seenPatterns.contains( name ) )
+                        {
+                            Assert.fail( httpServletClass.getName() + " servlet pattern duplicate detected: " + name );
+                        }
+                        seenPatterns.add( name );
+                    }
+                }
+            }
+        }
+    }
+
+
+    private Set<Class<? extends HttpServlet>> getServletClasses()
+    {
+        final var reflections = new Reflections( new ConfigurationBuilder()
+                .setUrls( ClasspathHelper.forPackage( PwmConstants.PWM_BASE_PACKAGE.getName() ) )
+                .setScanners( Scanners.SubTypes ) );
+
+        return Collections.unmodifiableSet( reflections.getSubTypesOf( HttpServlet.class ) );
+    }
+}

+ 2 - 1
server/src/test/java/password/pwm/http/servlet/ControlledPwmServletTest.java

@@ -26,6 +26,7 @@ import org.reflections.Reflections;
 import org.reflections.scanners.Scanners;
 import org.reflections.util.ClasspathHelper;
 import org.reflections.util.ConfigurationBuilder;
+import password.pwm.PwmConstants;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.util.java.JavaHelper;
@@ -193,7 +194,7 @@ public class ControlledPwmServletTest
     private Map<Class<? extends ControlledPwmServlet>, Map<String, Method>> getClassAndMethods()
     {
         final Reflections reflections = new Reflections( new ConfigurationBuilder()
-                .setUrls( ClasspathHelper.forPackage( "password.pwm" ) )
+                .setUrls( ClasspathHelper.forPackage( PwmConstants.PWM_BASE_PACKAGE.getName() ) )
                 .setScanners( Scanners.SubTypes,
                         Scanners.TypesAnnotated,
                         Scanners.FieldsAnnotated

+ 2 - 1
server/src/test/java/password/pwm/ws/server/rest/RestServletTest.java

@@ -26,6 +26,7 @@ import org.reflections.Reflections;
 import org.reflections.scanners.Scanners;
 import org.reflections.util.ClasspathHelper;
 import org.reflections.util.ConfigurationBuilder;
+import password.pwm.PwmConstants;
 import password.pwm.http.HttpContentType;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.ws.server.RestMethodHandler;
@@ -141,7 +142,7 @@ public class RestServletTest
     private Set<Class<? extends RestServlet>> getClasses()
     {
         final Reflections reflections = new Reflections( new ConfigurationBuilder()
-                .setUrls( ClasspathHelper.forPackage( "password.pwm" ) )
+                .setUrls( ClasspathHelper.forPackage( PwmConstants.PWM_BASE_PACKAGE.getName() ) )
                 .setScanners( Scanners.SubTypes,
                         Scanners.TypesAnnotated,
                         Scanners.FieldsAnnotated

+ 13 - 11
webapp/src/main/webapp/public/resources/js/configeditor-settings-action.js

@@ -245,7 +245,7 @@ ActionHandler.showActionsDialog = function(keyName, iteration) {
             for (var iter in value['ldapActions']) {
                 (function (ldapActionsIter) {
                     var inputID = keyName + '_' + iteration + "_ldapActions_"  + ldapActionsIter;
-                    PWM_MAIN.addEventHandler('icon-editLdapAction-' + inputID ,'click',function(){
+                    PWM_MAIN.addEventHandler('tableRow-' + inputID ,'click',function(){
                         ActionHandler.addOrEditLdapAction(keyName,iteration,ldapActionsIter);
                     });
                     PWM_MAIN.addEventHandler('button-deleteRow-' + inputID ,'click',function(){
@@ -266,7 +266,7 @@ ActionHandler.showActionsDialog = function(keyName, iteration) {
             for (var iter in value['webActions']) {
                 (function (webActionIter) {
                     var inputID = keyName + '_' + iteration + "_webActions_"  + webActionIter;
-                    PWM_MAIN.addEventHandler('icon-editWebAction-' + inputID ,'click',function(){
+                    PWM_MAIN.addEventHandler('tableRow-' + inputID ,'click',function(){
                         ActionHandler.addOrEditWebAction(keyName,iteration,webActionIter);
                     });
                     PWM_MAIN.addEventHandler('button-deleteRow-' + inputID ,'click',function(){
@@ -481,7 +481,7 @@ ActionHandler.addOrEditWebAction = function(keyName, iteration, webActionIter) {
             }
             if (value['certificates']) {
                 PWM_MAIN.addEventHandler('button-' + inputID + '-certDetail','click',function(){
-                    var extraData = JSON.stringify({iteration:iteration, webActionIter: webActionIter});
+                    let extraData = JSON.stringify({iteration:iteration, webActionIter: webActionIter, keyName:keyName});
                     PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.http.servlet.configeditor.function.ActionCertViewerFunction',
                         ActionHandler.showCertificateViewerDialog, extraData)
                 });
@@ -500,7 +500,7 @@ ActionHandler.addOrEditWebAction = function(keyName, iteration, webActionIter) {
                         PWM_MAIN.showDialog({width:700,title: 'Results', text: msgBody, okAction: function () {
                                 PWM_CFGEDIT.readSetting(keyName, function(resultValue) {
                                     PWM_VAR['clientSettingCache'][keyName] = resultValue;
-                                    ActidonHandler.addOrEditWebAction(keyName,iteration,webActionIter)
+                                    ActionHandler.addOrEditWebAction(keyName,iteration,webActionIter)
                                 });
                             }});
                     };
@@ -597,15 +597,17 @@ ActionHandler.addHeader = function(keyName, iteration, webActionIter) {
     });
 };
 
-ActionHandler.showCertificateViewerDialog = function(data) {
-    var certInfos = data['data'];
-    var bodyText = '';
-    for (var i in certInfos) {
+ActionHandler.showCertificateViewerDialog = function(data,extraDataJson) {
+    let extraData = JSON.parse(extraDataJson)
+    let keyName = extraData['keyName'];
+    let certInfos = data['data'];
+    let bodyText = '';
+    for (let i in certInfos) {
         bodyText += X509CertificateHandler.certificateToHtml(certInfos[i],keyName,i);
     }
-    var cancelFunction = function(){ ActionHandler.addOrEditWebAction(keyName,iteration); };
-    var loadFunction = function(){
-        for (var i in certInfos) {
+    let cancelFunction = function(){ ActionHandler.addOrEditWebAction(keyName, extraData['iteration'], extraData['webActionIter']); };
+    let loadFunction = function(){
+        for (let i in certInfos) {
             X509CertificateHandler.certHtmlActions(certInfos[i],keyName,i);
         }
     };

+ 26 - 3
webapp/src/main/webapp/public/resources/js/configeditor-settings-remotewebservices.js

@@ -254,9 +254,10 @@ RemoteWebServiceHandler.showOptionsDialog = function(keyName, iteration) {
             });
             if (value['certificates']) {
                 PWM_MAIN.addEventHandler('button-' + inputID + '-certDetail','click',function(){
-                    var extraData = JSON.stringify({iteration:iteration});
+                    let extraData = JSON.stringify({iteration:iteration,keyName:keyName});
+                    debugger;
                     PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.http.servlet.configeditor.function.RemoteWebServiceCertViewerFunction',
-                        ActionHandler.showCertificateViewerDialog, extraData)
+                        RemoteWebServiceHandler.showCertificateViewerDialog, extraData)
 
                 });
                 PWM_MAIN.addEventHandler('button-' + inputID + '-clearCertificates','click',function() {
@@ -368,5 +369,27 @@ RemoteWebServiceHandler.addHeader = function(keyName, iteration) {
             RemoteWebServiceHandler.showHeadersDialog(keyName, iteration);
         }
     });
+};
 
-};
+RemoteWebServiceHandler.showCertificateViewerDialog = function(data,extraDataJson) {
+    let extraData = JSON.parse(extraDataJson)
+    let keyName = extraData['keyName'];
+    let certInfos = data['data'];
+    let bodyText = '';
+    for (let i in certInfos) {
+        bodyText += X509CertificateHandler.certificateToHtml(certInfos[i],keyName,i);
+    }
+    let cancelFunction = function(){ RemoteWebServiceHandler.showOptionsDialog(keyName, extraData['iteration'])};
+    let loadFunction = function(){
+        for (let i in certInfos) {
+            X509CertificateHandler.certHtmlActions(certInfos[i],keyName,i);
+        }
+    };
+    PWM_MAIN.showDialog({
+        title:'Certificate Detail',
+        dialogClass: 'wide',
+        text:bodyText,
+        okAction:cancelFunction,
+        loadFunction:loadFunction
+    });
+};

+ 2 - 1
webapp/src/main/webapp/public/resources/js/configeditor-settings.js

@@ -1035,7 +1035,8 @@ X509CertificateHandler.certHtmlActions = function(certificate, keyName, id) {
             title: 'Detail - ' + PWM_SETTINGS['settings'][keyName]['label'] + ' - Certificate ' + id,
             text: '<pre>' + certificate['detail'] + '</pre>',
             dialogClass: 'wide',
-            showClose: true
+            showClose: true,
+            showOk: false
         });
     });
 };

+ 1 - 1
webapp/src/main/webapp/public/resources/js/configeditor.js

@@ -522,7 +522,7 @@ PWM_CFGEDIT.executeSettingFunction = function (setting, name, resultHandler, ext
                 if (data['error']) {
                     PWM_MAIN.showErrorDialog(data);
                 } else {
-                    resultHandler(data);
+                    resultHandler(data,extraData);
                 }
             };
             PWM_MAIN.ajaxRequest(requestUrl, loadFunction, {content:jsonSendData});