@@ -31,20 +31,18 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3131import java .security .cert .Certificate ;
3232import java .security .cert .CertificateFactory ;
3333import java .security .cert .X509Certificate ;
34- import java .sql .Timestamp ;
3534import java .time .Duration ;
3635import java .time .LocalDateTime ;
3736import java .util .ArrayList ;
38- import java .util .Calendar ;
3937import java .util .Collection ;
4038import java .util .Collections ;
4139import java .util .HashMap ;
4240import java .util .HashSet ;
43- import java .util .Locale ;
4441import java .util .Map ;
4542import java .util .concurrent .TimeUnit ;
4643
4744import org .apache .commons .io .IOUtils ;
45+ import org .apache .commons .lang3 .StringUtils ;
4846import org .bouncycastle .cms .CMSProcessableByteArray ;
4947import org .bouncycastle .cms .CMSSignedData ;
5048import org .bouncycastle .cms .SignerInformation ;
@@ -73,12 +71,13 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
7371import lombok .extern .slf4j .Slf4j ;
7472import reactor .core .publisher .Mono ;
7573import reactor .netty .http .client .HttpClient ;
74+ import reactor .netty .resources .ConnectionProvider ;
7675
7776/**
7877 * Default implementation of the {@linkplain BundleRefreshProcessor} interface.
7978 * <p>
8079 * The implementation allows for bundles to be downloaded from SSL protected sites that may not
81- * chain back to a trust CA. This is useful in developement environments and is not recommended in
80+ * chain back to a trust CA. This is useful in development environments and is not recommended in
8281 * a production environment. By default, this feature is disable, but can be enabled using the
8382 * {@link DefaultBundleRefreshProcessorImpl#BUNDLE_REFRESH_PROCESSOR_ALLOW_DOWNLOAD_FROM_UNTRUSTED} options parameter.
8483 * @author Greg Meyer
@@ -95,7 +94,8 @@ public class DefaultBundleRefreshProcessorImpl implements BundleRefreshProcessor
9594 public final static String BUNDLE_REFRESH_PROCESSOR_ALLOW_DOWNLOAD_FROM_UNTRUSTED = "BUNDLE_REFRESH_PROCESSOR_ALLOW_DOWNLOAD_FROM_UNTRUSTED" ;
9695
9796 protected static final int DEFAULT_URL_CONNECTION_TIMEOUT = 10000 ; // 10 seconds
98- protected static final int DEFAULT_URL_READ_TIMEOUT = 10000 ; // 10 hour seconds
97+ protected static final int DEFAULT_URL_READ_TIMEOUT = 10000 ; // 10 seconds
98+ protected static final int POOL_MAX_IDLE_TIME = 20 ; // 20 seconds
9999
100100 /**
101101 * Trust bundle repo
@@ -171,7 +171,7 @@ public void setRepositories(TrustBundleRepository bundleRepo, TrustBundleAnchorR
171171 public Mono <?> refreshBundle (TrustBundle bundle )
172172 {
173173 // track when the process started
174- final Calendar processAttempStart = Calendar . getInstance ( Locale . getDefault () );
174+ final LocalDateTime processAttempStart = LocalDateTime . now ( );
175175
176176 // get the bundle from the URL
177177 return downloadBundleToByteArray (bundle , processAttempStart )
@@ -184,7 +184,7 @@ public Mono<?> refreshBundle(TrustBundle bundle)
184184 // use a checksum
185185 boolean update = false ;
186186 String checkSum = "" ;
187- if (bundle .getCheckSum () == null )
187+ if (StringUtils . isBlank ( bundle .getCheckSum ()) )
188188 // never got a check sum...
189189 update = true ;
190190 else
@@ -193,14 +193,17 @@ public Mono<?> refreshBundle(TrustBundle bundle)
193193 {
194194 checkSum = BundleThumbprint .toThumbprint (rawBundle ).toString ();
195195 update = !bundle .getCheckSum ().equals (BundleThumbprint .toThumbprint (rawBundle ).toString ());
196+ if (update ) {
197+ log .info ("Detected a change in bundle [{}] (old checksum: {}; new checkSum: {})!" , bundle .getBundleName (), bundle .getCheckSum (), checkSum );
198+ }
196199 }
197200 ///CLOVER:OFF
198201 catch (NoSuchAlgorithmException ex )
199202 {
200- bundle .setLastRefreshAttempt (new Timestamp ( processAttempStart . getTime (). getTime ()). toLocalDateTime () );
203+ bundle .setLastRefreshAttempt (processAttempStart );
201204 bundle .setLastRefreshError (BundleRefreshError .INVALID_BUNDLE_FORMAT .ordinal ());
202205
203- log .error ("Failed to generate downloaded bundle thumbprint " , ex );
206+ log .error ("Failed to generate downloaded bundle thumbprint" , ex );
204207
205208 return bundleRepo .save (bundle );
206209
@@ -212,10 +215,11 @@ public Mono<?> refreshBundle(TrustBundle bundle)
212215
213216 if (!update )
214217 {
215- bundle .setLastRefreshAttempt (new Timestamp (processAttempStart .getTime ().getTime ()).toLocalDateTime ());
218+ // bundle was not updated, but mark it as a successful refresh
219+ bundle .setLastRefreshAttempt (processAttempStart );
220+ bundle .setLastSuccessfulRefresh (LocalDateTime .now ());
216221 bundle .setLastRefreshError (BundleRefreshError .SUCCESS .ordinal ());
217222 return bundleRepo .save (bundle );
218-
219223 }
220224
221225 return convertRawBundleToAnchorCollection (rawBundle , bundle , processAttempStart )
@@ -241,7 +245,7 @@ public Mono<?> refreshBundle(TrustBundle bundle)
241245 ///CLOVER:OFF
242246 catch (Exception e )
243247 {
244- log .warn ("Failed to convert downloaded anchor to byte array. " , e );
248+ log .warn ("Failed to convert downloaded anchor to byte array." , e );
245249 return Mono .empty ();
246250 }
247251 ///CLOVER:ON
@@ -251,18 +255,21 @@ public Mono<?> refreshBundle(TrustBundle bundle)
251255 .then (bundleAnchorRepo .saveAll (newAnchors ).collectList ())
252256 .flatMap (res ->
253257 {
254- bundle .setLastRefreshAttempt (new Timestamp ( processAttempStart . getTime (). getTime ()). toLocalDateTime () );
258+ bundle .setLastRefreshAttempt (processAttempStart );
255259 bundle .setLastRefreshError (BundleRefreshError .SUCCESS .ordinal ());
256260 bundle .setCheckSum (finalCheckSum );
257261 bundle .setLastSuccessfulRefresh (LocalDateTime .now ());
258262
259263 return bundleRepo .save (bundle )
264+ .doOnSuccess (savedBundle -> {
265+ log .info ("successfully refreshed bundle {}" , bundle .getBundleName ());
266+ })
260267 .onErrorResume (ex ->
261268 {
262- log .error ("Failed to write updated bundle anchors to data store " , ex );
269+ log .error ("Failed to write updated bundle anchors to data store" , ex );
263270
264271
265- bundle .setLastRefreshAttempt (new Timestamp ( processAttempStart . getTime (). getTime ()). toLocalDateTime () );
272+ bundle .setLastRefreshAttempt (processAttempStart );
266273 bundle .setLastRefreshError (BundleRefreshError .INVALID_BUNDLE_FORMAT .ordinal ());
267274 return bundleRepo .save (bundle );
268275
@@ -283,7 +290,7 @@ public Mono<?> refreshBundle(TrustBundle bundle)
283290 */
284291 @ SuppressWarnings ({ "unchecked" , "deprecation" })
285292 protected Mono <Collection <X509Certificate >> convertRawBundleToAnchorCollection (byte [] rawBundle , final TrustBundle existingBundle ,
286- final Calendar processAttempStart )
293+ final LocalDateTime processAttempStart )
287294 {
288295 Collection <? extends Certificate > bundleCerts = null ;
289296 InputStream inStream = null ;
@@ -339,7 +346,7 @@ protected Mono<Collection<X509Certificate>> convertRawBundleToAnchorCollection(b
339346
340347 if (!sigVerified )
341348 {
342- existingBundle .setLastRefreshAttempt (new Timestamp ( processAttempStart . getTime (). getTime ()). toLocalDateTime () );
349+ existingBundle .setLastRefreshAttempt (processAttempStart );
343350 existingBundle .setLastRefreshError (BundleRefreshError .UNMATCHED_SIGNATURE .ordinal ());
344351 log .warn ("Downloaded bundle signature did not match configured signing certificate." );
345352
@@ -356,7 +363,7 @@ protected Mono<Collection<X509Certificate>> convertRawBundleToAnchorCollection(b
356363 }
357364 catch (Exception e )
358365 {
359- existingBundle .setLastRefreshAttempt (new Timestamp ( processAttempStart . getTime (). getTime ()). toLocalDateTime () );
366+ existingBundle .setLastRefreshAttempt (processAttempStart );
360367 existingBundle .setLastRefreshError (BundleRefreshError .INVALID_BUNDLE_FORMAT .ordinal ());
361368
362369 log .warn ("Failed to extract anchors from downloaded bundle at URL " + existingBundle .getBundleURL ());
@@ -379,15 +386,15 @@ protected Mono<Collection<X509Certificate>> convertRawBundleToAnchorCollection(b
379386 * @param processAttempStart The time that the update process started.
380387 * @return A byte array representing the raw data of the bundle.
381388 */
382- @ SuppressWarnings ("deprecation" )
383- protected Mono <byte []> downloadBundleToByteArray (TrustBundle bundle , Calendar processAttempStart )
389+ protected Mono <byte []> downloadBundleToByteArray (TrustBundle bundle , LocalDateTime processAttempStart )
384390 {
385391 try
386392 {
387393 final URI uri = new URI (bundle .getBundleURL ());
388394
389395 if (uri .getScheme ().compareToIgnoreCase ("file" ) == 0 )
390396 {
397+ // file scheme URIs are used by unit tests
391398 final ByteArrayOutputStream ouStream = new ByteArrayOutputStream ();
392399
393400
@@ -413,7 +420,11 @@ protected Mono<byte[]> downloadBundleToByteArray(TrustBundle bundle, Calendar pr
413420 }
414421 else
415422 {
416- final HttpClient httpClient = HttpClient .create ()
423+ // this custom connection provider with a configured pool max idle time prevents
424+ // "Connection reset by peer" errors
425+ ConnectionProvider provider = ConnectionProvider .builder ("custom" )
426+ .maxIdleTime (Duration .ofSeconds (POOL_MAX_IDLE_TIME )).build ();
427+ final HttpClient httpClient = HttpClient .create (provider )
417428 .option (ChannelOption .CONNECT_TIMEOUT_MILLIS , DEFAULT_URL_CONNECTION_TIMEOUT )
418429 .secure (t -> t .sslContext (sslContext ))
419430 .responseTimeout (Duration .ofMillis (DEFAULT_URL_CONNECTION_TIMEOUT ))
@@ -425,14 +436,13 @@ protected Mono<byte[]> downloadBundleToByteArray(TrustBundle bundle, Calendar pr
425436 .clientConnector (new ReactorClientHttpConnector (httpClient ))
426437 .build ()
427438 .get ()
428- .exchange ()
429- .flatMap (response -> response .bodyToMono (ByteArrayResource .class ))
439+ .exchangeToMono (response -> response .bodyToMono (ByteArrayResource .class ))
430440 .map (ByteArrayResource ::getByteArray )
431441 .onErrorResume (ex ->
432442 {
433- log .warn ("Failed to download bundle from URL " + bundle .getBundleURL (), ex );
443+ log .warn ("Failed to download bundle from URL {}" , bundle .getBundleURL (), ex );
434444
435- bundle .setLastRefreshAttempt (new Timestamp ( processAttempStart . getTime (). getTime ()). toLocalDateTime () );
445+ bundle .setLastRefreshAttempt (processAttempStart );
436446 bundle .setLastRefreshError (BundleRefreshError .DOWNLOAD_TIMEOUT .ordinal ());
437447 return bundleRepo .save (bundle )
438448 .then (Mono .empty ());
@@ -442,9 +452,9 @@ protected Mono<byte[]> downloadBundleToByteArray(TrustBundle bundle, Calendar pr
442452 }
443453 catch (Exception e )
444454 {
445- log .warn ("Failed to download bundle from URL " + bundle .getBundleURL (), e );
455+ log .warn ("Failed to download bundle from URL {}" , bundle .getBundleURL (), e );
446456
447- bundle .setLastRefreshAttempt (new Timestamp ( processAttempStart . getTime (). getTime ()). toLocalDateTime () );
457+ bundle .setLastRefreshAttempt (processAttempStart );
448458 bundle .setLastRefreshError (BundleRefreshError .NOT_FOUND .ordinal ());
449459 return bundleRepo .save (bundle )
450460 .then (Mono .empty ());
0 commit comments