16
16
17
17
package org .springframework .boot .info ;
18
18
19
- import java .io .BufferedReader ;
20
- import java .io .IOException ;
21
- import java .io .InputStreamReader ;
22
- import java .nio .charset .StandardCharsets ;
23
- import java .nio .file .Path ;
19
+ import java .time .Clock ;
24
20
import java .time .Duration ;
21
+ import java .time .Instant ;
22
+ import java .time .ZoneId ;
25
23
import java .util .List ;
26
- import java .util .stream .Collectors ;
27
24
28
25
import org .junit .jupiter .api .Test ;
29
- import org .junit .jupiter .api .io .TempDir ;
30
26
31
27
import org .springframework .boot .info .SslInfo .BundleInfo ;
32
28
import org .springframework .boot .info .SslInfo .CertificateChainInfo ;
46
42
* Tests for {@link SslInfo}.
47
43
*
48
44
* @author Jonatan Ivanov
45
+ * @author Moritz Halbritter
49
46
*/
50
47
class SslInfoTests {
51
48
49
+ private static final Clock CLOCK = Clock .fixed (Instant .parse ("2025-06-18T13:00:00Z" ), ZoneId .of ("UTC" ));
50
+
52
51
@ Test
53
52
@ WithPackageResources ("test.p12" )
54
53
void validCertificatesShouldProvideSslInfo () {
@@ -71,8 +70,8 @@ void validCertificatesShouldProvideSslInfo() {
71
70
assertThat (cert1 .getSerialNumber ()).isNotEmpty ();
72
71
assertThat (cert1 .getVersion ()).isEqualTo ("V3" );
73
72
assertThat (cert1 .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
74
- assertThat (cert1 .getValidityStarts ()).isInThePast ( );
75
- assertThat (cert1 .getValidityEnds ()).isInTheFuture ( );
73
+ assertThat (cert1 .getValidityStarts ()).isBefore ( CLOCK . instant () );
74
+ assertThat (cert1 .getValidityEnds ()).isAfter ( CLOCK . instant () );
76
75
assertThat (cert1 .getValidity ()).isNotNull ();
77
76
assertThat (cert1 .getValidity ().getStatus ()).isSameAs (Status .VALID );
78
77
assertThat (cert1 .getValidity ().getMessage ()).isNull ();
@@ -82,8 +81,8 @@ void validCertificatesShouldProvideSslInfo() {
82
81
assertThat (cert2 .getSerialNumber ()).isNotEmpty ();
83
82
assertThat (cert2 .getVersion ()).isEqualTo ("V3" );
84
83
assertThat (cert2 .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
85
- assertThat (cert2 .getValidityStarts ()).isInThePast ( );
86
- assertThat (cert2 .getValidityEnds ()).isInTheFuture ( );
84
+ assertThat (cert2 .getValidityStarts ()).isBefore ( CLOCK . instant () );
85
+ assertThat (cert2 .getValidityEnds ()).isAfter ( CLOCK . instant () );
87
86
assertThat (cert2 .getValidity ()).isNotNull ();
88
87
assertThat (cert2 .getValidity ().getStatus ()).isSameAs (Status .VALID );
89
88
assertThat (cert2 .getValidity ().getMessage ()).isNull ();
@@ -107,8 +106,8 @@ void notYetValidCertificateShouldProvideSslInfo() {
107
106
assertThat (cert .getSerialNumber ()).isNotEmpty ();
108
107
assertThat (cert .getVersion ()).isEqualTo ("V3" );
109
108
assertThat (cert .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
110
- assertThat (cert .getValidityStarts ()).isInTheFuture ( );
111
- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
109
+ assertThat (cert .getValidityStarts ()).isAfter ( CLOCK . instant () );
110
+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
112
111
assertThat (cert .getValidity ()).isNotNull ();
113
112
assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .NOT_YET_VALID );
114
113
assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid before" );
@@ -132,18 +131,17 @@ void expiredCertificateShouldProvideSslInfo() {
132
131
assertThat (cert .getSerialNumber ()).isNotEmpty ();
133
132
assertThat (cert .getVersion ()).isEqualTo ("V3" );
134
133
assertThat (cert .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
135
- assertThat (cert .getValidityStarts ()).isInThePast ( );
136
- assertThat (cert .getValidityEnds ()).isInThePast ( );
134
+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
135
+ assertThat (cert .getValidityEnds ()).isBefore ( CLOCK . instant () );
137
136
assertThat (cert .getValidity ()).isNotNull ();
138
137
assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .EXPIRED );
139
138
assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid after" );
140
139
}
141
140
142
141
@ Test
143
- void soonToBeExpiredCertificateShouldProvideSslInfo (@ TempDir Path tempDir )
144
- throws IOException , InterruptedException {
145
- Path keyStore = createKeyStore (tempDir );
146
- SslInfo sslInfo = createSslInfo (keyStore .toString ());
142
+ @ WithPackageResources ({ "will-expire-soon.p12" })
143
+ void soonToBeExpiredCertificateShouldProvideSslInfo () {
144
+ SslInfo sslInfo = createSslInfo ("classpath:will-expire-soon.p12" );
147
145
assertThat (sslInfo .getBundles ()).hasSize (1 );
148
146
BundleInfo bundle = sslInfo .getBundles ().get (0 );
149
147
assertThat (bundle .getName ()).isEqualTo ("test-0" );
@@ -158,19 +156,18 @@ void soonToBeExpiredCertificateShouldProvideSslInfo(@TempDir Path tempDir)
158
156
assertThat (cert .getSerialNumber ()).isNotEmpty ();
159
157
assertThat (cert .getVersion ()).isEqualTo ("V3" );
160
158
assertThat (cert .getSignatureAlgorithmName ()).isNotEmpty ();
161
- assertThat (cert .getValidityStarts ()).isInThePast ( );
162
- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
159
+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
160
+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
163
161
assertThat (cert .getValidity ()).isNotNull ();
164
- assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .WILL_EXPIRE_SOON );
162
+ assertThat (cert .getValidity ().getStatus ()).isEqualTo (Status .WILL_EXPIRE_SOON );
165
163
assertThat (cert .getValidity ().getMessage ()).startsWith ("Certificate will expire within threshold" );
166
164
}
167
165
168
166
@ Test
169
- @ WithPackageResources ({ "test.p12" , "test-not-yet-valid.p12" , "test-expired.p12" })
170
- void multipleBundlesShouldProvideSslInfo (@ TempDir Path tempDir ) throws IOException , InterruptedException {
171
- Path keyStore = createKeyStore (tempDir );
167
+ @ WithPackageResources ({ "test.p12" , "test-not-yet-valid.p12" , "test-expired.p12" , "will-expire-soon.p12" })
168
+ void multipleBundlesShouldProvideSslInfo () {
172
169
SslInfo sslInfo = createSslInfo ("classpath:test.p12" , "classpath:test-not-yet-valid.p12" ,
173
- "classpath:test-expired.p12" , keyStore . toString () );
170
+ "classpath:test-expired.p12" , "classpath:will-expire-soon.p12" );
174
171
assertThat (sslInfo .getBundles ()).hasSize (4 );
175
172
assertThat (sslInfo .getBundles ()).allSatisfy ((bundle ) -> assertThat (bundle .getName ()).startsWith ("test-" ));
176
173
List <CertificateInfo > certs = sslInfo .getBundles ()
@@ -188,29 +185,29 @@ void multipleBundlesShouldProvideSslInfo(@TempDir Path tempDir) throws IOExcepti
188
185
assertThat (cert .getValidity ()).isNotNull ();
189
186
});
190
187
assertThat (certs ).anySatisfy ((cert ) -> {
191
- assertThat (cert .getValidityStarts ()).isInThePast ( );
192
- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
188
+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
189
+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
193
190
assertThat (cert .getValidity ()).isNotNull ();
194
191
assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .VALID );
195
192
assertThat (cert .getValidity ().getMessage ()).isNull ();
196
193
});
197
194
assertThat (certs ).satisfiesOnlyOnce ((cert ) -> {
198
- assertThat (cert .getValidityStarts ()).isInTheFuture ( );
199
- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
195
+ assertThat (cert .getValidityStarts ()).isAfter ( CLOCK . instant () );
196
+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
200
197
assertThat (cert .getValidity ()).isNotNull ();
201
198
assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .NOT_YET_VALID );
202
199
assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid before" );
203
200
});
204
201
assertThat (certs ).satisfiesOnlyOnce ((cert ) -> {
205
- assertThat (cert .getValidityStarts ()).isInThePast ( );
206
- assertThat (cert .getValidityEnds ()).isInThePast ( );
202
+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
203
+ assertThat (cert .getValidityEnds ()).isBefore ( CLOCK . instant () );
207
204
assertThat (cert .getValidity ()).isNotNull ();
208
205
assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .EXPIRED );
209
206
assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid after" );
210
207
});
211
208
assertThat (certs ).satisfiesOnlyOnce ((cert ) -> {
212
- assertThat (cert .getValidityStarts ()).isInThePast ( );
213
- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
209
+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
210
+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
214
211
assertThat (cert .getValidity ()).isNotNull ();
215
212
assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .WILL_EXPIRE_SOON );
216
213
assertThat (cert .getValidity ().getMessage ()).startsWith ("Certificate will expire within threshold" );
@@ -221,7 +218,7 @@ void multipleBundlesShouldProvideSslInfo(@TempDir Path tempDir) throws IOExcepti
221
218
void nullKeyStore () {
222
219
DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry ();
223
220
sslBundleRegistry .registerBundle ("test" , SslBundle .of (SslStoreBundle .NONE , SslBundleKey .NONE ));
224
- SslInfo sslInfo = new SslInfo (sslBundleRegistry , Duration .ofDays (7 ));
221
+ SslInfo sslInfo = new SslInfo (sslBundleRegistry , Duration .ofDays (7 ), CLOCK );
225
222
assertThat (sslInfo .getBundles ()).hasSize (1 );
226
223
assertThat (sslInfo .getBundles ().get (0 ).getCertificateChains ()).isEmpty ();
227
224
}
@@ -233,41 +230,7 @@ private SslInfo createSslInfo(String... locations) {
233
230
SslStoreBundle sslStoreBundle = new JksSslStoreBundle (keyStoreDetails , null );
234
231
sslBundleRegistry .registerBundle ("test-%d" .formatted (i ), SslBundle .of (sslStoreBundle ));
235
232
}
236
- return new SslInfo (sslBundleRegistry , Duration .ofDays (7 ));
237
- }
238
-
239
- private Path createKeyStore (Path directory ) throws IOException , InterruptedException {
240
- Path keyStore = directory .resolve ("test.p12" );
241
- Process process = createProcessBuilder (keyStore ).start ();
242
- int exitCode = process .waitFor ();
243
- if (exitCode != 0 ) {
244
- try (BufferedReader reader = new BufferedReader (
245
- new InputStreamReader (process .getInputStream (), StandardCharsets .UTF_8 ))) {
246
- String out = reader .lines ().collect (Collectors .joining ("\n " ));
247
- throw new RuntimeException ("Unexpected exit code from keytool: %d\n %s" .formatted (exitCode , out ));
248
- }
249
- }
250
- return keyStore ;
251
- }
252
-
253
- private ProcessBuilder createProcessBuilder (Path keystore ) {
254
- // @formatter:off
255
- ProcessBuilder processBuilder = new ProcessBuilder (
256
- "keytool" ,
257
- "-genkeypair" ,
258
- "-storetype" , "PKCS12" ,
259
- "-alias" , "spring-boot" ,
260
- "-keyalg" , "RSA" ,
261
- "-storepass" , "secret" ,
262
- "-keypass" , "secret" ,
263
- "-keystore" , keystore .toString (),
264
- "-dname" , "CN=localhost,OU=Spring,O=VMware,L=Palo Alto,ST=California,C=US" ,
265
- "-validity" , "1" ,
266
- "-ext" , "SAN=DNS:localhost,IP:::1,IP:127.0.0.1"
267
- );
268
- // @formatter:on
269
- processBuilder .redirectErrorStream (true );
270
- return processBuilder ;
233
+ return new SslInfo (sslBundleRegistry , Duration .ofDays (7 ), CLOCK );
271
234
}
272
235
273
236
}
0 commit comments