[java-identity-provider] 02/11: IDP-2039 - Add audit logging to login flows

Scott Cantor cantor.2 at osu.edu
Tue Jan 3 22:01:42 UTC 2023


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=bd1f417aef496198595669b70115f8e583b30762

commit bd1f417aef496198595669b70115f8e583b30762
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Dec 6 14:02:52 2022 -0500

    IDP-2039 - Add audit logging to login flows
    
    https://shibboleth.atlassian.net/browse/IDP-2039
    
    Adjust servlet to populate CertificateContext.
    Add extractors for cert issuer/subject.
    Audit the X509 flows.
---
 .../net/shibboleth/idp/authn/AuthnAuditFields.java | 14 ++++++
 .../impl/CertificateIssuerAuditExtractor.java      | 51 ++++++++++++++++++++++
 .../impl/CertificateSubjectAuditExtractor.java     | 51 ++++++++++++++++++++++
 .../impl/AbstractAuditingValidationAction.java     | 14 +++---
 .../authn/impl/ValidateExternalAuthentication.java |  2 +-
 .../shibboleth/idp/authn/impl/X509AuthServlet.java | 17 +++++++-
 .../idp/flows/authn/x509-authn-beans.xml           | 35 ++++++++++++++-
 .../idp/flows/authn/x509-internal-authn-beans.xml  | 50 ++++++++++++++++++++-
 8 files changed, 221 insertions(+), 13 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java
index a3ec311f0..097e18709 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnAuditFields.java
@@ -59,6 +59,20 @@ public final class AuthnAuditFields {
      */
     @Nonnull @NotEmpty public static final String AUTHN_RESULT = "AR";
 
+    /**
+     * X.509 cerificate subject.
+     * 
+     * @since 4.3.0
+     */
+    @Nonnull @NotEmpty public static final String X509_SUBJECT = "X509S";
+
+    /**
+     * X.509 cerificate subject.
+     * 
+     * @since 4.3.0
+     */
+    @Nonnull @NotEmpty public static final String X509_ISSUER = "X509I";
+
     /** Constructor. */
     private AuthnAuditFields() {
 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateIssuerAuditExtractor.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateIssuerAuditExtractor.java
new file mode 100644
index 000000000..40f68925a
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateIssuerAuditExtractor.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.idp.authn.audit.impl;
+
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.CertificateContext;
+
+/** {@link Function} that returns the issuer of a client certificate. */
+public class CertificateIssuerAuditExtractor implements Function<ProfileRequestContext,String> {
+
+    /** {@inheritDoc} */
+    @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+
+        final AuthenticationContext authnCtx = input.getSubcontext(AuthenticationContext.class);
+        if (authnCtx != null) {
+            final CertificateContext cc = authnCtx.getSubcontext(CertificateContext.class);
+            if (cc != null) {
+                final Certificate cert = cc.getCertificate();
+                if (cert instanceof X509Certificate) {
+                    return ((X509Certificate) cert).getIssuerX500Principal().getName();
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateSubjectAuditExtractor.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateSubjectAuditExtractor.java
new file mode 100644
index 000000000..9bd7e1f67
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/audit/impl/CertificateSubjectAuditExtractor.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.idp.authn.audit.impl;
+
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.CertificateContext;
+
+/** {@link Function} that returns the subject of a client certificate. */
+public class CertificateSubjectAuditExtractor implements Function<ProfileRequestContext,String> {
+
+    /** {@inheritDoc} */
+    @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+
+        final AuthenticationContext authnCtx = input.getSubcontext(AuthenticationContext.class);
+        if (authnCtx != null) {
+            final CertificateContext cc = authnCtx.getSubcontext(CertificateContext.class);
+            if (cc != null) {
+                final Certificate cert = cc.getCertificate();
+                if (cert instanceof X509Certificate) {
+                    return ((X509Certificate) cert).getSubjectX500Principal().getName();
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AbstractAuditingValidationAction.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AbstractAuditingValidationAction.java
index e6a68bc08..fdcdd5c66 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AbstractAuditingValidationAction.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AbstractAuditingValidationAction.java
@@ -34,9 +34,8 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.profile.audit.impl.PopulateAuditContext;
 import net.shibboleth.idp.profile.audit.impl.WriteAuditLog;
 import net.shibboleth.idp.profile.context.AuditContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.logic.Constraint;
 
 /**
  * Base class for validation actions that includes new audit logging support.
@@ -75,8 +74,7 @@ public abstract class AbstractAuditingValidationAction extends AbstractValidatio
      * @param strategy lookup strategy
      */
     public void setAuditContextCreationStrategy(@Nonnull final Function<ProfileRequestContext,AuditContext> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
+        checkSetterPreconditions();
         auditContextCreationStrategy = Constraint.isNotNull(strategy, "AuditContext creation strategy cannot be null");
     }
     
@@ -88,8 +86,7 @@ public abstract class AbstractAuditingValidationAction extends AbstractValidatio
      * @since 4.3.0
      */
     public void setPopulateAuditContextAction(@Nullable final PopulateAuditContext action) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
+        checkSetterPreconditions();
         populateAuditContextAction = action;
     }
 
@@ -101,8 +98,7 @@ public abstract class AbstractAuditingValidationAction extends AbstractValidatio
      * @since 4.3.0
      */
     public void setWriteAuditLogAction(@Nullable final WriteAuditLog action) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
+        checkSetterPreconditions();
         writeAuditLogAction = action;
     }
 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java
index 7744c37fa..7536fe2d0 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthentication.java
@@ -67,7 +67,7 @@ import net.shibboleth.shared.service.ServiceableComponent;
  * {@link AbstractValidationAction#handleError(ProfileRequestContext, AuthenticationContext, Exception, String)}
  * method is called.
  */
-public class ValidateExternalAuthentication extends AbstractValidationAction {
+public class ValidateExternalAuthentication extends AbstractAuditingValidationAction {
 
     /** Default prefix for metrics. */
     @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.external"; 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
index fc73369a8..38fd9e47f 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
@@ -34,9 +34,12 @@ import jakarta.servlet.http.HttpServletResponse;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.ExternalAuthentication;
 import net.shibboleth.idp.authn.ExternalAuthenticationException;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.CertificateContext;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
+import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.security.SecurityException;
 import org.opensaml.security.trust.TrustEngine;
 import org.opensaml.security.x509.BasicX509Credential;
@@ -154,6 +157,18 @@ public class X509AuthServlet extends HttpServlet {
             log.debug("End-entity X.509 certificate found with subject '{}', issued by '{}'",
                     cert.getSubjectX500Principal().getName(), cert.getIssuerX500Principal().getName());
             
+            final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
+            final AuthenticationContext authnCtx = prc.getSubcontext(AuthenticationContext.class);
+            if (authnCtx != null) {
+                final CertificateContext cc = authnCtx.getSubcontext(CertificateContext.class, true);
+                cc.setCertificate(cert);
+                if (certs.length > 1) {
+                    for (int i = 1; i < certs.length; i++) {
+                        cc.getIntermediates().add(certs[i]);
+                    }
+                }
+            }
+
             if (trustEngine != null) {
                 try {
                     final BasicX509Credential cred = new BasicX509Credential(cert);
@@ -207,4 +222,4 @@ public class X509AuthServlet extends HttpServlet {
     }
 // Checkstyle: CyclomaticComplexity|MethodLength ON
     
-}
\ No newline at end of file
+}
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml
index c2f90266c..10abd2916 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-authn-beans.xml
@@ -29,10 +29,43 @@
         p:metricName="net.shibboleth.idp.authn.x509"
         p:addDefaultPrincipals="#{getObject('shibboleth.authn.X509.addDefaultPrincipals') ?: %{idp.authn.X509.addDefaultPrincipals:true}}"
         p:classifiedMessages="#{getObject('shibboleth.authn.X509.ClassifiedMessageMap')}"
-        p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}" />
+        p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}"
+        p:populateAuditContextAction="#{%{idp.authn.X509.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('shibboleth.authn.X509.PopulateAuditContext') : null}"
+        p:writeAuditLogAction="#{%{idp.authn.X509.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuditLog') : null}" />
 
     <bean id="PopulateSubjectCanonicalizationContext"
         class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
         p:availableFlows-ref="shibboleth.PostLoginSubjectCanonicalizationFlows" />
 
+    <!-- Audit logging beans. -->
+
+    <util:map id="shibboleth.authn.AuditFormattingMap">
+        <entry key="#{'%{idp.authn.X509.audit.category:Shibboleth-Audit.X509}'.trim()}"
+            value="#{'%{idp.authn.X509.audit.format:%a|%T|%SP|%s|%AF|%X509S|%X509I|%AR|%UA}'.trim()}" />
+    </util:map>
+
+    <bean id="shibboleth.authn.X509.DefaulAuditExtractors" parent="shibboleth.authn.DefaulAuditExtractors" lazy-init="true"
+            class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+             <map merge="true">
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_SUBJECT"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateSubjectAuditExtractor" />
+                </entry>
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_ISSUER"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateIssuerAuditExtractor" />
+                </entry>
+             </map>
+        </property>
+    </bean>
+    
+    <bean id="shibboleth.authn.X509.PopulateAuditContext" parent="shibboleth.authn.AbstractPopulateAuditContext" lazy-init="true"
+        p:fieldExtractors="#{getObject('shibboleth.authn.X509.AuditExtractors') ?: getObject('shibboleth.authn.X509.DefaulAuditExtractors')}"
+        p:clearAuditContext="true" />
+
 </beans>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml
index 70b15e11e..21fe19d85 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/x509-internal-authn-beans.xml
@@ -18,6 +18,20 @@
     <bean class="net.shibboleth.shared.spring.config.IdentifiableBeanPostProcessor" />
     <bean class="net.shibboleth.idp.profile.impl.ProfileActionBeanPostProcessor" />
 
+    <!-- Default message map. -->
+    <util:map id="shibboleth.authn.X509Internal.ClassifiedMessageMap">
+        <entry key="RequestUnsupported">
+            <list>
+                <value>RequestUnsupported</value>
+            </list>
+        </entry>
+        <entry key="InvalidCredentials">
+            <list>
+                <value>InvalidCredentials</value>
+            </list>
+        </entry>
+    </util:map>
+
     <import resource="conditional:%{idp.home}/conf/authn/x509-internal-authn-config.xml" />
     
     <bean id="ExtractX509CertificateFromRequest"
@@ -27,7 +41,10 @@
     <bean id="ValidateX509Certificate"
             class="net.shibboleth.idp.authn.impl.ValidateCredentials" scope="prototype"
             p:addDefaultPrincipals="#{getObject('shibboleth.authn.X509.addDefaultPrincipals') ?: %{idp.authn.X509Internal.addDefaultPrincipals:true}}"
-            p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}">
+            p:classifiedMessages="#{getObject('shibboleth.authn.X509Internal.ClassifiedMessageMap')}"
+            p:resultCachingPredicate="#{getObject('shibboleth.authn.X509.resultCachingPredicate')}"
+            p:populateAuditContextAction="#{%{idp.authn.X509Internal.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('shibboleth.authn.X509Internal.PopulateAuditContext') : null}"
+            p:writeAuditLogAction="#{%{idp.authn.X509Internal.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuditLog') : null}">
         <property name="validators">
             <bean id="x509" class="net.shibboleth.idp.authn.impl.X509CertificateCredentialValidator"
                 p:trustEngine="#{getObject('shibboleth.authn.X509.TrustEngine')}"
@@ -39,4 +56,35 @@
         class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
         p:availableFlows-ref="shibboleth.PostLoginSubjectCanonicalizationFlows" />
 
+    <!-- Audit logging beans. -->
+
+    <util:map id="shibboleth.authn.AuditFormattingMap">
+        <entry key="#{'%{idp.authn.X509Internal.audit.category:Shibboleth-Audit.X509Internal}'.trim()}"
+            value="#{'%{idp.authn.X509Internal.audit.format:%a|%T|%SP|%s|%AF|%X509S|%X509I|%AR|%UA}'.trim()}" />
+    </util:map>
+
+    <bean id="shibboleth.authn.X509Internal.DefaulAuditExtractors" parent="shibboleth.authn.DefaulAuditExtractors" lazy-init="true"
+            class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+             <map merge="true">
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_SUBJECT"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateSubjectAuditExtractor" />
+                </entry>
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.X509_ISSUER"/>
+                    </key>
+                    <bean class="net.shibboleth.idp.authn.audit.impl.CertificateIssuerAuditExtractor" />
+                </entry>
+             </map>
+        </property>
+    </bean>
+    
+    <bean id="shibboleth.authn.X509Internal.PopulateAuditContext" parent="shibboleth.authn.AbstractPopulateAuditContext" lazy-init="true"
+        p:fieldExtractors="#{getObject('shibboleth.authn.X509Internal.AuditExtractors') ?: getObject('shibboleth.authn.X509Internal.DefaulAuditExtractors')}"
+        p:clearAuditContext="true" />
+        
 </beans>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list