org.springframework.security.oauth2.provider.token.store.jwk.JwkDefinitionSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-security-oauth2 Show documentation
Show all versions of spring-security-oauth2 Show documentation
Module for providing OAuth2 support to Spring Security
/*
* Copyright 2012-2019 the original author or authors.
*
* 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
*
* https://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 org.springframework.security.oauth2.provider.token.store.jwk;
import org.springframework.security.jwt.codec.Codecs;
import org.springframework.security.jwt.crypto.sign.EllipticCurveVerifier;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* A source for JSON Web Key(s) (JWK) that is solely responsible for fetching (and caching)
* the JWK Set (a set of JWKs) from the URL supplied to the constructor.
*
* @see JwkSetConverter
* @see JwkDefinition
* @see SignatureVerifier
* @see JWK Set Format
*
* @author Joe Grandja
* @author Michael Duergner
*/
class JwkDefinitionSource {
private final List jwkSetUrls;
private final Map jwkDefinitions = new ConcurrentHashMap();
private static final JwkSetConverter jwkSetConverter = new JwkSetConverter();
/**
* Creates a new instance using the provided URL as the location for the JWK Set.
*
* @param jwkSetUrl the JWK Set URL
*/
JwkDefinitionSource(String jwkSetUrl) {
this(Arrays.asList(jwkSetUrl));
}
/**
* Creates a new instance using the provided URLs as the location for the JWK Sets.
*
* @param jwkSetUrls the JWK Set URLs
*/
JwkDefinitionSource(List jwkSetUrls) {
this.jwkSetUrls = new ArrayList();
for(String jwkSetUrl : jwkSetUrls) {
try {
this.jwkSetUrls.add(new URL(jwkSetUrl));
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Invalid JWK Set URL: " + ex.getMessage(), ex);
}
}
}
/**
* Returns the JWK definition matching the provided keyId ("kid").
* If the JWK definition is not available in the internal cache then {@link #loadJwkDefinitions(URL)}
* will be called (to re-load the cache) and then followed-up with a second attempt to locate the JWK definition.
*
* @param keyId the Key ID ("kid")
* @return the matching {@link JwkDefinition} or null if not found
*/
JwkDefinitionHolder getDefinitionLoadIfNecessary(String keyId) {
JwkDefinitionHolder result = this.getDefinition(keyId);
if (result != null) {
return result;
}
synchronized (this.jwkDefinitions) {
result = this.getDefinition(keyId);
if (result != null) {
return result;
}
Map newJwkDefinitions = new LinkedHashMap();
for (URL jwkSetUrl : jwkSetUrls) {
newJwkDefinitions.putAll(loadJwkDefinitions(jwkSetUrl));
}
this.jwkDefinitions.clear();
this.jwkDefinitions.putAll(newJwkDefinitions);
return this.getDefinition(keyId);
}
}
/**
* Returns the JWK definition matching the provided keyId ("kid").
*
* @param keyId the Key ID ("kid")
* @return the matching {@link JwkDefinition} or null if not found
*/
private JwkDefinitionHolder getDefinition(String keyId) {
return this.jwkDefinitions.get(keyId);
}
/**
* Fetches the JWK Set from the provided URL
and
* returns a Map
keyed by the JWK keyId ("kid")
* and mapped to an association of the {@link JwkDefinition} and {@link SignatureVerifier}.
* Uses a {@link JwkSetConverter} to convert the JWK Set URL source to a set of {@link JwkDefinition}(s)
* followed by the instantiation of a {@link SignatureVerifier} which is associated to it's {@link JwkDefinition}.
*
* @param jwkSetUrl the JWK Set URL
* @return a Map
keyed by the JWK keyId and mapped to an association of {@link JwkDefinition} and {@link SignatureVerifier}
* @see JwkSetConverter
*/
static Map loadJwkDefinitions(URL jwkSetUrl) {
InputStream jwkSetSource;
try {
jwkSetSource = jwkSetUrl.openStream();
} catch (IOException ex) {
throw new JwkException("An I/O error occurred while reading from the JWK Set source: " + ex.getMessage(), ex);
}
Set jwkDefinitionSet = jwkSetConverter.convert(jwkSetSource);
Map jwkDefinitions = new LinkedHashMap();
for (JwkDefinition jwkDefinition : jwkDefinitionSet) {
if (JwkDefinition.KeyType.RSA.equals(jwkDefinition.getKeyType())) {
jwkDefinitions.put(jwkDefinition.getKeyId(),
new JwkDefinitionHolder(jwkDefinition, createRsaVerifier((RsaJwkDefinition) jwkDefinition)));
} else if (JwkDefinition.KeyType.EC.equals(jwkDefinition.getKeyType())) {
jwkDefinitions.put(jwkDefinition.getKeyId(),
new JwkDefinitionHolder(jwkDefinition, createEcVerifier((EllipticCurveJwkDefinition) jwkDefinition)));
}
}
return jwkDefinitions;
}
private static RsaVerifier createRsaVerifier(RsaJwkDefinition rsaDefinition) {
RsaVerifier result;
try {
BigInteger modulus = new BigInteger(1, Codecs.b64UrlDecode(rsaDefinition.getModulus()));
BigInteger exponent = new BigInteger(1, Codecs.b64UrlDecode(rsaDefinition.getExponent()));
RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new RSAPublicKeySpec(modulus, exponent));
if (rsaDefinition.getAlgorithm() != null) {
result = new RsaVerifier(rsaPublicKey, rsaDefinition.getAlgorithm().standardName());
} else {
result = new RsaVerifier(rsaPublicKey);
}
} catch (Exception ex) {
throw new JwkException("An error occurred while creating a RSA Public Key Verifier for " +
rsaDefinition.getKeyId() + " : " + ex.getMessage(), ex);
}
return result;
}
private static EllipticCurveVerifier createEcVerifier(EllipticCurveJwkDefinition ecDefinition) {
EllipticCurveVerifier result;
try {
BigInteger x = new BigInteger(1, Codecs.b64UrlDecode(ecDefinition.getX()));
BigInteger y = new BigInteger(1, Codecs.b64UrlDecode(ecDefinition.getY()));
String algorithm = null;
if (EllipticCurveJwkDefinition.NamedCurve.P256.value().equals(ecDefinition.getCurve())) {
algorithm = JwkDefinition.CryptoAlgorithm.ES256.standardName();
} else if (EllipticCurveJwkDefinition.NamedCurve.P384.value().equals(ecDefinition.getCurve())) {
algorithm = JwkDefinition.CryptoAlgorithm.ES384.standardName();
} else if (EllipticCurveJwkDefinition.NamedCurve.P521.value().equals(ecDefinition.getCurve())) {
algorithm = JwkDefinition.CryptoAlgorithm.ES512.standardName();
}
result = new EllipticCurveVerifier(x, y, ecDefinition.getCurve(), algorithm);
} catch (Exception ex) {
throw new JwkException("An error occurred while creating an EC Public Key Verifier for " +
ecDefinition.getKeyId() + " : " + ex.getMessage(), ex);
}
return result;
}
static class JwkDefinitionHolder {
private final JwkDefinition jwkDefinition;
private final SignatureVerifier signatureVerifier;
private JwkDefinitionHolder(JwkDefinition jwkDefinition, SignatureVerifier signatureVerifier) {
this.jwkDefinition = jwkDefinition;
this.signatureVerifier = signatureVerifier;
}
JwkDefinition getJwkDefinition() {
return jwkDefinition;
}
SignatureVerifier getSignatureVerifier() {
return signatureVerifier;
}
}
}