1616
1717package org .springframework .boot .info ;
1818
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 ;
2420import java .time .Duration ;
21+ import java .time .Instant ;
22+ import java .time .ZoneId ;
2523import java .util .List ;
26- import java .util .stream .Collectors ;
2724
2825import org .junit .jupiter .api .Test ;
29- import org .junit .jupiter .api .io .TempDir ;
3026
3127import org .springframework .boot .info .SslInfo .BundleInfo ;
3228import org .springframework .boot .info .SslInfo .CertificateChainInfo ;
4642 * Tests for {@link SslInfo}.
4743 *
4844 * @author Jonatan Ivanov
45+ * @author Moritz Halbritter
4946 */
5047class SslInfoTests {
5148
49+ private static final Clock CLOCK = Clock .fixed (Instant .parse ("2025-06-18T13:00:00Z" ), ZoneId .of ("UTC" ));
50+
5251 @ Test
5352 @ WithPackageResources ("test.p12" )
5453 void validCertificatesShouldProvideSslInfo () {
@@ -71,8 +70,8 @@ void validCertificatesShouldProvideSslInfo() {
7170 assertThat (cert1 .getSerialNumber ()).isNotEmpty ();
7271 assertThat (cert1 .getVersion ()).isEqualTo ("V3" );
7372 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 () );
7675 assertThat (cert1 .getValidity ()).isNotNull ();
7776 assertThat (cert1 .getValidity ().getStatus ()).isSameAs (Status .VALID );
7877 assertThat (cert1 .getValidity ().getMessage ()).isNull ();
@@ -82,8 +81,8 @@ void validCertificatesShouldProvideSslInfo() {
8281 assertThat (cert2 .getSerialNumber ()).isNotEmpty ();
8382 assertThat (cert2 .getVersion ()).isEqualTo ("V3" );
8483 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 () );
8786 assertThat (cert2 .getValidity ()).isNotNull ();
8887 assertThat (cert2 .getValidity ().getStatus ()).isSameAs (Status .VALID );
8988 assertThat (cert2 .getValidity ().getMessage ()).isNull ();
@@ -107,8 +106,8 @@ void notYetValidCertificateShouldProvideSslInfo() {
107106 assertThat (cert .getSerialNumber ()).isNotEmpty ();
108107 assertThat (cert .getVersion ()).isEqualTo ("V3" );
109108 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 () );
112111 assertThat (cert .getValidity ()).isNotNull ();
113112 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .NOT_YET_VALID );
114113 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid before" );
@@ -132,18 +131,17 @@ void expiredCertificateShouldProvideSslInfo() {
132131 assertThat (cert .getSerialNumber ()).isNotEmpty ();
133132 assertThat (cert .getVersion ()).isEqualTo ("V3" );
134133 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 () );
137136 assertThat (cert .getValidity ()).isNotNull ();
138137 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .EXPIRED );
139138 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid after" );
140139 }
141140
142141 @ 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" );
147145 assertThat (sslInfo .getBundles ()).hasSize (1 );
148146 BundleInfo bundle = sslInfo .getBundles ().get (0 );
149147 assertThat (bundle .getName ()).isEqualTo ("test-0" );
@@ -158,19 +156,18 @@ void soonToBeExpiredCertificateShouldProvideSslInfo(@TempDir Path tempDir)
158156 assertThat (cert .getSerialNumber ()).isNotEmpty ();
159157 assertThat (cert .getVersion ()).isEqualTo ("V3" );
160158 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 () );
163161 assertThat (cert .getValidity ()).isNotNull ();
164- assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .WILL_EXPIRE_SOON );
162+ assertThat (cert .getValidity ().getStatus ()).isEqualTo (Status .WILL_EXPIRE_SOON );
165163 assertThat (cert .getValidity ().getMessage ()).startsWith ("Certificate will expire within threshold" );
166164 }
167165
168166 @ 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 () {
172169 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" );
174171 assertThat (sslInfo .getBundles ()).hasSize (4 );
175172 assertThat (sslInfo .getBundles ()).allSatisfy ((bundle ) -> assertThat (bundle .getName ()).startsWith ("test-" ));
176173 List <CertificateInfo > certs = sslInfo .getBundles ()
@@ -188,29 +185,29 @@ void multipleBundlesShouldProvideSslInfo(@TempDir Path tempDir) throws IOExcepti
188185 assertThat (cert .getValidity ()).isNotNull ();
189186 });
190187 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 () );
193190 assertThat (cert .getValidity ()).isNotNull ();
194191 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .VALID );
195192 assertThat (cert .getValidity ().getMessage ()).isNull ();
196193 });
197194 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 () );
200197 assertThat (cert .getValidity ()).isNotNull ();
201198 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .NOT_YET_VALID );
202199 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid before" );
203200 });
204201 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 () );
207204 assertThat (cert .getValidity ()).isNotNull ();
208205 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .EXPIRED );
209206 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid after" );
210207 });
211208 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 () );
214211 assertThat (cert .getValidity ()).isNotNull ();
215212 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .WILL_EXPIRE_SOON );
216213 assertThat (cert .getValidity ().getMessage ()).startsWith ("Certificate will expire within threshold" );
@@ -221,7 +218,7 @@ void multipleBundlesShouldProvideSslInfo(@TempDir Path tempDir) throws IOExcepti
221218 void nullKeyStore () {
222219 DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry ();
223220 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 );
225222 assertThat (sslInfo .getBundles ()).hasSize (1 );
226223 assertThat (sslInfo .getBundles ().get (0 ).getCertificateChains ()).isEmpty ();
227224 }
@@ -233,41 +230,7 @@ private SslInfo createSslInfo(String... locations) {
233230 SslStoreBundle sslStoreBundle = new JksSslStoreBundle (keyStoreDetails , null );
234231 sslBundleRegistry .registerBundle ("test-%d" .formatted (i ), SslBundle .of (sslStoreBundle ));
235232 }
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 );
271234 }
272235
273236}
0 commit comments