From a9423c6b13d47ca2aae083352838e02064dbbb2b Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Sun, 25 Oct 2020 19:58:29 -0400 Subject: [PATCH] Replace ManagedKey with CryptoKey Closes gh-105 --- .../OAuth2AuthorizationServerConfigurer.java | 32 +-- .../security/crypto/key/AsymmetricKey.java | 78 ++++++ .../security/crypto/key/CryptoKey.java | 231 ++++++++++++++++ .../security/crypto/key/CryptoKeySource.java | 38 +++ .../{keys => key}/KeyGeneratorUtils.java | 2 +- .../StaticKeyGeneratingCryptoKeySource.java | 62 +++++ .../security/crypto/key/SymmetricKey.java | 61 +++++ .../security/crypto/keys/KeyManager.java | 58 ----- .../security/crypto/keys/ManagedKey.java | 246 ------------------ .../keys/StaticKeyGeneratingKeyManager.java | 89 ------- .../oauth2/jose/jws/NimbusJwsEncoder.java | 52 ++-- .../web/JwkSetEndpointFilter.java | 41 +-- .../OAuth2AuthorizationCodeGrantTests.java | 12 +- .../OAuth2ClientCredentialsGrantTests.java | 12 +- .../OAuth2RefreshTokenGrantTests.java | 10 +- .../OAuth2TokenRevocationTests.java | 12 +- .../security/crypto/key/CryptoKeyTests.java | 113 ++++++++ .../security/crypto/key/TestCryptoKeys.java | 42 +++ .../security/crypto/keys/ManagedKeyTests.java | 120 --------- .../security/crypto/keys/TestManagedKeys.java | 50 ---- .../jose/jws/NimbusJwsEncoderTests.java | 52 ++-- .../web/JwkSetEndpointFilterTests.java | 67 ++--- .../config/AuthorizationServerConfig.java | 8 +- 23 files changed, 757 insertions(+), 731 deletions(-) create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/AsymmetricKey.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKey.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKeySource.java rename oauth2-authorization-server/src/main/java/org/springframework/security/crypto/{keys => key}/KeyGeneratorUtils.java (98%) create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/StaticKeyGeneratingCryptoKeySource.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/SymmetricKey.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyManager.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/ManagedKey.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/StaticKeyGeneratingKeyManager.java create mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/CryptoKeyTests.java create mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/TestCryptoKeys.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/ManagedKeyTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/TestManagedKeys.java diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java index ab94ea7..6f338b3 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java +++ b/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 keyManager(KeyManager keyManager) { - Assert.notNull(keyManager, "keyManager cannot be null"); - this.getBuilder().setSharedObject(KeyManager.class, keyManager); + public OAuth2AuthorizationServerConfigurer 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> KeyManager getKeyManager(B builder) { - KeyManager keyManager = builder.getSharedObject(KeyManager.class); - if (keyManager == null) { - keyManager = getKeyManagerBean(builder); - builder.setSharedObject(KeyManager.class, keyManager); + private static > 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 > KeyManager getKeyManagerBean(B builder) { - return builder.getSharedObject(ApplicationContext.class).getBean(KeyManager.class); + private static > CryptoKeySource getKeySourceBean(B builder) { + return builder.getSharedObject(ApplicationContext.class).getBean(CryptoKeySource.class); } } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/AsymmetricKey.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/AsymmetricKey.java new file mode 100644 index 0000000..36b222e --- /dev/null +++ b/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 { + private final PublicKey publicKey; + + private AsymmetricKey(PrivateKey privateKey, PublicKey publicKey, String id, Map 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 { + 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); + } + } +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKey.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKey.java new file mode 100644 index 0000000..f028ca3 --- /dev/null +++ b/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 the type of {@code java.security.Key} + * @author Joe Grandja + * @since 0.1.0 + * @see CryptoKeySource + */ +public class CryptoKey implements Serializable { + private static final long serialVersionUID = Version.SERIAL_VERSION_UID; + private final K key; + private final String id; + private final Map 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 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 the type of the metadata + * @return the metadata value, or {@code null} if not available + */ + @SuppressWarnings("unchecked") + public final 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 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 the type of {@link CryptoKey} + * @param the type of {@link AbstractBuilder} + */ + protected abstract static class AbstractBuilder, B extends AbstractBuilder> implements Serializable { + private static final long serialVersionUID = Version.SERIAL_VERSION_UID; + protected Key key; + protected String id; + protected Map 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> metadataConsumer) { + metadataConsumer.accept(this.metadata); + return (B) this; + } + + /** + * Creates the {@link CryptoKey}. + * + * @return the {@link CryptoKey} + */ + protected abstract T build(); + + } +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKeySource.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKeySource.java new file mode 100644 index 0000000..657750c --- /dev/null +++ b/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> getKeys(); + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyGeneratorUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/KeyGeneratorUtils.java similarity index 98% rename from oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyGeneratorUtils.java rename to oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/KeyGeneratorUtils.java index 8822acb..748235e 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyGeneratorUtils.java +++ b/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; diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/StaticKeyGeneratingCryptoKeySource.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/StaticKeyGeneratingCryptoKeySource.java new file mode 100644 index 0000000..f54cfd7 --- /dev/null +++ b/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. + * + *

+ * NOTE: 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> keys; + + public StaticKeyGeneratingCryptoKeySource() { + this.keys = Collections.unmodifiableMap(generateKeys()); + } + + @Override + public Set> getKeys() { + return new HashSet<>(this.keys.values()); + } + + private static Map> 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)); + } +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/SymmetricKey.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/SymmetricKey.java new file mode 100644 index 0000000..8f70efa --- /dev/null +++ b/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 { + + private SymmetricKey(SecretKey key, String id, Map metadata) { + super(key, id, metadata); + } + + /** + * A builder for {@link SymmetricKey}. + */ + public static class Builder extends AbstractBuilder { + + 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); + } + } +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyManager.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyManager.java deleted file mode 100644 index 5d16f62..0000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/KeyManager.java +++ /dev/null @@ -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 findByAlgorithm(String algorithm); - - /** - * Returns a {@code Set} of the {@link ManagedKey}(s). - * - * @return a {@code Set} of the {@link ManagedKey}(s) - */ - Set getKeys(); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/ManagedKey.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/ManagedKey.java deleted file mode 100644 index 704de42..0000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/ManagedKey.java +++ /dev/null @@ -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 the type of {@code java.security.Key} - * @return the type of {@code java.security.Key} - */ - @SuppressWarnings("unchecked") - public 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; - } - } -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/StaticKeyGeneratingKeyManager.java b/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/StaticKeyGeneratingKeyManager.java deleted file mode 100644 index 23ec759..0000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/crypto/keys/StaticKeyGeneratingKeyManager.java +++ /dev/null @@ -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. - * - *

- * NOTE: 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 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 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 getKeys() { - return new HashSet<>(this.keys.values()); - } - - private static Map 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)); - } -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoder.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoder.java index 5cc7776..d3d5899 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoder.java +++ b/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. * *

* NOTE: 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 JSON Web Token (JWT) * @see JSON Web Signature (JWS) * @see JWS Compact Serialization @@ -92,16 +93,16 @@ public final class NimbusJwsEncoder implements JwtEncoder { }; private static final Converter jwsHeaderConverter = new JwsHeaderConverter(); private static final Converter 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 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 { @Override diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilter.java index e2f8a52..5e1c918 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilter.java +++ b/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 JSON Web Key (JWK) * @see Section 5 JWK Set Format */ @@ -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; diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java index 4c904ff..cf4588a 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java +++ b/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; } } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java index e7bc6c6..ed1a4cc 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java +++ b/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; } } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java index d601773..6d48071 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java +++ b/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; + } } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java index 816d44c..51186ac 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java +++ b/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; } } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/CryptoKeyTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/CryptoKeyTests.java new file mode 100644 index 0000000..9e2ed38 --- /dev/null +++ b/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 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 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); + } +} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/TestCryptoKeys.java b/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/TestCryptoKeys.java new file mode 100644 index 0000000..f55cd59 --- /dev/null +++ b/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()); + } +} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/ManagedKeyTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/ManagedKeyTests.java deleted file mode 100644 index 100cc04..0000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/ManagedKeyTests.java +++ /dev/null @@ -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.getKey()).isInstanceOf(SecretKey.class); - assertThat(managedKey.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.getKey()).isInstanceOf(PrivateKey.class); - assertThat(managedKey.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()); - } -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/TestManagedKeys.java b/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/TestManagedKeys.java deleted file mode 100644 index 4ab92cf..0000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/crypto/keys/TestManagedKeys.java +++ /dev/null @@ -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()); - } -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoderTests.java index 2fc2235..9822e61 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoderTests.java +++ b/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()); } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilterTests.java index cf427ab..b4b3aeb 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilterTests.java +++ b/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); diff --git a/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java index 8970529..9c232ea 100644 --- a/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java +++ b/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