-
Notifications
You must be signed in to change notification settings - Fork 6k
8347067: Load certificates without explicit trust settings in KeyChainStore #22911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c53463f
5102dad
0052cd0
865520d
c1e0860
abb220d
28227c9
db386f9
7cf32dd
b8d9e0f
2d95570
c1176f5
d9605e1
7d22f46
2125a8e
59ceebc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/* | ||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. | ||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
* | ||
* This code is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License version 2 only, as | ||
* published by the Free Software Foundation. | ||
* | ||
* This code is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
* version 2 for more details (a copy is included in the LICENSE file that | ||
* accompanied this code). | ||
* | ||
* You should have received a copy of the GNU General Public License version | ||
* 2 along with this work; if not, write to the Free Software Foundation, | ||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
* | ||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
* or visit www.oracle.com if you need additional information or have any | ||
* questions. | ||
*/ | ||
|
||
import java.nio.file.Path; | ||
import java.security.KeyStore; | ||
import java.security.KeyStoreException; | ||
import java.security.cert.X509Certificate; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Spliterator; | ||
import java.util.Spliterators; | ||
import java.util.stream.StreamSupport; | ||
|
||
import jdk.test.lib.process.ProcessTools; | ||
|
||
import static org.junit.jupiter.api.Assertions.fail; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
/* | ||
* @test | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexeybakhtin quick question on how this should be marked as manual. I see all tests in https://github.com/openjdk/jdk/blob/master/test/jdk/TEST.groups#L256-L259 are manual ones. Or should it be added here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test should be marked as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any idea how I can run the test after making those changes? The test just gets skipped with:
I've checked through: and I saw somewhere mention of passing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was able to run it by setting up
Although it seems it ran anyway without passing manual:
|
||
* @bug 8347067 | ||
* @library /test/lib | ||
* @requires os.family == "mac" | ||
* @summary Check whether loading of certificates from macOS Keychain correctly | ||
* loads intermediate CA certificates | ||
* @run junit/manual CheckMacOSKeyChainIntermediateCATrust | ||
timja marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
public class CheckMacOSKeyChainIntermediateCATrust { | ||
|
||
private static final String DIR = System.getProperty("test.src", "."); | ||
static boolean verbose = false; // avoid too verbose output | ||
|
||
@Test | ||
public void test() throws Throwable { | ||
KeyStore ks = KeyStore.getInstance("KeychainStore"); | ||
ks.load(null, null); | ||
|
||
Iterator<String> iterator = ks.aliases().asIterator(); | ||
List<X509Certificate> certificates = StreamSupport.stream( | ||
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) | ||
.sorted() | ||
.map(alias -> { | ||
try { | ||
return (X509Certificate) ks.getCertificate(alias); | ||
} catch (KeyStoreException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}) | ||
.toList(); | ||
|
||
System.out.println("Verifying expected certificates are trusted"); | ||
|
||
String rootCASubjectName = "CN=Example CA,O=Example,C=US"; | ||
assertThat(containsSubjectName(certificates, rootCASubjectName), | ||
"Root CA not found " + rootCASubjectName, certificates); | ||
|
||
String intermediateCASubjectName = "CN=Example Intermediate CA,O=Example,C=US"; | ||
assertThat(containsSubjectName(certificates, intermediateCASubjectName), | ||
"Intermediate CA not found " + intermediateCASubjectName, certificates); | ||
|
||
String nonTrustedCASubjectName = "CN=Non Trusted Example CA,O=Example,C=US"; | ||
assertThat(not(containsSubjectName(certificates, nonTrustedCASubjectName)), | ||
"Non trusted CA found " + nonTrustedCASubjectName, certificates); | ||
|
||
String nonTrustedIntermediateCASubjectName = "CN=Non Trusted Example Intermediate CA,O=Example,C=US"; | ||
assertThat(not(containsSubjectName(certificates, nonTrustedIntermediateCASubjectName)), | ||
"Non trusted intermediate CA found " + nonTrustedIntermediateCASubjectName, certificates); | ||
} | ||
|
||
@BeforeEach | ||
void setup() { | ||
System.out.println("Adding certificates to key chain"); | ||
addCertificatesToKeyChain(); | ||
} | ||
|
||
@AfterEach | ||
void cleanup() { | ||
System.out.println("Cleaning up"); | ||
deleteCertificatesFromKeyChain(); | ||
} | ||
|
||
private static void addCertificatesToKeyChain() { | ||
String loginKeyChain = getLoginKeyChain(); | ||
|
||
Path caPath = Path.of("%s/%s".formatted(DIR, "test-ca.pem")); | ||
List<String> args = List.of( | ||
"/usr/bin/security", | ||
"add-trusted-cert", | ||
"-k", loginKeyChain, | ||
caPath.toString() | ||
); | ||
executeProcess(args); | ||
|
||
caPath = Path.of("%s/%s".formatted(DIR, "non-trusted-test-ca.pem")); | ||
args = List.of( | ||
"/usr/bin/security", | ||
"add-certificates", | ||
"-k", loginKeyChain, | ||
caPath.toString() | ||
); | ||
executeProcess(args); | ||
|
||
caPath = Path.of("%s/%s".formatted(DIR, "test-intermediate-ca.pem")); | ||
args = List.of( | ||
"/usr/bin/security", | ||
"add-certificates", | ||
"-k", loginKeyChain, | ||
caPath.toString() | ||
); | ||
executeProcess(args); | ||
|
||
} | ||
|
||
private static String getLoginKeyChain() { | ||
return Path.of(System.getProperty("user.home"), "Library/Keychains/login.keychain-db").toString(); | ||
} | ||
|
||
private static void executeProcess(List<String> params) { | ||
System.out.println("Command line: " + params); | ||
try { | ||
ProcessTools.executeProcess(params.toArray(new String[0])) | ||
.shouldHaveExitValue(0); | ||
} catch (Exception e) { | ||
fail("Unexpected exception: " + e); | ||
} | ||
} | ||
|
||
private static void deleteCertificatesFromKeyChain() { | ||
executeProcess( | ||
List.of( | ||
"/usr/bin/security", | ||
"delete-certificate", | ||
"-c", "Non Trusted Example CA", | ||
"-t" | ||
) | ||
); | ||
|
||
executeProcess( | ||
List.of( | ||
"/usr/bin/security", | ||
"delete-certificate", | ||
"-c", "Example CA", | ||
"-t" | ||
) | ||
); | ||
|
||
executeProcess( | ||
List.of( | ||
"/usr/bin/security", | ||
"delete-certificate", | ||
"-c", "Example Intermediate CA", | ||
"-t" | ||
) | ||
); | ||
} | ||
|
||
private static boolean not(boolean condition) { | ||
return !condition; | ||
} | ||
|
||
private static boolean containsSubjectName(List<X509Certificate> certificates, String subjectName) { | ||
return certificates.stream() | ||
.map(cert -> cert.getSubjectX500Principal().getName()) | ||
.anyMatch(name -> name.contains(subjectName)); | ||
} | ||
|
||
private static List<String> getSubjects(List<X509Certificate> certificates) { | ||
return certificates.stream() | ||
.map(cert -> cert.getSubjectX500Principal().getName()) | ||
.toList(); | ||
} | ||
|
||
private static void assertThat(boolean expected, String message, List<X509Certificate> certificates) { | ||
if (!expected) { | ||
String errMessage = message; | ||
if (verbose) { | ||
errMessage += ", subjects: " + String.join(" | ", getSubjects(certificates)); | ||
} | ||
throw new AssertionError(errMessage); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -ex | ||
|
||
cd "$(dirname "$0")" | ||
|
||
openssl genrsa -out root.key 2048 | ||
openssl req -x509 -sha256 -nodes -extensions v3_ca -key root.key -subj "/C=US/O=Example/CN=Example CA" -days 3650 -out test-ca.pem | ||
|
||
openssl genrsa -out intermediate.key 2048 | ||
openssl req -new -sha256 -nodes -key intermediate.key \ | ||
-subj "/C=US/O=Example/CN=Example Intermediate CA" -out test-intermediate-ca.csr | ||
|
||
openssl x509 -req \ | ||
-extensions v3_ca \ | ||
-extfile openssl.cnf \ | ||
-in test-intermediate-ca.csr \ | ||
-CA test-ca.pem \ | ||
-CAkey root.key \ | ||
-CAcreateserial \ | ||
-out test-intermediate-ca.pem \ | ||
-days 3650 \ | ||
-sha256 | ||
|
||
openssl genrsa -out non-trusted-root.key 2048 | ||
openssl req -x509 -sha256 -nodes -extensions v3_ca -key non-trusted-root.key -subj "/C=US/O=Example/CN=Non Trusted Example CA" -days 3650 -out non-trusted-test-ca.pem | ||
|
||
openssl genrsa -out non-trusted-intermediate.key 2048 | ||
openssl req -new -sha256 -nodes -key non-trusted-intermediate.key \ | ||
-subj "/C=US/O=Example/CN=Non Trusted Example Intermediate CA" -out non-trusted-intermediate-ca.csr | ||
|
||
openssl x509 -req \ | ||
-extensions v3_ca \ | ||
-extfile openssl.cnf \ | ||
-in non-trusted-intermediate-ca.csr \ | ||
-CA non-trusted-test-ca.pem \ | ||
-CAkey non-trusted-root.key \ | ||
-CAcreateserial \ | ||
-out non-trusted-intermediate-ca.pem \ | ||
-days 3650 \ | ||
-sha256 | ||
|
||
rm -f non-trusted-root.key root.key test-intermediate-ca.csr intermediate.key test-ca.srl non-trusted-intermediate.key \ | ||
non-trusted-intermediate-ca.csr non-trusted-test-ca.srl |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIDyTCCArGgAwIBAgIUFqJJqie2b55pDsftOOBqkWJuX6owDQYJKoZIhvcNAQEL | ||
BQAwQDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHzAdBgNVBAMMFk5v | ||
biBUcnVzdGVkIEV4YW1wbGUgQ0EwHhcNMjUwMTI3MjEyNDM2WhcNMzUwMTI1MjEy | ||
NDM2WjBNMQswCQYDVQQGEwJVUzEQMA4GA1UECgwHRXhhbXBsZTEsMCoGA1UEAwwj | ||
Tm9uIFRydXN0ZWQgRXhhbXBsZSBJbnRlcm1lZGlhdGUgQ0EwggEiMA0GCSqGSIb3 | ||
DQEBAQUAA4IBDwAwggEKAoIBAQCszrhwXVvR5QkSOoIJg0S5Ij1CqAuYf+WS9Ym6 | ||
itSzk744j4P2pVd7rM+1SfUfVqY7dFoE7JSQBTw3i/ruo7Zl/USuXFZa93w6g3TB | ||
g0sqA2N4UmNm5Y588Dd2cGIBZgNVvwQzsiG6zWdTppH0rL91hgy32M/CGkVH6WaU | ||
nK4xCe5PiheGkvJBn6OaeqWpG0K76fDWmucUzaMv0b+DmRQ4GsXbwloiKndchGzE | ||
/56EcHNAr1mgvnh+ADTXPKjEsHWv+1cQk4ql4Di53GvVJ/n6eh6K8aGD10OyQlIL | ||
Tk82LI+FxiPvRHF/uBUjndgZsK9k/txDm5VY1z4mpMCd2h1nAgMBAAGjga0wgaow | ||
HQYDVR0OBBYEFIhW8ihXHrc2ywJqt9xZbrcYcNzlMHsGA1UdIwR0MHKAFAy/+rvH | ||
8b9QCumbjj7Q6+9hW9ImoUSkQjBAMQswCQYDVQQGEwJVUzEQMA4GA1UECgwHRXhh | ||
bXBsZTEfMB0GA1UEAwwWTm9uIFRydXN0ZWQgRXhhbXBsZSBDQYIUQWptFePVwo9i | ||
HM6KUT93F4mkQvYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAfEsL | ||
/d07gw8xFs8hMTvXK/5PKEw4WuxoytrWDfwIUJpWJBOjcCmlZnSmwdZ12PHQ1mB9 | ||
oHLr/RZKxYYn2MP9GqDrFTV+wAAAiIw5eU92HQOmPEgXIYhzsvfm29qwR1tdiAQE | ||
4tXc7Y3O2B1b7lcJGbhjAt+/RkDcvT7Pi5jYd2F3apsKPo8GsC0zCsX/t7SnOxhj | ||
qmPsEsBphBfE/dzqkow/iVWPGjvaV2rOrspGOfjF+j+APJNqBuH05uFR7GGaQ2Fm | ||
mhh+nWmmDod/rJpQP5ToTdLeYki9DMsaJjqth1VF5rWUhOzfWczdOFD5szrxxJcV | ||
L+iJJspxFIvQpS3Pgg== | ||
-----END CERTIFICATE----- |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIDYTCCAkmgAwIBAgIUQWptFePVwo9iHM6KUT93F4mkQvYwDQYJKoZIhvcNAQEL | ||
BQAwQDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHzAdBgNVBAMMFk5v | ||
biBUcnVzdGVkIEV4YW1wbGUgQ0EwHhcNMjUwMTI3MjEyMzU4WhcNMzUwMTI1MjEy | ||
MzU4WjBAMQswCQYDVQQGEwJVUzEQMA4GA1UECgwHRXhhbXBsZTEfMB0GA1UEAwwW | ||
Tm9uIFRydXN0ZWQgRXhhbXBsZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC | ||
AQoCggEBAKMh6zqtUcvDLwQJTQcKX4XLQ65MrS81OXpzgajZmgd4vgdv/1PtcXkR | ||
Tzqpeyi137BSvn0VM3CM5e4YLHVwg7iarv92v+gEq+sZcErONbdIvHYGp4J9t+1o | ||
YQHfFsGf1juJe6Ey2s4P10FdWLQN+3BMZXwAnaaGCXnYCixs7ocdIpbobUdRLasF | ||
N0NRQ4BZk+vgmqcC69rB66bNUJhkb40mdcqf0aPCpWnd/MHit/NQ/VXyAIEjllZk | ||
i4Jd5aoouOvjI8a7Pp0z+GHFU4RwQjuFfbJvSyeQ5OL1PAn74amwLMnHF+FBJIuW | ||
jwIWmPNDyU9I9WL2YEmi42xhc3R5XIkCAwEAAaNTMFEwHQYDVR0OBBYEFAy/+rvH | ||
8b9QCumbjj7Q6+9hW9ImMB8GA1UdIwQYMBaAFAy/+rvH8b9QCumbjj7Q6+9hW9Im | ||
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHouuX960Xzb4M1m | ||
QcNV6m7mAjZDdAlRkuTq3ba1dycH8hJrsT3uE3YNXEKiBIkGCYcX1KXomCgWNOdR | ||
AFc10nV4GV0Com149kmD5oeBp9auqmaSPWTJQxyi7dj/gjNGCfbjf/GD3O1qmfqN | ||
RjKQqluP0koXZjUqL3LJro91XNMDxcQH0CcDdhHv6R7Ob0UndaW1neIljo8U0JFx | ||
yMA+pIua3QppElvynSBSlK5jSDmuVTLrRM401hx/1isKAUmCoz9zuVzjygxt8bdL | ||
gKu66Ze/7RRUee2HxEOxkw53gYfBan+918UXd6feq3nC8A9g6v2L6M6/UkWeTiLs | ||
4KjanI4= | ||
-----END CERTIFICATE----- |
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 |
Uh oh!
There was an error while loading. Please reload this page.