From 70a8255eae0af64628f07326df1c73d86c1b9fd2 Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Wed, 2 Nov 2016 08:36:03 +0100 Subject: [PATCH] KEYCLOAK-1881 Basic key locator support --- .../rotation/CompositeKeyLocator.java | 159 ++++++++++++++++++ .../rotation/HardcodedKeyLocator.java | 69 ++++++++ .../org/keycloak/rotation/KeyLocator.java | 50 ++++++ 3 files changed, 278 insertions(+) create mode 100644 saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java create mode 100644 saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java create mode 100644 saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java diff --git a/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java new file mode 100644 index 0000000000..4b3cb57a7d --- /dev/null +++ b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java @@ -0,0 +1,159 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 + * + * http://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.keycloak.rotation; + +import java.security.Key; +import java.security.KeyManagementException; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * {@link KeyLocator} that represents a list of multiple {@link KeyLocator}s. Key is searched + * from the first to the last {@link KeyLocator} in the order given by the list. If there are + * multiple {@link KeyLocator}s providing key with the same key ID, the first matching key is + * returned. + * + * @author hmlnarik + */ +public class CompositeKeyLocator implements KeyLocator, Iterable { + + private final List keyLocators = new LinkedList<>(); + + @Override + public Key getKey(String kid) throws KeyManagementException { + for (KeyLocator keyLocator : keyLocators) { + Key k = keyLocator.getKey(kid); + if (k != null) { + return k; + } + } + + return null; + } + + @Override + public void refreshKeyCache() { + for (KeyLocator keyLocator : keyLocators) { + keyLocator.refreshKeyCache(); + } + } + + /** + * Registers a given {@link KeyLocator} as the first {@link KeyLocator}. + */ + public void addFirst(KeyLocator keyLocator) { + this.keyLocators.add(0, keyLocator); + } + + /** + * Registers a given {@link KeyLocator} as the last {@link KeyLocator}. + */ + public void add(KeyLocator keyLocator) { + this.keyLocators.add(keyLocator); + } + + /** + * Clears the list of registered {@link KeyLocator}s + */ + public void clear() { + this.keyLocators.clear(); + } + + @Override + public String toString() { + if (this.keyLocators.size() == 1) { + return this.keyLocators.get(0).toString(); + } + + StringBuilder sb = new StringBuilder("Key locator chain: ["); + for (Iterator it = keyLocators.iterator(); it.hasNext();) { + KeyLocator keyLocator = it.next(); + sb.append(keyLocator.toString()); + if (it.hasNext()) { + sb.append(", "); + } + } + return sb.append("]").toString(); + } + + @Override + public Iterator iterator() { + final Iterator> iterablesIterator = getKeyLocatorIterators().iterator(); + + return new JointKeyIterator(iterablesIterator).iterator(); + } + + @SuppressWarnings("unchecked") + private Iterable> getKeyLocatorIterators() { + List> res = new LinkedList<>(); + for (KeyLocator kl : this.keyLocators) { + if (kl instanceof Iterable) { + res.add(((Iterable) kl)); + } + } + return Collections.unmodifiableCollection(res); + } + + private class JointKeyIterator implements Iterable { + + // based on http://stackoverflow.com/a/34126154/6930869 + private final Iterator> iterablesIterator; + + public JointKeyIterator(Iterator> iterablesIterator) { + this.iterablesIterator = iterablesIterator; + } + + @Override + public Iterator iterator() { + if (! iterablesIterator.hasNext()) { + return Collections.emptyIterator(); + } + + return new Iterator() { + private Iterator currentIterator = nextIterator(); + + @Override + public boolean hasNext() { + return currentIterator.hasNext(); + } + + @Override + public Key next() { + final Key next = currentIterator.next(); + findNext(); + return next; + } + + private Iterator nextIterator() { + return iterablesIterator.next().iterator(); + } + + private Iterator findNext() { + while (! currentIterator.hasNext()) { + if (! iterablesIterator.hasNext()) { + break; + } + currentIterator = nextIterator(); + } + return this; + } + }.findNext(); + } + } +} diff --git a/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java new file mode 100644 index 0000000000..ae2615a07b --- /dev/null +++ b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 + * + * http://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.keycloak.rotation; + +import java.security.Key; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * Key locator that always returns a specified key. + * + * @author Hynek Mlnařík + */ +public class HardcodedKeyLocator implements KeyLocator, Iterable { + + private final Collection keys; + + public HardcodedKeyLocator(Key key) { + this.keys = Collections.singleton(key); + } + + public HardcodedKeyLocator(Collection keys) { + if (keys == null) { + throw new NullPointerException("keys"); + } + this.keys = new LinkedList<>(keys); + } + + @Override + public Key getKey(String kid) { + if (this.keys.size() == 1) { + return this.keys.iterator().next(); + } else { + return null; + } + } + + @Override + public void refreshKeyCache() { + // do nothing + } + + @Override + public String toString() { + return "hardcoded keys, count: " + this.keys.size(); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableCollection(keys).iterator(); + } +} diff --git a/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java new file mode 100644 index 0000000000..7112eca299 --- /dev/null +++ b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 + * + * http://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.keycloak.rotation; + +import java.security.Key; +import java.security.KeyManagementException; + +/** + * This interface defines a method for obtaining a security key by ID. + *

+ * If the {@code KeyLocator} implementor wants to make all its keys available for iteration, + * it should implement {@link Iterable}<{@code T extends }{@link Key}> interface. + * The base {@code KeyLocator} does not extend this interface to enable {@code KeyLocators} + * that do not support listing their keys. + * + * @author Hynek Mlnařík + */ +public interface KeyLocator { + + /** + * Returns a key with a particular ID. + * @param kid Key ID + * @param configuration Configuration + * @return key, which should be used for verify signature on given "input" + * @throws KeyManagementException + */ + Key getKey(String kid) throws KeyManagementException; + + /** + * If this key locator caches keys in any way, forces this cache cleanup + * and refreshing the keys. + */ + void refreshKeyCache(); + +}