Replace ManagedKey with CryptoKey
Closes gh-105
This commit is contained in:
parent
8100568613
commit
a9423c6b13
@ -24,7 +24,7 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.oauth2.jose.jws.NimbusJwsEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
@ -106,14 +106,14 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key manager.
|
||||
* Sets the source for cryptographic keys.
|
||||
*
|
||||
* @param keyManager the key manager
|
||||
* @param keySource the source for cryptographic keys
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> keyManager(KeyManager keyManager) {
|
||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
||||
this.getBuilder().setSharedObject(KeyManager.class, keyManager);
|
||||
public OAuth2AuthorizationServerConfigurer<B> keySource(CryptoKeySource keySource) {
|
||||
Assert.notNull(keySource, "keySource cannot be null");
|
||||
this.getBuilder().setSharedObject(CryptoKeySource.class, keySource);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
getAuthorizationService(builder));
|
||||
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
|
||||
|
||||
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeyManager(builder));
|
||||
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeySource(builder));
|
||||
|
||||
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
|
||||
new OAuth2AuthorizationCodeAuthenticationProvider(
|
||||
@ -172,7 +172,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
|
||||
@Override
|
||||
public void configure(B builder) {
|
||||
JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(getKeyManager(builder));
|
||||
JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(getKeySource(builder));
|
||||
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
|
||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||
@ -237,16 +237,16 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
return (!authorizationServiceMap.isEmpty() ? authorizationServiceMap.values().iterator().next() : null);
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManager(B builder) {
|
||||
KeyManager keyManager = builder.getSharedObject(KeyManager.class);
|
||||
if (keyManager == null) {
|
||||
keyManager = getKeyManagerBean(builder);
|
||||
builder.setSharedObject(KeyManager.class, keyManager);
|
||||
private static <B extends HttpSecurityBuilder<B>> CryptoKeySource getKeySource(B builder) {
|
||||
CryptoKeySource keySource = builder.getSharedObject(CryptoKeySource.class);
|
||||
if (keySource == null) {
|
||||
keySource = getKeySourceBean(builder);
|
||||
builder.setSharedObject(CryptoKeySource.class, keySource);
|
||||
}
|
||||
return keyManager;
|
||||
return keySource;
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManagerBean(B builder) {
|
||||
return builder.getSharedObject(ApplicationContext.class).getBean(KeyManager.class);
|
||||
private static <B extends HttpSecurityBuilder<B>> CryptoKeySource getKeySourceBean(B builder) {
|
||||
return builder.getSharedObject(ApplicationContext.class).getBean(CryptoKeySource.class);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.key;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A {@link CryptoKey} that holds a {@code java.security.PrivateKey}
|
||||
* and {@code java.security.PublicKey} used for asymmetric algorithm's.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
* @see CryptoKey
|
||||
* @see PrivateKey
|
||||
* @see PublicKey
|
||||
*/
|
||||
public final class AsymmetricKey extends CryptoKey<PrivateKey> {
|
||||
private final PublicKey publicKey;
|
||||
|
||||
private AsymmetricKey(PrivateKey privateKey, PublicKey publicKey, String id, Map<String, Object> metadata) {
|
||||
super(privateKey, id, metadata);
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code java.security.PublicKey}.
|
||||
*
|
||||
* @return the {@code java.security.PublicKey}
|
||||
*/
|
||||
public PublicKey getPublicKey() {
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link AsymmetricKey}.
|
||||
*/
|
||||
public static class Builder extends AbstractBuilder<AsymmetricKey, Builder> {
|
||||
private PublicKey publicKey;
|
||||
|
||||
Builder(PrivateKey privateKey, PublicKey publicKey) {
|
||||
super(privateKey);
|
||||
Assert.notNull(publicKey, "publicKey cannot be null");
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link AsymmetricKey}.
|
||||
*
|
||||
* @return the {@link AsymmetricKey}
|
||||
*/
|
||||
@Override
|
||||
public AsymmetricKey build() {
|
||||
if (!StringUtils.hasText(this.id)) {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
}
|
||||
return new AsymmetricKey((PrivateKey) this.key, this.publicKey, this.id, this.metadata);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.key;
|
||||
|
||||
import org.springframework.security.oauth2.server.authorization.Version;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.Serializable;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A holder of a {@code java.security.Key} used for cryptographic operations.
|
||||
*
|
||||
* @param <K> the type of {@code java.security.Key}
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
* @see CryptoKeySource
|
||||
*/
|
||||
public class CryptoKey<K extends Key> implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private final K key;
|
||||
private final String id;
|
||||
private final Map<String, Object> metadata;
|
||||
|
||||
/**
|
||||
* Constructs a {@code CryptoKey} using the provided parameters.
|
||||
*
|
||||
* @param key the {@code java.security.Key}
|
||||
* @param id the logical identifier for the {@code key}
|
||||
*/
|
||||
protected CryptoKey(K key, String id) {
|
||||
this(key, id, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code CryptoKey} using the provided parameters.
|
||||
*
|
||||
* @param key the {@code java.security.Key}
|
||||
* @param id the logical identifier for the {@code key}
|
||||
* @param metadata the metadata describing the {@code key}
|
||||
*/
|
||||
protected CryptoKey(K key, String id, Map<String, Object> metadata) {
|
||||
Assert.notNull(key, "key cannot be null");
|
||||
Assert.hasText(id, "id cannot be empty");
|
||||
Assert.notNull(metadata, "metadata cannot be null");
|
||||
this.key = key;
|
||||
this.id = id;
|
||||
this.metadata = Collections.unmodifiableMap(new LinkedHashMap<>(metadata));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a type of {@code java.security.Key},
|
||||
* e.g. {@code javax.crypto.SecretKey} or {@code java.security.PrivateKey}.
|
||||
*
|
||||
* @return the type of {@code java.security.Key}
|
||||
*/
|
||||
public final K getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logical identifier for this key.
|
||||
*
|
||||
* @return the logical identifier for this key
|
||||
*/
|
||||
public final String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata value associated to this key.
|
||||
*
|
||||
* @param name the name of the metadata
|
||||
* @param <T> the type of the metadata
|
||||
* @return the metadata value, or {@code null} if not available
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final <T> T getMetadata(String name) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
return (T) this.metadata.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata associated to this key.
|
||||
*
|
||||
* @return a {@code Map} of the metadata
|
||||
*/
|
||||
public final Map<String, Object> getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the algorithm for this key.
|
||||
*
|
||||
* @return the algorithm for this key
|
||||
*/
|
||||
public final String getAlgorithm() {
|
||||
return getKey().getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CryptoKey<?> that = (CryptoKey<?>) obj;
|
||||
return Objects.equals(this.id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SymmetricKey} via {@link SymmetricKey.Builder}.
|
||||
*
|
||||
* @param secretKey the {@code javax.crypto.SecretKey}
|
||||
* @return the {@link SymmetricKey.Builder}
|
||||
*/
|
||||
public static SymmetricKey.Builder symmetric(SecretKey secretKey) {
|
||||
return new SymmetricKey.Builder(secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AsymmetricKey} via {@link AsymmetricKey.Builder}.
|
||||
*
|
||||
* @param privateKey the {@code java.security.PrivateKey}
|
||||
* @param publicKey the {@code java.security.PublicKey}
|
||||
* @return the {@link AsymmetricKey.Builder}
|
||||
*/
|
||||
public static AsymmetricKey.Builder asymmetric(PrivateKey privateKey, PublicKey publicKey) {
|
||||
return new AsymmetricKey.Builder(privateKey, publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base builder for {@link CryptoKey}.
|
||||
*
|
||||
* @param <T> the type of {@link CryptoKey}
|
||||
* @param <B> the type of {@link AbstractBuilder}
|
||||
*/
|
||||
protected abstract static class AbstractBuilder<T extends CryptoKey<?>, B extends AbstractBuilder<T, B>> implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
protected Key key;
|
||||
protected String id;
|
||||
protected Map<String, Object> metadata = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Sub-class constructor.
|
||||
*
|
||||
* @param key the {@code java.security.Key}
|
||||
*/
|
||||
protected AbstractBuilder(Key key) {
|
||||
Assert.notNull(key, "key cannot be null");
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logical identifier for this key.
|
||||
*
|
||||
* @param id the logical identifier for this key
|
||||
* @return the type of {@link AbstractBuilder}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public B id(String id) {
|
||||
this.id = id;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds metadata for this key.
|
||||
*
|
||||
* @param name the name of the metadata
|
||||
* @param value the value of the metadata
|
||||
* @return the type of {@link AbstractBuilder}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public B metadata(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.metadata.put(name, value);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the metadata {@code Map}
|
||||
* allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param metadataConsumer a {@link Consumer} of the metadata {@code Map}
|
||||
* @return the type of {@link AbstractBuilder}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public B metadata(Consumer<Map<String, Object>> metadataConsumer) {
|
||||
metadataConsumer.accept(this.metadata);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link CryptoKey}.
|
||||
*
|
||||
* @return the {@link CryptoKey}
|
||||
*/
|
||||
protected abstract T build();
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.key;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A source for {@link CryptoKey}'s.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
* @see CryptoKey
|
||||
* @see SymmetricKey
|
||||
* @see AsymmetricKey
|
||||
*/
|
||||
public interface CryptoKeySource {
|
||||
|
||||
/**
|
||||
* Returns a {@code Set} of {@link CryptoKey}'s.
|
||||
*
|
||||
* @return a {@code Set} of {@link CryptoKey}'s
|
||||
*/
|
||||
Set<CryptoKey<?>> getKeys();
|
||||
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.keys;
|
||||
package org.springframework.security.crypto.key;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.key;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateRsaKey;
|
||||
import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateSecretKey;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link CryptoKeySource} that generates the {@link CryptoKey}'s when constructed.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
* @see CryptoKeySource
|
||||
*/
|
||||
public final class StaticKeyGeneratingCryptoKeySource implements CryptoKeySource {
|
||||
private final Map<String, CryptoKey<?>> keys;
|
||||
|
||||
public StaticKeyGeneratingCryptoKeySource() {
|
||||
this.keys = Collections.unmodifiableMap(generateKeys());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<CryptoKey<?>> getKeys() {
|
||||
return new HashSet<>(this.keys.values());
|
||||
}
|
||||
|
||||
private static Map<String, CryptoKey<?>> generateKeys() {
|
||||
KeyPair rsaKeyPair = generateRsaKey();
|
||||
AsymmetricKey asymmetricKey = CryptoKey.asymmetric(rsaKeyPair.getPrivate(), rsaKeyPair.getPublic()).build();
|
||||
|
||||
SecretKey hmacKey = generateSecretKey();
|
||||
SymmetricKey symmetricKey = CryptoKey.symmetric(hmacKey).build();
|
||||
|
||||
return Stream.of(asymmetricKey, symmetricKey)
|
||||
.collect(Collectors.toMap(CryptoKey::getId, v -> v));
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.key;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A {@link CryptoKey} that holds a {@code javax.crypto.SecretKey}
|
||||
* used for symmetric algorithm's.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
* @see CryptoKey
|
||||
* @see SecretKey
|
||||
*/
|
||||
public final class SymmetricKey extends CryptoKey<SecretKey> {
|
||||
|
||||
private SymmetricKey(SecretKey key, String id, Map<String, Object> metadata) {
|
||||
super(key, id, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link SymmetricKey}.
|
||||
*/
|
||||
public static class Builder extends AbstractBuilder<SymmetricKey, Builder> {
|
||||
|
||||
Builder(SecretKey secretKey) {
|
||||
super(secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link SymmetricKey}.
|
||||
*
|
||||
* @return the {@link SymmetricKey}
|
||||
*/
|
||||
@Override
|
||||
public SymmetricKey build() {
|
||||
if (!StringUtils.hasText(this.id)) {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
}
|
||||
return new SymmetricKey((SecretKey) this.key, this.id, this.metadata);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.keys;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the management of {@link ManagedKey}(s),
|
||||
* e.g. {@code javax.crypto.SecretKey}, {@code java.security.PrivateKey}, {@code java.security.PublicKey}, etc.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see ManagedKey
|
||||
*/
|
||||
public interface KeyManager {
|
||||
|
||||
/**
|
||||
* Returns the {@link ManagedKey} identified by the provided {@code keyId},
|
||||
* or {@code null} if not found.
|
||||
*
|
||||
* @param keyId the key ID
|
||||
* @return the {@link ManagedKey}, or {@code null} if not found
|
||||
*/
|
||||
@Nullable
|
||||
ManagedKey findByKeyId(String keyId);
|
||||
|
||||
/**
|
||||
* Returns a {@code Set} of {@link ManagedKey}(s) having the provided key {@code algorithm},
|
||||
* or an empty {@code Set} if not found.
|
||||
*
|
||||
* @param algorithm the key algorithm
|
||||
* @return a {@code Set} of {@link ManagedKey}(s), or an empty {@code Set} if not found
|
||||
*/
|
||||
Set<ManagedKey> findByAlgorithm(String algorithm);
|
||||
|
||||
/**
|
||||
* Returns a {@code Set} of the {@link ManagedKey}(s).
|
||||
*
|
||||
* @return a {@code Set} of the {@link ManagedKey}(s)
|
||||
*/
|
||||
Set<ManagedKey> getKeys();
|
||||
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.keys;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.server.authorization.Version;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.Serializable;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@code java.security.Key} that is managed by a {@link KeyManager}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see KeyManager
|
||||
*/
|
||||
public final class ManagedKey implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private Key key;
|
||||
private PublicKey publicKey;
|
||||
private String keyId;
|
||||
private Instant activatedOn;
|
||||
private Instant deactivatedOn;
|
||||
|
||||
private ManagedKey() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this is a symmetric key, {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if this is a symmetric key, {@code false} otherwise
|
||||
*/
|
||||
public boolean isSymmetric() {
|
||||
return SecretKey.class.isAssignableFrom(this.key.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this is a asymmetric key, {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if this is a asymmetric key, {@code false} otherwise
|
||||
*/
|
||||
public boolean isAsymmetric() {
|
||||
return PrivateKey.class.isAssignableFrom(this.key.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a type of {@code java.security.Key},
|
||||
* e.g. {@code javax.crypto.SecretKey} or {@code java.security.PrivateKey}.
|
||||
*
|
||||
* @param <T> the type of {@code java.security.Key}
|
||||
* @return the type of {@code java.security.Key}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Key> T getKey() {
|
||||
return (T) this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise.
|
||||
*
|
||||
* @return the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public PublicKey getPublicKey() {
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key ID.
|
||||
*
|
||||
* @return the key ID
|
||||
*/
|
||||
public String getKeyId() {
|
||||
return this.keyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time when this key was activated.
|
||||
*
|
||||
* @return the time when this key was activated
|
||||
*/
|
||||
public Instant getActivatedOn() {
|
||||
return this.activatedOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time when this key was deactivated, {@code null} if still active.
|
||||
*
|
||||
* @return the time when this key was deactivated, {@code null} if still active
|
||||
*/
|
||||
@Nullable
|
||||
public Instant getDeactivatedOn() {
|
||||
return this.deactivatedOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this key is active, {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if this key is active, {@code false} otherwise
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return getDeactivatedOn() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key algorithm.
|
||||
*
|
||||
* @return the key algorithm
|
||||
*/
|
||||
public String getAlgorithm() {
|
||||
return this.key.getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ManagedKey that = (ManagedKey) obj;
|
||||
return Objects.equals(this.keyId, that.keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the provided {@code javax.crypto.SecretKey}.
|
||||
*
|
||||
* @param secretKey the {@code javax.crypto.SecretKey}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withSymmetricKey(SecretKey secretKey) {
|
||||
return new Builder(secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the provided
|
||||
* {@code java.security.PublicKey} and {@code java.security.PrivateKey}.
|
||||
*
|
||||
* @param publicKey the {@code java.security.PublicKey}
|
||||
* @param privateKey the {@code java.security.PrivateKey}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withAsymmetricKey(PublicKey publicKey, PrivateKey privateKey) {
|
||||
return new Builder(publicKey, privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link ManagedKey}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private Key key;
|
||||
private PublicKey publicKey;
|
||||
private String keyId;
|
||||
private Instant activatedOn;
|
||||
private Instant deactivatedOn;
|
||||
|
||||
private Builder(SecretKey secretKey) {
|
||||
Assert.notNull(secretKey, "secretKey cannot be null");
|
||||
this.key = secretKey;
|
||||
}
|
||||
|
||||
private Builder(PublicKey publicKey, PrivateKey privateKey) {
|
||||
Assert.notNull(publicKey, "publicKey cannot be null");
|
||||
Assert.notNull(privateKey, "privateKey cannot be null");
|
||||
this.key = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key ID.
|
||||
*
|
||||
* @param keyId the key ID
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder keyId(String keyId) {
|
||||
this.keyId = keyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time when this key was activated.
|
||||
*
|
||||
* @param activatedOn the time when this key was activated
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder activatedOn(Instant activatedOn) {
|
||||
this.activatedOn = activatedOn;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time when this key was deactivated.
|
||||
*
|
||||
* @param deactivatedOn the time when this key was deactivated
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder deactivatedOn(Instant deactivatedOn) {
|
||||
this.deactivatedOn = deactivatedOn;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link ManagedKey}.
|
||||
*
|
||||
* @return a {@link ManagedKey}
|
||||
*/
|
||||
public ManagedKey build() {
|
||||
Assert.hasText(this.keyId, "keyId cannot be empty");
|
||||
Assert.notNull(this.activatedOn, "activatedOn cannot be null");
|
||||
|
||||
ManagedKey managedKey = new ManagedKey();
|
||||
managedKey.key = this.key;
|
||||
managedKey.publicKey = this.publicKey;
|
||||
managedKey.keyId = this.keyId;
|
||||
managedKey.activatedOn = this.activatedOn;
|
||||
managedKey.deactivatedOn = this.deactivatedOn;
|
||||
return managedKey;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.keys;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.KeyPair;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link KeyManager} that generates the {@link ManagedKey}(s) when constructed.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see KeyManager
|
||||
*/
|
||||
public final class StaticKeyGeneratingKeyManager implements KeyManager {
|
||||
private final Map<String, ManagedKey> keys;
|
||||
|
||||
public StaticKeyGeneratingKeyManager() {
|
||||
this.keys = Collections.unmodifiableMap(new HashMap<>(generateKeys()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ManagedKey findByKeyId(String keyId) {
|
||||
Assert.hasText(keyId, "keyId cannot be empty");
|
||||
return this.keys.get(keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ManagedKey> findByAlgorithm(String algorithm) {
|
||||
Assert.hasText(algorithm, "algorithm cannot be empty");
|
||||
return this.keys.values().stream()
|
||||
.filter(managedKey -> managedKey.getAlgorithm().equals(algorithm))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ManagedKey> getKeys() {
|
||||
return new HashSet<>(this.keys.values());
|
||||
}
|
||||
|
||||
private static Map<String, ManagedKey> generateKeys() {
|
||||
KeyPair rsaKeyPair = generateRsaKey();
|
||||
ManagedKey rsaManagedKey = ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate())
|
||||
.keyId(UUID.randomUUID().toString())
|
||||
.activatedOn(Instant.now())
|
||||
.build();
|
||||
|
||||
SecretKey hmacKey = generateSecretKey();
|
||||
ManagedKey secretManagedKey = ManagedKey.withSymmetricKey(hmacKey)
|
||||
.keyId(UUID.randomUUID().toString())
|
||||
.activatedOn(Instant.now())
|
||||
.build();
|
||||
|
||||
return Stream.of(rsaManagedKey, secretManagedKey)
|
||||
.collect(Collectors.toMap(ManagedKey::getKeyId, v -> v));
|
||||
}
|
||||
}
|
@ -29,8 +29,9 @@ import com.nimbusds.jose.util.Base64URL;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.ManagedKey;
|
||||
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||
import org.springframework.security.crypto.key.CryptoKey;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
@ -58,7 +59,7 @@ import java.util.stream.Collectors;
|
||||
* An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT)
|
||||
* using the JSON Web Signature (JWS) Compact Serialization format.
|
||||
* The private/secret key used for signing the JWS is obtained
|
||||
* from the {@link KeyManager} supplied via the constructor.
|
||||
* from the {@link CryptoKeySource} supplied via the constructor.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
|
||||
@ -66,7 +67,7 @@ import java.util.stream.Collectors;
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see JwtEncoder
|
||||
* @see KeyManager
|
||||
* @see CryptoKeySource
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a>
|
||||
@ -92,16 +93,16 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
||||
};
|
||||
private static final Converter<JoseHeader, JWSHeader> jwsHeaderConverter = new JwsHeaderConverter();
|
||||
private static final Converter<JwtClaimsSet, JWTClaimsSet> jwtClaimsSetConverter = new JwtClaimsSetConverter();
|
||||
private final KeyManager keyManager;
|
||||
private final CryptoKeySource keySource;
|
||||
|
||||
/**
|
||||
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
|
||||
*
|
||||
* @param keyManager the key manager
|
||||
* @param keySource the source for cryptographic keys
|
||||
*/
|
||||
public NimbusJwsEncoder(KeyManager keyManager) {
|
||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
||||
this.keyManager = keyManager;
|
||||
public NimbusJwsEncoder(CryptoKeySource keySource) {
|
||||
Assert.notNull(keySource, "keySource cannot be null");
|
||||
this.keySource = keySource;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -109,24 +110,24 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
||||
Assert.notNull(headers, "headers cannot be null");
|
||||
Assert.notNull(claims, "claims cannot be null");
|
||||
|
||||
ManagedKey managedKey = selectKey(headers);
|
||||
if (managedKey == null) {
|
||||
CryptoKey<?> cryptoKey = selectKey(headers);
|
||||
if (cryptoKey == null) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Unsupported key for algorithm '" + headers.getJwsAlgorithm().getName() + "'"));
|
||||
}
|
||||
|
||||
JWSSigner jwsSigner;
|
||||
if (managedKey.isAsymmetric()) {
|
||||
if (!managedKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
|
||||
if (AsymmetricKey.class.isAssignableFrom(cryptoKey.getClass())) {
|
||||
if (!cryptoKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
|
||||
throw new JwtEncodingException(String.format(
|
||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||
"Unsupported key type '" + managedKey.getAlgorithm() + "'"));
|
||||
"Unsupported key type '" + cryptoKey.getAlgorithm() + "'"));
|
||||
}
|
||||
PrivateKey privateKey = managedKey.getKey();
|
||||
PrivateKey privateKey = (PrivateKey) cryptoKey.getKey();
|
||||
jwsSigner = new RSASSASigner(privateKey);
|
||||
} else {
|
||||
SecretKey secretKey = managedKey.getKey();
|
||||
SecretKey secretKey = (SecretKey) cryptoKey.getKey();
|
||||
try {
|
||||
jwsSigner = new MACSigner(secretKey);
|
||||
} catch (KeyLengthException ex) {
|
||||
@ -137,7 +138,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
||||
|
||||
headers = JoseHeader.from(headers)
|
||||
.type(JOSEObjectType.JWT.getType())
|
||||
.keyId(managedKey.getKeyId())
|
||||
.keyId(cryptoKey.getId())
|
||||
.build();
|
||||
JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
|
||||
|
||||
@ -159,28 +160,19 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
||||
headers.getHeaders(), claims.getClaims());
|
||||
}
|
||||
|
||||
private ManagedKey selectKey(JoseHeader headers) {
|
||||
private CryptoKey<?> selectKey(JoseHeader headers) {
|
||||
JwsAlgorithm jwsAlgorithm = headers.getJwsAlgorithm();
|
||||
String keyAlgorithm = jcaKeyAlgorithmMappings.get(jwsAlgorithm);
|
||||
if (!StringUtils.hasText(keyAlgorithm)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<ManagedKey> matchingKeys = this.keyManager.findByAlgorithm(keyAlgorithm);
|
||||
if (CollectionUtils.isEmpty(matchingKeys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return matchingKeys.stream()
|
||||
.filter(ManagedKey::isActive)
|
||||
.max(this::mostRecentActivated)
|
||||
return this.keySource.getKeys().stream()
|
||||
.filter(key -> key.getAlgorithm().equals(keyAlgorithm))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private int mostRecentActivated(ManagedKey managedKey1, ManagedKey managedKey2) {
|
||||
return managedKey1.getActivatedOn().isAfter(managedKey2.getActivatedOn()) ? 1 : -1;
|
||||
}
|
||||
|
||||
private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
|
||||
|
||||
@Override
|
||||
|
@ -24,8 +24,8 @@ import com.nimbusds.jose.jwk.KeyUse;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.ManagedKey;
|
||||
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
@ -47,7 +47,7 @@ import java.util.stream.Collectors;
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.0.1
|
||||
* @see KeyManager
|
||||
* @see CryptoKeySource
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">Section 5 JWK Set Format</a>
|
||||
*/
|
||||
@ -57,28 +57,28 @@ public class JwkSetEndpointFilter extends OncePerRequestFilter {
|
||||
*/
|
||||
public static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks";
|
||||
|
||||
private final KeyManager keyManager;
|
||||
private final CryptoKeySource keySource;
|
||||
private final RequestMatcher requestMatcher;
|
||||
|
||||
/**
|
||||
* Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
|
||||
*
|
||||
* @param keyManager the key manager
|
||||
* @param keySource the source for cryptographic keys
|
||||
*/
|
||||
public JwkSetEndpointFilter(KeyManager keyManager) {
|
||||
this(keyManager, DEFAULT_JWK_SET_ENDPOINT_URI);
|
||||
public JwkSetEndpointFilter(CryptoKeySource keySource) {
|
||||
this(keySource, DEFAULT_JWK_SET_ENDPOINT_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
|
||||
*
|
||||
* @param keyManager the key manager
|
||||
* @param keySource the source for cryptographic keys
|
||||
* @param jwkSetEndpointUri the endpoint {@code URI} for JWK Set requests
|
||||
*/
|
||||
public JwkSetEndpointFilter(KeyManager keyManager, String jwkSetEndpointUri) {
|
||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
||||
public JwkSetEndpointFilter(CryptoKeySource keySource, String jwkSetEndpointUri) {
|
||||
Assert.notNull(keySource, "keySource cannot be null");
|
||||
Assert.hasText(jwkSetEndpointUri, "jwkSetEndpointUri cannot be empty");
|
||||
this.keyManager = keyManager;
|
||||
this.keySource = keySource;
|
||||
this.requestMatcher = new AntPathRequestMatcher(jwkSetEndpointUri, HttpMethod.GET.name());
|
||||
}
|
||||
|
||||
@ -101,30 +101,31 @@ public class JwkSetEndpointFilter extends OncePerRequestFilter {
|
||||
|
||||
private JWKSet buildJwkSet() {
|
||||
return new JWKSet(
|
||||
this.keyManager.getKeys().stream()
|
||||
.filter(managedKey -> managedKey.isActive() && managedKey.isAsymmetric())
|
||||
this.keySource.getKeys().stream()
|
||||
.filter(key -> AsymmetricKey.class.isAssignableFrom(key.getClass()))
|
||||
.map(AsymmetricKey.class::cast)
|
||||
.map(this::convert)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
private JWK convert(ManagedKey managedKey) {
|
||||
private JWK convert(AsymmetricKey asymmetricKey) {
|
||||
JWK jwk = null;
|
||||
if (managedKey.getPublicKey() instanceof RSAPublicKey) {
|
||||
RSAPublicKey publicKey = (RSAPublicKey) managedKey.getPublicKey();
|
||||
if (asymmetricKey.getPublicKey() instanceof RSAPublicKey) {
|
||||
RSAPublicKey publicKey = (RSAPublicKey) asymmetricKey.getPublicKey();
|
||||
jwk = new RSAKey.Builder(publicKey)
|
||||
.keyUse(KeyUse.SIGNATURE)
|
||||
.algorithm(JWSAlgorithm.RS256)
|
||||
.keyID(managedKey.getKeyId())
|
||||
.keyID(asymmetricKey.getId())
|
||||
.build();
|
||||
} else if (managedKey.getPublicKey() instanceof ECPublicKey) {
|
||||
ECPublicKey publicKey = (ECPublicKey) managedKey.getPublicKey();
|
||||
} else if (asymmetricKey.getPublicKey() instanceof ECPublicKey) {
|
||||
ECPublicKey publicKey = (ECPublicKey) asymmetricKey.getPublicKey();
|
||||
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
|
||||
jwk = new ECKey.Builder(curve, publicKey)
|
||||
.keyUse(KeyUse.SIGNATURE)
|
||||
.algorithm(JWSAlgorithm.ES256)
|
||||
.keyID(managedKey.getKeyId())
|
||||
.keyID(asymmetricKey.getId())
|
||||
.build();
|
||||
}
|
||||
return jwk;
|
||||
|
@ -27,8 +27,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
@ -85,7 +85,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
|
||||
private static RegisteredClientRepository registeredClientRepository;
|
||||
private static OAuth2AuthorizationService authorizationService;
|
||||
private static KeyManager keyManager;
|
||||
private static CryptoKeySource keySource;
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
@ -97,7 +97,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
public static void init() {
|
||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
keyManager = new StaticKeyGeneratingKeyManager();
|
||||
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||
}
|
||||
|
||||
@Before
|
||||
@ -266,8 +266,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
KeyManager keyManager() {
|
||||
return keyManager;
|
||||
CryptoKeySource keySource() {
|
||||
return keySource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
@ -61,7 +61,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
public class OAuth2ClientCredentialsGrantTests {
|
||||
private static RegisteredClientRepository registeredClientRepository;
|
||||
private static OAuth2AuthorizationService authorizationService;
|
||||
private static KeyManager keyManager;
|
||||
private static CryptoKeySource keySource;
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
@ -73,7 +73,7 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
public static void init() {
|
||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
keyManager = new StaticKeyGeneratingKeyManager();
|
||||
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||
}
|
||||
|
||||
@Before
|
||||
@ -159,8 +159,8 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
KeyManager keyManager() {
|
||||
return keyManager;
|
||||
CryptoKeySource keySource() {
|
||||
return keySource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
@ -67,6 +67,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
public class OAuth2RefreshTokenGrantTests {
|
||||
private static RegisteredClientRepository registeredClientRepository;
|
||||
private static OAuth2AuthorizationService authorizationService;
|
||||
private static CryptoKeySource keySource;
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
@ -78,6 +79,7 @@ public class OAuth2RefreshTokenGrantTests {
|
||||
public static void init() {
|
||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||
}
|
||||
|
||||
@Before
|
||||
@ -151,6 +153,8 @@ public class OAuth2RefreshTokenGrantTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
KeyManager keyManager() { return new StaticKeyGeneratingKeyManager(); }
|
||||
CryptoKeySource keySource() {
|
||||
return keySource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
@ -66,7 +66,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
public class OAuth2TokenRevocationTests {
|
||||
private static RegisteredClientRepository registeredClientRepository;
|
||||
private static OAuth2AuthorizationService authorizationService;
|
||||
private static KeyManager keyManager;
|
||||
private static CryptoKeySource keySource;
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
@ -78,7 +78,7 @@ public class OAuth2TokenRevocationTests {
|
||||
public static void init() {
|
||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
keyManager = new StaticKeyGeneratingKeyManager();
|
||||
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||
}
|
||||
|
||||
@Before
|
||||
@ -181,8 +181,8 @@ public class OAuth2TokenRevocationTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
KeyManager keyManager() {
|
||||
return keyManager;
|
||||
CryptoKeySource keySource() {
|
||||
return keySource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.key;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.KeyPair;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateRsaKey;
|
||||
import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateSecretKey;
|
||||
|
||||
/**
|
||||
* Tests for {@link CryptoKey}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class CryptoKeyTests {
|
||||
private static SecretKey secretKey;
|
||||
private static KeyPair rsaKeyPair;
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
secretKey = generateSecretKey();
|
||||
rsaKeyPair = generateRsaKey();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void symmetricWhenSecretKeyNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> CryptoKey.symmetric(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("key cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metadataWhenNameNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> CryptoKey.symmetric(secretKey).metadata(null, "value"))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("name cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metadataWhenValueNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> CryptoKey.symmetric(secretKey).metadata("name", null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void symmetricWhenAllAttributesProvidedThenAllAttributesAreSet() {
|
||||
Map<String, Object> keyMetadata = new HashMap<>();
|
||||
keyMetadata.put("name1", "value1");
|
||||
keyMetadata.put("name2", "value2");
|
||||
|
||||
SymmetricKey symmetricKey = CryptoKey.symmetric(secretKey)
|
||||
.id("id")
|
||||
.metadata(metadata -> metadata.putAll(keyMetadata))
|
||||
.build();
|
||||
|
||||
assertThat(symmetricKey.getKey()).isEqualTo(secretKey);
|
||||
assertThat(symmetricKey.getId()).isEqualTo("id");
|
||||
assertThat(symmetricKey.getMetadata()).isEqualTo(keyMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asymmetricWhenPrivateKeyNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> CryptoKey.asymmetric(null, rsaKeyPair.getPublic()))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("key cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asymmetricWhenPublicKeyNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> CryptoKey.asymmetric(rsaKeyPair.getPrivate(), null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("publicKey cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asymmetricWhenAllAttributesProvidedThenAllAttributesAreSet() {
|
||||
Map<String, Object> keyMetadata = new HashMap<>();
|
||||
keyMetadata.put("name1", "value1");
|
||||
keyMetadata.put("name2", "value2");
|
||||
|
||||
AsymmetricKey asymmetricKey = CryptoKey.asymmetric(rsaKeyPair.getPrivate(), rsaKeyPair.getPublic())
|
||||
.id("id")
|
||||
.metadata(metadata -> metadata.putAll(keyMetadata))
|
||||
.build();
|
||||
|
||||
assertThat(asymmetricKey.getKey()).isEqualTo(rsaKeyPair.getPrivate());
|
||||
assertThat(asymmetricKey.getPublicKey()).isEqualTo(rsaKeyPair.getPublic());
|
||||
assertThat(asymmetricKey.getId()).isEqualTo("id");
|
||||
assertThat(asymmetricKey.getMetadata()).isEqualTo(keyMetadata);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.key;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateEcKey;
|
||||
import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateRsaKey;
|
||||
import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateSecretKey;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class TestCryptoKeys {
|
||||
|
||||
public static SymmetricKey.Builder secretKey() {
|
||||
return CryptoKey.symmetric(generateSecretKey());
|
||||
}
|
||||
|
||||
public static AsymmetricKey.Builder rsaKey() {
|
||||
KeyPair rsaKeyPair = generateRsaKey();
|
||||
return CryptoKey.asymmetric(rsaKeyPair.getPrivate(), rsaKeyPair.getPublic());
|
||||
}
|
||||
|
||||
public static AsymmetricKey.Builder ecKey() {
|
||||
KeyPair ecKeyPair = generateEcKey();
|
||||
return CryptoKey.asymmetric(ecKeyPair.getPrivate(), ecKeyPair.getPublic());
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.keys;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
|
||||
|
||||
/**
|
||||
* Tests for {@link ManagedKey}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class ManagedKeyTests {
|
||||
private static SecretKey secretKey;
|
||||
private static KeyPair rsaKeyPair;
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
secretKey = generateSecretKey();
|
||||
rsaKeyPair = generateRsaKey();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSymmetricKeyWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> ManagedKey.withSymmetricKey(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("secretKey cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenKeyIdNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> ManagedKey.withSymmetricKey(secretKey).build())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("keyId cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenActivatedOnNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> ManagedKey.withSymmetricKey(secretKey).keyId("keyId").build())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("activatedOn cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenSymmetricKeyAllAttributesProvidedThenAllAttributesAreSet() {
|
||||
ManagedKey expectedManagedKey = TestManagedKeys.secretManagedKey().build();
|
||||
|
||||
ManagedKey managedKey = ManagedKey.withSymmetricKey(expectedManagedKey.getKey())
|
||||
.keyId(expectedManagedKey.getKeyId())
|
||||
.activatedOn(expectedManagedKey.getActivatedOn())
|
||||
.build();
|
||||
|
||||
assertThat(managedKey.isSymmetric()).isTrue();
|
||||
assertThat(managedKey.<Key>getKey()).isInstanceOf(SecretKey.class);
|
||||
assertThat(managedKey.<SecretKey>getKey()).isEqualTo(expectedManagedKey.getKey());
|
||||
assertThat(managedKey.getPublicKey()).isNull();
|
||||
assertThat(managedKey.getKeyId()).isEqualTo(expectedManagedKey.getKeyId());
|
||||
assertThat(managedKey.getActivatedOn()).isEqualTo(expectedManagedKey.getActivatedOn());
|
||||
assertThat(managedKey.getDeactivatedOn()).isEqualTo(expectedManagedKey.getDeactivatedOn());
|
||||
assertThat(managedKey.isActive()).isTrue();
|
||||
assertThat(managedKey.getAlgorithm()).isEqualTo(expectedManagedKey.getAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withAsymmetricKeyWhenPublicKeyNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> ManagedKey.withAsymmetricKey(null, rsaKeyPair.getPrivate()))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("publicKey cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withAsymmetricKeyWhenPrivateKeyNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("privateKey cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAsymmetricKeyAllAttributesProvidedThenAllAttributesAreSet() {
|
||||
ManagedKey expectedManagedKey = TestManagedKeys.rsaManagedKey().build();
|
||||
|
||||
ManagedKey managedKey = ManagedKey.withAsymmetricKey(expectedManagedKey.getPublicKey(), expectedManagedKey.getKey())
|
||||
.keyId(expectedManagedKey.getKeyId())
|
||||
.activatedOn(expectedManagedKey.getActivatedOn())
|
||||
.build();
|
||||
|
||||
assertThat(managedKey.isAsymmetric()).isTrue();
|
||||
assertThat(managedKey.<Key>getKey()).isInstanceOf(PrivateKey.class);
|
||||
assertThat(managedKey.<PrivateKey>getKey()).isEqualTo(expectedManagedKey.getKey());
|
||||
assertThat(managedKey.getPublicKey()).isNotNull();
|
||||
assertThat(managedKey.getKeyId()).isEqualTo(expectedManagedKey.getKeyId());
|
||||
assertThat(managedKey.getActivatedOn()).isEqualTo(expectedManagedKey.getActivatedOn());
|
||||
assertThat(managedKey.getDeactivatedOn()).isEqualTo(expectedManagedKey.getDeactivatedOn());
|
||||
assertThat(managedKey.isActive()).isTrue();
|
||||
assertThat(managedKey.getAlgorithm()).isEqualTo(expectedManagedKey.getAlgorithm());
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.crypto.keys;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateEcKey;
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
|
||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class TestManagedKeys {
|
||||
|
||||
public static ManagedKey.Builder secretManagedKey() {
|
||||
return ManagedKey.withSymmetricKey(generateSecretKey())
|
||||
.keyId(UUID.randomUUID().toString())
|
||||
.activatedOn(Instant.now());
|
||||
}
|
||||
|
||||
public static ManagedKey.Builder rsaManagedKey() {
|
||||
KeyPair rsaKeyPair = generateRsaKey();
|
||||
return ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate())
|
||||
.keyId(UUID.randomUUID().toString())
|
||||
.activatedOn(Instant.now());
|
||||
}
|
||||
|
||||
public static ManagedKey.Builder ecManagedKey() {
|
||||
KeyPair ecKeyPair = generateEcKey();
|
||||
return ManagedKey.withAsymmetricKey(ecKeyPair.getPublic(), ecKeyPair.getPrivate())
|
||||
.keyId(UUID.randomUUID().toString())
|
||||
.activatedOn(Instant.now());
|
||||
}
|
||||
}
|
@ -17,9 +17,9 @@ package org.springframework.security.oauth2.jose.jws;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.ManagedKey;
|
||||
import org.springframework.security.crypto.keys.TestManagedKeys;
|
||||
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.crypto.key.TestCryptoKeys;
|
||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jose.TestJoseHeaders;
|
||||
@ -30,14 +30,12 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
|
||||
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ -47,20 +45,20 @@ import static org.mockito.Mockito.when;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class NimbusJwsEncoderTests {
|
||||
private KeyManager keyManager;
|
||||
private CryptoKeySource keySource;
|
||||
private NimbusJwsEncoder jwtEncoder;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.keyManager = mock(KeyManager.class);
|
||||
this.jwtEncoder = new NimbusJwsEncoder(this.keyManager);
|
||||
this.keySource = mock(CryptoKeySource.class);
|
||||
this.jwtEncoder = new NimbusJwsEncoder(this.keySource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenKeyManagerNullThenThrowIllegalArgumentException() {
|
||||
public void constructorWhenKeySourceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new NimbusJwsEncoder(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("keyManager cannot be null");
|
||||
.hasMessage("keySource cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -103,8 +101,8 @@ public class NimbusJwsEncoderTests {
|
||||
|
||||
@Test
|
||||
public void encodeWhenUnsupportedKeyTypeThenThrowJwtEncodingException() {
|
||||
ManagedKey managedKey = TestManagedKeys.ecManagedKey().build();
|
||||
when(this.keyManager.findByAlgorithm(any())).thenReturn(Collections.singleton(managedKey));
|
||||
AsymmetricKey ecKey = TestCryptoKeys.ecKey().build();
|
||||
when(this.keySource.getKeys()).thenReturn(Collections.singleton(ecKey));
|
||||
|
||||
JoseHeader joseHeader = TestJoseHeaders.joseHeader(SignatureAlgorithm.ES256).build();
|
||||
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
|
||||
@ -116,8 +114,8 @@ public class NimbusJwsEncoderTests {
|
||||
|
||||
@Test
|
||||
public void encodeWhenSuccessThenDecodes() {
|
||||
ManagedKey managedKey = TestManagedKeys.rsaManagedKey().build();
|
||||
when(this.keyManager.findByAlgorithm(any())).thenReturn(Collections.singleton(managedKey));
|
||||
AsymmetricKey rsaKey = TestCryptoKeys.rsaKey().build();
|
||||
when(this.keySource.getKeys()).thenReturn(Collections.singleton(rsaKey));
|
||||
|
||||
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
|
||||
.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
|
||||
@ -126,25 +124,17 @@ public class NimbusJwsEncoderTests {
|
||||
|
||||
Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
||||
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) managedKey.getPublicKey()).build();
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) rsaKey.getPublicKey()).build();
|
||||
jwtDecoder.decode(jws.getTokenValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeWhenMultipleActiveKeysThenUseMostRecent() {
|
||||
ManagedKey managedKeyActivated2DaysAgo = TestManagedKeys.rsaManagedKey()
|
||||
.activatedOn(Instant.now().minus(2, ChronoUnit.DAYS))
|
||||
.build();
|
||||
ManagedKey managedKeyActivated1DayAgo = TestManagedKeys.rsaManagedKey()
|
||||
.activatedOn(Instant.now().minus(1, ChronoUnit.DAYS))
|
||||
.build();
|
||||
ManagedKey managedKeyActivatedToday = TestManagedKeys.rsaManagedKey()
|
||||
.activatedOn(Instant.now())
|
||||
.build();
|
||||
|
||||
when(this.keyManager.findByAlgorithm(any())).thenReturn(
|
||||
Stream.of(managedKeyActivated2DaysAgo, managedKeyActivated1DayAgo, managedKeyActivatedToday)
|
||||
.collect(Collectors.toSet()));
|
||||
public void encodeWhenMultipleActiveKeysThenUseFirst() {
|
||||
AsymmetricKey rsaKey1 = TestCryptoKeys.rsaKey().build();
|
||||
AsymmetricKey rsaKey2 = TestCryptoKeys.rsaKey().build();
|
||||
when(this.keySource.getKeys()).thenReturn(
|
||||
Stream.of(rsaKey1, rsaKey2)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||
|
||||
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
|
||||
.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
|
||||
@ -153,7 +143,7 @@ public class NimbusJwsEncoderTests {
|
||||
|
||||
Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
||||
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) managedKeyActivatedToday.getPublicKey()).build();
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) rsaKey1.getPublicKey()).build();
|
||||
jwtDecoder.decode(jws.getTokenValue());
|
||||
}
|
||||
}
|
||||
|
@ -25,14 +25,14 @@ import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.ManagedKey;
|
||||
import org.springframework.security.crypto.keys.TestManagedKeys;
|
||||
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.crypto.key.SymmetricKey;
|
||||
import org.springframework.security.crypto.key.TestCryptoKeys;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
@ -52,25 +52,25 @@ import static org.mockito.Mockito.when;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class JwkSetEndpointFilterTests {
|
||||
private KeyManager keyManager;
|
||||
private CryptoKeySource keySource;
|
||||
private JwkSetEndpointFilter filter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.keyManager = mock(KeyManager.class);
|
||||
this.filter = new JwkSetEndpointFilter(this.keyManager);
|
||||
this.keySource = mock(CryptoKeySource.class);
|
||||
this.filter = new JwkSetEndpointFilter(this.keySource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenKeyManagerNullThenThrowIllegalArgumentException() {
|
||||
public void constructorWhenKeySourceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new JwkSetEndpointFilter(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("keyManager cannot be null");
|
||||
.hasMessage("keySource cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenJwkSetEndpointUriNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new JwkSetEndpointFilter(this.keyManager, null))
|
||||
assertThatThrownBy(() -> new JwkSetEndpointFilter(this.keySource, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("jwkSetEndpointUri cannot be empty");
|
||||
}
|
||||
@ -103,10 +103,10 @@ public class JwkSetEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAsymmetricKeysThenJwkSetResponse() throws Exception {
|
||||
ManagedKey rsaManagedKey = TestManagedKeys.rsaManagedKey().build();
|
||||
ManagedKey ecManagedKey = TestManagedKeys.ecManagedKey().build();
|
||||
when(this.keyManager.getKeys()).thenReturn(
|
||||
Stream.of(rsaManagedKey, ecManagedKey).collect(Collectors.toSet()));
|
||||
AsymmetricKey rsaKey = TestCryptoKeys.rsaKey().build();
|
||||
AsymmetricKey ecKey = TestCryptoKeys.ecKey().build();
|
||||
when(this.keySource.getKeys()).thenReturn(
|
||||
Stream.of(rsaKey, ecKey).collect(Collectors.toSet()));
|
||||
|
||||
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
@ -123,17 +123,17 @@ public class JwkSetEndpointFilterTests {
|
||||
JWKSet jwkSet = JWKSet.parse(response.getContentAsString());
|
||||
assertThat(jwkSet.getKeys()).hasSize(2);
|
||||
|
||||
RSAKey rsaJwk = (RSAKey) jwkSet.getKeyByKeyId(rsaManagedKey.getKeyId());
|
||||
RSAKey rsaJwk = (RSAKey) jwkSet.getKeyByKeyId(rsaKey.getId());
|
||||
assertThat(rsaJwk).isNotNull();
|
||||
assertThat(rsaJwk.toRSAPublicKey()).isEqualTo(rsaManagedKey.getPublicKey());
|
||||
assertThat(rsaJwk.toRSAPublicKey()).isEqualTo(rsaKey.getPublicKey());
|
||||
assertThat(rsaJwk.toRSAPrivateKey()).isNull();
|
||||
assertThat(rsaJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
|
||||
assertThat(rsaJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.RS256);
|
||||
|
||||
ECKey ecJwk = (ECKey) jwkSet.getKeyByKeyId(ecManagedKey.getKeyId());
|
||||
ECKey ecJwk = (ECKey) jwkSet.getKeyByKeyId(ecKey.getId());
|
||||
assertThat(ecJwk).isNotNull();
|
||||
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecManagedKey.getPublicKey());
|
||||
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecManagedKey.getPublicKey());
|
||||
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecKey.getPublicKey());
|
||||
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecKey.getPublicKey());
|
||||
assertThat(ecJwk.toECPrivateKey()).isNull();
|
||||
assertThat(ecJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
|
||||
assertThat(ecJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.ES256);
|
||||
@ -141,32 +141,9 @@ public class JwkSetEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenSymmetricKeysThenJwkSetResponseEmpty() throws Exception {
|
||||
ManagedKey secretManagedKey = TestManagedKeys.secretManagedKey().build();
|
||||
when(this.keyManager.getKeys()).thenReturn(
|
||||
new HashSet<>(Collections.singleton(secretManagedKey)));
|
||||
|
||||
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
JWKSet jwkSet = JWKSet.parse(response.getContentAsString());
|
||||
assertThat(jwkSet.getKeys()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNoActiveKeysThenJwkSetResponseEmpty() throws Exception {
|
||||
ManagedKey rsaManagedKey = TestManagedKeys.rsaManagedKey().deactivatedOn(Instant.now()).build();
|
||||
ManagedKey ecManagedKey = TestManagedKeys.ecManagedKey().deactivatedOn(Instant.now()).build();
|
||||
when(this.keyManager.getKeys()).thenReturn(
|
||||
Stream.of(rsaManagedKey, ecManagedKey).collect(Collectors.toSet()));
|
||||
SymmetricKey secretKey = TestCryptoKeys.secretKey().build();
|
||||
when(this.keySource.getKeys()).thenReturn(
|
||||
new HashSet<>(Collections.singleton(secretKey)));
|
||||
|
||||
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
|
@ -22,8 +22,8 @@ import org.springframework.security.config.annotation.web.configuration.OAuth2Au
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||
@ -60,8 +60,8 @@ public class AuthorizationServerConfig {
|
||||
// @formatter:on
|
||||
|
||||
@Bean
|
||||
public KeyManager keyManager() {
|
||||
return new StaticKeyGeneratingKeyManager();
|
||||
public CryptoKeySource keySource() {
|
||||
return new StaticKeyGeneratingCryptoKeySource();
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
|
Loading…
Reference in New Issue
Block a user