Skip to content

Commit 9fa59ab

Browse files
authored
Add support for adding additional identity material at runtime (#655)
* Add support for adding additional identity material at runtime * Added possibility to add, remove based on aliases * Made test passing * Added test * Added test * Added test * Added test * Added test * Added test * Added test * Resolve unstable test * removed empty line * Added option to remove identity route * Updated docs * Applied sonar recommendation
1 parent b53b5c0 commit 9fa59ab

14 files changed

Lines changed: 569 additions & 72 deletions

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,30 @@ KeyManagerUtils.addIdentityRoute(keyManager, "client-alias-one", "https://localh
659659
// Override existing routes
660660
KeyManagerUtils.overrideIdentityRoute(keyManager, "client-alias-two", "https://localhost:9463/", "https://localhost:9473/")
661661
```
662+
##### Managing additional identities at runtime
663+
Please beware when using multiple identities from a keystore. The alias name from the key entry within the keystore should be unique across all the identities.
664+
```text
665+
SSLFactory sslFactory = SSLFactory.builder()
666+
.withInflatableIdentityMaterial()
667+
.withIdentityMaterial(Paths.get("/path/to/your/identity-1.jks"), "password".toCharArray())
668+
.withTrustMaterial("truststore.jks", password)
669+
.withIdentityRoute("client-alias-one", "https://localhost:8443/", "https://localhost:8453/")
670+
.build();
671+
672+
X509ExtendedKeyManager keyManager = sslFactory.getKeyManager().get();
673+
674+
// Adding identity and an additional route
675+
KeyStore identityTwo = KeyStoreUtils.loadKeyStore(Paths.get("/path/to/your/identity-2.jks"), "password".toCharArray());
676+
KeyManagerUtils.addIdentityMaterial(keyManager, "client-alias-two", identityTwo, "password".toCharArray());
677+
KeyManagerUtils.addIdentityRoute(keyManager, "client-alias-two", "https://localhost:8463/", "https://localhost:8473/");
678+
679+
// Getting existing list of aliases from the inflatable key manager
680+
List<String> aliases = KeyManagerUtils.getAliases(keyManager);
681+
682+
// Removing identity
683+
KeyManagerUtils.removeIdentityMaterial(keyManager, "client-alias-two");
684+
KeyManagerUtils.removeIdentityRoute(keyManager, "client-alias-two");
685+
```
662686
##### Managing ssl session
663687
```text
664688
SSLFactory sslFactory = SSLFactory.builder()

sslcontext-kickstart/src/main/java/nl/altindag/ssl/SSLFactory.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ public static class Builder {
203203
private boolean swappableSslParametersEnabled = false;
204204
private boolean loggingKeyManagerEnabled = false;
205205
private boolean loggingTrustManagerEnabled = false;
206+
private boolean inflatableKeyManagerEnabled = false;
206207

207208
private int sessionTimeoutInSeconds = -1;
208209
private int sessionCacheSizeInBytes = -1;
@@ -708,6 +709,11 @@ public Builder withLoggingIdentityMaterial() {
708709
return this;
709710
}
710711

712+
public Builder withInflatableIdentityMaterial() {
713+
inflatableKeyManagerEnabled = true;
714+
return this;
715+
}
716+
711717
public Builder withInflatableTrustMaterial() {
712718
trustManagers.add(TrustManagerUtils.createInflatableTrustManager());
713719
return this;
@@ -949,6 +955,7 @@ private X509ExtendedKeyManager createKeyManager() {
949955
.withIdentities(identities)
950956
.withSwappableKeyManager(swappableKeyManagerEnabled)
951957
.withLoggingKeyManager(loggingKeyManagerEnabled)
958+
.withInflatableKeyManager(inflatableKeyManagerEnabled)
952959
.withIdentityRoute(preferredAliasToHost)
953960
.build();
954961
}

sslcontext-kickstart/src/main/java/nl/altindag/ssl/keymanager/AggregatedX509ExtendedKeyManager.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@
2626
import java.security.PrivateKey;
2727
import java.security.cert.X509Certificate;
2828
import java.util.Collections;
29-
import java.util.HashMap;
29+
import java.util.LinkedHashMap;
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.Objects;
33+
import java.util.concurrent.ConcurrentHashMap;
3334

3435
/**
3536
* Represents an ordered list of {@link X509ExtendedKeyManager} with most-preferred managers first.
@@ -64,15 +65,15 @@
6465
*/
6566
public final class AggregatedX509ExtendedKeyManager extends X509ExtendedKeyManager implements CombinableX509KeyManager, RoutableX509KeyManager {
6667

67-
private final List<X509ExtendedKeyManager> keyManagers;
68+
final Map<String, X509ExtendedKeyManager> keyManagers;
6869
private final Map<String, List<URI>> preferredAliasToHost;
6970

7071
/**
7172
* Creates a new {@link AggregatedX509ExtendedKeyManager}.
7273
*
7374
* @param keyManagers the {@link X509ExtendedKeyManager}, ordered with the most-preferred managers first.
7475
*/
75-
public AggregatedX509ExtendedKeyManager(List<? extends X509ExtendedKeyManager> keyManagers) {
76+
public AggregatedX509ExtendedKeyManager(Map<String, ? extends X509ExtendedKeyManager> keyManagers) {
7677
this(keyManagers, Collections.emptyMap());
7778
}
7879

@@ -82,10 +83,10 @@ public AggregatedX509ExtendedKeyManager(List<? extends X509ExtendedKeyManager> k
8283
* @param keyManagers the {@link X509ExtendedKeyManager}, ordered with the most-preferred managers first.
8384
* @param preferredAliasToHost the preferred client alias to be used for the given host
8485
*/
85-
public AggregatedX509ExtendedKeyManager(List<? extends X509ExtendedKeyManager> keyManagers,
86+
public AggregatedX509ExtendedKeyManager(Map<String, ? extends X509ExtendedKeyManager> keyManagers,
8687
Map<String, List<URI>> preferredAliasToHost) {
87-
this.keyManagers = Collections.unmodifiableList(keyManagers);
88-
this.preferredAliasToHost = new HashMap<>(preferredAliasToHost);
88+
this.keyManagers = Collections.synchronizedMap(new LinkedHashMap<>(keyManagers));
89+
this.preferredAliasToHost = new ConcurrentHashMap<>(preferredAliasToHost);
8990
}
9091

9192
/**
@@ -187,8 +188,8 @@ public String[] getServerAliases(String keyType, Principal[] issuers) {
187188
}
188189

189190
@Override
190-
public List<X509ExtendedKeyManager> getInnerKeyManagers() {
191-
return keyManagers;
191+
public Map<String, X509ExtendedKeyManager> getInnerKeyManagers() {
192+
return Collections.unmodifiableMap(keyManagers);
192193
}
193194

194195
@Override

sslcontext-kickstart/src/main/java/nl/altindag/ssl/keymanager/CombinableX509KeyManager.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import javax.net.ssl.X509KeyManager;
2020
import java.util.Arrays;
2121
import java.util.List;
22+
import java.util.Map;
2223
import java.util.Objects;
2324
import java.util.function.Function;
2425
import java.util.function.Predicate;
@@ -30,20 +31,20 @@
3031
*
3132
* @author Hakan Altindag
3233
*/
33-
interface CombinableX509KeyManager extends X509KeyManager {
34+
public interface CombinableX509KeyManager extends X509KeyManager {
3435

35-
List<X509ExtendedKeyManager> getInnerKeyManagers();
36+
Map<String, X509ExtendedKeyManager> getInnerKeyManagers();
3637

3738
default <T> T extractInnerField(Function<X509ExtendedKeyManager, T> keyManagerMapper, Predicate<T> predicate) {
38-
return getInnerKeyManagers().stream()
39+
return getInnerKeyManagers().values().stream()
3940
.map(keyManagerMapper)
4041
.filter(predicate)
4142
.findFirst()
4243
.orElse(null);
4344
}
4445

4546
default String[] getAliases(Function<X509ExtendedKeyManager, String[]> aliasExtractor) {
46-
List<String> aliases = getInnerKeyManagers().stream()
47+
List<String> aliases = getInnerKeyManagers().values().stream()
4748
.map(aliasExtractor)
4849
.filter(Objects::nonNull)
4950
.flatMap(Arrays::stream)

sslcontext-kickstart/src/main/java/nl/altindag/ssl/keymanager/DelegatingX509ExtendedKeyManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @author Hakan Altindag
2727
*/
28-
class DelegatingX509ExtendedKeyManager extends DelegatingKeyManager<X509ExtendedKeyManager> {
28+
public class DelegatingX509ExtendedKeyManager extends DelegatingKeyManager<X509ExtendedKeyManager> {
2929

3030
public DelegatingX509ExtendedKeyManager(X509ExtendedKeyManager keyManager) {
3131
super(keyManager);

sslcontext-kickstart/src/main/java/nl/altindag/ssl/keymanager/HotSwappableX509ExtendedKeyManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@
3737
*
3838
* @author Hakan Altindag
3939
*/
40-
public final class HotSwappableX509ExtendedKeyManager extends DelegatingX509ExtendedKeyManager {
40+
public class HotSwappableX509ExtendedKeyManager extends DelegatingX509ExtendedKeyManager {
4141

4242
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
43-
private final Lock readLock = readWriteLock.readLock();
44-
private final Lock writeLock = readWriteLock.writeLock();
43+
protected final Lock readLock = readWriteLock.readLock();
44+
protected final Lock writeLock = readWriteLock.writeLock();
4545

4646
public HotSwappableX509ExtendedKeyManager(X509ExtendedKeyManager keyManager) {
4747
super(keyManager);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2019 Thunderberry.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nl.altindag.ssl.keymanager;
17+
18+
import nl.altindag.ssl.util.KeyManagerUtils;
19+
20+
import javax.net.ssl.X509ExtendedKeyManager;
21+
import java.security.KeyStore;
22+
import java.util.Collections;
23+
import java.util.Map;
24+
25+
/**
26+
* <strong>NOTE:</strong>
27+
* Please don't use this class directly as it is part of the internal API. Class name and methods can be changed any time.
28+
* Instead, use the {@link KeyManagerUtils KeyManagerUtils} which provides the same functionality
29+
* while it has a stable API because it is part of the public API.
30+
* <p>
31+
* The Inflatable KeyManager has the capability to grow with newly added identity material at any moment in time.
32+
* It can be added with {@link KeyManagerUtils#addIdentityMaterial(X509ExtendedKeyManager, String, KeyStore, char[])}
33+
* or with {@link KeyManagerUtils#addIdentityMaterial(X509ExtendedKeyManager, String, X509ExtendedKeyManager)}
34+
*
35+
* @author Hakan Altindag
36+
*/
37+
public class InflatableX509ExtendedKeyManager extends HotSwappableX509ExtendedKeyManager {
38+
39+
public InflatableX509ExtendedKeyManager() {
40+
this("dummy", KeyManagerUtils.createDummyKeyManager());
41+
}
42+
43+
public InflatableX509ExtendedKeyManager(String alias, X509ExtendedKeyManager keyManager) {
44+
super(keyManager instanceof AggregatedX509ExtendedKeyManager ? keyManager : new AggregatedX509ExtendedKeyManager(Collections.singletonMap(alias, keyManager)));
45+
}
46+
47+
public void addIdentity(String alias, X509ExtendedKeyManager keyManager) {
48+
writeLock.lock();
49+
50+
try {
51+
AggregatedX509ExtendedKeyManager aggregatedKeyManager = (AggregatedX509ExtendedKeyManager) getInnerKeyManager();
52+
aggregatedKeyManager.keyManagers.remove("dummy");
53+
aggregatedKeyManager.keyManagers.put(alias, keyManager);
54+
} finally {
55+
writeLock.unlock();
56+
}
57+
}
58+
59+
public void removeIdentity(String alias) {
60+
writeLock.lock();
61+
62+
try {
63+
((AggregatedX509ExtendedKeyManager) getInnerKeyManager()).keyManagers.remove(alias);
64+
} finally {
65+
writeLock.unlock();
66+
}
67+
}
68+
69+
public Map<String, X509ExtendedKeyManager> getAliasToIdentity() {
70+
readLock.lock();
71+
72+
try {
73+
return Collections.unmodifiableMap(((AggregatedX509ExtendedKeyManager) getInnerKeyManager()).keyManagers);
74+
} finally {
75+
readLock.unlock();
76+
}
77+
}
78+
79+
}

0 commit comments

Comments
 (0)