[java-oidc-common] branch dev/JCOMOIDC-41 updated: Cleanup direct and key wrapping decryption
Phil Smart
philip.smart at jisc.ac.uk
Mon Sep 26 16:55:34 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch dev/JCOMOIDC-41
in repository java-oidc-common.
View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=34d2ad0c6a5ccd868daec74c602726293d0c9d3d
The following commit(s) were added to refs/heads/dev/JCOMOIDC-41 by this push:
new 34d2ad0 Cleanup direct and key wrapping decryption
34d2ad0 is described below
commit 34d2ad0c6a5ccd868daec74c602726293d0c9d3d
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Sep 26 17:55:28 2022 +0100
Cleanup direct and key wrapping decryption
---
.../DefaultJWTEncryptionParametersResolver.java | 41 +++---------
.../oidc/security/impl/JWKCredentialSupport.java | 66 +++++++++++++++++++
.../oidc/security/impl/JWTDecrypter.java | 75 ++++++++++++++++------
3 files changed, 130 insertions(+), 52 deletions(-)
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/DefaultJWTEncryptionParametersResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/DefaultJWTEncryptionParametersResolver.java
index 980054e..f7564c3 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/DefaultJWTEncryptionParametersResolver.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/DefaultJWTEncryptionParametersResolver.java
@@ -28,7 +28,6 @@ import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import javax.crypto.SecretKey;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialResolver;
@@ -49,9 +48,6 @@ import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
-import com.nimbusds.jose.util.StandardCharset;
-import com.nimbusds.oauth2.sdk.auth.Secret;
-import com.nimbusds.oauth2.sdk.jose.SecretKeyDerivation;
import net.shibboleth.oidc.security.JWTEncryptionConfiguration;
import net.shibboleth.oidc.security.JWTEncryptionParameters;
@@ -490,50 +486,29 @@ public class DefaultJWTEncryptionParametersResolver extends AbstractSecurityPara
}
/**
- * Derive a *new* symmetric key credential suitable for the given 'alg' and 'enc' algorithms. The
- * new credential is a copy of the given credential but replacing the keyID and secret. If any of
- * the inputs are {@code null} then {@code null} is returned.
+ * Wrapper to
+ * {@link JWKCredentialSupport#deriveSymmetricKeyForAlgAndEnc(JWKCredential, JWEAlgorithm, EncryptionMethod)}
+ * which derives a *new* symmetric key credential suitable for the given 'alg' and 'enc' algorithms. Catches
+ * and logs errors, returning {@literal null} on error.
*
* @param credential the credential that contains a secret key to derive a new credential from
* @param alg the key management mode algorithm
* @param enc the content encryption algorithm
*
* @return a *new* derived credential, or {@code null} if an error occurs
+ * @throws JOSEException on error deriving the credential
*/
@Nullable private JWKCredential deriveSymmetricKeyForAlgAndEnc(
- @Nullable final JWKCredential credential, @Nullable final JWEAlgorithm alg, @Nullable final EncryptionMethod enc) {
+ @Nullable final JWKCredential credential, @Nullable final JWEAlgorithm alg,
+ @Nullable final EncryptionMethod enc) {
- if (credential == null || credential.getSecretKey() == null || alg == null || enc == null) {
- return null;
- }
try {
- final SecretKey derivedKey = generateSymmetricKey(credential.getSecretKey().getEncoded(), alg, enc);
- //build a new credential so the old is left in its original state
- return CredentialConversionUtil.copySymmetricCredentialWithNewSecret(
- credential, "derived-"+CredentialConversionUtil.resolveKid(credential), derivedKey);
+ return JWKCredentialSupport.deriveSymmetricKeyForAlgAndEnc(credential, alg, enc);
} catch (final JOSEException e) {
log.warn("Unable to generate secret key: {}", e.getMessage());
return null;
}
- }
-
- /**
- * Generate symmetric key from client_secret using the algorithms supplied.
- *
- * @param clientSecret client secret to derive a key from
- * @param alg the key management mode or key transport algorithm
- * @param enc the content encryption algorithm
- *
- * @return key derived from client secret.
-
- * @throws JOSEException on error
- */
- private final SecretKey generateSymmetricKey(final byte[] clientSecret, final JWEAlgorithm alg,
- final EncryptionMethod enc) throws JOSEException {
-
- return SecretKeyDerivation.deriveSecretKey(
- new Secret(new String(clientSecret, StandardCharset.UTF_8)), alg, enc);
}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWKCredentialSupport.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWKCredentialSupport.java
new file mode 100644
index 0000000..359f8e6
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWKCredentialSupport.java
@@ -0,0 +1,66 @@
+package net.shibboleth.oidc.security.impl;
+
+import javax.annotation.Nullable;
+import javax.crypto.SecretKey;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.jose.SecretKeyDerivation;
+
+import net.shibboleth.oidc.security.credential.JWKCredential;
+
+/** Provide JWK specific credential support.*/
+public class JWKCredentialSupport {
+
+ /** Constructor. */
+ private JWKCredentialSupport() { }
+
+
+ /**
+ * Derive a *new* symmetric key credential suitable for the given 'alg' and 'enc' algorithms. The
+ * new credential is a copy of the given credential but replacing the keyID and secret. If any of
+ * the inputs are {@code null} then {@code null} is returned.
+ *
+ * @param credential the credential that contains a secret key to derive a new credential from
+ * @param alg the key management mode algorithm
+ * @param enc the content encryption algorithm
+ *
+ * @return a *new* derived credential, or {@code null} if an error occurs
+ * @throws JOSEException on error deriving the credential
+ */
+ @Nullable public static final JWKCredential deriveSymmetricKeyForAlgAndEnc(
+ @Nullable final JWKCredential credential, @Nullable final JWEAlgorithm alg,
+ @Nullable final EncryptionMethod enc) throws JOSEException {
+
+ if (credential == null || credential.getSecretKey() == null || alg == null || enc == null) {
+ return null;
+ }
+ final SecretKey derivedKey = generateSymmetricKey(credential.getSecretKey().getEncoded(), alg, enc);
+ //build a new credential so the old is left in its original state
+ return CredentialConversionUtil.copySymmetricCredentialWithNewSecret(
+ credential, "derived-"+CredentialConversionUtil.resolveKid(credential), derivedKey);
+ }
+
+
+ /**
+ * Generate symmetric key from client_secret using the algorithms supplied.
+ *
+ * @param clientSecret client secret to derive a key from
+ * @param alg the key management mode or key transport algorithm
+ * @param enc the content encryption algorithm
+ *
+ * @return key derived from client secret.
+
+ * @throws JOSEException on error
+ */
+ public static final SecretKey generateSymmetricKey(final byte[] clientSecret, final JWEAlgorithm alg,
+ final EncryptionMethod enc) throws JOSEException {
+
+ return SecretKeyDerivation.deriveSecretKey(
+ new Secret(new String(clientSecret, StandardCharset.UTF_8)), alg, enc);
+ }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java
index dfe4448..4f0275c 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/JWTDecrypter.java
@@ -79,6 +79,9 @@ import net.shibboleth.utilities.java.support.resolver.ResolverException;
* The {@link EncryptedJWT} will be decrypted in-place, with its {@link State} changing to {@link State#DECRYPTED}
* on successful decryption. Any error that occurs decrypting the JWT will throw an {@link DecryptionException}.
*
+ * <p>Note, as per OIDC specification, symmetric decryption keys are derived from resolved symmetric keys
+ * (e.g. a client_secret).</p>
+ *
* <p>A decrypter should be created for each new decryption operation.</p>
*/
public class JWTDecrypter {
@@ -344,8 +347,8 @@ public class JWTDecrypter {
/**
* Decrypt the encrypted JWT by first decrypting the wrapped content encryption key using on of the shared
- * (symmetric) key encryption keys resolved by the {@link JWTDecryptionParameters#getKEKCredentialResolver()} resolver.
- *
+ * key wrapping keys derived from a symmetric key resolved by the
+ * {@link JWTDecryptionParameters#getKEKCredentialResolver()} resolver.
*
* <p>For each credential, algorithm compatibly is checked against that described by the JWE and any
* includes and excludes lists configured.</p>
@@ -369,9 +372,22 @@ public class JWTDecrypter {
try {
for (final Credential cred : params.getKEKCredentialResolver().resolve(criteria)) {
+ if (!(cred instanceof JWKCredential)) {
+ if (log.isTraceEnabled()) {
+ log.trace("JWT decryption requires a JWK credential, resolved credential '{}' was not",
+ CredentialConversionUtil.resolveKid(cred));
+ }
+ // Require a JWKCredential
+ continue;
+ }
try {
- validateKeyManagmentAlgorithm(encryptedObject, cred);
- final JWEDecrypter decrypter = new AESDecrypter(cred.getSecretKey());
+ final JWEAlgorithm alg = validateKeyManagmentAlgorithm(encryptedObject, cred);
+ final EncryptionMethod enc = validateContentEncryptionAlgorithm(encryptedObject, cred);
+ //Key wrapping requires a derived credential
+ final JWKCredential derivedKey =
+ JWKCredentialSupport.deriveSymmetricKeyForAlgAndEnc((JWKCredential)cred,
+ alg, enc);
+ final JWEDecrypter decrypter = new AESDecrypter(derivedKey.getSecretKey());
encryptedObject.decrypt(decrypter);
return;
} catch (final JOSEException | DecryptionException e) {
@@ -429,14 +445,15 @@ public class JWTDecrypter {
}
/**
- * Decrypt the encrypted JWT using direct encryption. A shared symmetric key resolved by the
- * {@link JWTDecryptionParameters#getContentEncryptionKeyCredentialResolver()} is used directly
- * as the content encryption key.
+ * Decrypt the encrypted JWT using direct encryption. One of the shared symmetric keys resolved by the
+ * {@link JWTDecryptionParameters#getContentEncryptionKeyCredentialResolver()} is used to derive
+ * a content encryption key to decrypt the JWT - if any are appropriate.
*
* <p>For each credential, algorithm compatibly is checked against that described by the JWE and any
* includes and excludes lists configured.</p>
*
- * <p>The first resolved credential that decrypts the JWT produces a result, and the process terminates.</p>
+ * <p>The first resolved (and derived) credential that decrypts the JWT produces a result,
+ * and the process terminates.</p>
*
* @param encryptedObject the encrypted JWT to decrypt - in place.
*
@@ -458,10 +475,22 @@ public class JWTDecrypter {
try {
for (final Credential cred : params.getContentEncryptionKeyCredentialResolver().resolve(criteria)) {
+ if (!(cred instanceof JWKCredential)) {
+ if (log.isTraceEnabled()) {
+ log.trace("JWT direct decryption requires a JWK credential, resolved credential "
+ + "'{}' was not", CredentialConversionUtil.resolveKid(cred));
+ }
+ // Require a JWKCredential
+ continue;
+ }
try {
validateKeyManagmentAlgorithm(encryptedObject, cred);
- validateContentEncryptionAlgorithm(encryptedObject, cred);
- final JWEDecrypter decrypter = new DirectDecrypter(cred.getSecretKey());
+ final EncryptionMethod enc = validateContentEncryptionAlgorithm(encryptedObject, cred);
+ // Symmetric key to use is derived from shared symmetric key e.g. client_secret
+ final JWKCredential derivedKey =
+ JWKCredentialSupport.deriveSymmetricKeyForAlgAndEnc((JWKCredential)cred,
+ JWEAlgorithm.DIR, enc);
+ final JWEDecrypter decrypter = new DirectDecrypter(derivedKey.getSecretKey());
encryptedObject.decrypt(decrypter);
return;
} catch (final JOSEException | DecryptionException e) {
@@ -476,16 +505,19 @@ public class JWTDecrypter {
}
/**
- * Validates the 'alg' algorithm in the header matches the algorithm specified for the credential,
- * validates against the include and exclude algorithm URI lists, and the credential contains the
- * correct key type.
+ * If the credential contains algorithm 'alg' information, validate the 'alg' algorithm in the JWT JOSE
+ * header matches the algorithm specified for the credential. Then, validates the algorithm in the JWT
+ * JOSE Header against the include and exclude algorithm URI lists. Finally, validates the credential
+ * contains the correct key type.
*
* @param encryptedObject the JWE
* @param cred the credential to validate against the 'alg' header
*
+ * @return the validated algorithm to use
+ *
* @throws DecryptionException if there is an algorithm mismatch.
*/
- private void validateKeyManagmentAlgorithm(
+ private JWEAlgorithm validateKeyManagmentAlgorithm(
@Nonnull final EncryptedJWT encryptedObject, @Nonnull final Credential cred) throws DecryptionException {
if (encryptedObject.getHeader() == null) {
@@ -495,9 +527,9 @@ public class JWTDecrypter {
final JWEAlgorithm headerAlg = encryptedObject.getHeader().getAlgorithm();
validateAlgorithmURI(headerAlg.getName());
- if (cred instanceof JWKCredential) {
+ if (cred instanceof JWKCredential && ((JWKCredential)cred).getAlgorithm() != null) {
final JWKCredential jwkCred = (JWKCredential)cred;
- if (jwkCred.getAlgorithm() == null || !headerAlg.equals(jwkCred.getAlgorithm())) {
+ if (!headerAlg.equals(jwkCred.getAlgorithm())) {
throw new DecryptionException("Credential algorithm '"+jwkCred.getAlgorithm()+"' "
+ "was not a match for the "
+ "algorithm '"+encryptedObject.getHeader().getAlgorithm()+"'");
@@ -520,19 +552,23 @@ public class JWTDecrypter {
}
//All fine
+ return headerAlg;
}
/**
* Validates the 'enc' algorithm in the header matches the encryption algorithm specified for the credential,
- * and validates against the include and exclude algorithm URI lists.
+ * and validates against the include and exclude algorithm URI lists. If valid, the encryption algorithm from
+ * the header is returned. If not valid, an {@link DecryptionException} is thrown.
*
* @param encryptedObject the JWE
* @param cred the credential to validate the 'enc' header
*
+ * @return the content encryption algorithm from the JOSE header.
+ *
* @throws DecryptionException if there is an algorithm mismatch.
*/
- private void validateContentEncryptionAlgorithm(
+ @Nonnull private EncryptionMethod validateContentEncryptionAlgorithm(
@Nonnull final EncryptedJWT encryptedObject, @Nonnull final Credential cred) throws DecryptionException {
if (encryptedObject.getHeader() == null) {
@@ -558,8 +594,9 @@ public class JWTDecrypter {
throw new DecryptionException("JOSE Header 'enc' algorithm "
+jcaKeyAlgorithm+" does not match credential algorithm "+cred.getSecretKey().getAlgorithm());
}
-
+
// Otherwise, all fine.
+ return enc;
}
/**
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list