Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/main/java/org/jetbrains/nativecerts/win32/Crypt32ExtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,26 @@ public static Collection<X509Certificate> getCustomTrustedRootCertificates() {
HashSet<X509Certificate> result = new HashSet<>();

for (Map.Entry<String, Integer> entry : customTrustedCertificatesLocations.entrySet()) {
List<X509Certificate> list = gatherEnterpriseCertsForLocation(entry.getValue(), "ROOT");
List<X509Certificate> root = gatherEnterpriseCertsForLocation(entry.getValue(), "ROOT");
List<X509Certificate> intermediates = gatherEnterpriseCertsForLocation(entry.getValue(), "CA");

if (LOGGER.isLoggable(Level.FINE)) {
StringBuilder message = new StringBuilder();
message.append("Received ").append(list.size()).append(" certificates from store ROOT / ").append(entry.getKey());
message.append("Received ").append(root.size()).append(" certificates from store ROOT / ").append(entry.getKey());
message.append("Received ").append(intermediates.size()).append(" certificates from store CA (Intermediates) / ").append(entry.getKey());

for (X509Certificate certificate : list) {
message.append("\n ROOT/").append(entry.getKey()).append(": ").append(certificate.getSubjectDN());
for (X509Certificate certificate : root) {
message.append("\n ROOT/").append(entry.getKey()).append(": ").append(certificate.getSubjectX500Principal());
}
for (X509Certificate certificate : intermediates) {
message.append("\n CA/").append(entry.getKey()).append(": ").append(certificate.getSubjectX500Principal());
}

LOGGER.fine(message.toString());
}

result.addAll(list);
result.addAll(root);
result.addAll(intermediates);
}

return result;
Expand Down
15 changes: 14 additions & 1 deletion src/test/java/org/jetbrains/nativecerts/NativeCertsTestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ private static boolean toBooleanChecked(String value) {

public static Path getTestCertificatePath() {
String fileName = "/mock-ca/root.cer";
return getCertificatePath(fileName);
}

public static @NotNull Path getCertificatePath(String certName) {
try {
Path path = Path.of(Objects.requireNonNull(NativeCertsTestUtil.class.getResource(fileName)).toURI());
Path path = Path.of(Objects.requireNonNull(NativeCertsTestUtil.class.getResource(certName)).toURI());
if (!Files.isRegularFile(path)) {
throw new IllegalStateException("Path not found: " + path);
}
Expand All @@ -50,6 +54,15 @@ public static X509Certificate getTestCertificate() {
}
}

public static X509Certificate getTestCertificate(String certificateName) {
try {
byte[] bytes = Files.readAllBytes(getCertificatePath(certificateName));
return NativeTrustedRootsInternalUtils.parseCertificate(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static byte[] getResourceBytes(@NotNull String resourceName) {
try (InputStream stream = NativeCertsTestUtil.class.getResourceAsStream(resourceName)) {
return Objects.requireNonNull(stream, "Resource not found: " + resourceName).readAllBytes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static org.jetbrains.nativecerts.NativeCertsTestUtil.ExitCodeHandling;
import static org.jetbrains.nativecerts.NativeCertsTestUtil.executeProcess;
import static org.jetbrains.nativecerts.NativeCertsTestUtil.executeProcessGetStdout;
import static org.jetbrains.nativecerts.NativeCertsTestUtil.getCertificatePath;
import static org.jetbrains.nativecerts.NativeCertsTestUtil.getTestCertificate;
import static org.jetbrains.nativecerts.NativeCertsTestUtil.getTestCertificatePath;
import static org.jetbrains.nativecerts.NativeCertsTestUtil.isManualTestingEnabled;
Expand Down Expand Up @@ -60,8 +61,6 @@ public void realUserTrustedCertificateTest() throws Exception {
byte[] encoded = getTestCertificate().getEncoded();
String sha1 = sha1hex(encoded);
String sha256 = sha256hex(encoded);
assertEquals("a2133a948547091abc0e0f62aa27bb1927b03f10", sha1);
assertEquals("d5976cf01a27686e61c1ab79907ceed01a9d74a5c7495aad617a7df88fbec204", sha256);

// cleanup just in case it was imported before
removeTrustedCert(sha1);
Expand Down Expand Up @@ -91,8 +90,65 @@ public void realUserTrustedCertificateTest() throws Exception {
}
}

@Test
public void intermediateCACertsAreIncluded() throws Exception {
Assume.assumeTrue(isManualTestingEnabled);

X509Certificate rootCertificate = getTestCertificate();
byte[] rootEncoded = rootCertificate.getEncoded();
String sha1Root = sha1hex(rootEncoded);

String intermediateCertResourcePath = "/mock-ca/intermediate-ca.pem";
X509Certificate intermediateCertificate = getTestCertificate(intermediateCertResourcePath);
byte[] intermediateEncoded = intermediateCertificate.getEncoded();
String sha1Intermediate = sha1hex(intermediateEncoded);

// cleanup just in case it was imported before
removeTrustedCert(sha1Root);
removeTrustedCert(sha1Intermediate, "CA");

try {
Collection<X509Certificate> rootsBefore = Crypt32ExtUtil.getCustomTrustedRootCertificates();
assertFalse(rootsBefore.contains(rootCertificate));

Assert.assertFalse(verifyCert(sha1Root));

executeProcess(
List.of("certutil", "-user", "-addstore", "Root", getTestCertificatePath().toString())
);
assertTrue(verifyCert(sha1Root));

String intermediateCertPath = getCertificatePath(intermediateCertResourcePath).toString();
executeProcess(
List.of("certutil", "-user", "-addstore", "CA", intermediateCertPath)
);

Collection<X509Certificate> rootsAfter = Crypt32ExtUtil.getCustomTrustedRootCertificates();
assertTrue(rootsAfter.contains(rootCertificate));
assertTrue(rootsAfter.contains(intermediateCertificate));

assertTrue(removeTrustedCert(sha1Root));
Assert.assertFalse(verifyCert(sha1Root));

assertTrue(removeTrustedCert(sha1Intermediate, "CA"));
Assert.assertFalse(verifyCert(sha1Intermediate));

Collection<X509Certificate> rootsAfterRemoval = Crypt32ExtUtil.getCustomTrustedRootCertificates();
assertFalse(rootsAfterRemoval.contains(rootCertificate));
assertFalse(rootsAfterRemoval.contains(intermediateCertificate));
} finally {
// always clean-up
removeTrustedCert(sha1Root);
removeTrustedCert(sha1Intermediate);
}
}

private boolean removeTrustedCert(String sha1) {
String out = executeProcessGetStdout(ExitCodeHandling.ASSERT, "certutil", "-user", "-delstore", "Root", sha1);
return removeTrustedCert(sha1, "Root");
}

private boolean removeTrustedCert(String sha1, String store) {
String out = executeProcessGetStdout(ExitCodeHandling.ASSERT, "certutil", "-user", "-delstore", store, sha1);
return out.contains("Deleting Certificate");
}

Expand Down
18 changes: 17 additions & 1 deletion src/test/resources/mock-ca/gen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,21 @@ set -e -x -u -o pipefail
openssl genrsa -out root.key 2048
openssl req -x509 -sha256 -nodes -extensions v3_ca -key root.key \
-subj "/O=JETBRAINS/CN=JVM-NATIVE-TRUSTED-ROOTS-MOCK-CA" -days 10950 -out root.crt
rm -f root.key
openssl x509 -inform PEM -in root.crt -outform DER -out root.cer

openssl genrsa -out intermediate.key 2048
openssl req -new -sha256 -nodes -key intermediate.key \
-subj "/O=JETBRAINS/CN=JVM-NATIVE-TRUSTED-ROOTS-MOCK-INTERMEDIATE-CA" -out test-intermediate-ca.csr

openssl x509 -req \
-extensions v3_ca \
-extfile openssl.cnf \
-in test-intermediate-ca.csr \
-CA root.crt \
-CAkey root.key \
-CAcreateserial \
-out intermediate-ca.pem \
-days 10950 \
-sha256

rm -f root.key root.srl test-intermediate-ca.csr intermediate.key
23 changes: 23 additions & 0 deletions src/test/resources/mock-ca/intermediate-ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDyDCCArCgAwIBAgIUA9l90Iqu8TWV2o/6lJ2Glq2SkpswDQYJKoZIhvcNAQEL
BQAwPzESMBAGA1UECgwJSkVUQlJBSU5TMSkwJwYDVQQDDCBKVk0tTkFUSVZFLVRS
VVNURUQtUk9PVFMtTU9DSy1DQTAgFw0yNTAyMjAxMTUxNDJaGA8yMDU1MDIxMzEx
NTE0MlowTDESMBAGA1UECgwJSkVUQlJBSU5TMTYwNAYDVQQDDC1KVk0tTkFUSVZF
LVRSVVNURUQtUk9PVFMtTU9DSy1JTlRFUk1FRElBVEUtQ0EwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDiz+2wA1M4W2IpXfx9L7Pp35DYGVQKLknGHP92
sKordlJuM6tUzSx7ZL3bi2/poV+ud9V+T4EvebvLXo+rULO63SIcpEItHzdFh6lH
OwMXaDGsjmlODMLaGCRr1jLd7GsMr4nhEGKahr9DCE9KMvJaSMxSCopoajgAFz5O
2/x1LupQceULuseb0PDM3RNrDmuz6FdZNAna3FZvOGzR0tWOKjQEqwCWm6sD5hcl
DUxMXPCz6WEm7uyclyiBrNgXXEbXdlmDiA454tfmKOzBMxPVeO7Ot33rIxqfgcIL
+3/ChTmppG2oO2EI6bTtXiR8XhTTk4j5tcYmgyP3fKA2K2MTAgMBAAGjgawwgakw
HQYDVR0OBBYEFNQO+eA4pw4zrTwCcag53FZZ1YoiMHoGA1UdIwRzMHGAFDGKtBRS
cNdeKAr8rcRxHR0ljG5noUOkQTA/MRIwEAYDVQQKDAlKRVRCUkFJTlMxKTAnBgNV
BAMMIEpWTS1OQVRJVkUtVFJVU1RFRC1ST09UUy1NT0NLLUNBghQpTfREnRaALfRs
+w2P4s7yk4MH5zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBHCpID
G89OBpLbsbRNoRcfvAGZcMaHDEw6R0t0PzYGEwfvGHusoF5JjelQ0myOoZaXJoxs
p/UUjp5jPUL8l+usKGTNKRMIuIYnHOCMwu52IwJ5dFN79Sc4J3dX7ze0i6p0jdfY
6ZK1BgLnus74Ynz0sUT9c4UsZhFs1V3YiGkvjDaeWjn192rcvLOSidZm1qgrh9y2
KTrEkSke5GcGj4WLK2I2kZ1OAbQ5/wVU7QcWPY4yuy79gIP0jcoBZEGL2LkChwHD
JD04REnq3DIMIbN//ETBTWsy4bNDXQPt0bZSDHKNbEb76/3RhxpYkkM03pA8Wz/z
5ZxiXY2rY8+1Tmme
-----END CERTIFICATE-----
17 changes: 17 additions & 0 deletions src/test/resources/mock-ca/openssl.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[ v3_ca ]


# Extensions for a typical CA


# PKIX recommendation.

subjectKeyIdentifier=hash

authorityKeyIdentifier=keyid:always,issuer:always

# This is what PKIX recommends but some broken software chokes on critical
# extensions.
#basicConstraints = critical,CA:true
# So we do this instead.
basicConstraints = CA:true
Binary file modified src/test/resources/mock-ca/root.cer
Binary file not shown.
34 changes: 17 additions & 17 deletions src/test/resources/mock-ca/root.crt
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDYTCCAkmgAwIBAgIUBzxADvAEZ7SIsmp4XsZTvWFiElgwDQYJKoZIhvcNAQEL
MIIDYTCCAkmgAwIBAgIUKU30RJ0WgC30bPsNj+LO8pODB+cwDQYJKoZIhvcNAQEL
BQAwPzESMBAGA1UECgwJSkVUQlJBSU5TMSkwJwYDVQQDDCBKVk0tTkFUSVZFLVRS
VVNURUQtUk9PVFMtTU9DSy1DQTAgFw0yNTAyMTQyMDE3MjlaGA8yMDU1MDIwNzIw
MTcyOVowPzESMBAGA1UECgwJSkVUQlJBSU5TMSkwJwYDVQQDDCBKVk0tTkFUSVZF
VVNURUQtUk9PVFMtTU9DSy1DQTAgFw0yNTAyMjAxMTUxNDJaGA8yMDU1MDIxMzEx
NTE0MlowPzESMBAGA1UECgwJSkVUQlJBSU5TMSkwJwYDVQQDDCBKVk0tTkFUSVZF
LVRSVVNURUQtUk9PVFMtTU9DSy1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAO5MY71yLLOVSa2WJ1A7vQnL61huXUIeAYPChCeeXg9L7z9FYoaxMBcX
nkDYPRDPnvOey1INr606VCFonnIKLwc4pxVCuXoKfcPVk/xjBSHDbXa2NT8MHJZl
x9XjMt/yWNbD3Bw5D/mER9zQ17d3oP9QNJJgP1O5lcb2G/D9r0iA6XwsoWpSkBi4
fZlAkrL+rxdMospEVG75NiKOL3QRfieC+8Oou/sMcNCgmgg23DaydLdVAhlSTdxR
q22Q6GLQHwhr2ok3qYRzTjhjksX+BpamDRimx0WioJNwXJNgs08sD72fbwGzg35q
Uijvc9fJhYnZcXhg/7zZwzmMdgR8dNUCAwEAAaNTMFEwHQYDVR0OBBYEFOEV+RVa
CLJozb00HN7MnzGXfsPVMB8GA1UdIwQYMBaAFOEV+RVaCLJozb00HN7MnzGXfsPV
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAC/X1O5CBvuB1azW
CUljYAyREu8S69DSWyxGAlJxT9Ur+VzGh+s4V3aoPvEpvPomVLjx7AGrp7fa+wbE
RzpeIIH5Rx8PVdZIiYclYkkele29f7a/Qdqu6cVqUmcsR69m1EabbyLKh//TyxLv
J/y97D8S4WogrIBoIjB+DdPMz8hvcF0gUJnIBfnwLaHjKiuka5ubdgWwIinmR+Ux
AYLFdrdp0YWUllQP0vNavqXYD+9dxFPpJQda5bSeL6Nhofs7gO7Jr5HdV/yWSSJu
E6XVWWThT712fhjwRc+i/U0A216ISYB0j6dhlaIXMRoYWQtQ8CKzfD0jqVYfBlAc
MAwzUKc=
AQoCggEBAJLb3H9+fU2eSQmamZjBnuAvu+6+6M4Qp9WJklwJYZ/WarF/vP62wpBk
UXniYZX0wt6GhfPshaOLQhatLxpVDf5/XhoyWqOhG9qtPDnOUL9K45e/UVprQC/v
biSta0/GWx50QKbLNaRGQLTopszaqvLGi/MMMAHF+VSXOLI683uiD1YOlUleJ6qR
sLkz2/w+PnwjMHBM4VI49ZgbMIwdnOesMGD+AirYs/KCz77FI+uughX8fxMfV5qB
Ch/vBvfH1a7Mklze5Hwg/xFDPyw6/Jpg/PvznPYekBFqOJKbciu1iRUlHtnVm5Dz
WffiL9h9eHdBgjgRCN0LOkA5Cc3CgiUCAwEAAaNTMFEwHQYDVR0OBBYEFDGKtBRS
cNdeKAr8rcRxHR0ljG5nMB8GA1UdIwQYMBaAFDGKtBRScNdeKAr8rcRxHR0ljG5n
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFkb8V0LwzAqlwxg
qJivDR5MxLo/K7YksvO3Op0Cz9CJGD7Eoqrsx+D0vpgXRey22krGwq2aJkUhb+zE
89gLI00FlCLU+7QkSs54CuPw/aKU4hn1e11Sc0jmDC6BzvhxN4F5WDTehXIYnZMk
c/wEuiJHMXyXd7lZBZbuJX+jaML2Kosr++pi1Lh2fxmDsClXy5FadDF2YIMHHTFa
ow33q2oB9HWh4sjTjqUPngNIUuyMsW268VRCoxvpdMQFxpLJe4jGIeMtqml1k1ID
Iohx/+jhDy2Cg1ckfCxOTEbLg169mCKT627YKZ6sjnzGB3YFGg7CaFUe1HbRB+r9
RQJajAU=
-----END CERTIFICATE-----