com.nimbusds.openid.connect.provider.jwkset.JWKSetSpec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of c2id-server-jwkset Show documentation
Show all versions of c2id-server-jwkset Show documentation
JSON Web Key (JWK) set specification, utilities and generator for
Connect2id server deployments.
package com.nimbusds.openid.connect.provider.jwkset;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton;
import com.nimbusds.jose.crypto.impl.RSASSAProvider;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator;
import com.nimbusds.jose.jwk.gen.OctetSequenceKeyGenerator;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.jwt.util.DateUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.crypto.SecretKey;
import java.util.*;
/**
* Connect2id server OpenID Provider / OAuth 2.0 authorisation server JWK set
* specification.
*/
public final class JWKSetSpec {
/**
* The permitted RSA signing and encryption key bit sizes. The weak
* 1024-bit key requires a special {@code jose.allowWeakKeys=true}
* Connect2id server configuration to be accepted.
*/
public static final int[] RSA_KEY_BIT_SIZES = new int[]{1024, 2048, 3072, 4096};
/**
* Rotated signing RSA keys.
*/
public static class RotatedRSASigning {
/**
* The default RSA JWK size.
*/
public static final int KEY_BIT_SIZE = 2048;
/**
* Returns a JWK matcher.
*
* @param alg The expected JWS algorithm. Should not be
* {@code null}.
*
* @return The JWK matcher.
*/
public static JWKMatcher createKeyMatcher(final @Nullable JWSAlgorithm alg) {
return new JWKMatcher.Builder()
.keyType(KeyType.RSA)
.privateOnly(true)
.algorithms(alg, null) // The JWS alg may be omitted
.keyUses(KeyUse.SIGNATURE)
.keySizes(RSA_KEY_BIT_SIZES)
.hasKeyID(true)
.build();
}
/**
* Generates a JWK with the specified key ID.
*
* @param kid The key ID, {@code null} if not specified.
*
* @return The JWK.
*/
public static RSAKey generateKey(final String kid)
throws JOSEException {
return generateKey(kid, KEY_BIT_SIZE);
}
/**
* Generates a JWK with the specified key ID and key size.
*
* @param kid The key ID, {@code null} if not specified.
* @param keyBitSize The key bit size.
*
* @return The JWK.
*/
public static RSAKey generateKey(final String kid, final int keyBitSize)
throws JOSEException {
return new RSAKeyGenerator(keyBitSize)
.keyUse(KeyUse.SIGNATURE)
.keyID(kid)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* Loads the matching JWKs from the specified JWK set.
*
* @param jwkSet The JWK set.
* @param jwsAlg The expected JWS algorithm. Should not be
* {@code null}.
*
* @return The matching JWKs, empty set if none.
*
* @throws JOSEException If the JWS algorithm is not supported.
*/
public static List loadKeys(final JWKSet jwkSet, final JWSAlgorithm jwsAlg)
throws JOSEException {
if (! RSASSAProvider.SUPPORTED_ALGORITHMS.contains(jwsAlg)) {
throw new JOSEException("Invalid / unsupported RSA signature algorithm: " + jwsAlg);
}
List jwkMatches = new JWKSelector(createKeyMatcher(jwsAlg)).select(jwkSet);
List rsaJWKMatches = new LinkedList<>();
jwkMatches.forEach(jwk -> rsaJWKMatches.add(jwk.toRSAKey()));
return rsaJWKMatches;
}
private RotatedRSASigning() {}
}
/**
* Rotated signing EC keys.
*/
public static class RotatedECSigning {
/**
* The supported EC curves.
*/
public static final Set SUPPORTED_CURVES = Collections.unmodifiableSet(
new LinkedHashSet<>(Arrays.asList(Curve.P_256, Curve.P_384, Curve.P_521, Curve.SECP256K1))
);
/**
* Returns a signing EC JWK matcher.
*
* @param alg The expected EC DSA algorithm.
*
* @return The JWK matcher.
*/
public static JWKMatcher createKeyMatcher(final JWSAlgorithm alg) {
Set curves = Curve.forJWSAlgorithm(alg);
if (curves == null) {
throw new IllegalArgumentException("Invalid / unsupported EC DSA algorithm: " + alg);
}
return new JWKMatcher.Builder()
.keyType(KeyType.EC)
.curves(curves)
.privateOnly(true)
.algorithms(alg, null) // The JWS alg may be unspecified
.keyUses(KeyUse.SIGNATURE)
.hasKeyID(true)
.build();
}
/**
* Generates a JWK with the specified curve and key ID.
*
* @param crv The curve. Must not be {@code null}.
* @param kid The key ID, {@code null} if not specified.
*
* @return The JWK.
*/
public static ECKey generateKey(final Curve crv, final String kid)
throws JOSEException {
return new ECKeyGenerator(crv)
.keyUse(KeyUse.SIGNATURE)
.keyID(kid)
.issueTime(DateUtils.nowWithSecondsPrecision())
.provider(BouncyCastleProviderSingleton.getInstance())
.generate();
}
/**
* Loads the matching JWKs from the specified JWK set.
*
* @param jwkSet The JWK set.
* @param jwsAlg The expected JWS algorithm. Should not be
* {@code null}.
*
* @return The matching JWKs, empty set if none.
*
* @throws JOSEException If the JWS algorithm is not supported.
*/
public static List loadKeys(final JWKSet jwkSet, final JWSAlgorithm jwsAlg)
throws JOSEException {
JWKSelector jwkSelector;
try {
jwkSelector = new JWKSelector(createKeyMatcher(jwsAlg));
} catch (IllegalArgumentException e) {
throw new JOSEException(e.getMessage());
}
List jwkMatches = jwkSelector.select(jwkSet);
List ecMatches = new LinkedList<>();
jwkMatches.forEach(jwk -> ecMatches.add(jwk.toECKey()));
return ecMatches;
}
private RotatedECSigning() {}
}
/**
* Rotated signing EdDSA keys.
*/
public static class RotatedEdDSASigning {
/**
* The JWK matcher.
*/
public static final JWKMatcher KEY_MATCHER = new JWKMatcher.Builder()
.keyType(KeyType.OKP)
.curve(Curve.Ed25519)
.privateOnly(true)
.algorithms(JWSAlgorithm.EdDSA, null) // The JWS alg may be unspecified
.keyUses(KeyUse.SIGNATURE)
.hasKeyID(true)
.build();
/**
* Generates a JWK with the specified key ID.
*
* @param kid The key ID, {@code null} if not specified.
*
* @return The JWK.
*/
public static OctetKeyPair generateKey(final String kid)
throws JOSEException {
return new OctetKeyPairGenerator(Curve.Ed25519)
.keyUse(KeyUse.SIGNATURE)
.keyID(kid)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* Loads the matching JWKs from the specified JWK set.
*
* @param jwkSet The JWK set.
*
* @return The matching JWKs, empty set if none.
*/
public static List loadKeys(final JWKSet jwkSet) {
List jwkMatches = new JWKSelector(KEY_MATCHER).select(jwkSet);
List okpMatches = new LinkedList<>();
jwkMatches.forEach(jwk -> okpMatches.add(jwk.toOctetKeyPair()));
return okpMatches;
}
private RotatedEdDSASigning() {}
}
/**
* Rotated encryption RSA keys.
*/
public static class RotatedRSAEncryption {
/**
* The default RSA JWK size.
*/
public static final int KEY_BIT_SIZE = 2048;
/**
* Generates a JWK with the specified key ID.
*
* @param kid The key ID, {@code null} if not specified.
*
* @return The JWK.
*/
public static RSAKey generateKey(final String kid)
throws JOSEException {
return new RSAKeyGenerator(KEY_BIT_SIZE)
.keyUse(KeyUse.ENCRYPTION)
.keyID(kid)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* The JWK matcher.
*/
public static final JWKMatcher KEY_MATCHER = new JWKMatcher.Builder()
.keyType(KeyType.RSA)
.privateOnly(true)
.keyUse(KeyUse.ENCRYPTION)
.hasKeyID(true)
.keySizes(RSA_KEY_BIT_SIZES)
.build();
private RotatedRSAEncryption() {}
}
/**
* Rotated ECDH encryption keys.
*/
public static class RotatedECDHEncryption {
/**
* The supported EC curves.
*/
public static final Set SUPPORTED_CURVES = Collections.unmodifiableSet(
new LinkedHashSet<>(Arrays.asList(Curve.P_256, Curve.P_384, Curve.P_521))
);
/**
* Generates a JWK the specified curve and key ID.
*
* @param crv The curve. Must not be {@code null}.
* @param kid The key ID, {@code null} if not specified.
*
* @return The JWK.
*/
public static ECKey generateKey(final Curve crv, final String kid)
throws JOSEException {
return new ECKeyGenerator(crv)
.keyUse(KeyUse.ENCRYPTION)
.keyID(kid)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* The JWK matcher.
*/
public static final JWKMatcher KEY_MATCHER = new JWKMatcher.Builder()
.keyType(KeyType.EC)
.privateOnly(true)
.keyUse(KeyUse.ENCRYPTION)
.hasKeyID(true)
.build();
private RotatedECDHEncryption() {}
}
/**
* Rotated AES and ChaCha20 direct encryption keys for JWT-encoded
* access tokens.
*/
public static class RotatedAccessTokenDirectEncryption {
/**
* The JWK sizes.
*/
public static final int[] KEY_BIT_SIZES = new int[]{128, 192, 256, 384, 512};
/**
* The JWK matcher.
*
* @deprecated Use {@link #createKeyMatcher} instead.
*/
@Deprecated
public static final JWKMatcher KEY_MATCHER = new JWKMatcher.Builder()
.keyType(KeyType.OCT)
.keySizes(KEY_BIT_SIZES)
.privateOnly(true)
.algorithms(JWEAlgorithm.DIR, null) // The JWE alg must be 'dir' or unspecified
.keyUses(KeyUse.ENCRYPTION)
.hasKeyID(true)
.build();
/**
* Returns a JWK matcher for the specified JWE encryption
* method.
*
* @param enc The JWE encryption method.
*
* @return The JWK matcher.
*/
public static JWKMatcher createKeyMatcher(final EncryptionMethod enc) {
return new JWKMatcher.Builder()
.keyType(KeyType.OCT)
.keySize(enc.cekBitLength())
.privateOnly(true)
.algorithms(JWEAlgorithm.DIR, null) // The JWE alg must be 'dir' or unspecified
.keyUses(KeyUse.ENCRYPTION)
.hasKeyID(true)
.build();
}
/**
* Generates a 128 bit JWK with the specified key ID.
*
* @param kid The key ID, {@code null} if not specified.
*
* @return The JWK.
*/
public static OctetSequenceKey generateKey(final String kid)
throws JOSEException {
return generateKey(kid, KEY_BIT_SIZES[0]);
}
/**
* Generates a JWK with the specified key ID and bit size.
*
* @param kid The key ID, {@code null} if not specified.
* @param bitSize The key bit size.
*
* @return The JWK.
*/
public static OctetSequenceKey generateKey(final String kid, final int bitSize)
throws JOSEException {
return new OctetSequenceKeyGenerator(bitSize)
.keyUse(KeyUse.ENCRYPTION)
.keyID(kid)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* Loads the matching JWKs from the specified JWK set.
*
* @param jwkSet The JWK set.
*
* @return The matching JWKs, empty set if none.
*
* @deprecated Use {@link #loadKeys(JWKSet, EncryptionMethod)}
* instead.
*/
@Deprecated
public static List loadKeys(final JWKSet jwkSet) {
List jwkMatches = new JWKSelector(KEY_MATCHER).select(jwkSet);
List aesMatches = new LinkedList<>();
jwkMatches.forEach(jwk -> aesMatches.add(jwk.toOctetSequenceKey()));
return aesMatches;
}
/**
* Loads the matching JWKs from the specified JWK set.
*
* @param jwkSet The JWK set.
* @param enc The JWE encryption method.
*
* @return The matching JWKs, empty set if none.
*/
public static List loadKeys(final JWKSet jwkSet, final EncryptionMethod enc) {
List jwkMatches = new JWKSelector(createKeyMatcher(enc)).select(jwkSet);
List aesMatches = new LinkedList<>();
jwkMatches.forEach(jwk -> aesMatches.add(jwk.toOctetSequenceKey()));
return aesMatches;
}
private RotatedAccessTokenDirectEncryption() {}
}
/**
* HMAC key for the subject sessions, authorisation codes, etc.
*/
public static class HMAC {
/**
* The JWK ID.
*/
public static final String KEY_ID = "hmac";
/**
* The JWK size.
*/
public static final int KEY_BIT_SIZE = 256;
/**
* The JWK matcher.
*/
public static final JWKMatcher KEY_MATCHER = new JWKMatcher.Builder()
.keyType(KeyType.OCT)
.keySize(KEY_BIT_SIZE)
.privateOnly(true)
.keyUse(KeyUse.SIGNATURE)
.keyID(KEY_ID)
.build();
/**
* Generates a JWK.
*
* @return The JWK.
*/
public static OctetSequenceKey generateKey()
throws JOSEException {
return new OctetSequenceKeyGenerator(KEY_BIT_SIZE)
.keyUse(KeyUse.SIGNATURE)
.keyID(KEY_ID)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* Loads the JWK from the specified JWK set.
*
* @param jwkSet The JWK set.
*
* @return The HMAC key as Java SecretKey.
*
* @throws JOSEException If no matching JWK could be found.
*/
public static SecretKey loadKey(final JWKSet jwkSet)
throws JOSEException {
ensureNotEmpty(jwkSet);
List matches = new JWKSelector(KEY_MATCHER).select(jwkSet);
if (matches.isEmpty()) {
throw new JOSEException(
"Couldn't find eligible secret JSON Web Key (JWK) " +
"for applying HMAC to objects: " +
"Required key ID \"" + KEY_ID + "\", " +
"required key use \"sig\", " +
"required key size " + KEY_BIT_SIZE + " bits"
);
}
return ((OctetSequenceKey)matches.get(0)).toSecretKey("HmacSha256");
}
private HMAC() {}
}
/**
* AES/SIV key for pairwise subject encryption.
*/
public static class SubjectEncryption {
/**
* The JWK ID.
*/
public static final String KEY_ID = "subject-encrypt";
/**
* The JWK sizes.
*/
public static final int[] KEY_BIT_SIZES = new int[]{256, 384, 512};
/**
* The JWK matcher.
*/
public static final JWKMatcher KEY_MATCHER = new JWKMatcher.Builder()
.keyType(KeyType.OCT)
.keySizes(KEY_BIT_SIZES)
.privateOnly(true)
.keyUse(KeyUse.ENCRYPTION)
.keyID(KEY_ID)
.build();
/**
* Generates a 256 bit JWK.
*
* @return The JWK.
*/
public static OctetSequenceKey generateKey()
throws JOSEException {
return new OctetSequenceKeyGenerator(KEY_BIT_SIZES[0])
.keyUse(KeyUse.ENCRYPTION)
.keyID(KEY_ID)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* Loads the JWK from the specified JWK set.
*
* @param jwkSet The JWK set.
*
* @return The subject encryption key as Java secret key.
*
* @throws JOSEException If no matching JWK could be found.
*/
public static SecretKey loadKey(final JWKSet jwkSet)
throws JOSEException {
ensureNotEmpty(jwkSet);
List keyMatches = new JWKSelector(KEY_MATCHER).select(jwkSet);
if (keyMatches.isEmpty()) {
throw new JOSEException(
"Couldn't find eligible secret JSON Web Key (JWK) " +
"for pairwise subject encryption: " +
"Required key ID \"" + KEY_ID + "\", " +
"required key use \"enc\", " +
"required key sizes " + Arrays.toString(KEY_BIT_SIZES) + " bits"
);
}
if (keyMatches.size() > 1) {
throw new JOSEException("Too many pairwise subject encryption keys, must be one");
}
return ((OctetSequenceKey)keyMatches.get(0)).toSecretKey("AES");
}
private SubjectEncryption() {}
}
/**
* AES/SIV key for refresh token payload encryption.
*/
public static class RefreshTokenEncryption {
/**
* The JWK ID.
*/
public static final String KEY_ID = "refresh-token-encrypt";
/**
* The JWK size.
*/
public static final int KEY_BIT_SIZE = 256;
/**
* The JWK matcher.
*/
public static final JWKMatcher KEY_MATCHER = new JWKMatcher.Builder()
.keyType(KeyType.OCT)
.keySize(KEY_BIT_SIZE)
.privateOnly(true)
.keyUse(KeyUse.ENCRYPTION)
.keyID(KEY_ID)
.build();
/**
* Generates a JWK.
*
* @return The JWK.
*/
public static OctetSequenceKey generateKey()
throws JOSEException {
return new OctetSequenceKeyGenerator(KEY_BIT_SIZE)
.keyUse(KeyUse.ENCRYPTION)
.keyID(KEY_ID)
.issueTime(DateUtils.nowWithSecondsPrecision())
.generate();
}
/**
* Loads the JWK from the specified JWK set.
*
* @param jwkSet The JWK set.
*
* @return The refresh token encryption key as Java secret key.
*
* @throws JOSEException If no matching JWK could be found.
*/
public static SecretKey loadKey(final JWKSet jwkSet)
throws JOSEException {
ensureNotEmpty(jwkSet);
List keyMatches = new JWKSelector(KEY_MATCHER).select(jwkSet);
if (keyMatches.isEmpty()) {
throw new JOSEException(
"Couldn't find eligible secret JSON Web Key (JWK) " +
"for refresh token encryption: " +
"Required key ID \"" + KEY_ID + "\", " +
"required key use \"enc\", " +
"required key size " + KEY_BIT_SIZE + " bits"
);
}
if (keyMatches.size() > 1) {
throw new JOSEException("Too many refresh token encryption keys, must be one");
}
return ((OctetSequenceKey)keyMatches.get(0)).toSecretKey("AES");
}
private RefreshTokenEncryption() {}
}
private static void ensureNotEmpty(final JWKSet jwkSet)
throws JOSEException {
if (jwkSet == null || jwkSet.getKeys().isEmpty()) {
throw new JOSEException("Missing or empty JSON Web Key (JWK) set");
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy