[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