Skip to content

Commit b5681e0

Browse files
committed
Fix unsafe implementation to generate cert aliases
Was based on an assumption that a path string obtained from a classpath URL can always be converted to a Path instance, which is not the case, esp. on Windows. Doing e.g. Paths.get("file:/C:/path/WEB-INF/lib/lib.jar!/file/in/jar") will make sun.nio.fs.WindowsPathParser throw an InvalidPathException.
1 parent 2a52e35 commit b5681e0

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

src/main/java/no/digipost/signature/client/core/internal/security/TrustStoreLoader.java

+20-9
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@
1919
import java.security.cert.CertificateException;
2020
import java.security.cert.CertificateFactory;
2121
import java.security.cert.X509Certificate;
22-
import java.util.UUID;
22+
import java.util.concurrent.atomic.AtomicInteger;
2323
import java.util.stream.Stream;
2424

2525
import static java.nio.file.Files.isDirectory;
26-
import static java.util.stream.Collectors.joining;
27-
import static java.util.stream.StreamSupport.stream;
2826

2927
public class TrustStoreLoader {
3028

@@ -86,7 +84,7 @@ public void forEachFile(ForFile forEachFile) {
8684
}
8785

8886
try (InputStream inputStream = resourceUrl.openStream()) {
89-
forEachFile.call(generateAlias(Paths.get(resourceUrl.getPath())), inputStream);
87+
forEachFile.call(generateAlias(resourceName), inputStream);
9088
} catch (Exception e) {
9189
throw new ConfigurationException("Unable to load certificate from classpath: " + resourceName, e);
9290
}
@@ -130,12 +128,25 @@ private interface ForFile {
130128
void call(String fileName, InputStream contents) throws IOException, GeneralSecurityException;
131129
}
132130

133-
private static String generateAlias(Path location) {
134-
return stream(location.normalize().spliterator(), false)
135-
.reduce((e1, e2) -> e1.getFileName().resolve(e2))
136-
.map(nameBase -> stream(nameBase.spliterator(), false).map(Path::toString).collect(joining(":")))
137-
.orElseGet(() -> UUID.randomUUID().toString());
131+
static String generateAlias(Path location) {
132+
return generateAlias(location.toString());
138133
}
139134

135+
private static final AtomicInteger aliasSequence = new AtomicInteger();
136+
137+
static String generateAlias(String resourceName) {
138+
if (resourceName == null || resourceName.trim().isEmpty()) {
139+
return "certificate-alias-" + aliasSequence.getAndIncrement();
140+
}
141+
String[] splitOnSlashes = resourceName.split("/");
142+
int size = splitOnSlashes.length;
143+
if (size == 1) {
144+
return splitOnSlashes[0];
145+
} else {
146+
return splitOnSlashes[size - 2] + ":" + splitOnSlashes[size - 1];
147+
}
148+
}
149+
150+
140151
}
141152

src/test/java/no/digipost/signature/client/core/internal/security/TrustStoreLoaderTest.java

+58
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,33 @@
66
import org.hamcrest.Matcher;
77
import org.hamcrest.TypeSafeDiagnosingMatcher;
88
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Nested;
910
import org.junit.jupiter.api.Test;
1011

12+
import java.nio.file.Paths;
1113
import java.security.KeyStore;
1214
import java.security.KeyStoreException;
1315
import java.util.List;
16+
import java.util.stream.IntStream;
17+
import java.util.stream.Stream;
1418

1519
import static java.util.Collections.list;
20+
import static java.util.stream.Collectors.toList;
1621
import static no.digipost.signature.client.Certificates.PRODUCTION;
1722
import static no.digipost.signature.client.Certificates.TEST;
23+
import static no.digipost.signature.client.core.internal.security.TrustStoreLoader.generateAlias;
1824
import static org.hamcrest.MatcherAssert.assertThat;
1925
import static org.hamcrest.Matchers.containsInAnyOrder;
26+
import static org.hamcrest.Matchers.everyItem;
27+
import static org.hamcrest.Matchers.hasSize;
2028
import static org.hamcrest.Matchers.is;
29+
import static org.hamcrest.Matchers.matchesRegex;
30+
import static org.hamcrest.Matchers.notNullValue;
31+
import static org.junit.jupiter.api.Assertions.assertAll;
2132
import static org.junit.jupiter.api.Assertions.assertTrue;
33+
import static org.quicktheories.QuickTheory.qt;
34+
import static org.quicktheories.generators.SourceDSL.strings;
35+
import static uk.co.probablyfine.matchers.Java8Matchers.where;
2236

2337
class TrustStoreLoaderTest {
2438

@@ -78,6 +92,50 @@ void loads_certificates_from_file_location() throws KeyStoreException {
7892
assertThat(trustStore, containsExactlyTheAliases("certificatetest:commfides_test_ca.cer"));
7993
}
8094

95+
@Nested
96+
class GenerateAlias {
97+
@Test
98+
void generateAliasFromUnixPath() {
99+
assertThat(generateAlias(Paths.get("/blah/blah/funny/env/MyCert.cer")), is("env:MyCert.cer"));
100+
}
101+
102+
@Test
103+
void generateAliasFromWindowsPath() {
104+
assertThat(generateAlias(Paths.get("C:/blah/blah/funny/env/MyCert.cer")), is("env:MyCert.cer"));
105+
}
106+
107+
@Test
108+
void generateAliasFromUnixFileInJarUrlString() {
109+
assertThat(generateAlias("/blah/fun/prod/WEB-INF/lib/mylib.jar!/certificates/env/MyCert.cer"), is("env:MyCert.cer"));
110+
}
111+
112+
@Test
113+
void generateAliasFromWindowsFileInJarUrlString() {
114+
assertThat(generateAlias("file:/C:/blah/fun/prod/WEB-INF/lib/mylib.jar!/certificates/env/MyCert.cer"), is("env:MyCert.cer"));
115+
}
116+
117+
@Test
118+
void generateUniqueDefaultAliasesForNullsAndEmptyStrings() {
119+
List<String> defaultAliases = Stream.<String>of(null, "", " ", " \n ").map(s -> TrustStoreLoader.generateAlias(s)).collect(toList());
120+
int aliasCount = defaultAliases.size();
121+
assertAll("default aliases",
122+
() -> assertThat(defaultAliases, everyItem(matchesRegex("certificate-alias-\\d+"))),
123+
() -> assertAll(IntStream.range(0, aliasCount).mapToObj(defaultAliases::get).map(alias -> () -> {
124+
List<String> otherAliases = defaultAliases.stream().filter(a -> !alias.equals(a)).collect(toList());
125+
assertThat("other alises than '" + alias + "'", otherAliases, hasSize(aliasCount - 1));
126+
})));
127+
}
128+
129+
@Test
130+
void alwaysGeneratesAnAlias() {
131+
qt()
132+
.forAll(strings().allPossible().ofLengthBetween(0, 100))
133+
.checkAssert(s -> assertThat(s, where(TrustStoreLoader::generateAlias, notNullValue())));
134+
}
135+
136+
137+
}
138+
81139

82140
private static Matcher<KeyStore> containsExactlyTheAliases(String ... certAliases) {
83141
return new TypeSafeDiagnosingMatcher<KeyStore>() {

0 commit comments

Comments
 (0)