Browse Source

Replace ManagedKey with CryptoKey

Closes gh-105
Joe Grandja 4 years ago
parent
commit
a9423c6b13
23 changed files with 757 additions and 731 deletions
  1. 16 16
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 78 0
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/AsymmetricKey.java
  3. 231 0
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKey.java
  4. 38 0
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKeySource.java
  5. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/KeyGeneratorUtils.java
  6. 62 0
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/StaticKeyGeneratingCryptoKeySource.java
  7. 61 0
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/SymmetricKey.java
  8. 0 58
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyManager.java
  9. 0 246
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/ManagedKey.java
  10. 0 89
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/StaticKeyGeneratingKeyManager.java
  11. 22 30
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoder.java
  12. 21 20
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilter.java
  13. 6 6
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java
  14. 6 6
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java
  15. 7 3
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java
  16. 6 6
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java
  17. 113 0
      oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/CryptoKeyTests.java
  18. 42 0
      oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/TestCryptoKeys.java
  19. 0 120
      oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/ManagedKeyTests.java
  20. 0 50
      oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/TestManagedKeys.java
  21. 21 31
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoderTests.java
  22. 22 45
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilterTests.java
  23. 4 4
      samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

+ 16 - 16
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@@ -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);
 	}
 }

+ 78 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/AsymmetricKey.java

@@ -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);
+		}
+	}
+}

+ 231 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKey.java

@@ -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();
+
+	}
+}

+ 38 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKeySource.java

@@ -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();
+
+}

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyGeneratorUtils.java → oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/KeyGeneratorUtils.java

@@ -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;

+ 62 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/StaticKeyGeneratingCryptoKeySource.java

@@ -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));
+	}
+}

+ 61 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/SymmetricKey.java

@@ -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);
+		}
+	}
+}

+ 0 - 58
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyManager.java

@@ -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();
-
-}

+ 0 - 246
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/ManagedKey.java

@@ -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;
-		}
-	}
-}

+ 0 - 89
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/StaticKeyGeneratingKeyManager.java

@@ -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));
-	}
-}

+ 22 - 30
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoder.java

@@ -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

+ 21 - 20
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilter.java

@@ -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;

+ 6 - 6
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java

@@ -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;
 		}
 	}
 }

+ 6 - 6
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java

@@ -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;
 		}
 	}
 }

+ 7 - 3
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java

@@ -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;
+		}
 	}
 }

+ 6 - 6
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java

@@ -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;
 		}
 	}
 }

+ 113 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/CryptoKeyTests.java

@@ -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);
+	}
+}

+ 42 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/TestCryptoKeys.java

@@ -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());
+	}
+}

+ 0 - 120
oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/ManagedKeyTests.java

@@ -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());
-	}
-}

+ 0 - 50
oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/TestManagedKeys.java

@@ -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());
-	}
-}

+ 21 - 31
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoderTests.java

@@ -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());
 	}
 }

+ 22 - 45
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilterTests.java

@@ -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);

+ 4 - 4
samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

@@ -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