[java-idp-plugin-duo] branch main updated: JDUO-103 - Forward max_age requirement for forced authentication

Codeberg noreply at shibboleth.net
Fri Sep 18 17:14:40 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-idp-plugin-duo.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-duo/commit/31beeb4e629d58f2ee9b46dde16a44daeadacc13

The following commit(s) were added to refs/heads/main by this push:
     new 31beeb4e JDUO-103 - Forward max_age requirement for forced authentication
31beeb4e is described below

commit 31beeb4e629d58f2ee9b46dde16a44daeadacc13
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Sep 18 18:14:31 2026 +0100

    JDUO-103 - Forward max_age requirement for forced authentication
    
     - Added Duo WebSDK version 1.4.0
     - Updated certificate handling to be compatible with their new
    expectations for cert pinning. That is, PEM-encoded X.509 certs rather
    than public key pins. This is backward incompatible, but we are driven
    by changes to Duo's SDK.
     - Now forwarding max_age and prompt to Duo via their SDK changes
     - Moved the Nimbus PEM certs from the Nimbus plugin to the impl module,
    because both Nimbus and the WebSDK version now use those.
    
    https://shibboleth.atlassian.net/browse/JDUO-103
---
 .../shibboleth/idp/plugin/authn/duo/trust/5718.crt |  0
 .../shibboleth/idp/plugin/authn/duo/trust/5842.crt |  0
 .../idp/plugin/authn/duo/trust/AmazonRootCA1.crt   |  0
 .../idp/plugin/authn/duo/trust/AmazonRootCA2.crt   |  0
 .../idp/plugin/authn/duo/trust/AmazonRootCA3.crt   |  0
 .../idp/plugin/authn/duo/trust/AmazonRootCA4.crt   |  0
 .../duo/trust/DigiCertHighAssuranceEVRootCA.crt    |  0
 .../authn/duo/trust/DigiCertTLSECCP384RootG5.crt   |  0
 .../authn/duo/trust/DigiCertTLSRSA4096RootG5.crt   |  0
 .../idp/plugin/authn/duo/trust/SFSRootCAG2.crt     |  0
 .../shibboleth/idp/plugin/authn/duo/trust/r2.crt   |  0
 .../shibboleth/idp/plugin/authn/duo/trust/r4.crt   |  0
 .../idp/plugin/authn/duo/trust/roote46.crt         |  0
 .../idp/plugin/authn/duo/trust/rootr46.crt         |  0
 .../idp/plugin/authn/duo/trust/sfroot-g2.crt       |  0
 .../plugin/authn/duo/nimbus/impl/NimbusClient.java |  4 +-
 .../authn/duo/sdk/impl/DuoSDKClientAdaptor.java    | 61 ++++++++++++++----
 .../authn/duo/sdk/impl/DuoSDKClientFactory.java    | 75 ++++++++++++++++++++--
 .../plugin/authn/duo/duo-client-factory-bean.xml   | 42 ++++++------
 .../duo/sdk/impl/DuoSDKClientFactoryTest.java      | 35 ++++++----
 .../src/test/resources/trust/example-bad.crt       |  3 +
 .../src/test/resources/trust/example.crt           | 17 +++++
 pom.xml                                            |  2 +-
 23 files changed, 189 insertions(+), 50 deletions(-)

diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5718.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5718.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5718.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5718.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5842.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5842.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5842.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/5842.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA1.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA1.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA1.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA1.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA2.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA2.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA2.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA2.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA3.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA3.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA3.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA3.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA4.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA4.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA4.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA4.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertHighAssuranceEVRootCA.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertHighAssuranceEVRootCA.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertHighAssuranceEVRootCA.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertHighAssuranceEVRootCA.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSECCP384RootG5.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSECCP384RootG5.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSECCP384RootG5.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSECCP384RootG5.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSRSA4096RootG5.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSRSA4096RootG5.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSRSA4096RootG5.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSRSA4096RootG5.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/SFSRootCAG2.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/SFSRootCAG2.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/SFSRootCAG2.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/SFSRootCAG2.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r2.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r2.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r2.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r2.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r4.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r4.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r4.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/r4.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/roote46.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/roote46.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/roote46.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/roote46.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/rootr46.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/rootr46.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/rootr46.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/rootr46.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/sfroot-g2.crt b/idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/sfroot-g2.crt
similarity index 100%
rename from idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/sfroot-g2.crt
rename to idp-duo-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/trust/sfroot-g2.crt
diff --git a/idp-duo-nimbus-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/impl/NimbusClient.java b/idp-duo-nimbus-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/impl/NimbusClient.java
index adcdc2b7..e47e11c4 100644
--- a/idp-duo-nimbus-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/impl/NimbusClient.java
+++ b/idp-duo-nimbus-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/impl/NimbusClient.java
@@ -138,7 +138,9 @@ public final class NimbusClient extends AbstractDuoOIDCClient{
     }
     
     @Override
-    @Nonnull public String createAuthUrl(@Nonnull final AuthenticationRequestOptions requestOptions) throws DuoClientException {
+    @Nonnull public String createAuthUrl(@Nonnull final AuthenticationRequestOptions requestOptions) 
+    		throws DuoClientException {
+    	Constraint.isNotNull(requestOptions, "Authentication request options can not be null");
         Constraint.isNotEmpty(requestOptions.getUsername(), "Username can not be null or empty");
         Constraint.isNotEmpty(requestOptions.getState(), "State can not be null or empty");
         Constraint.isNotEmpty(requestOptions.getNonce(), "Nonce can not be null or empty for this client");
diff --git a/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientAdaptor.java b/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientAdaptor.java
index 18ab9009..8bd38afc 100644
--- a/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientAdaptor.java
+++ b/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientAdaptor.java
@@ -27,6 +27,8 @@ import javax.annotation.concurrent.ThreadSafe;
 
 import org.slf4j.Logger;
 
+import com.duosecurity.AuthUrlOptions;
+import com.duosecurity.AuthUrlOptions.Prompt;
 import com.duosecurity.Client;
 import com.duosecurity.exception.DuoException;
 import com.duosecurity.model.HealthCheckResponse;
@@ -98,6 +100,7 @@ public final class DuoSDKClientAdaptor extends AbstractDuoOIDCClient{
 
          try {
              if (caCerts == null) {
+            	 log.trace("Creating a new Duo client without pinned certificates");
                  //will use the default certs in the Client if the caCerts are null
                  final Client newClient = new Client.Builder(integration.getClientId(), integration.getSecretKey(),
                          integration.getAPIHost(), integration.getRedirectURI())
@@ -106,6 +109,7 @@ public final class DuoSDKClientAdaptor extends AbstractDuoOIDCClient{
                  assert newClient != null;
                  client = newClient;
              } else {
+            	 log.trace("Creating a new Duo client with pinned certificates");
                  final Client newClient = new Client.Builder(integration.getClientId(), integration.getSecretKey(),
                          integration.getAPIHost(), integration.getRedirectURI()).setCACerts(
                                  caCerts.toArray(new String[caCerts.size()]))
@@ -209,8 +213,7 @@ public final class DuoSDKClientAdaptor extends AbstractDuoOIDCClient{
     /** 
      * {@inheritDoc}
      *  
-     * <p>The Duo WebSDK Client does not support either the {@code nonce} or {@code redirectURIOverride} 
-     * parameters.</p>
+     * <p>The Duo WebSDK Client does not support the {@code redirectURIOverride} parameter.</p>
      * 
      */
     @Override
@@ -218,21 +221,57 @@ public final class DuoSDKClientAdaptor extends AbstractDuoOIDCClient{
             @Nonnull @NotEmpty final String state, @Nullable final String nonce,
             @Nullable final String redirectURIOverride) 
             throws DuoClientException {
-        Constraint.isNotEmpty(username, "Username can not be null or empty");
-        Constraint.isNotEmpty(state, "State can not be null or empty");
-        //does not support the nonce or redirect_uri override
+    	// Duo client does not support the redirectURIOverride
+    	final AuthenticationRequestOptions options = new AuthenticationRequestOptions()
+        		.setUsername(username)
+        		.setState(state)
+        		.setNonce(nonce);
+        
+        return createAuthUrl(options);
+    }
+    
+    @Override
+    @Nonnull public String createAuthUrl(@Nonnull final AuthenticationRequestOptions requestOptions) 
+    		throws DuoClientException {
+    	Constraint.isNotNull(requestOptions, "Authentication request options can not be null");
+        final String username = 
+        		Constraint.isNotEmpty(requestOptions.getUsername(), "Username can not be null or empty");
+        final String state = 
+        		Constraint.isNotEmpty(requestOptions.getState(), "State can not be null or empty");
+        Constraint.isGreaterThan(21, state.length(), "State must be at least 22 characters");
+        Constraint.isLessThan(1025, state.length(),"State must be at maximum 1024 characters");
         
-        // TODO remove warning when it does.
-        log.debug("Underlying DuoSDK does not support max_age or prompt, do not try to verfiy auth_time");
-
         try {
-            final String authUrl = client.createAuthUrl(username, state);
+        	final AuthUrlOptions.Builder options = new AuthUrlOptions.Builder(username, state);
+        	
+        	final var maxAge = requestOptions.getMaxAge();
+        	final var prompt = requestOptions.getPrompt();
+        	final var nonce = requestOptions.getNonce();
+        	
+        	if (nonce != null) {
+        		options.setNonce(nonce);
+        	}        	
+        	if (maxAge != null) {
+        		options.setMaxAge(Math.toIntExact(maxAge.toSeconds()));
+        	}
+        	// Duo only support login, so an easy conversion
+        	if (prompt != null) {
+        		if ("login".equals(prompt)) {
+        			options.setPrompt(Prompt.LOGIN);
+        		} else {
+        			log.error("Attempting to set prompt '{}' which is not supported by Duo", prompt);
+        			throw new DuoClientException("Unsupported Duo prompt: " + prompt);
+        		}
+        	}
+        	
+            final String authUrl = client.createAuthUrl(options.build());
             assert authUrl != null;
             return authUrl;
-        } catch (final DuoException e) {
+        } catch (final DuoException | ArithmeticException e) {
             //wrap duo specific exception.
             throw new DuoClientException(e);
         }
+        
     }
 
     /** 
@@ -322,7 +361,7 @@ public final class DuoSDKClientAdaptor extends AbstractDuoOIDCClient{
 
     @Override
     public boolean isSupportsNonce() {
-        return false;
+        return true;
     }
 
 }
diff --git a/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactory.java b/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactory.java
index fe7de05b..93f6689e 100644
--- a/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactory.java
+++ b/idp-duo-sdk-client-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactory.java
@@ -14,6 +14,10 @@
 
 package net.shibboleth.idp.plugin.authn.duo.sdk.impl;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.annotation.Nonnull;
@@ -21,6 +25,9 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.GuardedBy;
 import javax.annotation.concurrent.ThreadSafe;
 
+import org.opensaml.security.x509.X509Support;
+import org.slf4j.Logger;
+
 import net.shibboleth.idp.plugin.authn.duo.DuoClientException;
 import net.shibboleth.idp.plugin.authn.duo.DuoOIDCClient;
 import net.shibboleth.idp.plugin.authn.duo.DuoOIDCClientFactory;
@@ -29,15 +36,21 @@ import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.logic.ConstraintViolationException;
 import net.shibboleth.shared.primitive.DeprecationSupport;
 import net.shibboleth.shared.primitive.DeprecationSupport.ObjectType;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resource.Resource;
 
 
 
 /** Abstract factory implementation for the {@link DuoSDKClientAdaptor}. */
 @ThreadSafe
-public final class DuoSDKClientFactory extends AbstractInitializableComponent implements DuoOIDCClientFactory{     
+public final class DuoSDKClientFactory extends AbstractInitializableComponent implements DuoOIDCClientFactory{   
+	
+	/** Class logger. */
+    @Nonnull private static final Logger log = LoggerFactory.getLogger(DuoSDKClientFactory.class);
     
     /** List of CA Certificate pins. If null, the client's default set are used.*/
     @GuardedBy("this") @Nullable @NonnullElements @Unmodifiable private List<String> caCerts;
@@ -53,16 +66,64 @@ public final class DuoSDKClientFactory extends AbstractInitializableComponent im
      * to the API host. If {@code null}, the client will use the internal defaults.
      * 
      * @param certs the list of certificate pins.
+     * 
+     * @deprecated use {@link #setPinnedCertificates(List)}
      */
+    @Deprecated(since = "2.4.0", forRemoval = true)
     public synchronized void setCaCerts(@Nullable final List<String> certs) {
         checkSetterPreconditions();
-        //check if null, as the native duo client uses the internal defaults if null
-        //but will respect an empty list.
         if (certs != null) {
-            caCerts = List.copyOf(StringSupport.normalizeStringCollection(certs));
-        } else {
-            caCerts = null;
-        }       
+        	log.warn("Public key hash pinning is no longer supported");
+	        DeprecationSupport.warnOnce(ObjectType.CONFIGURATION, "Public key hash pinning is no longer supported" , 
+	        		null, "PEM encoded certificates: shibboleth.authn.DuoOIDC.sdk.TrustedCertificateResources");
+	        throw new ConstraintViolationException("Public key hash pinning not supported");
+        }
+    }
+    
+    /**
+     * Set the resources which we will convert into PEM encoded strings.
+     * 
+     * @param certResources the resources
+     */
+    public synchronized void setPinnedCertificates(@Nullable final List<Resource> certResources) {
+    	checkSetterPreconditions();
+    	
+    	if (certResources != null) {
+    		caCerts = new ArrayList<String>(certResources.size());
+	    	for (final Resource f : certResources) {
+	    		try (InputStream inputStream = f.getInputStream()) {
+	    			final String certificate =	new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+	    			validateCertificate(certificate);
+	    			caCerts.add(certificate);
+	    		} catch (final IOException e) {
+	    			log.error("Failed to read certificate resource", e);
+	    			throw new ConstraintViolationException("Failed to read certificate resource: " + f.getDescription());
+	    		}
+	        }	    	
+    	}
+    }
+    
+    /**
+     * Check the certificate strings is a valid X.509 certificates, and not, for example, a public key pin.
+     * 
+     * @param certificate the certificate to check
+     */
+    private static void validateCertificate(@Nonnull final String certificate) {
+    	
+    	// Quick check this is a PEM and not a DER encoded certificate
+    	if (!certificate.contains("-----BEGIN CERTIFICATE-----") || 
+    			!certificate.contains("-----END CERTIFICATE-----")) {
+			log.error("Pinned CA certificate must be PEM-encoded");
+			throw new ConstraintViolationException("Pinned CA certificate must be PEM-encoded");
+    	}
+		try {
+			// We don't do anything, we just try to parse it
+			X509Support.decodeCertificate(certificate.getBytes(StandardCharsets.UTF_8));
+		} catch (final Exception e) {
+			log.error("Pinned CA certificate is of the wrong type, expected PEM encoded X.509 certificate", e);
+			throw new ConstraintViolationException(
+					"Pinned CA certificate is of the wrong type, expected PEM encoded X.509 certificate");
+		}
     }
     
     /**
diff --git a/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/duo-client-factory-bean.xml b/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/duo-client-factory-bean.xml
index 8d941ac2..418f2a6e 100644
--- a/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/duo-client-factory-bean.xml
+++ b/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/duo-client-factory-bean.xml
@@ -12,30 +12,34 @@
     <!-- Client factory with is own unique ID but common (default) alias/name  -->
     <bean id="shibboleth.authn.DuoOIDC.sdk.clientFactory" name="shibboleth.authn.DuoOIDC.clientFactory"
         class="net.shibboleth.idp.plugin.authn.duo.sdk.impl.DuoSDKClientFactory" scope="singleton"
-        p:caCerts="#{getObject('shibboleth.authn.DuoOIDC.sdk.TrustedCertificates') ?: getObject('shibboleth.authn.DuoOIDC.sdk.DefaultTrustedCertificates')}"
+        p:caCerts="#{getObject('shibboleth.authn.DuoOIDC.sdk.TrustedCertificates')}"
+        p:pinnedCertificates="#{getObject('shibboleth.authn.DuoOIDC.sdk.TrustedCertificateResources') 
+                ?: getObject('shibboleth.authn.DuoOIDC.sdk.DefaultTrustedCertificateResources')}"      
         p:proxyPort="%{idp.duo.oidc.http.proxy.port:#{null}}"
         p:proxyHost="%{idp.duo.oidc.http.proxy.host:#{null}}">
 
     </bean>
-
-    <!-- The Default set of CA Certificate Pins used by the Duo SDK -->
-    <util:list id="shibboleth.authn.DuoOIDC.sdk.DefaultTrustedCertificates" value-type="java.lang.String"
+    
+    <!-- Default set of trusted CAs -->
+     <util:list id="shibboleth.authn.DuoOIDC.sdk.DefaultTrustedCertificateResources" value-type="java.lang.String"
         list-class="java.util.ArrayList">
-        <value>sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=</value>
-        <value>sha256/++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=</value>
-        <value>sha256/f0KW/FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=</value>
-        <value>sha256/NqvDJlas/GRcYbcWE8S/IceH9cq77kg0jVhZeAPXq8k=</value>
-        <value>sha256/9+ze1cZgR9KO1kZrVDxA4HQ6voHRCSVNz4RdTCx4U8U=</value>       
-        <value>sha256/KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I=</value>
-        <value>sha256/oC+voZLIy4HLE0FVT5wFtxzKKokLDRKY1oNkfJYe+98=</value>
-        <value>sha256/ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=</value>
-        <value>sha256/rn+WLLnmp9v3uDP7GPqbcaiRdd+UnCMrap73yz3yu/w=</value>
-        <value>sha256/4EoCLOMvTM8sf2BGKHuCijKpCfXnUUR/g/0scfb9gXM=</value>
-        <value>sha256/Vfd95BwDeSQo+NUYxVEEIlvkOlWY2SalKK1lPhzOx78=</value>
-        <value>sha256/mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=</value>
-        <value>sha256/B+hU8mp8vTiZJ6oEG/7xts0h3RQ4GK2UfcZVqeWH/og=</value>
-        <value>sha256/uu5PB+MS9L3/ffB/PuTG6A+WjsTtTaF52qqjrcHFXRU=</value>
-        <value>sha256/gI1os/q0iEpflxrOfRBVDXqVoWN3Tz7Dav/7IT++THQ=</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA1.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA2.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA3.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/AmazonRootCA4.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/5718.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/5842.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSECCP384RootG5.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertTLSRSA4096RootG5.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/r2.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/r4.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/roote46.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/rootr46.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/sfroot-g2.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/SFSRootCAG2.crt</value>
+        <value>classpath:/net/shibboleth/idp/plugin/authn/duo/trust/DigiCertHighAssuranceEVRootCA.crt</value>
     </util:list>
+    
+
 
 </beans>
\ No newline at end of file
diff --git a/idp-duo-sdk-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java b/idp-duo-sdk-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java
index bed64710..d9359f02 100644
--- a/idp-duo-sdk-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java
+++ b/idp-duo-sdk-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java
@@ -33,6 +33,7 @@ import static org.testng.Assert.assertNotNull;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.springframework.core.io.ClassPathResource;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -40,6 +41,9 @@ import net.shibboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration;
 import net.shibboleth.idp.plugin.authn.duo.DuoClientException;
 import net.shibboleth.idp.plugin.authn.duo.DuoOIDCClient;
 import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.shared.spring.resource.ResourceHelper;
 
 /** Test for the DuoSDKClientFactory.*/
 public class DuoSDKClientFactoryTest {
@@ -57,20 +61,28 @@ public class DuoSDKClientFactoryTest {
         factory = new DuoSDKClientFactory();
         
     }
-
-    /** Test succesful set of null CA certs.*/
-    @Test
-    public final void testSetNullCaCerts() {
-        factory.setCaCerts(null);
-    }
     
-    /** Test succesful set of CA certs.*/
-    @Test
-    public final void testSetCaCerts() {
+    /** Test unsuccesful set invalid X.509 cert.*/
+    @Test(expectedExceptions = ConstraintViolationException.class)
+    public final void testOldSetCaCerts() {
         final List<String> certs = new ArrayList<>();
         certs.add("sha256/I/Lt/z7ekCWanjD0Cvj5EqXls2lOaThEA0H2Bg4BT/o=");
         factory.setCaCerts(certs);
     }
+    
+    @Test
+    public final void testSetPinnedCertificates() {
+        final List<Resource> certs = new ArrayList<>();
+        certs.add(ResourceHelper.of(new ClassPathResource("trust/example.crt")));
+        factory.setPinnedCertificates(certs);
+    }
+    
+    @Test(expectedExceptions = ConstraintViolationException.class)
+    public final void testSetBadPinnedCertificates() {
+        final List<Resource> certs = new ArrayList<>();
+        certs.add(ResourceHelper.of(new ClassPathResource("trust/example-bad.crt")));
+        factory.setPinnedCertificates(certs);
+    }
 
     /**
      * Test creation.
@@ -80,8 +92,9 @@ public class DuoSDKClientFactoryTest {
      */
     @Test
     public final void testCreateInstance() throws DuoClientException, ComponentInitializationException {
-        final List<String> certs = new ArrayList<>();
-        certs.add("sha256/I/Lt/z7ekCWanjD0Cvj5EqXls2lOaThEA0H2Bg4BT/o=");
+    	final List<Resource> certs = new ArrayList<>();
+        certs.add(ResourceHelper.of(new ClassPathResource("trust/example.crt")));
+        factory.setPinnedCertificates(certs);
         final DefaultDuoOIDCIntegration integ = new DefaultDuoOIDCIntegration();
         integ.setAPIHost("api-c9f24c5a.duosecurity.com");
         integ.setClientId("DIU6GEFWG5LIUTVV2M3N");
diff --git a/idp-duo-sdk-client-impl/src/test/resources/trust/example-bad.crt b/idp-duo-sdk-client-impl/src/test/resources/trust/example-bad.crt
new file mode 100644
index 00000000..dd3aaff4
--- /dev/null
+++ b/idp-duo-sdk-client-impl/src/test/resources/trust/example-bad.crt
@@ -0,0 +1,3 @@
+-----BEGIN CERTIFICATE-----
+bad
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/idp-duo-sdk-client-impl/src/test/resources/trust/example.crt b/idp-duo-sdk-client-impl/src/test/resources/trust/example.crt
new file mode 100644
index 00000000..31d70b8d
--- /dev/null
+++ b/idp-duo-sdk-client-impl/src/test/resources/trust/example.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsjCCAZoCCQCaPHbdhD7mkDANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9l
+eGFtcGxlLmludmFsaWQwIBcNMjYwOTE4MDg0OTM0WhgPMjA1NTAyMDMwODQ5MzRa
+MBoxGDAWBgNVBAMMD2V4YW1wbGUuaW52YWxpZDCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAJj0PkmYpHQP8GDBXoNI99xUIYE43aTn0tw2nJ/vWumXuEY1
+7XuBTlrRObAKOVzZjp9aCfLlDSoHxOSzDE8wen8P6PKxcDtfM8UIWDyLBuZ7U5nL
+DB6KzGr+QV7cPNDycciA06sl8EqRIloHpVO+yjNSMcaDc9STLqpHxNsgPES79JlH
+10xkOBbrOqxwkVGrjrttLv7YeombAZlx77ZYn2HUkWMlDCQ5VZHXu5VU2grIuLwG
+cHNeTxlNo7M00Y4Drm2xvde8i1krK7Y61iNTgxHwK8oN7kZMtS27kK/xBIPArwXV
+S3sPmB0btCHAM41lfCp/O+NB9qJ5Z/zqwDmSdHUCAwEAATANBgkqhkiG9w0BAQsF
+AAOCAQEAVIZQmO1ubKvet5rnGGQjoIbE9QhMCK/fQks2qHlIf+L6Zuw4hCVmgAab
+1lEGCyEoH5TYfyT5ChjjBb1f3uEPqjP2NRgE9UBl5LF0iJibM7sKKW4rTT24/wo5
+aX+/fSWV1tsanvELLu0IulXThIkkzQ3rNqczjHk1L8yCi5GkWAW3+WM3WmYdj2Ij
+HJxBT2cZ3cpC+wYLBswEIPuOHJL/2W3rttVRqCQ5Og2Szy/RuhYIE0sHxF/PywBW
+OlsSZ5Em9YHhlc7/jMdOcL0jykFwS3v3PE4SpqUqewPOqU/OsR0m4alIqVs9IjFV
+YeFD9XrQprhNawlqgTV7831mmifYtA==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index afe650c5..a0fb47b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
         <shibboleth.projectName>java-idp-plugin-duo</shibboleth.projectName>
         <idp.groupId>net.shibboleth.idp</idp.groupId>
         <idp.version>5.0.0</idp.version>
-        <duo.client.version>1.3.2</duo.client.version>
+        <duo.client.version>1.4.0</duo.client.version>
         <okhttp3.mockserver.version>4.9.3</okhttp3.mockserver.version>
         <okhttp3.tls.version>4.9.3</okhttp3.tls.version>
         <opensaml.groupId>org.opensaml</opensaml.groupId>

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


More information about the commits mailing list