[java-oidc-common] branch main updated: JCOMOIDC-76 - Decouple signature signing logic from SignJWTHandler

Phil Smart philip.smart at jisc.ac.uk
Tue Oct 3 10:52:50 UTC 2023


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

philsmart pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=c7f577c51480e3bb56b3e035321da50442030f09

The following commit(s) were added to refs/heads/main by this push:
     new c7f577c  JCOMOIDC-76 - Decouple signature signing logic from SignJWTHandler
c7f577c is described below

commit c7f577c51480e3bb56b3e035321da50442030f09
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Oct 3 11:52:43 2023 +0100

    JCOMOIDC-76 - Decouple signature signing logic from SignJWTHandler
    
     - Create a new JWSTokenSigner in a similar way to the
    JWETokenDecrypter.
     - Use the JWSTokenSigner in the SignJWTHandler
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-76
---
 .../oidc/security/jose/SignatureException.java     |  67 ++++++
 .../oidc/security/impl/JWETokenDecrypter.java      |   2 +-
 .../oidc/security/impl/JWSTokenSigner.java         | 170 +++++++++++++++
 .../oidc/security/impl/SignJWTHandler.java         | 143 ++++---------
 .../oidc/security/impl/JWSTokenSignerTest.java     | 234 +++++++++++++++++++++
 .../oidc/security/impl/SignJWTHandlerTest.java     |  29 ++-
 6 files changed, 525 insertions(+), 120 deletions(-)

diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/jose/SignatureException.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/jose/SignatureException.java
new file mode 100644
index 0000000..0ddaa6c
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/jose/SignatureException.java
@@ -0,0 +1,67 @@
+/*
+ * 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.oidc.security.jose;
+
+import javax.annotation.Nullable;
+
+/**
+ * Exception thrown when an error occurs during JWS signature operations.
+ */
+public class SignatureException extends Exception {
+    
+    /**
+     * Serial version UID.
+     */
+    private static final long serialVersionUID = 5709850116643139822L;
+
+    /**
+     * Constructor.
+     */
+    public SignatureException() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message exception message
+     */
+    public SignatureException(@Nullable final String message) {
+        super(message);
+    }
+    
+    /**
+     * Constructor.
+     * 
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public SignatureException(@Nullable final Exception wrappedException) {
+        super(wrappedException);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public SignatureException(@Nullable final String message, @Nullable final Exception wrappedException) {
+        super(message, wrappedException);
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWETokenDecrypter.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWETokenDecrypter.java
index de2e32e..38da9cb 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWETokenDecrypter.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWETokenDecrypter.java
@@ -75,7 +75,7 @@ public class JWETokenDecrypter {
     private final Logger log = LoggerFactory.getLogger(JWETokenDecrypter.class);
 
     /** The JWT decryption parameters. */
-    private final DecryptionParameters params;
+    @Nonnull private final DecryptionParameters params;
 
     /**
      * 
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWSTokenSigner.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWSTokenSigner.java
new file mode 100644
index 0000000..75d2215
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWSTokenSigner.java
@@ -0,0 +1,170 @@
+/*
+ * 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 net.shibboleth.oidc.security.impl;
+
+import java.security.PrivateKey;
+import java.security.interfaces.ECPrivateKey;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSObject.State;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.CredentialConversionUtil;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.jose.SignatureException;
+import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Supports signing a JSON Web Token (JWT) claims set using the JSON Web Signature standard using the algorithm and
+ * credential contained inside the {@link SignatureSigningParameters}. Any error that occurs signing the JWT will 
+ * throw an {@link SignatureException}.
+ * 
+ * <p>A signer will need to be created for each new signing operation.</p>
+ * 
+ * @since 3.1.0
+ */
+public class JWSTokenSigner {
+    
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(JWSTokenSigner.class);
+    
+    /** The signature signing parameters. */
+    @Nonnull private final SignatureSigningParameters params;
+    
+    /**
+     * Constructor.
+     *
+     * @param signingParams the algorithm and credential to use during signing
+     */
+    public JWSTokenSigner(@Nonnull final SignatureSigningParameters signingParams) {
+        params = Constraint.isNotNull(signingParams, "Signing params can not be null");
+    }
+    
+    /**
+     * Sign the given JWT claims set using the signing parameters.
+     * 
+     * @param jwtClaims the claims to sign
+     * @param typeHeader the optional JOSE object type to add to the JWS JOSE header
+     * 
+     * @return a signed JWT or {@code null} if an error occurs.
+     */
+    @Nonnull public SignedJWT sign(@Nonnull final JWTClaimsSet jwtClaims, 
+            @Nullable final String typeHeader) throws SignatureException{
+        
+        try {            
+            final Credential credential = params.getSigningCredential();
+            final String algorithm = params.getSignatureAlgorithm(); 
+            if (credential == null) {
+                throw new SignatureException("Signature signinig credential is not available");
+            }
+            if (algorithm == null) {
+                throw new SignatureException("Signature signinig algorithm is not available");
+            }
+            
+            final Algorithm jwsAlgorithm = resolveAlgorithm(credential, algorithm);
+            final JWSSigner signer = getSigner(jwsAlgorithm, credential);
+            final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(new JWSAlgorithm(jwsAlgorithm.getName()))
+                    .keyID(CredentialConversionUtil.resolveKid(credential));
+            if (typeHeader != null) {
+                headerBuilder.type(new JOSEObjectType(typeHeader));
+            }
+            final SignedJWT jwt = new SignedJWT(headerBuilder.build(), jwtClaims);
+            jwt.sign(signer);
+            if (log.isDebugEnabled()) {
+                log.debug("Signed JWT using kid '{}'", CredentialConversionUtil.resolveKid(credential));
+            }          
+            if (jwt.getState() != State.SIGNED) {
+                // Should not really happen, as JOSEException should be thrown
+                throw new SignatureException("JWT was not signed, unknown cause");
+            }
+            
+            return jwt;
+            
+        } catch (final JOSEException e) {
+            throw new SignatureException(e.getMessage(), e);
+        }      
+    }
+    
+    /**
+     * Returns the correct implementation of the {@link JWSSigner} based on the algorithm type and credential given.
+     * 
+     * @param jwsAlgorithm the JWS algorithm
+     * @param credential the credential
+     * 
+     * @return signer for the given algorithm and credential
+     * 
+     * @throws JOSEException if algorithm and credential combinations is not supported
+     */
+    @Nonnull protected JWSSigner getSigner(@Nonnull final Algorithm jwsAlgorithm, 
+            @Nonnull final Credential credential) throws JOSEException {
+        
+        if (JWSAlgorithm.Family.EC.contains(jwsAlgorithm) && 
+                credential.getPrivateKey() instanceof final ECPrivateKey ecKey) {           
+            return new ECDSASigner(ecKey);
+        }
+        if (JWSAlgorithm.Family.RSA.contains(jwsAlgorithm)) {
+            final PrivateKey key = credential.getPrivateKey();
+            if (key != null && "RSA".equals(key.getAlgorithm())) {
+                return new RSASSASigner(credential.getPrivateKey());
+            }            
+        }
+        if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm) && credential.getSecretKey() != null) {
+            return new MACSigner(credential.getSecretKey());
+        }
+        throw new JOSEException("Unsupported algorithm " + jwsAlgorithm.getName()+
+                " for key '" + CredentialConversionUtil.resolveKid(credential) + "'");
+    }
+    
+    /**
+     * Resolves JWS algorithm from signature signing parameters. Warns if an unsuitable credential and algorithm are
+     * found, but the algorithm is still returned.
+     * 
+     * @param credential the credential used to sign the JWT claims
+     * @param algorithmString the JWS algorithm as a string
+     * 
+     * @return JWS algorithm
+     */
+    @Nonnull protected JWSAlgorithm resolveAlgorithm(@Nonnull final Credential credential, 
+            @Nonnull final String algorithmString) {
+
+        final JWSAlgorithm algorithm = new JWSAlgorithm(algorithmString);
+        if (credential instanceof final JWKCredential jwkCred && !algorithm.equals(jwkCred.getAlgorithm())) {
+            log.debug("Signature signing algorithm {} differs from JWK algorithm '{}'",
+                    algorithm.getName(), jwkCred.getAlgorithm() != null ? 
+                            jwkCred.getAlgorithm() : "not specified");            
+        }
+        log.trace("Algorithm resolved {}", algorithm.getName());
+        return algorithm;
+    }
+
+        
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/SignJWTHandler.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/SignJWTHandler.java
index 5572b1e..b217fec 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/SignJWTHandler.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/SignJWTHandler.java
@@ -14,7 +14,7 @@
 
 package net.shibboleth.oidc.security.impl;
 
-import java.security.interfaces.ECPrivateKey;
+import java.text.ParseException;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 
@@ -25,32 +25,21 @@ import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.handler.AbstractMessageHandler;
 import org.opensaml.messaging.handler.MessageHandlerException;
-import org.opensaml.security.credential.Credential;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import com.nimbusds.jose.Algorithm;
-import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jose.JOSEObjectType;
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.JWSObject.State;
-import com.nimbusds.jose.JWSSigner;
-import com.nimbusds.jose.crypto.ECDSASigner;
-import com.nimbusds.jose.crypto.MACSigner;
-import com.nimbusds.jose.crypto.RSASSASigner;
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 
-import net.shibboleth.oidc.security.CredentialConversionUtil;
-import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.jose.SignatureException;
 import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
 import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
@@ -69,22 +58,19 @@ public class SignJWTHandler extends AbstractMessageHandler {
     
     /** Strategy used to locate the payload to encrypt.*/
     @NonnullAfterInit private Function<MessageContext, JWTClaimsSet> claimsToSignLookupStrategy;    
-       
-    /** The signature signing parameters. */
-    @Nonnull private SignatureSigningParameters signatureSigningParameters;
     
-    /** resolved credential. */
-    @Nullable private Credential credential;
+    /** The stasehd claims to sign.*/
+    @NonnullBeforeExec private JWTClaimsSet jwtClaimSetToSign;
     
-    /** The claims to sign.*/
-    @Nullable private JWTClaimsSet jwtClaimSetToSign;
-    
-    /** "typ" header to insert while signing. */
-    @Nullable @NotEmpty private String typeHeader;
+    /** Optional "typ" header to insert while signing. */
+    @Nullable private String typeHeader;
     
     /** A friendly name to log as the subject of signing.*/
     @Nonnull @NotEmpty private String logName;
     
+    /** The signing engine used to sign the JWT claims set. */
+    @NonnullBeforeExec private JWSTokenSigner signer;
+    
     /** Constructor.*/
     public SignJWTHandler() {
         logName = "not-specified";
@@ -97,8 +83,7 @@ public class SignJWTHandler extends AbstractMessageHandler {
      * @param name the friendly name
      */
     public void setLogName(@Nonnull @NotEmpty final String name) {
-        ifInitializedThrowUnmodifiabledComponentException();
-        ifDestroyedThrowDestroyedComponentException();
+        checkSetterPreconditions();
 
         logName = Constraint.isNotEmpty(name, "ForFriendlyName can not be null or empty");
     }
@@ -120,8 +105,7 @@ public class SignJWTHandler extends AbstractMessageHandler {
      * @param strategy the strategy
      */
     public void setClaimsToSignLookupStrategy(@Nonnull final Function<MessageContext, JWTClaimsSet> strategy) {
-        ifInitializedThrowUnmodifiabledComponentException();
-        ifDestroyedThrowDestroyedComponentException();
+        checkSetterPreconditions();
 
         claimsToSignLookupStrategy =
                 Constraint.isNotNull(strategy, "Claims To Sign Lookup Strategy can not be null");
@@ -133,8 +117,7 @@ public class SignJWTHandler extends AbstractMessageHandler {
      * @param consumer the consumer
      */
     public void setJwtUpdateConsumer(final BiConsumer<JWT, MessageContext> consumer) {
-        ifInitializedThrowUnmodifiabledComponentException();
-        ifDestroyedThrowDestroyedComponentException();
+        checkSetterPreconditions();
 
         jwtUpdateConsumer = Constraint.isNotNull(consumer, "JWT Update Consumer can not be null");
     }
@@ -145,8 +128,7 @@ public class SignJWTHandler extends AbstractMessageHandler {
      * @param type header value
      */
     public void setTypeHeader(@Nullable @NotEmpty final String type) {
-        ifInitializedThrowUnmodifiabledComponentException();
-        ifDestroyedThrowDestroyedComponentException();
+        checkSetterPreconditions();
         
         typeHeader = StringSupport.trimOrNull(type);
     }
@@ -158,8 +140,7 @@ public class SignJWTHandler extends AbstractMessageHandler {
      */
     public void setSecurityParametersLookupStrategy(
             @Nonnull final Function<MessageContext, SecurityParametersContext> strategy) {
-        ifInitializedThrowUnmodifiabledComponentException();
-        ifDestroyedThrowDestroyedComponentException();
+        checkSetterPreconditions();
 
         securityParametersLookupStrategy =
                 Constraint.isNotNull(strategy, "SecurityParameterContext lookup strategy cannot be null");
@@ -181,11 +162,11 @@ public class SignJWTHandler extends AbstractMessageHandler {
             return false;
         }
         
-        signatureSigningParameters = secParamCtx.getSignatureSigningParameters();
-        if (signatureSigningParameters == null || signatureSigningParameters.getSigningCredential() == null) {
-            log.debug("{} No signature signing credentials available", getLogPrefix());
+        final SignatureSigningParameters signatureSigningParameters = secParamCtx.getSignatureSigningParameters();
+        if (signatureSigningParameters == null) {
+            log.debug("{} No signature signing parameters available, signing skipped", getLogPrefix());
             return false;
-        }
+        }        
         
         jwtClaimSetToSign = claimsToSignLookupStrategy.apply(messageContext);
         if (jwtClaimSetToSign == null) {
@@ -193,7 +174,7 @@ public class SignJWTHandler extends AbstractMessageHandler {
             return false;
         }
         
-        credential = signatureSigningParameters.getSigningCredential();
+        signer = new JWSTokenSigner(signatureSigningParameters);
         
         return true;
          
@@ -203,78 +184,32 @@ public class SignJWTHandler extends AbstractMessageHandler {
     protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
                     
         try {
-            SignedJWT jwt = null; 
-            final Algorithm jwsAlgorithm = resolveAlgorithm();
-            final JWSSigner signer = getSigner(jwsAlgorithm);
-            final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(new JWSAlgorithm(jwsAlgorithm.getName()))
-                    .keyID(CredentialConversionUtil.resolveKid(credential));
-            if (typeHeader != null) {
-                headerBuilder.type(new JOSEObjectType(typeHeader));
-            }
-            jwt = new SignedJWT(headerBuilder.build(), jwtClaimSetToSign);
-            jwt.sign(signer);
-            if (log.isDebugEnabled() && !log.isTraceEnabled()) {
-                log.debug("{} Signed JWT '{}' using kid '{}'", getLogPrefix(), logName, 
-                        CredentialConversionUtil.resolveKid(credential));
-            } else if (log.isTraceEnabled()) {
-                log.trace("{} Signed JWT '{}' using kid '{}': {}", getLogPrefix(), logName, 
-                        CredentialConversionUtil.resolveKid(credential),jwt.serialize());
+            assert jwtClaimSetToSign != null;
+            final SignedJWT signedJWT = signer.sign(jwtClaimSetToSign, typeHeader);
+            log.debug("{} '{}' signed successfully", getLogPrefix(), logName);
+            if (log.isTraceEnabled()) {
+                logJWT(signedJWT);
             }            
-            
-            if (jwt.getState() != State.SIGNED) {
-                // Should not really happen, as JOSEException should be thrown
-                log.error("{} JWT '{}' was not signed", getLogPrefix(), logName);
-                throw new MessageHandlerException("JWT was not signed, unknown cause");
-            }
-            
-            // Use consumer to update the signed JWT
-            jwtUpdateConsumer.accept(jwt, messageContext);
-            
-        } catch (final JOSEException e) {
-            log.error("{} Error signing claims set: {}", getLogPrefix(), e.getMessage());
-            throw new MessageHandlerException("Error signing claims set",e);
+            jwtUpdateConsumer.accept(signedJWT, messageContext);            
+        } catch (final SignatureException e) {
+            log.error("{} Error signing '{}' : {}", getLogPrefix(), logName, e.getMessage());
+            throw new MessageHandlerException("Error signing token",e);
         }
         
-        
     }
     
     /**
-     * Returns correct implementation of signer based on algorithm type.
+     * Log (on trace) the JWT. 
      * 
-     * @param jwsAlgorithm JWS algorithm
-     * @return signer for algorithm and private key
-     * @throws JOSEException if algorithm cannot be supported
+     * @param jwt the JWT to log.
      */
-    private JWSSigner getSigner(final Algorithm jwsAlgorithm) throws JOSEException {
-        if (JWSAlgorithm.Family.EC.contains(jwsAlgorithm)) {
-            return new ECDSASigner((ECPrivateKey) credential.getPrivateKey());
-        }
-        if (JWSAlgorithm.Family.RSA.contains(jwsAlgorithm)) {
-            return new RSASSASigner(credential.getPrivateKey());
-        }
-        if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
-            return new MACSigner(credential.getSecretKey());
-        }
-        throw new JOSEException("Unsupported algorithm " + jwsAlgorithm.getName());
+    private void logJWT(@Nonnull final SignedJWT jwt) {
+        try {
+            log.trace("{} Signed JWT: {}, signature '{}'", getLogPrefix(), jwt.getJWTClaimsSet(), 
+                    jwt.getSignature());
+        } catch (final IllegalStateException | ParseException e) {
+            log.trace("{} Unable to print signed JWT: {}", getLogPrefix(), e.getMessage());
+        }        
     }
     
-    /**
-     * Resolves JWS algorithm from signature signing parameters.
-     * 
-     * @return JWS algorithm
-     */
-    protected JWSAlgorithm resolveAlgorithm() {
-
-        final JWSAlgorithm algorithm = new JWSAlgorithm(signatureSigningParameters.getSignatureAlgorithm());
-        if (credential instanceof JWKCredential && !algorithm.equals(((JWKCredential) credential).getAlgorithm())) {
-            log.debug("{} Signature signing algorithm {} differs from JWK algorithm '{}'", getLogPrefix(),
-                    algorithm.getName(), ((JWKCredential) credential).getAlgorithm() != null ? 
-                            ((JWKCredential) credential).getAlgorithm() : "not specified");            
-        }
-        log.trace("{} Algorithm resolved {}", getLogPrefix(), algorithm.getName());
-        return algorithm;
-    }
-
-
-
 }
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWSTokenSignerTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWSTokenSignerTest.java
new file mode 100644
index 0000000..f557d5a
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWSTokenSignerTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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 net.shibboleth.oidc.security.impl;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.testng.AssertJUnit;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.impl.support.TestCredentialHelper;
+import net.shibboleth.oidc.security.jose.SignatureException;
+import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
+
+/**
+ * Tests for the {@link JWSTokenSigner}
+ */
+public class JWSTokenSignerTest {
+    
+    /** A client_secret to use.*/
+    @Nonnull private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+    
+    /** The claims to sign.*/
+    private JWTClaimsSet claims;
+    
+    @BeforeMethod
+    public void setup() throws Exception {
+        claims = new JWTClaimsSet.Builder()
+                .issuer("test-client")
+                .audience("test-op")
+                .issueTime(new Date())
+                .build();       
+    }
+    
+    @Test
+    public void testSignHMAC_Success() throws Exception {
+        final var params = new SignatureSigningParameters();
+        params.setSigningCredential(
+                TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET).toSigningCredential());
+        params.setSignatureAlgorithm("HS256");
+        
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        assertNotNull(claims);
+        assert claims != null;         
+        final SignedJWT signed = signer.sign(claims, null);
+        assertNotNull(signed);
+        assertNotNull(signed.getSignature());
+    }
+    
+    @Test(expectedExceptions = SignatureException.class)
+    public void testSignHMAC_WrongAlgorithm_Asymmetric() throws Exception {
+        
+        final var params = new SignatureSigningParameters();
+        params.setSigningCredential(
+                TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET).toSigningCredential());
+        params.setSignatureAlgorithm("RS256");
+        
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        assert claims != null;
+        signer.sign(claims, null);
+        
+    }
+    
+    @Test(expectedExceptions = SignatureException.class)
+    public void testSignES256_WrongCredentialType_Asymmetric_RSAKey() throws Exception {
+        
+        final var params = new SignatureSigningParameters();
+        final RSAKey rsaKey = new RSAKeyGenerator(2048)
+                .keyID("RSA-Mock-Key")
+                .keyUse(KeyUse.SIGNATURE)
+                .algorithm(new JWSAlgorithm("RS256"))
+                .generate();
+        params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(rsaKey));
+        params.setSignatureAlgorithm("ES256");
+        
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        signer.sign(claims, null);
+        
+    }
+    
+    @Test(expectedExceptions = SignatureException.class)
+    public void testSignRS256_WrongCredentialType_Asymmetric_ECKey() throws Exception {
+        
+        final var params = new SignatureSigningParameters();
+        final ECKey ecKey = new ECKeyGenerator(Curve.P_256)
+                .keyID("EC-Mock-Key")
+                .keyUse(KeyUse.SIGNATURE)
+                .algorithm(new JWSAlgorithm("ES256"))
+                .generate();
+        params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(ecKey));
+        params.setSignatureAlgorithm("RS256");
+        
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        signer.sign(claims, null);
+        
+    }
+    
+    @Test(expectedExceptions = SignatureException.class)
+    public void testSignHMAC_WrongCredentialType_Asymmetric_RSAKey() throws Exception {
+        
+        final SignatureSigningParameters params = new SignatureSigningParameters();
+        final RSAKey rsaKey = new RSAKeyGenerator(2048)
+                .keyID("RSA-Mock-Key")
+                .keyUse(KeyUse.SIGNATURE)
+                .algorithm(new JWSAlgorithm("RS256"))
+                .generate();
+        params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(rsaKey));
+        params.setSignatureAlgorithm("HS256");
+        
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        signer.sign(claims, null);
+        
+    }
+    
+    @Test
+    public void testSignRS256_Success() throws Exception {        
+       
+        final var params = new SignatureSigningParameters();
+        final RSAKey rsaKey = new RSAKeyGenerator(2048)
+                .keyID("RSA-Mock-Key")
+                .keyUse(KeyUse.SIGNATURE)
+                .algorithm(new JWSAlgorithm("RS256"))
+                .generate();
+        params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(rsaKey));
+        params.setSignatureAlgorithm("RS256");
+        
+        assertNotNull(claims);
+        assert claims != null; 
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        final SignedJWT signed = signer.sign(claims, null);
+        assertNotNull(signed);
+        assertNotNull(signed.getSignature());
+        assertTrue(JWSAlgorithm.Family.RSA.contains(signed.getHeader().getAlgorithm()));
+    }
+    
+    @Test
+    public void testSignES256_Success() throws Exception {
+
+        final var params = new SignatureSigningParameters();
+        final ECKey ecKey = new ECKeyGenerator(Curve.P_256)
+                .keyID("EC-Mock-Key")
+                .keyUse(KeyUse.SIGNATURE)
+                .algorithm(new JWSAlgorithm("ES256"))
+                .generate();
+        params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(ecKey));
+        params.setSignatureAlgorithm("ES256");  
+        
+        assertNotNull(claims);
+        assert claims != null; 
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        final SignedJWT signed = signer.sign(claims, null);
+        assertNotNull(signed);
+        assertNotNull(signed.getSignature());
+     
+        AssertJUnit.assertTrue(JWSAlgorithm.Family.EC.contains(signed.getHeader().getAlgorithm()));
+    }
+    
+    @Test
+    public void testSignPS256_Success() throws Exception {
+        
+        final var params = new SignatureSigningParameters();
+        final RSAKey rsaKey = new RSAKeyGenerator(2048)
+                .keyID("RSA-Mock-Key")
+                .keyUse(KeyUse.SIGNATURE)
+                .algorithm(new JWSAlgorithm("PS256"))
+                .generate();
+        params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(rsaKey));
+        params.setSignatureAlgorithm("PS256");
+        
+        assertNotNull(claims);
+        assert claims != null; 
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        final SignedJWT signed = signer.sign(claims, null);
+        assertNotNull(signed);
+        assertNotNull(signed.getSignature());
+       
+        AssertJUnit.assertTrue(JWSAlgorithm.Family.RSA.contains(signed.getHeader().getAlgorithm()));
+    }
+    
+    @Test(expectedExceptions = SignatureException.class)
+    public void testSignNoCredential_Failure() throws Exception {
+        final var params = new SignatureSigningParameters();       
+        params.setSignatureAlgorithm("PS256");
+        
+        assertNotNull(claims);
+        assert claims != null; 
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        final SignedJWT signed = signer.sign(claims, null);
+    }
+    
+    @Test(expectedExceptions = SignatureException.class)
+    public void testSignNoAlgorithm_Failure() throws Exception {
+        final var params = new SignatureSigningParameters(); 
+        final RSAKey rsaKey = new RSAKeyGenerator(2048)
+                .keyID("RSA-Mock-Key")
+                .keyUse(KeyUse.SIGNATURE)
+                .algorithm(new JWSAlgorithm("PS256"))
+                .generate();
+        params.setSigningCredential(TestCredentialHelper.createAsymmetricSigningCredential(rsaKey));
+                
+        assertNotNull(claims);
+        assert claims != null; 
+        final JWSTokenSigner signer = new JWSTokenSigner(params);
+        final SignedJWT signed = signer.sign(claims, null);
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/SignJWTHandlerTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/SignJWTHandlerTest.java
index fca2311..2e06f55 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/SignJWTHandlerTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/SignJWTHandlerTest.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.oidc.security.impl;
 
+import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 import java.text.ParseException;
@@ -21,7 +22,7 @@ import java.util.Date;
 
 import javax.annotation.Nonnull;
 
-import org.testng.AssertJUnit;
+import org.opensaml.messaging.handler.MessageHandlerException;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -64,6 +65,7 @@ public class SignJWTHandlerTest extends AbstractHandlerTest {
     public void setup() throws Exception {
         super.setup();
         signer = new SignJWTHandler();
+        signer.setLogName("Mock Token");
         
         signer.setClaimsToSignLookupStrategy(mc -> {
             final OIDCAuthenticationRequest ar = (OIDCAuthenticationRequest)mc.getMessage();
@@ -104,13 +106,13 @@ public class SignJWTHandlerTest extends AbstractHandlerTest {
         signer.initialize();
         signer.invoke(prc.getOutboundMessageContext());
         final JWT jwt = request.getRequestObject();
-        AssertJUnit.assertTrue(jwt instanceof SignedJWT);
+        assertTrue(jwt instanceof SignedJWT);
         final var signedJWT = (SignedJWT)jwt;
-        AssertJUnit.assertTrue(JWSAlgorithm.Family.HMAC_SHA.contains(signedJWT.getHeader().getAlgorithm()));
+        assertTrue(JWSAlgorithm.Family.HMAC_SHA.contains(signedJWT.getHeader().getAlgorithm()));
     }
     
-    @Test(expectedExceptions = Exception.class)
-    public void testSignHMAC_WrongCredentialType() throws Exception {
+    @Test(expectedExceptions = MessageHandlerException.class)
+    public void testSignRS256_WrongCredentialType() throws Exception {
         
         final SecurityParametersContext secParamCtx = new SecurityParametersContext();
         final var params = new SignatureSigningParameters();
@@ -122,10 +124,7 @@ public class SignJWTHandlerTest extends AbstractHandlerTest {
         
         signer.initialize();
         signer.invoke(prc.getOutboundMessageContext());
-        final JWT jwt = request.getRequestObject();
-        AssertJUnit.assertTrue(jwt instanceof SignedJWT);
-        final var signedJWT = (SignedJWT)jwt;
-        AssertJUnit.assertTrue(JWSAlgorithm.Family.HMAC_SHA.contains(signedJWT.getHeader().getAlgorithm()));
+        
     }
     
     @Test
@@ -145,9 +144,9 @@ public class SignJWTHandlerTest extends AbstractHandlerTest {
         signer.initialize();
         signer.invoke(prc.getOutboundMessageContext());
         final JWT jwt = request.getRequestObject();
-        AssertJUnit.assertTrue(jwt instanceof SignedJWT);
+        assertTrue(jwt instanceof SignedJWT);
         final var signedJWT = (SignedJWT)jwt;
-        AssertJUnit.assertTrue(JWSAlgorithm.Family.RSA.contains(signedJWT.getHeader().getAlgorithm()));
+        assertTrue(JWSAlgorithm.Family.RSA.contains(signedJWT.getHeader().getAlgorithm()));
     }
     
     @Test
@@ -167,9 +166,9 @@ public class SignJWTHandlerTest extends AbstractHandlerTest {
         signer.initialize();
         signer.invoke(prc.getOutboundMessageContext());
         final JWT jwt = request.getRequestObject();
-        AssertJUnit.assertTrue(jwt instanceof SignedJWT);
+        assertTrue(jwt instanceof SignedJWT);
         final var signedJWT = (SignedJWT)jwt;
-        AssertJUnit.assertTrue(JWSAlgorithm.Family.EC.contains(signedJWT.getHeader().getAlgorithm()));
+        assertTrue(JWSAlgorithm.Family.EC.contains(signedJWT.getHeader().getAlgorithm()));
     }
     
     @Test
@@ -189,9 +188,9 @@ public class SignJWTHandlerTest extends AbstractHandlerTest {
         signer.initialize();
         signer.invoke(prc.getOutboundMessageContext());
         final JWT jwt = request.getRequestObject();
-        AssertJUnit.assertTrue(jwt instanceof SignedJWT);
+        assertTrue(jwt instanceof SignedJWT);
         final var signedJWT = (SignedJWT)jwt;
-        AssertJUnit.assertTrue(JWSAlgorithm.Family.RSA.contains(signedJWT.getHeader().getAlgorithm()));
+        assertTrue(JWSAlgorithm.Family.RSA.contains(signedJWT.getHeader().getAlgorithm()));
     }
 
 }

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


More information about the commits mailing list