Skip to content

Commit 2a52e35

Browse files
committed
Trust Buypass SEID2 enterprise certificates
Modifies the alias-generating for certificate to include the name of the containing folder, because the file names for test and production CA certs are the same, and to be able to reliably test that the trusts are containing the expected certifiates. The aliases are not used by the client. Add test that we recognize orgnr in SEID2 certs
1 parent 05fb866 commit 2a52e35

File tree

5 files changed

+170
-74
lines changed

5 files changed

+170
-74
lines changed

src/main/java/no/digipost/signature/client/Certificates.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,27 @@ public enum Certificates {
1010
TEST(
1111
"test/Buypass_Class_3_Test4_CA_3.cer",
1212
"test/Buypass_Class_3_Test4_Root_CA.cer",
13+
14+
"test/BPCl3CaG2HTBS.cer",
15+
"test/BPCl3CaG2STBS.cer",
16+
"test/BPCl3RootCaG2HT.cer",
17+
"test/BPCl3RootCaG2ST.cer",
18+
1319
"test/commfides_test_ca.cer",
1420
"test/commfides_test_root_ca.cer",
21+
1522
"test/digipost_test_root_ca.cert.pem"
23+
1624
),
1725
PRODUCTION(
1826
"prod/BPClass3CA3.cer",
1927
"prod/BPClass3RootCA.cer",
28+
29+
"prod/BPCl3CaG2HTBS.cer",
30+
"prod/BPCl3CaG2STBS.cer",
31+
"prod/BPCl3RootCaG2HT.cer",
32+
"prod/BPCl3RootCaG2ST.cer",
33+
2034
"prod/commfides_ca.cer",
2135
"prod/commfides_root_ca.cer"
2236
);

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

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
package no.digipost.signature.client.core.internal.security;
22

3-
import no.digipost.signature.client.Certificates;
43
import no.digipost.signature.client.core.exceptions.ConfigurationException;
54

65
import javax.net.ssl.SSLContext;
76
import javax.net.ssl.TrustManagerFactory;
87

9-
import java.io.File;
10-
import java.io.FileInputStream;
118
import java.io.IOException;
129
import java.io.InputStream;
1310
import java.net.URL;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.nio.file.Paths;
14+
import java.security.GeneralSecurityException;
1415
import java.security.KeyManagementException;
1516
import java.security.KeyStore;
1617
import java.security.KeyStoreException;
1718
import java.security.NoSuchAlgorithmException;
1819
import java.security.cert.CertificateException;
1920
import java.security.cert.CertificateFactory;
2021
import java.security.cert.X509Certificate;
22+
import java.util.UUID;
23+
import java.util.stream.Stream;
24+
25+
import static java.nio.file.Files.isDirectory;
26+
import static java.util.stream.Collectors.joining;
27+
import static java.util.stream.StreamSupport.stream;
2128

2229
public class TrustStoreLoader {
2330

@@ -31,93 +38,103 @@ public static KeyStore build(ProvidesCertificateResourcePaths hasCertificatePath
3138
}
3239

3340
return trustStore;
34-
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | KeyManagementException e) {
41+
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
3542
throw new ConfigurationException("Unable to load certificates into truststore", e);
3643
}
3744
}
3845

39-
private static void loadCertificatesInto(String certificateFolder, final KeyStore trustStore) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
46+
private static void loadCertificatesInto(String certificateLocation, KeyStore trustStore) {
4047
ResourceLoader certificateLoader;
41-
if (certificateFolder.indexOf("classpath:") == 0) {
42-
certificateLoader = new ClassPathFileLoader(certificateFolder);
48+
if (certificateLocation.startsWith(ClassPathResourceLoader.CLASSPATH_PATH_PREFIX)) {
49+
certificateLoader = new ClassPathResourceLoader(certificateLocation);
4350
} else {
44-
certificateLoader = new FileLoader(certificateFolder);
51+
certificateLoader = new FileLoader(certificateLocation);
4552
}
4653

47-
certificateLoader.forEachFile(new ForFile() {
48-
@Override
49-
void call(String fileName, InputStream contents) {
50-
try {
51-
X509Certificate ca = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(contents);
52-
trustStore.setCertificateEntry(fileName, ca);
53-
} catch (CertificateException | KeyStoreException e) {
54-
throw new ConfigurationException("Unable to load certificate in " + fileName);
55-
}
56-
}
54+
certificateLoader.forEachFile((fileName, contents) -> {
55+
X509Certificate ca = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(contents);
56+
trustStore.setCertificateEntry(fileName, ca);
5757
});
5858

59+
try {
60+
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
61+
tmf.init(trustStore);
5962

60-
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
61-
tmf.init(trustStore);
62-
63-
SSLContext context = SSLContext.getInstance("TLS");
64-
context.init(null, tmf.getTrustManagers(), null);
63+
SSLContext context = SSLContext.getInstance("TLS");
64+
context.init(null, tmf.getTrustManagers(), null);
65+
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
66+
throw new ConfigurationException("Error initializing SSLContext for certification location " + certificateLocation, e);
67+
}
6568
}
6669

67-
private static class ClassPathFileLoader implements ResourceLoader {
70+
71+
private static class ClassPathResourceLoader implements ResourceLoader {
6872

6973
static final String CLASSPATH_PATH_PREFIX = "classpath:";
7074

71-
private final String certificatePath;
75+
private final String resourceName;
7276

73-
ClassPathFileLoader(String certificateFolder) {
74-
this.certificatePath = certificateFolder.substring(CLASSPATH_PATH_PREFIX.length());
77+
ClassPathResourceLoader(String resourceName) {
78+
this.resourceName = resourceName.replaceFirst(CLASSPATH_PATH_PREFIX, "");
7579
}
7680

7781
@Override
78-
public void forEachFile(ForFile forEachFile) throws IOException {
79-
URL contentsUrl = Certificates.class.getResource(certificatePath);
80-
if (contentsUrl == null) {
81-
throw new ConfigurationException(certificatePath + " was not found");
82+
public void forEachFile(ForFile forEachFile) {
83+
URL resourceUrl = TrustStoreLoader.class.getResource(resourceName);
84+
if (resourceUrl == null) {
85+
throw new ConfigurationException(resourceName + " not found on classpath");
8286
}
83-
try (InputStream inputStream = contentsUrl.openStream()){
84-
forEachFile.call(new File(contentsUrl.getFile()).getName(), inputStream);
87+
88+
try (InputStream inputStream = resourceUrl.openStream()) {
89+
forEachFile.call(generateAlias(Paths.get(resourceUrl.getPath())), inputStream);
90+
} catch (Exception e) {
91+
throw new ConfigurationException("Unable to load certificate from classpath: " + resourceName, e);
8592
}
8693
}
8794
}
8895

8996
private static class FileLoader implements ResourceLoader {
90-
private final File path;
97+
private final Path path;
9198

9299
FileLoader(String certificateFolder) {
93-
this.path = new File(certificateFolder);
100+
this.path = Paths.get(certificateFolder);
94101
}
95102

96103
@Override
97-
public void forEachFile(ForFile forEachFile) throws IOException {
98-
if (!this.path.isDirectory()) {
104+
public void forEachFile(ForFile forEachFile) {
105+
if (!isDirectory(path)) {
99106
throw new ConfigurationException("Certificate path '" + this.path + "' is not a directory. " +
100107
"It should point to a directory containing certificates.");
101108
}
102-
File[] files = this.path.listFiles();
103-
if (files == null) {
104-
throw new ConfigurationException("Unable to read certificates from '" + path + "'. Make sure it's the correct path.");
105-
}
106109

107-
for (File file : files) {
108-
try (InputStream contents = new FileInputStream(file)) {
109-
forEachFile.call(file.getName(), contents);
110-
}
110+
try (Stream<Path> files = Files.list(path)) {
111+
files.forEach(file -> {
112+
try (InputStream contents = Files.newInputStream(file)) {
113+
forEachFile.call(generateAlias(file), contents);
114+
} catch (Exception e) {
115+
throw new ConfigurationException("Unable to load certificate from file " + file, e);
116+
}
117+
});
118+
} catch (IOException e) {
119+
throw new ConfigurationException("Error reading certificates from " + path, e);
111120
}
112121
}
113122
}
114123

115124
private interface ResourceLoader {
116-
void forEachFile(ForFile forEachFile) throws IOException;
125+
void forEachFile(ForFile forEachFile);
126+
}
127+
128+
@FunctionalInterface
129+
private interface ForFile {
130+
void call(String fileName, InputStream contents) throws IOException, GeneralSecurityException;
117131
}
118132

119-
private abstract static class ForFile {
120-
abstract void call(String fileName, InputStream contents);
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());
121138
}
122139

123140
}

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

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,114 @@
22

33
import no.digipost.signature.client.ClientConfiguration;
44
import no.digipost.signature.client.TestKonfigurasjon;
5+
import org.hamcrest.Description;
6+
import org.hamcrest.Matcher;
7+
import org.hamcrest.TypeSafeDiagnosingMatcher;
58
import org.junit.jupiter.api.BeforeEach;
69
import org.junit.jupiter.api.Test;
710

811
import java.security.KeyStore;
912
import java.security.KeyStoreException;
13+
import java.util.List;
1014

15+
import static java.util.Collections.list;
1116
import static no.digipost.signature.client.Certificates.PRODUCTION;
1217
import static no.digipost.signature.client.Certificates.TEST;
1318
import static org.hamcrest.MatcherAssert.assertThat;
19+
import static org.hamcrest.Matchers.containsInAnyOrder;
1420
import static org.hamcrest.Matchers.is;
15-
import static org.junit.jupiter.api.Assertions.assertFalse;
1621
import static org.junit.jupiter.api.Assertions.assertTrue;
1722

18-
public class TrustStoreLoaderTest {
23+
class TrustStoreLoaderTest {
1924

2025
private ClientConfiguration.Builder configBuilder;
2126

2227
@BeforeEach
23-
public void setUp() {
28+
void setUp() {
2429
configBuilder = ClientConfiguration.builder(TestKonfigurasjon.CLIENT_KEYSTORE);
2530
}
2631

2732
@Test
28-
public void loads_productions_certificates_by_default() throws KeyStoreException {
29-
KeyStore keyStore = TrustStoreLoader.build(configBuilder.build());
33+
void loads_productions_certificates_by_default() throws KeyStoreException {
34+
KeyStore trustStore = TrustStoreLoader.build(configBuilder.build());
3035

31-
assertThat(keyStore.size(), is(4));
32-
assertTrue(keyStore.containsAlias("bpclass3rootca.cer"), "Trust store should contain BuyPass root CA");
36+
assertTrue(trustStore.containsAlias("prod:bpclass3rootca.cer"), "Trust store should contain BuyPass root CA");
37+
assertThat(trustStore.size(), is(8));
3338
}
3439

3540
@Test
36-
public void loads_productions_certificates() throws KeyStoreException {
41+
void loads_productions_certificates() throws KeyStoreException {
3742
ClientConfiguration config = configBuilder.trustStore(PRODUCTION).build();
38-
KeyStore keyStore = TrustStoreLoader.build(config);
39-
40-
assertThat(keyStore.size(), is(4));
41-
assertTrue(keyStore.containsAlias("bpclass3rootca.cer"), "Trust store should contain bp root ca");
42-
assertFalse(keyStore.containsAlias("buypass_class_3_test4_root_ca.cer"), "Trust store should not contain buypass test root ca");
43+
KeyStore trustStore = TrustStoreLoader.build(config);
44+
45+
assertThat(trustStore, containsExactlyTheAliases(
46+
"prod:bpclass3rootca.cer",
47+
"prod:bpclass3ca3.cer",
48+
"prod:bpcl3rootcag2st.cer",
49+
"prod:bpcl3cag2stbs.cer",
50+
"prod:bpcl3rootcag2ht.cer",
51+
"prod:bpcl3cag2htbs.cer",
52+
"prod:commfides_root_ca.cer",
53+
"prod:commfides_ca.cer"));
4354
}
4455

4556
@Test
46-
public void loads_test_certificates() throws KeyStoreException {
57+
void loads_test_certificates() throws KeyStoreException {
4758
ClientConfiguration config = configBuilder.trustStore(TEST).build();
48-
KeyStore keyStore = TrustStoreLoader.build(config);
49-
50-
assertThat(keyStore.size(), is(5));
51-
assertFalse(keyStore.containsAlias("bpclass3rootca.cer"), "Trust store should not buypass root ca");
52-
assertTrue(keyStore.containsAlias("buypass_class_3_test4_root_ca.cer"), "Trust store should contain buypass test root ca");
59+
KeyStore trustStore = TrustStoreLoader.build(config);
60+
61+
assertThat(trustStore, containsExactlyTheAliases(
62+
"test:buypass_class_3_test4_root_ca.cer",
63+
"test:buypass_class_3_test4_ca_3.cer",
64+
"test:bpcl3rootcag2st.cer",
65+
"test:bpcl3cag2stbs.cer",
66+
"test:bpcl3rootcag2ht.cer",
67+
"test:bpcl3cag2htbs.cer",
68+
"test:commfides_test_root_ca.cer",
69+
"test:commfides_test_ca.cer",
70+
"test:digipost_test_root_ca.cert.pem"));
5371
}
5472

5573
@Test
56-
public void loads_certificates_from_file_location() throws KeyStoreException {
74+
void loads_certificates_from_file_location() throws KeyStoreException {
5775
ClientConfiguration config = configBuilder.trustStore("./src/test/files/certificateTest").build();
58-
KeyStore keyStore = TrustStoreLoader.build(config);
76+
KeyStore trustStore = TrustStoreLoader.build(config);
5977

60-
assertThat(keyStore.size(), is(1));
78+
assertThat(trustStore, containsExactlyTheAliases("certificatetest:commfides_test_ca.cer"));
6179
}
6280

6381

82+
private static Matcher<KeyStore> containsExactlyTheAliases(String ... certAliases) {
83+
return new TypeSafeDiagnosingMatcher<KeyStore>() {
84+
85+
@Override
86+
public void describeTo(Description description) {
87+
description
88+
.appendText("key store containing " + certAliases.length + " certificates with aliases: ")
89+
.appendValueList("", ", ", "", certAliases);
90+
}
91+
92+
@Override
93+
protected boolean matchesSafely(KeyStore keyStore, Description mismatchDescription) {
94+
try {
95+
List<String> actualAliases = list(keyStore.aliases());
96+
Matcher<Iterable<? extends String>> expectedAliases = containsInAnyOrder(certAliases);
97+
if (!expectedAliases.matches(actualAliases)) {
98+
expectedAliases.describeMismatch(actualAliases, mismatchDescription);
99+
return false;
100+
} else if (keyStore.size() != certAliases.length) {
101+
mismatchDescription.appendText("contained " + keyStore.size() + " certificates");
102+
}
103+
return true;
104+
} catch (KeyStoreException e) {
105+
mismatchDescription
106+
.appendText("threw exception retrieving the aliases from the key store: ")
107+
.appendValue(e.getClass().getSimpleName());
108+
throw new RuntimeException(e.getMessage(), e);
109+
}
110+
}
111+
112+
};
113+
}
114+
64115
}

src/test/java/no/digipost/signature/client/security/OrganizationNumberValidationTest.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import no.digipost.signature.client.TestCertificates;
44
import org.junit.jupiter.api.Test;
55

6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.security.cert.CertificateException;
9+
import java.security.cert.CertificateFactory;
610
import java.security.cert.X509Certificate;
711

812
import static no.digipost.signature.client.security.CertificateChainValidation.Result.TRUSTED;
@@ -14,10 +18,20 @@
1418
public class OrganizationNumberValidationTest {
1519

1620
@Test
17-
void trusts_Posten_Norge_SEID1_enterprise_certificate() {
18-
X509Certificate cert = TestCertificates.getOrganizationCertificateKeyStore().getCertificate();
19-
assertThat(cert.getSubjectX500Principal() + "is trusted",
20-
cert, where(c -> new OrganizationNumberValidation("988015814").validate(new X509Certificate[]{c}), is(TRUSTED)));
21+
void trusts_SEID1_enterprise_certificate() {
22+
X509Certificate seid1Cert = TestCertificates.getOrganizationCertificateKeyStore().getCertificate();
23+
assertThat(seid1Cert.getSubjectX500Principal() + "is trusted",
24+
seid1Cert, where(c -> new OrganizationNumberValidation("988015814").validate(new X509Certificate[]{c}), is(TRUSTED)));
25+
}
26+
27+
@Test
28+
void trusts_SEID2_enterprise_certificate() throws CertificateException, IOException {
29+
X509Certificate seid2Cert;
30+
try (InputStream certContents = getClass().getResourceAsStream("/test4-autentiseringssertifikat-vid-europa.cer")) {
31+
seid2Cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certContents);
32+
}
33+
assertThat(seid2Cert.getSubjectX500Principal() + "is trusted",
34+
seid2Cert, where(c -> new OrganizationNumberValidation("100101688").validate(new X509Certificate[]{c}), is(TRUSTED)));
2135
}
2236

2337
@Test
1.56 KB
Binary file not shown.

0 commit comments

Comments
 (0)