[java-oidc-common] branch main updated: JCOMOIDC-6 Move common crypto/security code from the OIDC plugin
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Dec 18 12:37:40 UTC 2020
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=8322a67c802dc03f9e67caddac49d9568844e00f
The following commit(s) were added to refs/heads/main by this push:
new 8322a67 JCOMOIDC-6 Move common crypto/security code from the OIDC plugin
8322a67 is described below
commit 8322a67c802dc03f9e67caddac49d9568844e00f
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Dec 18 14:35:43 2020 +0200
JCOMOIDC-6 Move common crypto/security code from the OIDC plugin
https://issues.shibboleth.net/jira/browse/JCOMOIDC-6
Imported JWTSignatureValidationUtil and its dependencies from java-idp-oidc.
Also added unit tests.
---
.../security/impl/JWTSignatureValidationUtil.java | 133 +++++++++++++
.../security/impl/OIDCDecryptionParameters.java | 40 ++++
.../impl/OIDCSignatureValidationParameters.java | 48 +++++
.../impl/JWTSignatureValidationUtilTest.java | 207 +++++++++++++++++++++
4 files changed, 428 insertions(+)
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTSignatureValidationUtil.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTSignatureValidationUtil.java
new file mode 100644
index 0000000..0f1d4a9
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTSignatureValidationUtil.java
@@ -0,0 +1,133 @@
+/*
+ * 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.impl;
+
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Iterator;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.context.SecurityParametersContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.ECDSAVerifier;
+import com.nimbusds.jose.crypto.MACVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.credential.JWKCredential;
+
+/**
+ * Generic utility class for helping JWT signature validation.
+ */
+public final class JWTSignatureValidationUtil {
+
+ /** Class logger. */
+ @Nonnull private static Logger log = LoggerFactory.getLogger(JWTSignatureValidationUtil.class);
+
+ /** Private constructor. */
+ private JWTSignatureValidationUtil() {
+
+ }
+
+ /**
+ * Validates the signature of the given JWT using the given security parameters context. If the validation fails for
+ * any reason, including insufficient prequisities in the context, an event identifier is returned. Successful
+ * validation produces null result.
+ *
+ * @param secParamCtx The {@link SecurityParametersContext} to use for signature validation.
+ * @param signedJwt The signed JWT to be validated.
+ * @param invalidJwtEventId The event identifier describing the invalid JWT.
+ * @return an event ID
+ */
+ public static String validateSignature(final SecurityParametersContext secParamCtx, final SignedJWT signedJwt,
+ final String invalidJwtEventId) {
+ if (secParamCtx == null) {
+ log.error("No security parameters context is available");
+ return EventIds.INVALID_SEC_CFG;
+ }
+ if (secParamCtx.getSignatureSigningParameters() == null
+ || !(secParamCtx.getSignatureSigningParameters() instanceof OIDCSignatureValidationParameters)) {
+ log.error("No signature validation credentials available");
+ return EventIds.INVALID_SEC_CFG;
+ }
+ final OIDCSignatureValidationParameters signatureValidationParameters =
+ (OIDCSignatureValidationParameters) secParamCtx.getSignatureSigningParameters();
+ if (signatureValidationParameters.getValidationCredentials().isEmpty()) {
+ log.error("Unable to find any keys to validate given JWT signature");
+ return EventIds.INVALID_SEC_CFG;
+ }
+ final Algorithm algorithm = signedJwt.getHeader().getAlgorithm();
+ final Iterator<?> it = signatureValidationParameters.getValidationCredentials().iterator();
+ while (it.hasNext()) {
+ final JWKCredential credential = (JWKCredential) it.next();
+ if (!algorithm.equals(credential.getAlgorithm())) {
+ log.debug("Credential alg {} not matching jwt header alg {}", credential.getAlgorithm().getName(),
+ algorithm.getName());
+ } else {
+ try {
+ final JWSVerifier verifier = initializeVerifier(algorithm, credential);
+ if (verifier == null) {
+ log.error("No verifier for given JWT for alg {}", algorithm.getName());
+ return EventIds.INVALID_SEC_CFG;
+ }
+ if (signedJwt.verify(verifier)) {
+ log.debug("JWT {} verified using algorithm {} and key {}", signedJwt.serialize(),
+ algorithm.getName(), credential.getKid());
+ return null;
+ }
+ log.debug("Unable to validate given JWT with credential, picking next key");
+ } catch (final JOSEException e) {
+ log.warn("Exception catched when validating given JWT with credential {}", credential.getKid(), e);
+ }
+ }
+ }
+ log.error("Unable to validate given JWT with any of the credentials");
+ return invalidJwtEventId;
+ }
+
+ /**
+ * Initializes a {@link JWSVerifier} for the given algorithm, using the provided {@link Credential}.
+ * @param algorithm The algorithm used for deciding the verifier.
+ * @param credential The credential to be used for the verifier.
+ * @return A corresponding verifier, or null if no supported found.
+ * @throws JOSEException If the credential doesn't meet the verifier requirements.
+ */
+ private static JWSVerifier initializeVerifier(final Algorithm algorithm, final Credential credential)
+ throws JOSEException {
+ if (JWSAlgorithm.Family.HMAC_SHA.contains(algorithm)) {
+ return new MACVerifier(credential.getSecretKey());
+ }
+ if (JWSAlgorithm.Family.RSA.contains(algorithm)) {
+ return new RSASSAVerifier((RSAPublicKey) credential.getPublicKey());
+ }
+ if (JWSAlgorithm.Family.EC.contains(algorithm)) {
+ return new ECDSAVerifier((ECPublicKey) credential.getPublicKey());
+ }
+ return null;
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCDecryptionParameters.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCDecryptionParameters.java
new file mode 100644
index 0000000..5654c7a
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCDecryptionParameters.java
@@ -0,0 +1,40 @@
+/*
+ * 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.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.EncryptionParameters;
+
+/** OIDC Decryption Parameters. Steals a bit EncryptionParameters as extending it for decryption purposes. */
+public class OIDCDecryptionParameters extends EncryptionParameters {
+
+ /** The list of decryption credentials. */
+ private List<Credential> keyTransportDecryptionCredentials = new ArrayList<Credential>();
+
+ /**
+ * Get the list of decryption credentials.
+ *
+ * @return he list of decryption credentials
+ */
+ public List<Credential> getKeyTransportDecryptionCredentials() {
+ return keyTransportDecryptionCredentials;
+ }
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCSignatureValidationParameters.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCSignatureValidationParameters.java
new file mode 100644
index 0000000..6d88ae6
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/OIDCSignatureValidationParameters.java
@@ -0,0 +1,48 @@
+/*
+ * 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.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.xmlsec.SignatureSigningParameters;
+
+import net.shibboleth.oidc.security.credential.JWKCredential;
+
+/**
+ * OIDC Signature Validation Parameters. Steals a bit SignatureSigningParameters as extending it also for validation
+ * purposes.
+ */
+public class OIDCSignatureValidationParameters extends SignatureSigningParameters {
+
+ /** The list of validation credentials. */
+ @Nonnull
+ private final List<JWKCredential> validationCredentials = new ArrayList<JWKCredential>();
+
+ /**
+ * Get the list of validation credentials.
+ *
+ * @return the list of validation credentials
+ */
+ @Nonnull
+ public List<JWKCredential> getValidationCredentials() {
+ return validationCredentials;
+ }
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTSignatureValidationUtilTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTSignatureValidationUtilTest.java
new file mode 100644
index 0000000..9a14434
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/JWTSignatureValidationUtilTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.impl;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPrivateKey;
+import java.text.ParseException;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+import org.opensaml.xmlsec.context.SecurityParametersContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+
+/**
+ * Unit tests for {@link JWTSignatureValidationUtil}.
+ */
+public class JWTSignatureValidationUtilTest {
+
+ final String invalidJwtEventId = "invalid_jwt";
+
+ BasicJWKCredential rsaCredential;
+
+ BasicJWKCredential ecCredential;
+
+ BasicJWKCredential sharedCredential;
+
+ @BeforeClass
+ public void init() throws NoSuchAlgorithmException {
+ rsaCredential = createKeyPairCredential("RSA", "RS256", "mockRSAKey", 2048);
+ ecCredential = createKeyPairCredential("EC", "ES256", "mockECKey", 256);
+ sharedCredential = createSharedSecretCredential("HS256", "mockSharedSecret");
+ }
+
+ @Test
+ public void validateSignature_shouldReturnInvalidSecCfgWhenNoSecCtx() {
+ Assert.assertEquals(JWTSignatureValidationUtil.validateSignature(null, null, invalidJwtEventId),
+ EventIds.INVALID_SEC_CFG);
+ }
+
+ @Test
+ public void validateSignature_shouldReturnInvalidSecCfgWhenNoSigningParameters() {
+ SecurityParametersContext secCtx = new SecurityParametersContext();
+ Assert.assertEquals(JWTSignatureValidationUtil.validateSignature(secCtx, null, invalidJwtEventId),
+ EventIds.INVALID_SEC_CFG);
+ }
+
+ @Test
+ public void validateSignature_shouldReturnInvalidSecCfgWhenWrongSigningParameters() {
+ SecurityParametersContext secCtx = new SecurityParametersContext();
+ secCtx.setSignatureSigningParameters(new SignatureSigningParameters());
+ Assert.assertEquals(JWTSignatureValidationUtil.validateSignature(secCtx, null, invalidJwtEventId),
+ EventIds.INVALID_SEC_CFG);
+ }
+
+ @Test
+ public void validateSignature_shouldReturnNullWhenValidRSASignature()
+ throws JOSEException, ParseException, NoSuchAlgorithmException {
+ SecurityParametersContext secCtx = initSecurityParamsContext("RS256", rsaCredential);
+ Assert.assertNull(JWTSignatureValidationUtil.validateSignature(secCtx, signRSA(rsaCredential.getPrivateKey()),
+ invalidJwtEventId));
+ }
+
+ @Test
+ public void validateSignature_shouldReturnEventIdWhenInvalidRSASignature()
+ throws NoSuchAlgorithmException, JOSEException, ParseException {
+ final BasicJWKCredential anotherRsaCredential = createKeyPairCredential("RSA", "RS256", "mockRSAKey2", 2048);
+ final SecurityParametersContext secCtx = initSecurityParamsContext("RS256", rsaCredential);
+ Assert.assertEquals(JWTSignatureValidationUtil.validateSignature(secCtx,
+ signRSA(anotherRsaCredential.getPrivateKey()), invalidJwtEventId), invalidJwtEventId);
+ }
+
+ @Test
+ public void validateSignature_shouldReturnNullWhenValidECSignature()
+ throws JOSEException, ParseException, NoSuchAlgorithmException {
+ SecurityParametersContext secCtx = initSecurityParamsContext("ES256", ecCredential);
+ Assert.assertNull(JWTSignatureValidationUtil.validateSignature(secCtx,
+ signEC((ECPrivateKey) ecCredential.getPrivateKey()), invalidJwtEventId));
+ }
+
+ @Test
+ public void validateSignature_shouldReturnEventIdWhenInvalidECSignature()
+ throws JOSEException, ParseException, NoSuchAlgorithmException {
+ final BasicJWKCredential anotherEcCredential = createKeyPairCredential("EC", "ES256", "mockECKey2", 256);
+ SecurityParametersContext secCtx = initSecurityParamsContext("ES256", ecCredential);
+ Assert.assertEquals(JWTSignatureValidationUtil.validateSignature(secCtx,
+ signEC((ECPrivateKey) anotherEcCredential.getPrivateKey()), invalidJwtEventId), invalidJwtEventId);
+ }
+
+ @Test
+ public void validateSignature_shouldReturnNullWhenValidMACSignature() throws JOSEException, ParseException {
+ SecurityParametersContext secCtx = initSecurityParamsContext("HS256", sharedCredential);
+ Assert.assertNull(JWTSignatureValidationUtil.validateSignature(secCtx,
+ signMAC(sharedCredential.getSecretKey()), invalidJwtEventId));
+ }
+
+ @Test
+ public void validateSignature_shouldReturnEventIdWhenInvalidMACSignature() throws JOSEException, ParseException {
+ SecurityParametersContext secCtx = initSecurityParamsContext("HS256", sharedCredential);
+ Assert.assertEquals(JWTSignatureValidationUtil.validateSignature(secCtx,
+ signMAC(generateSecretKey()), invalidJwtEventId), invalidJwtEventId);
+
+ }
+
+ protected SignedJWT signRSA(final PrivateKey privateKey) throws JOSEException, ParseException {
+ final JWSSigner signer = new RSASSASigner(privateKey);
+ final JWSObject jwsObject = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("mockId").build(),
+ new Payload("RSA payload"));
+
+ jwsObject.sign(signer);
+ return SignedJWT.parse(jwsObject.serialize());
+ }
+
+ protected SignedJWT signEC(final ECPrivateKey privateKey) throws JOSEException, ParseException {
+ final JWSSigner signer = new ECDSASigner(privateKey);
+ final JWSObject jwsObject = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.ES256).keyID("mockId").build(),
+ new Payload("RSA payload"));
+
+ jwsObject.sign(signer);
+ return SignedJWT.parse(jwsObject.serialize());
+ }
+
+ protected BasicJWKCredential createKeyPairCredential(final String keyPairAlgorithm, final String jwsAlgorithm,
+ final String kid, final int keysize) throws NoSuchAlgorithmException {
+ final KeyPairGenerator generator = KeyPairGenerator.getInstance(keyPairAlgorithm);
+ generator.initialize(keysize);
+ final KeyPair keyPair = generator.generateKeyPair();
+ final BasicJWKCredential credential = new BasicJWKCredential();
+ credential.setAlgorithm(JWSAlgorithm.parse(jwsAlgorithm));
+ credential.setPublicKey(keyPair.getPublic());
+ credential.setPrivateKey(keyPair.getPrivate());
+ credential.setKid(kid);
+
+ return credential;
+ }
+
+ protected BasicJWKCredential createSharedSecretCredential(final String jwsAlgorithm, final String kid) {
+ final BasicJWKCredential credential = new BasicJWKCredential();
+ credential.setAlgorithm(JWSAlgorithm.parse(jwsAlgorithm));
+ credential.setSecretKey(generateSecretKey());
+ credential.setKid(kid);
+
+ return credential;
+ }
+
+ protected SecretKey generateSecretKey() {
+ final SecureRandom random = new SecureRandom();
+ final byte[] sharedSecret = new byte[32];
+ random.nextBytes(sharedSecret);
+ return new SecretKeySpec(sharedSecret, "AES");
+ }
+
+ protected SignedJWT signMAC(final SecretKey secretKey) throws JOSEException, ParseException {
+ final JWSSigner signer = new MACSigner(secretKey.getEncoded());
+ final JWSObject jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload("MAC payload"));
+ jwsObject.sign(signer);
+ return SignedJWT.parse(jwsObject.serialize());
+ }
+
+ protected SecurityParametersContext initSecurityParamsContext(final String algorithm,
+ final BasicJWKCredential... credentials) {
+ SecurityParametersContext secCtx = new SecurityParametersContext();
+ OIDCSignatureValidationParameters params = new OIDCSignatureValidationParameters();
+ for (final BasicJWKCredential credential : credentials) {
+ params.getValidationCredentials().add(credential);
+ }
+ params.setSignatureAlgorithm(algorithm);
+ secCtx.setSignatureSigningParameters(params);
+ return secCtx;
+ }
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list