Skip to content

Commit 55da73f

Browse files
author
Greg Meyer
authored
Merge pull request #20 from DirectProjectJavaRI/develop
Releasing 8.0.3
2 parents 521067a + def6de2 commit 55da73f

28 files changed

+168
-236
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ hs_err_pid*
2525
/.project
2626
/.classpath
2727
/target/
28+
/.settings/
29+
/.factorypath

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>org.nhind</groupId>
55
<artifactId>config-service-jar</artifactId>
6-
<version>8.1.0-SNAPSHOT</version>
6+
<version>8.0.3</version>
77
<packaging>jar</packaging>
88
<name>NHIN Direct Java RI config service jar</name>
99
<description>NHIN Direct Java RI config service jar.</description>

src/main/java/org/nhindirect/config/processor/impl/DefaultBundleRefreshProcessorImpl.java

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,18 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3131
import java.security.cert.Certificate;
3232
import java.security.cert.CertificateFactory;
3333
import java.security.cert.X509Certificate;
34-
import java.sql.Timestamp;
3534
import java.time.Duration;
3635
import java.time.LocalDateTime;
3736
import java.util.ArrayList;
38-
import java.util.Calendar;
3937
import java.util.Collection;
4038
import java.util.Collections;
4139
import java.util.HashMap;
4240
import java.util.HashSet;
43-
import java.util.Locale;
4441
import java.util.Map;
4542
import java.util.concurrent.TimeUnit;
4643

4744
import org.apache.commons.io.IOUtils;
45+
import org.apache.commons.lang3.StringUtils;
4846
import org.bouncycastle.cms.CMSProcessableByteArray;
4947
import org.bouncycastle.cms.CMSSignedData;
5048
import org.bouncycastle.cms.SignerInformation;
@@ -73,12 +71,13 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
7371
import lombok.extern.slf4j.Slf4j;
7472
import reactor.core.publisher.Mono;
7573
import 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());

src/test/java/org/nhindirect/config/BaseTestPlan.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,7 @@
11
package org.nhindirect.config;
22

3-
import java.io.File;
4-
53
public abstract class BaseTestPlan
64
{
7-
static protected String filePrefix;
8-
9-
static
10-
{
11-
12-
// check for Windows... it doens't like file://<drive>... turns it into FTP
13-
File file = new File("./src/test/resources/bundles/signedbundle.p7b");
14-
if (file.getAbsolutePath().contains(":/"))
15-
filePrefix = "file:///";
16-
else
17-
filePrefix = "file:///";
18-
}
19-
20-
215
public void perform() throws Exception
226
{
237
try

src/test/java/org/nhindirect/config/SpringBaseTest.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
@TestPropertySource("classpath:bootstrap.properties")
3232
public abstract class SpringBaseTest
3333
{
34-
protected String filePrefix;
35-
3634
@Autowired
3735
protected TestRestTemplate testRestTemplate;
3836

@@ -81,14 +79,6 @@ public abstract class SpringBaseTest
8179
@BeforeEach
8280
public void setUp()
8381
{
84-
85-
// check for Windows... it doens't like file://<drive>... turns it into FTP
86-
File file = new File("./src/test/resources/bundles/signedbundle.p7b");
87-
if (file.getAbsolutePath().contains(":/"))
88-
filePrefix = "file:///";
89-
else
90-
filePrefix = "file:///";
91-
9282
try
9383
{
9484
cleanDataStore();
Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
package org.nhindirect.config;
22

3-
import java.io.File;
43
import java.io.InputStream;
5-
import java.io.UnsupportedEncodingException;
6-
import java.net.URLEncoder;
74
import java.security.cert.CertificateFactory;
85
import java.security.cert.X509Certificate;
96

10-
import org.apache.commons.io.FileUtils;
7+
import org.apache.commons.io.IOUtils;
118

129
public class TestUtils
1310
{
14-
private static final String certsBasePath = "src/test/resources/certs/";
15-
private static final String signerBasePath = "src/test/resources/signers/";
16-
private static final String bundleBasePath = "src/test/resources/bundles/";
11+
private static final String certsBasePath = "certs/";
12+
private static final String signerBasePath = "signers/";
13+
private static final String bundleBasePath = "bundles/";
1714

1815
public static byte[] loadBundle(String bundleFileName) throws Exception
1916
{
20-
File fl = new File(bundleBasePath + bundleFileName);
21-
22-
return FileUtils.readFileToByteArray(fl);
23-
17+
return IOUtils.resourceToByteArray(bundleBasePath + bundleFileName, TestUtils.class.getClassLoader());
2418
}
2519

2620
public static X509Certificate loadCert(String certFileName) throws Exception
@@ -35,9 +29,7 @@ public static X509Certificate loadSigner(String authorityFileName) throws Except
3529

3630
protected static final X509Certificate fromFile(String base, String file) throws Exception
3731
{
38-
File fl = new File(base + file);
39-
40-
try (final InputStream data = FileUtils.openInputStream(fl))
32+
try (final InputStream data = TestUtils.class.getClassLoader().getResourceAsStream(base + file))
4133
{
4234
X509Certificate retVal = (X509Certificate)CertificateFactory.getInstance("X.509").generateCertificate(data);
4335
return retVal;
@@ -46,19 +38,4 @@ protected static final X509Certificate fromFile(String base, String file) throws
4638
{
4739
}
4840
}
49-
50-
public static final String uriEscape(String val)
51-
{
52-
try
53-
{
54-
final String escapedVal = URLEncoder.encode(val, "UTF-8");
55-
// Spaces are treated differently in actual URLs. There don't appear to be any other
56-
// differences...
57-
return escapedVal.replace("+", "%20");
58-
}
59-
catch (UnsupportedEncodingException e)
60-
{
61-
throw new RuntimeException("Failed to encode value: " + val, e);
62-
}
63-
}
6441
}

0 commit comments

Comments
 (0)