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 ;
20+ import java .time .Instant ;
21+ import java .time .ZoneId ;
2422import java .util .List ;
25- import java .util .stream .Collectors ;
2623
2724import org .junit .jupiter .api .Test ;
28- import org .junit .jupiter .api .io .TempDir ;
2925
3026import org .springframework .boot .info .SslInfo .BundleInfo ;
3127import org .springframework .boot .info .SslInfo .CertificateChainInfo ;
4541 * Tests for {@link SslInfo}.
4642 *
4743 * @author Jonatan Ivanov
44+ * @author Moritz Halbritter
4845 */
4946class SslInfoTests {
5047
48+ private static final Clock CLOCK = Clock .fixed (Instant .parse ("2025-06-18T13:00:00Z" ), ZoneId .of ("UTC" ));
49+
5150 @ Test
5251 @ WithPackageResources ("test.p12" )
5352 void validCertificatesShouldProvideSslInfo () {
@@ -70,8 +69,8 @@ void validCertificatesShouldProvideSslInfo() {
7069 assertThat (cert1 .getSerialNumber ()).isNotEmpty ();
7170 assertThat (cert1 .getVersion ()).isEqualTo ("V3" );
7271 assertThat (cert1 .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
73- assertThat (cert1 .getValidityStarts ()).isInThePast ( );
74- assertThat (cert1 .getValidityEnds ()).isInTheFuture ( );
72+ assertThat (cert1 .getValidityStarts ()).isBefore ( CLOCK . instant () );
73+ assertThat (cert1 .getValidityEnds ()).isAfter ( CLOCK . instant () );
7574 assertThat (cert1 .getValidity ()).isNotNull ();
7675 assertThat (cert1 .getValidity ().getStatus ()).isSameAs (Status .VALID );
7776 assertThat (cert1 .getValidity ().getMessage ()).isNull ();
@@ -81,8 +80,8 @@ void validCertificatesShouldProvideSslInfo() {
8180 assertThat (cert2 .getSerialNumber ()).isNotEmpty ();
8281 assertThat (cert2 .getVersion ()).isEqualTo ("V3" );
8382 assertThat (cert2 .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
84- assertThat (cert2 .getValidityStarts ()).isInThePast ( );
85- assertThat (cert2 .getValidityEnds ()).isInTheFuture ( );
83+ assertThat (cert2 .getValidityStarts ()).isBefore ( CLOCK . instant () );
84+ assertThat (cert2 .getValidityEnds ()).isAfter ( CLOCK . instant () );
8685 assertThat (cert2 .getValidity ()).isNotNull ();
8786 assertThat (cert2 .getValidity ().getStatus ()).isSameAs (Status .VALID );
8887 assertThat (cert2 .getValidity ().getMessage ()).isNull ();
@@ -106,8 +105,8 @@ void notYetValidCertificateShouldProvideSslInfo() {
106105 assertThat (cert .getSerialNumber ()).isNotEmpty ();
107106 assertThat (cert .getVersion ()).isEqualTo ("V3" );
108107 assertThat (cert .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
109- assertThat (cert .getValidityStarts ()).isInTheFuture ( );
110- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
108+ assertThat (cert .getValidityStarts ()).isAfter ( CLOCK . instant () );
109+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
111110 assertThat (cert .getValidity ()).isNotNull ();
112111 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .NOT_YET_VALID );
113112 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid before" );
@@ -131,19 +130,18 @@ void expiredCertificateShouldProvideSslInfo() {
131130 assertThat (cert .getSerialNumber ()).isNotEmpty ();
132131 assertThat (cert .getVersion ()).isEqualTo ("V3" );
133132 assertThat (cert .getSignatureAlgorithmName ()).isEqualTo ("SHA256withRSA" );
134- assertThat (cert .getValidityStarts ()).isInThePast ( );
135- assertThat (cert .getValidityEnds ()).isInThePast ( );
133+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
134+ assertThat (cert .getValidityEnds ()).isBefore ( CLOCK . instant () );
136135 assertThat (cert .getValidity ()).isNotNull ();
137136 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .EXPIRED );
138137 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid after" );
139138 }
140139
141140 @ Test
142- @ WithPackageResources ({ "test.p12" , "test-not-yet-valid.p12" , "test-expired.p12" })
143- void multipleBundlesShouldProvideSslInfo (@ TempDir Path tempDir ) throws IOException , InterruptedException {
144- Path keyStore = createKeyStore (tempDir );
141+ @ WithPackageResources ({ "test.p12" , "test-not-yet-valid.p12" , "test-expired.p12" , "will-expire-soon.p12" })
142+ void multipleBundlesShouldProvideSslInfo () {
145143 SslInfo sslInfo = createSslInfo ("classpath:test.p12" , "classpath:test-not-yet-valid.p12" ,
146- "classpath:test-expired.p12" , keyStore . toString () );
144+ "classpath:test-expired.p12" , "classpath:will-expire-soon.p12" );
147145 assertThat (sslInfo .getBundles ()).hasSize (4 );
148146 assertThat (sslInfo .getBundles ()).allSatisfy ((bundle ) -> assertThat (bundle .getName ()).startsWith ("test-" ));
149147 List <CertificateInfo > certs = sslInfo .getBundles ()
@@ -161,22 +159,22 @@ void multipleBundlesShouldProvideSslInfo(@TempDir Path tempDir) throws IOExcepti
161159 assertThat (cert .getValidity ()).isNotNull ();
162160 });
163161 assertThat (certs ).anySatisfy ((cert ) -> {
164- assertThat (cert .getValidityStarts ()).isInThePast ( );
165- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
162+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
163+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
166164 assertThat (cert .getValidity ()).isNotNull ();
167165 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .VALID );
168166 assertThat (cert .getValidity ().getMessage ()).isNull ();
169167 });
170168 assertThat (certs ).satisfiesOnlyOnce ((cert ) -> {
171- assertThat (cert .getValidityStarts ()).isInTheFuture ( );
172- assertThat (cert .getValidityEnds ()).isInTheFuture ( );
169+ assertThat (cert .getValidityStarts ()).isAfter ( CLOCK . instant () );
170+ assertThat (cert .getValidityEnds ()).isAfter ( CLOCK . instant () );
173171 assertThat (cert .getValidity ()).isNotNull ();
174172 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .NOT_YET_VALID );
175173 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid before" );
176174 });
177175 assertThat (certs ).satisfiesOnlyOnce ((cert ) -> {
178- assertThat (cert .getValidityStarts ()).isInThePast ( );
179- assertThat (cert .getValidityEnds ()).isInThePast ( );
176+ assertThat (cert .getValidityStarts ()).isBefore ( CLOCK . instant () );
177+ assertThat (cert .getValidityEnds ()).isBefore ( CLOCK . instant () );
180178 assertThat (cert .getValidity ()).isNotNull ();
181179 assertThat (cert .getValidity ().getStatus ()).isSameAs (Status .EXPIRED );
182180 assertThat (cert .getValidity ().getMessage ()).startsWith ("Not valid after" );
@@ -187,7 +185,7 @@ void multipleBundlesShouldProvideSslInfo(@TempDir Path tempDir) throws IOExcepti
187185 void nullKeyStore () {
188186 DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry ();
189187 sslBundleRegistry .registerBundle ("test" , SslBundle .of (SslStoreBundle .NONE , SslBundleKey .NONE ));
190- SslInfo sslInfo = new SslInfo (sslBundleRegistry );
188+ SslInfo sslInfo = new SslInfo (sslBundleRegistry , CLOCK );
191189 assertThat (sslInfo .getBundles ()).hasSize (1 );
192190 assertThat (sslInfo .getBundles ().get (0 ).getCertificateChains ()).isEmpty ();
193191 }
@@ -199,41 +197,7 @@ private SslInfo createSslInfo(String... locations) {
199197 SslStoreBundle sslStoreBundle = new JksSslStoreBundle (keyStoreDetails , null );
200198 sslBundleRegistry .registerBundle ("test-%d" .formatted (i ), SslBundle .of (sslStoreBundle ));
201199 }
202- return new SslInfo (sslBundleRegistry );
203- }
204-
205- private Path createKeyStore (Path directory ) throws IOException , InterruptedException {
206- Path keyStore = directory .resolve ("test.p12" );
207- Process process = createProcessBuilder (keyStore ).start ();
208- int exitCode = process .waitFor ();
209- if (exitCode != 0 ) {
210- try (BufferedReader reader = new BufferedReader (
211- new InputStreamReader (process .getInputStream (), StandardCharsets .UTF_8 ))) {
212- String out = reader .lines ().collect (Collectors .joining ("\n " ));
213- throw new RuntimeException ("Unexpected exit code from keytool: %d\n %s" .formatted (exitCode , out ));
214- }
215- }
216- return keyStore ;
217- }
218-
219- private ProcessBuilder createProcessBuilder (Path keystore ) {
220- // @formatter:off
221- ProcessBuilder processBuilder = new ProcessBuilder (
222- "keytool" ,
223- "-genkeypair" ,
224- "-storetype" , "PKCS12" ,
225- "-alias" , "spring-boot" ,
226- "-keyalg" , "RSA" ,
227- "-storepass" , "secret" ,
228- "-keypass" , "secret" ,
229- "-keystore" , keystore .toString (),
230- "-dname" , "CN=localhost,OU=Spring,O=VMware,L=Palo Alto,ST=California,C=US" ,
231- "-validity" , "1" ,
232- "-ext" , "SAN=DNS:localhost,IP:::1,IP:127.0.0.1"
233- );
234- // @formatter:on
235- processBuilder .redirectErrorStream (true );
236- return processBuilder ;
200+ return new SslInfo (sslBundleRegistry , CLOCK );
237201 }
238202
239203}
0 commit comments