[java-oidc-common] 02/02: JCOMOIDC-113 - TrustEngine implementation for token derived credentials
Henri Mikkonen
henri.mikkonen at iki.fi
Wed May 8 09:46:05 UTC 2024
This is an automated email from the git hooks/post-receive script.
hjmikkon 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=a9ea3e9885ee833382c1c92f2f77604b4130248f
commit a9ea3e9885ee833382c1c92f2f77604b4130248f
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed May 8 12:43:09 2024 +0300
JCOMOIDC-113 - TrustEngine implementation for token derived credentials
https://shibboleth.atlassian.net/browse/JCOMOIDC-113
New TokenAsymmetricKeyTrustEngine bases trust on token derived asymmetric public key
---
.../impl/TokenAsymmetricKeyTrustEngine.java | 91 ++++++++++++
.../impl/TokenAsymmetricKeyTrustEngineTest.java | 157 +++++++++++++++++++++
2 files changed, 248 insertions(+)
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/TokenAsymmetricKeyTrustEngine.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/TokenAsymmetricKeyTrustEngine.java
new file mode 100644
index 0000000..bc27ad7
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/TokenAsymmetricKeyTrustEngine.java
@@ -0,0 +1,91 @@
+/*
+ * 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.PublicKey;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.trust.TrustEngine;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.jwk.AsymmetricJWK;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.CredentialConversionUtil;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * A {@link TrustEngine} implementation for Signed JSON Web Tokens. The token derived public key is used as the trust
+ * basis.
+ *
+ * @since 3.2.0
+ */
+public class TokenAsymmetricKeyTrustEngine extends BaseSignedJWTTrustEngine<Credential> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExplicitKeySignedJWTTrustEngine.class);
+
+ /**
+ * Constructor.
+ *
+ * @param joseObjectResolver resolver of credentials from JOSEObject headers.
+ */
+ protected TokenAsymmetricKeyTrustEngine(
+ @Nonnull final @ParameterName(name="JOSEObjectResolver") JOSEObjectCredentialResolver joseObjectResolver) {
+ super(joseObjectResolver);
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ protected boolean doValidate(@Nonnull final SignedJWT signedJWT, @Nonnull final CriteriaSet trustBasisCriteria)
+ throws SecurityException {
+ final JWK jwk = signedJWT.getHeader().getJWK();
+ if (jwk != null) {
+ try {
+ if (!(jwk instanceof AsymmetricJWK)) {
+ log.warn("JWK in JWT is not asymmetric");
+ return false;
+ }
+ return validate(signedJWT, CredentialConversionUtil.keyToCredential(jwk));
+ } catch (final SecurityException | JOSEException e) {
+ log.warn("Could not convert the incoming JWK into credential", e);
+ }
+ } else {
+ log.warn("No JWK found from the incoming JWT");
+ }
+ return false;
+ }
+
+ @Override
+ /** {@inheritDoc} */
+ protected boolean evaluateTrust(@Nonnull final Credential untrustedCredential,
+ @Nullable final Credential trustBasis) throws SecurityException {
+ final PublicKey publicKey = untrustedCredential.getPublicKey();
+ if (publicKey != null && trustBasis != null) {
+ return publicKey.equals(trustBasis.getPublicKey());
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/TokenAsymmetricKeyTrustEngineTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/TokenAsymmetricKeyTrustEngineTest.java
new file mode 100644
index 0000000..ab976d3
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/TokenAsymmetricKeyTrustEngineTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+
+import net.shibboleth.oidc.jwa.support.SignatureConstants;
+import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.jose.SignatureValidationParameters;
+import net.shibboleth.oidc.security.jose.criterion.SignatureValidationParametersCriterion;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Unit tests for {@link TokenAsymmetricKeyTrustEngine}.
+ */
+ at SuppressWarnings("null")
+public class TokenAsymmetricKeyTrustEngineTest {
+
+ private TokenAsymmetricKeyTrustEngine engine;
+
+ private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+
+ private SignatureValidationParameters params;
+
+ private CriteriaSet criteria;
+
+ private ECKey ecKey;
+
+ @BeforeMethod
+ public void setup() throws JOSEException {
+
+ //Setup a standard security params context
+ params = new SignatureValidationParameters();
+ params.setSignatureTrustEngine(engine);
+
+ // Setup standard criterion
+ criteria = new CriteriaSet();
+ criteria.add(new UsageCriterion(UsageType.SIGNING));
+ criteria.add(new SignatureValidationParametersCriterion(params));
+
+ final JOSEObjectCredentialResolver joseObjectCredResolver = new BasicJOSEObjectCredentialResolver();
+
+ engine = new TokenAsymmetricKeyTrustEngine(joseObjectCredResolver);
+ ecKey = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
+ }
+
+ @Test
+ public void testInvalid_WithoutInlineECCredential() throws JOSEException, SecurityException {
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createECSignedJWT(ecKey, ecKey.getKeyID(),
+ JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+ @Test
+ public void testInvalid_WithSymmetricKeyCredential() throws JOSEException, SecurityException {
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createMACSignedJWT(CLIENT_SECRET,
+ "mockId", JWSAlgorithm.HS256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+ @Test
+ public void testInvalid_WithSymmetricKeyCredential_JWSAlgorithm_Excluded() throws JOSEException, SecurityException {
+ params.setExcludedAlgorithms(List.of(SignatureConstants.ALGO_ID_SIGNATURE_HS_256));
+
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createMACSignedJWT(CLIENT_SECRET,
+ "mockId", JWSAlgorithm.HS256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+ @Test
+ public void testValid_WithInlineJWK() throws JOSEException, SecurityException {
+
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createECSignedJWTWithInlineJWK(ecKey,
+ ecKey.getKeyID(), JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertTrue(valid);
+ }
+
+ @Test
+ public void testInvalid_WithInlineJWK_JWSAlgorithm_Excluded() throws JOSEException, SecurityException {
+ params.setExcludedAlgorithms(List.of(SignatureConstants.ALGO_ID_SIGNATURE_ES_256));
+
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createECSignedJWTWithInlineJWK(ecKey,
+ ecKey.getKeyID(), JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+ @Test
+ public void testInvalid_WithInlineJWK_JWSAlgorithm_notIncluded() throws JOSEException, SecurityException {
+ params.setIncludedAlgorithms(List.of(SignatureConstants.ALGO_ID_SIGNATURE_RS_256));
+
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createECSignedJWTWithInlineJWK(ecKey,
+ ecKey.getKeyID(), JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+ @Test
+ public void testInvalid_InlineJWKWrongKid() throws JOSEException, SecurityException {
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createECSignedJWTWithInlineJWK(ecKey,
+ "WRONG-KID", JWSAlgorithm.ES256,
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+ @Test
+ public void testInvalid_InlineJWKInvalidSignature() throws JOSEException, SecurityException {
+
+ final var valid = engine.validate(ExplicitKeySignedJWTTrustEngineTest.createECSignedJWTWithDifferentInlineJWK(
+ ecKey, ecKey.getKeyID(),JWSAlgorithm.ES256,
+ // A new signing key, different to the one described in the header
+ new ECKeyGenerator(Curve.P_256).keyID("new").generate(),
+ "https://op.example.com/", "https://rp.example.com"),
+ criteria);
+ assertFalse(valid);
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list