1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package nl.altindag.ssl.util;
18
19 import nl.altindag.log.LogCaptor;
20 import nl.altindag.ssl.SSLFactory;
21 import nl.altindag.ssl.exception.GenericKeyStoreException;
22 import org.junit.jupiter.api.Test;
23 import org.junit.jupiter.api.extension.ExtendWith;
24 import org.mockito.MockedStatic;
25 import org.mockito.junit.jupiter.MockitoExtension;
26
27 import javax.net.ssl.X509ExtendedTrustManager;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.lang.reflect.Method;
31 import java.nio.file.Files;
32 import java.nio.file.OpenOption;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.nio.file.StandardOpenOption;
36 import java.security.KeyStore;
37 import java.security.KeyStoreException;
38 import java.security.cert.Certificate;
39 import java.security.cert.X509Certificate;
40 import java.util.List;
41
42 import static nl.altindag.ssl.TestConstants.IDENTITY_FILE_NAME;
43 import static nl.altindag.ssl.TestConstants.KEYSTORE_LOCATION;
44 import static nl.altindag.ssl.TestConstants.TRUSTSTORE_FILE_NAME;
45 import static nl.altindag.ssl.TestConstants.TRUSTSTORE_PASSWORD;
46 import static org.assertj.core.api.Assertions.assertThat;
47 import static org.assertj.core.api.Assertions.assertThatThrownBy;
48 import static org.mockito.ArgumentMatchers.any;
49 import static org.mockito.ArgumentMatchers.anyString;
50 import static org.mockito.Mockito.doThrow;
51 import static org.mockito.Mockito.mock;
52 import static org.mockito.Mockito.mockStatic;
53 import static org.mockito.Mockito.spy;
54
55
56
57
58 @ExtendWith(MockitoExtension.class)
59 class KeyStoreUtilsShould {
60
61 private static final String JCEKS_KEYSTORE_FILE_NAME = "identity.jceks";
62 private static final String PKCS12_KEYSTORE_FILE_NAME = "identity.p12";
63 private static final String NON_EXISTING_KEYSTORE_FILE_NAME = "black-hole.jks";
64
65 private static final char[] KEYSTORE_PASSWORD = new char[] {'s', 'e', 'c', 'r', 'e', 't'};
66 private static final String ORIGINAL_OS_NAME = System.getProperty("os.name");
67 private static final String TEST_RESOURCES_LOCATION = "src/test/resources/";
68
69 @Test
70 void loadKeyStoreFromClasspath() {
71 KeyStore keyStore = KeyStoreUtils.loadKeyStore(KEYSTORE_LOCATION + IDENTITY_FILE_NAME, KEYSTORE_PASSWORD);
72 assertThat(keyStore).isNotNull();
73 }
74
75 @Test
76 void loadJCEKSKeyStoreFromClasspath() {
77 KeyStore keyStore = KeyStoreUtils.loadKeyStore(KEYSTORE_LOCATION + JCEKS_KEYSTORE_FILE_NAME, KEYSTORE_PASSWORD, "JCEKS");
78 assertThat(keyStore).isNotNull();
79 }
80
81 @Test
82 void loadPKCS12KeyStoreFromClasspath() {
83 KeyStore keyStore = KeyStoreUtils.loadKeyStore(KEYSTORE_LOCATION + PKCS12_KEYSTORE_FILE_NAME, KEYSTORE_PASSWORD, "PKCS12");
84 assertThat(keyStore).isNotNull();
85 }
86
87 @Test
88 void loadKeyStoreWithPathFromDirectory() {
89 KeyStore keyStore = KeyStoreUtils.loadKeyStore(Paths.get(TEST_RESOURCES_LOCATION + KEYSTORE_LOCATION + IDENTITY_FILE_NAME).toAbsolutePath(), KEYSTORE_PASSWORD);
90 assertThat(keyStore).isNotNull();
91 }
92
93 @Test
94 void loadKeyStoreAsInputStream() throws IOException {
95 KeyStore keyStore;
96 try(InputStream inputStream = getResource(KEYSTORE_LOCATION + IDENTITY_FILE_NAME)) {
97 keyStore = KeyStoreUtils.loadKeyStore(inputStream, "secret".toCharArray());
98 }
99
100 assertThat(keyStore).isNotNull();
101 }
102
103 @Test
104 void loadSystemKeyStore() {
105 List<KeyStore> keyStores = KeyStoreUtils.loadSystemKeyStores();
106
107 String operatingSystem = System.getProperty("os.name").toLowerCase();
108 if (operatingSystem.contains("mac") || operatingSystem.contains("windows")) {
109 assertThat(keyStores).isNotEmpty();
110 }
111 }
112
113 @Test
114 void loadWindowsSystemKeyStore() {
115 System.setProperty("os.name", "windows");
116 KeyStore windowsRootKeyStore = mock(KeyStore.class);
117 KeyStore windowsMyKeyStore = mock(KeyStore.class);
118
119 try (MockedStatic<KeyStoreUtils> keyStoreUtilsMock = mockStatic(KeyStoreUtils.class, invocation -> {
120 Method method = invocation.getMethod();
121 if ("loadSystemKeyStores".equals(method.getName()) && method.getParameterCount() == 0) {
122 return invocation.callRealMethod();
123 } else {
124 return invocation.getMock();
125 }
126 })) {
127 keyStoreUtilsMock.when(() -> KeyStoreUtils.createKeyStore("Windows-ROOT", null)).thenReturn(windowsRootKeyStore);
128 keyStoreUtilsMock.when(() -> KeyStoreUtils.createKeyStore("Windows-MY", null)).thenReturn(windowsMyKeyStore);
129
130 List<KeyStore> keyStores = KeyStoreUtils.loadSystemKeyStores();
131 assertThat(keyStores).containsExactlyInAnyOrder(windowsRootKeyStore, windowsMyKeyStore);
132 }
133
134 resetOsName();
135 }
136
137 @Test
138 void loadMacSystemKeyStore() {
139 System.setProperty("os.name", "mac");
140 KeyStore macKeyStore = mock(KeyStore.class);
141
142 try (MockedStatic<KeyStoreUtils> keyStoreUtilsMock = mockStatic(KeyStoreUtils.class, invocation -> {
143 Method method = invocation.getMethod();
144 if ("loadSystemKeyStores".equals(method.getName()) && method.getParameterCount() == 0) {
145 return invocation.callRealMethod();
146 } else {
147 return invocation.getMock();
148 }
149 })) {
150 keyStoreUtilsMock.when(() -> KeyStoreUtils.createKeyStore("KeychainStore", null)).thenReturn(macKeyStore);
151
152 List<KeyStore> keyStores = KeyStoreUtils.loadSystemKeyStores();
153 assertThat(keyStores).containsExactly(macKeyStore);
154 }
155
156 resetOsName();
157 }
158
159 @Test
160 void loadLinuxSystemKeyStoreReturnsEmptyList() {
161 System.setProperty("os.name", "linux");
162
163 LogCaptor logCaptor = LogCaptor.forClass(KeyStoreUtils.class);
164
165 List<KeyStore> trustStores = KeyStoreUtils.loadSystemKeyStores();
166
167 assertThat(trustStores).isEmpty();
168 assertThat(logCaptor.getWarnLogs())
169 .hasSize(1)
170 .contains("No system KeyStores available for [linux]");
171
172 resetOsName();
173 }
174
175 @Test
176 void createTrustStoreFromX509CertificatesAsList() throws KeyStoreException {
177 SSLFactory sslFactory = SSLFactory.builder()
178 .withTrustMaterial(KEYSTORE_LOCATION + TRUSTSTORE_FILE_NAME, TRUSTSTORE_PASSWORD)
179 .withDefaultTrustMaterial()
180 .build();
181
182 List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates();
183 KeyStore trustStore = KeyStoreUtils.createTrustStore(trustedCertificates);
184
185 assertThat(trustedCertificates.size()).isNotZero();
186 assertThat(trustStore.size()).isNotZero();
187 assertThat(trustStore.size()).isEqualTo(trustedCertificates.size());
188 }
189
190 @Test
191 void createTrustStoreFromX509CertificatesAsArray() throws KeyStoreException {
192 SSLFactory sslFactory = SSLFactory.builder()
193 .withTrustMaterial(KEYSTORE_LOCATION + TRUSTSTORE_FILE_NAME, TRUSTSTORE_PASSWORD)
194 .withDefaultTrustMaterial()
195 .build();
196
197 X509Certificate[] trustedCertificates = sslFactory.getTrustedCertificates().toArray(new X509Certificate[0]);
198 KeyStore trustStore = KeyStoreUtils.createTrustStore(trustedCertificates);
199
200 assertThat(trustedCertificates.length).isNotZero();
201 assertThat(trustStore.size()).isNotZero();
202 assertThat(trustStore.size()).isEqualTo(trustedCertificates.length);
203 }
204
205 @Test
206 void createTrustStoreFromMultipleTrustManagers() throws KeyStoreException {
207 X509ExtendedTrustManager jdkTrustManager = TrustManagerUtils.createTrustManagerWithJdkTrustedCertificates();
208 X509ExtendedTrustManager customTrustManager = TrustManagerUtils.createTrustManager(KeyStoreUtils.loadKeyStore(KEYSTORE_LOCATION + TRUSTSTORE_FILE_NAME, TRUSTSTORE_PASSWORD));
209
210 KeyStore trustStore = KeyStoreUtils.createTrustStore(jdkTrustManager, customTrustManager);
211
212 assertThat(jdkTrustManager.getAcceptedIssuers().length).isNotZero();
213 assertThat(customTrustManager.getAcceptedIssuers().length).isNotZero();
214 assertThat(trustStore.size()).isNotZero();
215 assertThat(trustStore.size()).isEqualTo(jdkTrustManager.getAcceptedIssuers().length + customTrustManager.getAcceptedIssuers().length);
216 }
217
218 @Test
219 void throwExceptionWhenLoadingNonExistingKeystoreType() {
220 assertThatThrownBy(() -> KeyStoreUtils.loadKeyStore(KEYSTORE_LOCATION + IDENTITY_FILE_NAME, KEYSTORE_PASSWORD, "unknown"))
221 .isInstanceOf(GenericKeyStoreException.class)
222 .hasMessageContaining("unknown not found");
223 }
224
225 @Test
226 void throwExceptionWhenLoadingNonExistingKeystore() {
227 assertThatThrownBy(() -> KeyStoreUtils.loadKeyStore(KEYSTORE_LOCATION + NON_EXISTING_KEYSTORE_FILE_NAME, KEYSTORE_PASSWORD))
228 .isInstanceOf(GenericKeyStoreException.class)
229 .hasMessageContaining("KeyStore is not present for the giving input");
230 }
231
232 @Test
233 void throwGenericKeyStoreExceptionWhenFailingToCloseStreamProperly() throws IOException {
234 String keystoreType = KeyStore.getDefaultType();
235 Path keystorePath = Paths.get(TEST_RESOURCES_LOCATION + KEYSTORE_LOCATION + IDENTITY_FILE_NAME).toAbsolutePath();
236 InputStream inputStream = spy(Files.newInputStream(keystorePath, StandardOpenOption.READ));
237
238 try (MockedStatic<Files> filesMockedStatic = mockStatic(Files.class, invocation -> {
239 Method method = invocation.getMethod();
240 if ("newInputStream".equals(method.getName())) {
241 return invocation.getMock();
242 } else {
243 return invocation.callRealMethod();
244 }
245 })) {
246
247 doThrow(new IOException("Could not close the stream")).when(inputStream).close();
248
249 filesMockedStatic.when(() -> Files.newInputStream(any(Path.class), any(OpenOption.class))).thenReturn(inputStream);
250
251 assertThatThrownBy(() -> KeyStoreUtils.loadKeyStore(keystorePath, KEYSTORE_PASSWORD, keystoreType))
252 .isInstanceOf(GenericKeyStoreException.class)
253 .hasMessageContaining("Could not close the stream");
254 }
255 }
256
257 @Test
258 void throwExceptionWhenCreateKeyStoreForUnknownType() {
259 assertThatThrownBy(() -> KeyStoreUtils.createKeyStore("unknown", KEYSTORE_PASSWORD))
260 .isInstanceOf(GenericKeyStoreException.class)
261 .hasMessageContaining("unknown not found");
262 }
263
264 @Test
265 void throwGenericKeyStoreWhenSetCertificateEntryThrowsKeyStoreException() throws KeyStoreException {
266 SSLFactory sslFactory = SSLFactory.builder()
267 .withTrustMaterial(KEYSTORE_LOCATION + TRUSTSTORE_FILE_NAME, TRUSTSTORE_PASSWORD)
268 .withDefaultTrustMaterial()
269 .build();
270
271 X509Certificate[] trustedCertificates = sslFactory.getTrustedCertificates().toArray(new X509Certificate[0]);
272
273 KeyStore keyStore = mock(KeyStore.class);
274 doThrow(new KeyStoreException("lazy")).when(keyStore).setCertificateEntry(anyString(), any(Certificate.class));
275
276 try (MockedStatic<KeyStoreUtils> keyStoreUtilsMock = mockStatic(KeyStoreUtils.class, invocation -> {
277 Method method = invocation.getMethod();
278 if ("createKeyStore".equals(method.getName()) && method.getParameterCount() == 0) {
279 return invocation.getMock();
280 } else {
281 return invocation.callRealMethod();
282 }
283 })) {
284 keyStoreUtilsMock.when(KeyStoreUtils::createKeyStore).thenReturn(keyStore);
285
286 assertThatThrownBy(() -> KeyStoreUtils.createTrustStore(trustedCertificates))
287 .isInstanceOf(GenericKeyStoreException.class)
288 .hasMessageContaining("lazy");
289 }
290 }
291
292 private void resetOsName() {
293 System.setProperty("os.name", ORIGINAL_OS_NAME);
294 }
295
296 private InputStream getResource(String path) {
297 return this.getClass().getClassLoader().getResourceAsStream(path);
298 }
299
300 }