Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1a9d178

Browse files
committedMar 6, 2025·
Fix AliasX509ExtendedKeyManager to process both server and client aliases properly
Signed-off-by: Stéphane Gobancé <19359548+ecnabogs@users.noreply.github.com>
1 parent 0a84ebf commit 1a9d178

File tree

2 files changed

+335
-16
lines changed

2 files changed

+335
-16
lines changed
 

‎spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ssl/AliasKeyManagerFactory.java

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
import java.security.UnrecoverableKeyException;
2727
import java.security.cert.X509Certificate;
2828
import java.util.Arrays;
29+
import java.util.Objects;
30+
import java.util.Optional;
31+
import java.util.function.BiFunction;
32+
import java.util.stream.Stream;
2933

3034
import javax.net.ssl.KeyManager;
3135
import javax.net.ssl.KeyManagerFactory;
@@ -42,6 +46,7 @@
4246
* {@link KeyManagerFactory#getKeyManagers()} is final.
4347
*
4448
* @author Scott Frederick
49+
* @author Stéphane Gobancé
4550
*/
4651
final class AliasKeyManagerFactory extends KeyManagerFactory {
4752

@@ -105,23 +110,27 @@ private AliasX509ExtendedKeyManager(X509ExtendedKeyManager keyManager, String al
105110
}
106111

107112
@Override
108-
public String chooseEngineClientAlias(String[] strings, Principal[] principals, SSLEngine sslEngine) {
109-
return this.delegate.chooseEngineClientAlias(strings, principals, sslEngine);
113+
public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine sslEngine) {
114+
return findFirstMatchingAlias(keyTypes, issuers, this::getClientAliases)
115+
.orElseGet(() -> this.delegate.chooseEngineClientAlias(keyTypes, issuers, sslEngine));
110116
}
111117

112118
@Override
113-
public String chooseEngineServerAlias(String s, Principal[] principals, SSLEngine sslEngine) {
114-
return this.alias;
119+
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine sslEngine) {
120+
return findFirstMatchingAlias(keyType, issuers, this::getServerAliases)
121+
.orElseGet(() -> this.delegate.chooseEngineServerAlias(keyType, issuers, sslEngine));
115122
}
116123

117124
@Override
118-
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
119-
return this.delegate.chooseClientAlias(keyType, issuers, socket);
125+
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
126+
return findFirstMatchingAlias(keyTypes, issuers, this::getClientAliases)
127+
.orElseGet(() -> this.delegate.chooseClientAlias(keyTypes, issuers, socket));
120128
}
121129

122130
@Override
123131
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
124-
return this.delegate.chooseServerAlias(keyType, issuers, socket);
132+
return findFirstMatchingAlias(keyType, issuers, this::getServerAliases)
133+
.orElseGet(() -> this.delegate.chooseServerAlias(keyType, issuers, socket));
125134
}
126135

127136
@Override
@@ -144,6 +153,50 @@ public String[] getServerAliases(String keyType, Principal[] issuers) {
144153
return this.delegate.getServerAliases(keyType, issuers);
145154
}
146155

156+
/**
157+
* Typed-BiFunction for better readability.
158+
*/
159+
private interface KeyAliasFinder extends BiFunction<String, Principal[], String[]> {
160+
161+
}
162+
163+
/**
164+
* Gets this key manager's alias if it matches the given key algorithm and has
165+
* been issued by any of the specified issuers (might be {@code null}, meaning
166+
* issuer does not matter) otherwise returns an {@link Optional#empty() empty
167+
* result }.
168+
* @param keyType the required key algorithm.
169+
* @param issuers the list of acceptable CA issuer subject names or {@code null}
170+
* if it does not matter which issuers are used.
171+
* @param finder the function to find the underlying available key aliases.
172+
* @return this key manager's alias if appropriate or an empty result otherwise.
173+
*/
174+
private Optional<String> findFirstMatchingAlias(String keyType, Principal[] issuers, KeyAliasFinder finder) {
175+
return findFirstMatchingAlias(new String[] { keyType }, issuers, finder);
176+
}
177+
178+
/**
179+
* Gets this key manager's alias if it matches any of the given key algorithms and
180+
* has been issued by any of the specified issuers (might be {@code null}, meaning
181+
* issuer does not matter) otherwise returns an {@link Optional#empty() empty
182+
* result }.
183+
* @param keyTypes the required key algorithms.
184+
* @param issuers the list of acceptable CA issuer subject names or {@code null}
185+
* if it does not matter which issuers are used.
186+
* @param finder the function to find the underlying available key aliases.
187+
* @return this key manager's alias if appropriate or an empty result otherwise.
188+
*/
189+
private Optional<String> findFirstMatchingAlias(String[] keyTypes, Principal[] issuers, KeyAliasFinder finder) {
190+
return Optional.ofNullable(keyTypes)
191+
.flatMap(types -> Stream.of(types)
192+
.filter(Objects::nonNull)
193+
.map(type -> finder.apply(type, issuers))
194+
.filter(Objects::nonNull)
195+
.flatMap(Stream::of)
196+
.filter(this.alias::equals)
197+
.findFirst());
198+
}
199+
147200
}
148201

149202
}

‎spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ssl/AliasKeyManagerFactoryTests.java

Lines changed: 275 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,298 @@
2323
import javax.net.ssl.X509ExtendedKeyManager;
2424

2525
import org.junit.jupiter.api.Test;
26+
import org.mockito.ArgumentMatcher;
2627

2728
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.mockito.ArgumentMatchers.any;
30+
import static org.mockito.ArgumentMatchers.argThat;
31+
import static org.mockito.ArgumentMatchers.eq;
2832
import static org.mockito.BDDMockito.given;
2933
import static org.mockito.Mockito.mock;
3034

3135
/**
3236
* Tests for {@link AliasKeyManagerFactory}.
3337
*
3438
* @author Phillip Webb
39+
* @author Stéphane Gobancé
3540
*/
3641
class AliasKeyManagerFactoryTests {
3742

3843
@Test
39-
void chooseEngineServerAliasReturnsAlias() throws Exception {
40-
KeyManagerFactory delegate = mock(KeyManagerFactory.class);
41-
given(delegate.getKeyManagers()).willReturn(new KeyManager[] { mock(X509ExtendedKeyManager.class) });
42-
AliasKeyManagerFactory factory = new AliasKeyManagerFactory(delegate, "test-alias",
43-
KeyManagerFactory.getDefaultAlgorithm());
44+
void chooseEngineServerAliasReturnsTestAliasMatchingSupportedDelegateAliasAndAlgorithm() throws Exception {
45+
46+
final String delegateSupportedAlgorithm = "supported-alg";
47+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
48+
final String delegateChosenAlias = delegateSupportedAlias[0];
49+
final String testAlias = delegateSupportedAlias[1];
50+
final String testAlgorithm = delegateSupportedAlgorithm;
51+
final String expectedAlias = testAlias;
52+
53+
KeyManagerFactory delegate = mockServerKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
54+
delegateChosenAlias);
55+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
56+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
57+
58+
String chosenAlias = x509KeyManager.chooseEngineServerAlias(testAlgorithm, null, null);
59+
assertThat(chosenAlias).isEqualTo(expectedAlias);
60+
}
61+
62+
@Test
63+
void chooseEngineServerAliasReturnsDelegateAliasWhenTestAliasIsUnknown() throws Exception {
64+
65+
final String delegateSupportedAlgorithm = "supported-alg";
66+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
67+
final String delegateChosenAlias = delegateSupportedAlias[0];
68+
final String testAlias = "unknown-alias";
69+
final String testAlgorithm = delegateSupportedAlgorithm;
70+
final String expectedAlias = delegateChosenAlias;
71+
72+
KeyManagerFactory delegate = mockServerKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
73+
delegateChosenAlias);
74+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
75+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
76+
77+
String chosenAlias = x509KeyManager.chooseEngineServerAlias(testAlgorithm, null, null);
78+
assertThat(chosenAlias).isEqualTo(expectedAlias);
79+
}
80+
81+
@Test
82+
void chooseEngineServerAliasReturnsNullWhenAlgorithmDoesNotMatch() throws Exception {
83+
84+
final String delegateSupportedAlgorithm = "supported-alg";
85+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
86+
final String delegateChosenAlias = delegateSupportedAlias[1];
87+
final String testAlias = delegateSupportedAlias[0];
88+
final String testAlgorithm = "other-alg";
89+
final String expectedAlias = null;
90+
91+
KeyManagerFactory delegate = mockServerKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
92+
delegateChosenAlias);
93+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
94+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
95+
96+
String chosenAlias = x509KeyManager.chooseEngineServerAlias(testAlgorithm, null, null);
97+
assertThat(chosenAlias).isEqualTo(expectedAlias);
98+
}
99+
100+
@Test
101+
void chooseServerAliasReturnsTestAliasMatchingSupportedDelegateAliasAndAlgorithm() throws Exception {
102+
103+
final String delegateSupportedAlgorithm = "supported-alg";
104+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
105+
final String delegateChosenAlias = delegateSupportedAlias[0];
106+
final String testAlias = delegateSupportedAlias[1];
107+
final String testAlgorithm = delegateSupportedAlgorithm;
108+
final String expectedAlias = testAlias;
109+
110+
KeyManagerFactory delegate = mockServerKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
111+
delegateChosenAlias);
112+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
113+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
114+
115+
String chosenAlias = x509KeyManager.chooseServerAlias(testAlgorithm, null, null);
116+
assertThat(chosenAlias).isEqualTo(expectedAlias);
117+
}
118+
119+
@Test
120+
void chooseServerAliasReturnsDelegateAliasWhenTestAliasIsUnknown() throws Exception {
121+
122+
final String delegateSupportedAlgorithm = "supported-alg";
123+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
124+
final String delegateChosenAlias = delegateSupportedAlias[0];
125+
final String testAlias = "unknown-alias";
126+
final String testAlgorithm = delegateSupportedAlgorithm;
127+
final String expectedAlias = delegateChosenAlias;
128+
129+
KeyManagerFactory delegate = mockServerKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
130+
delegateChosenAlias);
131+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
132+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
133+
134+
String chosenAlias = x509KeyManager.chooseServerAlias(testAlgorithm, null, null);
135+
assertThat(chosenAlias).isEqualTo(expectedAlias);
136+
}
137+
138+
@Test
139+
void chooseServerAliasReturnsNullWhenAlgorithmDoesNotMatch() throws Exception {
140+
141+
final String delegateSupportedAlgorithm = "supported-alg";
142+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
143+
final String delegateChosenAlias = delegateSupportedAlias[1];
144+
final String testAlias = delegateSupportedAlias[0];
145+
final String testAlgorithm = "other-alg";
146+
final String expectedAlias = null;
147+
148+
KeyManagerFactory delegate = mockServerKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
149+
delegateChosenAlias);
150+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
151+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
152+
153+
String chosenAlias = x509KeyManager.chooseServerAlias(testAlgorithm, null, null);
154+
assertThat(chosenAlias).isEqualTo(expectedAlias);
155+
}
156+
157+
@Test
158+
void chooseEngineClientAliasReturnsTestAliasMatchingSupportedDelegateAliasAndAlgorithm() throws Exception {
159+
160+
final String delegateSupportedAlgorithm = "supported-alg";
161+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
162+
final String delegateChosenAlias = delegateSupportedAlias[0];
163+
final String testAlias = delegateSupportedAlias[1];
164+
final String[] testAlgorithms = new String[] { delegateSupportedAlgorithm };
165+
final String expectedAlias = testAlias;
166+
167+
KeyManagerFactory delegate = mockClientKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
168+
delegateChosenAlias);
169+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
170+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
171+
172+
String chosenAlias = x509KeyManager.chooseEngineClientAlias(testAlgorithms, null, null);
173+
assertThat(chosenAlias).isEqualTo(expectedAlias);
174+
}
175+
176+
@Test
177+
void chooseEngineClientAliasReturnsDelegateAliasWhenTestAliasIsUnknown() throws Exception {
178+
179+
final String delegateSupportedAlgorithm = "supported-alg";
180+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
181+
final String delegateChosenAlias = delegateSupportedAlias[0];
182+
final String testAlias = "unknown-alias";
183+
final String[] testAlgorithms = new String[] { delegateSupportedAlgorithm };
184+
final String expectedAlias = delegateChosenAlias;
185+
186+
KeyManagerFactory delegate = mockClientKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
187+
delegateChosenAlias);
188+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
189+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
190+
191+
String chosenAlias = x509KeyManager.chooseEngineClientAlias(testAlgorithms, null, null);
192+
assertThat(chosenAlias).isEqualTo(expectedAlias);
193+
}
194+
195+
@Test
196+
void chooseEngineClientAliasReturnsNullWhenAlgorithmDoesNotMatch() throws Exception {
197+
198+
final String delegateSupportedAlgorithm = "supported-alg";
199+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
200+
final String delegateChosenAlias = delegateSupportedAlias[1];
201+
final String testAlias = delegateSupportedAlias[0];
202+
final String[] testAlgorithms = new String[] { "other-alg" };
203+
final String expectedAlias = null;
204+
205+
KeyManagerFactory delegate = mockClientKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
206+
delegateChosenAlias);
207+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
208+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
209+
210+
String chosenAlias = x509KeyManager.chooseEngineClientAlias(testAlgorithms, null, null);
211+
assertThat(chosenAlias).isEqualTo(expectedAlias);
212+
}
213+
214+
@Test
215+
void chooseClientAliasReturnsTestAliasMatchingSupportedDelegateAliasAndAlgorithm() throws Exception {
216+
217+
final String delegateSupportedAlgorithm = "supported-alg";
218+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
219+
final String delegateChosenAlias = delegateSupportedAlias[0];
220+
final String testAlias = delegateSupportedAlias[1];
221+
final String[] testAlgorithms = new String[] { delegateSupportedAlgorithm };
222+
final String expectedAlias = testAlias;
223+
224+
KeyManagerFactory delegate = mockClientKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
225+
delegateChosenAlias);
226+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
227+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
228+
229+
String chosenAlias = x509KeyManager.chooseClientAlias(testAlgorithms, null, null);
230+
assertThat(chosenAlias).isEqualTo(expectedAlias);
231+
}
232+
233+
@Test
234+
void chooseClientAliasReturnsDelegateAliasWhenTestAliasIsUnknown() throws Exception {
235+
236+
final String delegateSupportedAlgorithm = "supported-alg";
237+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
238+
final String delegateChosenAlias = delegateSupportedAlias[0];
239+
final String testAlias = "unknown-alias";
240+
final String[] testAlgorithms = new String[] { delegateSupportedAlgorithm };
241+
final String expectedAlias = delegateChosenAlias;
242+
243+
KeyManagerFactory delegate = mockClientKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
244+
delegateChosenAlias);
245+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
246+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
247+
248+
String chosenAlias = x509KeyManager.chooseClientAlias(testAlgorithms, null, null);
249+
assertThat(chosenAlias).isEqualTo(expectedAlias);
250+
}
251+
252+
@Test
253+
void chooseClientAliasReturnsNullWhenAlgorithmDoesNotMatch() throws Exception {
254+
255+
final String delegateSupportedAlgorithm = "supported-alg";
256+
final String[] delegateSupportedAlias = new String[] { "alias0", "alias1", "alias2" };
257+
final String delegateChosenAlias = delegateSupportedAlias[1];
258+
final String testAlias = delegateSupportedAlias[0];
259+
final String[] testAlgorithms = new String[] { "other-alg" };
260+
final String expectedAlias = null;
261+
262+
KeyManagerFactory delegate = mockClientKeyManagerFactory(delegateSupportedAlgorithm, delegateSupportedAlias,
263+
delegateChosenAlias);
264+
AliasKeyManagerFactory factory = createAliasKeyManagerFactory(testAlias, delegate);
265+
X509ExtendedKeyManager x509KeyManager = getX509ExtendedKeyManager(factory);
266+
267+
String chosenAlias = x509KeyManager.chooseClientAlias(testAlgorithms, null, null);
268+
assertThat(chosenAlias).isEqualTo(expectedAlias);
269+
}
270+
271+
private static AliasKeyManagerFactory createAliasKeyManagerFactory(String alias, KeyManagerFactory delegate)
272+
throws Exception {
273+
AliasKeyManagerFactory factory = new AliasKeyManagerFactory(delegate, alias, delegate.getAlgorithm());
44274
factory.init(null, null);
45-
KeyManager[] keyManagers = factory.getKeyManagers();
46-
X509ExtendedKeyManager x509KeyManager = (X509ExtendedKeyManager) Arrays.stream(keyManagers)
275+
return factory;
276+
}
277+
278+
private static X509ExtendedKeyManager getX509ExtendedKeyManager(KeyManagerFactory factory) {
279+
return Arrays.stream(factory.getKeyManagers())
47280
.filter(X509ExtendedKeyManager.class::isInstance)
281+
.map(X509ExtendedKeyManager.class::cast)
48282
.findAny()
49283
.get();
50-
String chosenAlias = x509KeyManager.chooseEngineServerAlias(null, null, null);
51-
assertThat(chosenAlias).isEqualTo("test-alias");
284+
}
285+
286+
private static KeyManagerFactory mockServerKeyManagerFactory(String algorithm, String[] serverAliases,
287+
String serverChosenAlias) {
288+
289+
return mockKeyManagerFactory(algorithm, serverAliases, serverChosenAlias, null, null);
290+
}
291+
292+
private static KeyManagerFactory mockClientKeyManagerFactory(String algorithm, String[] clientAliases,
293+
String clientChosenAlias) {
294+
295+
return mockKeyManagerFactory(algorithm, null, null, clientAliases, clientChosenAlias);
296+
}
297+
298+
private static KeyManagerFactory mockKeyManagerFactory(String algorithm, String[] serverAliases,
299+
String serverChosenAlias, String[] clientAliases, String clientChosenAlias) {
300+
301+
KeyManagerFactory delegate = mock(KeyManagerFactory.class);
302+
X509ExtendedKeyManager x509KeyManagerMock = mock(X509ExtendedKeyManager.class);
303+
given(delegate.getAlgorithm()).willReturn(algorithm);
304+
given(delegate.getKeyManagers()).willReturn(new KeyManager[] { x509KeyManagerMock });
305+
given(x509KeyManagerMock.getServerAliases(eq(algorithm), any())).willReturn(serverAliases);
306+
given(x509KeyManagerMock.chooseServerAlias(eq(algorithm), any(), any())).willReturn(serverChosenAlias);
307+
given(x509KeyManagerMock.chooseEngineServerAlias(eq(algorithm), any(), any())).willReturn(serverChosenAlias);
308+
given(x509KeyManagerMock.getClientAliases(eq(algorithm), any())).willReturn(clientAliases);
309+
given(x509KeyManagerMock.chooseClientAlias(argThat(arrayContains(algorithm)), any(), any()))
310+
.willReturn(clientChosenAlias);
311+
given(x509KeyManagerMock.chooseEngineClientAlias(argThat(arrayContains(algorithm)), any(), any()))
312+
.willReturn(clientChosenAlias);
313+
return delegate;
314+
}
315+
316+
private static ArgumentMatcher<String[]> arrayContains(String expected) {
317+
return array -> array != null && Arrays.asList(array).contains(expected);
52318
}
53319

54320
}

0 commit comments

Comments
 (0)
Please sign in to comment.