Skip to content
Merged
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
37 changes: 37 additions & 0 deletions test/bench/java_ca_preparation_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require_relative "test_helper"

class JavaCaPreparationTest < Minitest::Test
CASES = %w[gradle/opentelemetry-java maven/spring-ai sbt/play sbt/lila].freeze

def run_preparation(id, mise_exit: 0)
Dir.mktmpdir("java preparation ") do |directory|
bin = File.join(directory, "bin")
FileUtils.mkdir_p(bin)
File.write(File.join(bin, "mise"), "#!/bin/sh\necho mise-$1 >> \"$LOG\"\nexit #{mise_exit}\n")
File.write(File.join(bin, "java"), "#!/bin/sh\ntest -f \"$1\" && test \"$2\" = --apply || exit 1\necho java >> \"$LOG\"\n")
FileUtils.chmod(0o755, Dir[File.join(bin, "*")])
log = File.join(directory, "calls")
kase = Bench::Catalog.new(Bench::ROOT).find_case(id)
success = system({"PATH" => "#{bin}:#{ENV.fetch('PATH')}", "JAVA_HOME" => directory,
"TEST_JAVA_HOME" => directory, "BENCH_ROOT" => Bench::ROOT, "LOG" => log},
"bash", "-c", kase.prepare.first, chdir: directory)
[success, File.readlines(log, chomp: true)]
end
end

def test_every_java_case_repairs_each_selected_jdk_after_installation
CASES.each do |id|
success, calls = run_preparation(id)

assert success, id
assert_equal ["mise-trust", "mise-install", *Array.new(id.start_with?("gradle/") ? 2 : 1, "java")], calls, id
end
end

def test_failed_installation_stops_before_java_store_changes
success, calls = run_preparation(CASES.first, mise_exit: 1)

refute success
assert_equal ["mise-trust"], calls
end
end
97 changes: 97 additions & 0 deletions test/bench/java_ca_trust_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require "minitest/autorun"
require "digest"
require "fileutils"
require "open3"
require "tmpdir"

class JavaCaTrustTest < Minitest::Test
ROOT = File.expand_path("../..", __dir__)
HELPER = File.join(ROOT, "tools/shared/JavaCaTrust.java")

def setup
_output, properties, status = Open3.capture3("java", "-XshowSettings:properties", "-version")
skip "A JDK is required for Java trust-store tests" unless status.success?
version = properties[/^\s*java.specification.version = (.+)$/, 1].to_i
skip "Java 17 or newer is required for Java trust-store tests" if version < 17
@java_home = properties[/^\s*java.home = (.+)$/, 1]&.strip
skip "Could not locate the JDK trust store" unless @java_home
@java = File.join(@java_home, "bin/java")
@keytool = File.join(@java_home, "bin/keytool")
rescue Errno::ENOENT
skip "A JDK is required for Java trust-store tests"
end

def with_store
Dir.mktmpdir("java-ca-trust") do |directory|
store = File.join(directory, "cacerts")
FileUtils.cp(File.join(@java_home, "lib/security/cacerts"), store)
File.chmod(0o644, store)
yield store
end
end

def fingerprints(store)
output, error, status = Open3.capture3(@keytool, "-J-Duser.language=en", "-list", "-rfc", "-keystore", store, "-storepass", "changeit")
assert status.success?, error
output.split("Alias name: ").drop(1).to_h do |entry|
alias_name = entry.lines.first.strip
certificate = entry[/-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----/m, 1].unpack1("m")
[alias_name, Digest::SHA256.hexdigest(certificate)]
end
end

def test_repair_preserves_other_roots_permissions_and_original_backup_and_is_idempotent
with_store do |store|
baseline = fingerprints(store)
%w[emsign-ecc-root-g3.pem aaa-certificate-services.pem].each_with_index do |fixture, index|
_output, error, status = Open3.capture3(@keytool, "-importcert", "-noprompt", "-alias", "affected-#{index}",
"-keystore", store, "-storepass", "changeit", "-file", File.join(ROOT, "test/fixtures/ca-trust", fixture))
assert status.success?, error
end
original = File.binread(store)
_output, error, status = Open3.capture3(@java, HELPER, "--store", store)
refute status.success?
assert_includes error, "affected root certificates"
assert_equal original, File.binread(store)

output, error, status = Open3.capture3(@java, HELPER, "--store", store, "--apply")
assert status.success?, error
assert_includes output, "Removed"
expected = baseline.reject { |_alias_name, fingerprint| %w[86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4].include?(fingerprint) }
assert_equal expected, fingerprints(store)
assert_equal original, File.binread("#{store}.before-emudhra-20261021")
assert_equal 0o644, File.stat(store).mode & 0o777

repaired = File.binread(store)
_output, error, status = Open3.capture3(@java, HELPER, "--store", store, "--apply")
assert status.success?, error
assert_equal repaired, File.binread(store)
end
end

def test_invalid_store_is_not_modified
with_store do |store|
File.binwrite(store, "invalid keystore")
_output, _error, status = Open3.capture3(@java, HELPER, "--store", store, "--apply")
refute status.success?
assert_equal "invalid keystore", File.binread(store)
refute File.exist?("#{store}.before-emudhra-20261021")
end
end

def test_existing_backup_is_not_overwritten
with_store do |store|
_output, error, status = Open3.capture3(@keytool, "-importcert", "-noprompt", "-alias", "affected",
"-keystore", store, "-storepass", "changeit", "-file", File.join(ROOT, "test/fixtures/ca-trust/emsign-ecc-root-g3.pem"))
assert status.success?, error
original = File.binread(store)
backup = "#{store}.before-emudhra-20261021"
File.binwrite(backup, "earlier backup")
_output, _error, status = Open3.capture3(@java, HELPER, "--store", store, "--apply")
refute status.success?
assert_equal original, File.binread(store)
assert_equal "earlier backup", File.binread(backup)
assert_empty Dir[File.join(File.dirname(store), "cacerts-emudhra-*.tmp")]
end
end
end
25 changes: 25 additions & 0 deletions test/fixtures/ca-trust/aaa-certificate-services.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions test/fixtures/ca-trust/emsign-ecc-root-g3.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----
11 changes: 11 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Each directory here is a BoringCache adapter command (`docker`, `cargo`, `turbo`, ...).

Case `mise.toml` files pin Node 22.23.4, 24.22.1 or 26.12.1 and Go 1.27.3,
which include the October 2026 certificate security updates. See the
[Node security release](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)
and [Go release history](https://go.dev/doc/devel/release).
Java cases run `tools/shared/JavaCaTrust.java --apply` with every selected JDK
after installation and before workload downloads. It removes only the two
reviewed eMudhra root fingerprints, preserves unrelated entries and permissions,
and saves the original store without replacing an existing backup. A repair
failure stops preparation. The same preparation applies to every provider lane.
Earlier result records retain their original harness and toolchain selection.

```
tools/<tool>/
tool.toml display name and levels: the capability sets lanes implement
Expand Down
2 changes: 1 addition & 1 deletion tools/docker/n8n-runners-distroless/mise.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tools]
node = "26.7.0"
node = "26.12.1"
pnpm = "12.4.2"
2 changes: 1 addition & 1 deletion tools/docker/n8n-runners/mise.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tools]
node = "26.7.0"
node = "26.12.1"
pnpm = "12.4.2"
2 changes: 1 addition & 1 deletion tools/docker/n8n/mise.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tools]
node = "26.7.0"
node = "26.12.1"
pnpm = "12.4.2"
2 changes: 1 addition & 1 deletion tools/go/hugo/mise.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tools]
go = "1.27.1"
go = "1.27.3"

[settings]
go_set_gobin = false
2 changes: 1 addition & 1 deletion tools/gradle/opentelemetry-java/case.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ repo = "open-telemetry/opentelemetry-java"
branch = "main"
start_sha = "bace7e2236b29c8a9da2cbd4b4d069e690a502dd"
directory = "upstream"
prepare = ["mise trust && mise install"]
prepare = ["mise trust && mise install && \"$JAVA_HOME/bin/java\" \"$BENCH_ROOT/tools/shared/JavaCaTrust.java\" --apply && \"$TEST_JAVA_HOME/bin/java\" \"$BENCH_ROOT/tools/shared/JavaCaTrust.java\" --apply"]
check = "test -s \"$(find upstream/sdk/all/build/libs -maxdepth 1 -type f -name 'opentelemetry-sdk-*.jar' -print -quit)\""
shared = [".gradle-user-home/caches/modules-2", ".gradle-user-home/wrapper/dists"]

Expand Down
2 changes: 1 addition & 1 deletion tools/maven/spring-ai/case.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ repo = "spring-projects/spring-ai"
branch = "main"
start_sha = "c9dde9c58bac76efd20eef2f344c24a43787c7b2"
directory = "upstream"
prepare = ["mise trust && mise install"]
prepare = ["mise trust && mise install && \"$JAVA_HOME/bin/java\" \"$BENCH_ROOT/tools/shared/JavaCaTrust.java\" --apply"]
check = "test -s \"$(find upstream/spring-ai-commons/target -maxdepth 1 -type f -name 'spring-ai-commons-*.jar' -print -quit)\""
shared = [".m2/repository", ".m2/wrapper/dists"]

Expand Down
2 changes: 1 addition & 1 deletion tools/moon/zitadel/mise.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[tools]
node = "24.13.0"
node = "24.22.1"
2 changes: 1 addition & 1 deletion tools/nx/storybook/mise.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[tools]
node = "22.22.3"
node = "22.23.4"
2 changes: 1 addition & 1 deletion tools/sbt/lila/case.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ start_sha = "fda690020b843721c3814ee7d3dd2c384cf0147d"
rolling_series = 2
directory = "upstream"
prepare = [
"mise trust && mise install",
"mise trust && mise install && \"$JAVA_HOME/bin/java\" \"$BENCH_ROOT/tools/shared/JavaCaTrust.java\" --apply",
"printf '\\naddRemoteCachePlugin\\n' >> upstream/project/plugins.sbt",
"cd upstream && cp .sbtopts.default .sbtopts && cp conf/application.conf.default conf/application.conf",
"cd upstream && TZ=UTC git log -1 --date=iso-strict-local --pretty='format:app.version.commit = \"%H\"%napp.version.date = \"%ad\"' > conf/version.conf",
Expand Down
2 changes: 1 addition & 1 deletion tools/sbt/play/case.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ start_sha = "07e5d2357243fc9d5362a71e8c8fa73d13f21f5b"
rolling_series = 2
directory = "upstream"
prepare = [
"mise trust && mise install",
"mise trust && mise install && \"$JAVA_HOME/bin/java\" \"$BENCH_ROOT/tools/shared/JavaCaTrust.java\" --apply",
"git -C upstream fetch --quiet --unshallow --tags origin",
"printf '\\naddRemoteCachePlugin\\n' >> upstream/project/plugins.sbt",
"cp overlay/version.sbt upstream/version.sbt",
Expand Down
82 changes: 82 additions & 0 deletions tools/shared/JavaCaTrust.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.HexFormat;
import java.util.Map;
import java.util.Set;

public class JavaCaTrust {
private static final Set<String> DISTRUSTED_ROOTS = Set.of(
"86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b",
"d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4"
);
private static final char[] STORE_PASSWORD = "changeit".toCharArray();

public static void main(String[] args) throws Exception {
boolean apply = false;
Path store = Path.of(System.getProperty("java.home"), "lib", "security", "cacerts");
for (int index = 0; index < args.length; index++) {
if (args[index].equals("--apply")) {
apply = true;
} else if (args[index].equals("--store") && index + 1 < args.length) {
store = Path.of(args[++index]);
} else {
throw new IllegalArgumentException("Usage: JavaCaTrust.java [--apply] [--store PATH]");
}
}
store = store.toRealPath();
byte[] original = Files.readAllBytes(store);
KeyStore certificates = KeyStore.getInstance(store.toFile(), STORE_PASSWORD);
Map<String, String> before = fingerprints(certificates);
Map<String, String> expected = new HashMap<>(before);
expected.entrySet().removeIf(entry -> DISTRUSTED_ROOTS.contains(entry.getValue()));
int removed = before.size() - expected.size();
if (removed == 0) {
System.out.println("Java trust store rejects the two affected root fingerprints.");
return;
}
if (!apply) {
throw new IllegalStateException("Java trust store contains " + removed + " affected root certificates.");
}
for (String alias : before.keySet()) {
if (!expected.containsKey(alias)) certificates.deleteEntry(alias);
}
Path candidate = Files.createTempFile(store.getParent(), "cacerts-emudhra-", ".tmp");
try {
Files.copy(store, candidate, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
try (var output = Files.newOutputStream(candidate)) {
certificates.store(output, STORE_PASSWORD);
}
if (!fingerprints(KeyStore.getInstance(candidate.toFile(), STORE_PASSWORD)).equals(expected)) {
throw new IllegalStateException("Candidate changed unrelated trust entries; original store is unchanged.");
}
if (!MessageDigest.isEqual(original, Files.readAllBytes(store))) {
throw new IllegalStateException("Trust store changed concurrently; candidate was not applied.");
}
Path backup = store.resolveSibling(store.getFileName() + ".before-emudhra-20261021");
Files.copy(store, backup, StandardCopyOption.COPY_ATTRIBUTES);
Files.move(candidate, store, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Removed " + removed + " affected Java root certificates; backup: " + backup);
} finally {
Files.deleteIfExists(candidate);
}
}

private static Map<String, String> fingerprints(KeyStore store) throws Exception {
Map<String, String> result = new HashMap<>();
var aliases = store.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (!store.isCertificateEntry(alias)) {
throw new IllegalStateException("Refusing a store containing non-certificate entries.");
}
byte[] encoded = store.getCertificate(alias).getEncoded();
result.put(alias, HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(encoded)));
}
if (result.isEmpty()) throw new IllegalStateException("Refusing an empty trust store.");
return result;
}
}
2 changes: 1 addition & 1 deletion tools/turbo/n8n/mise.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tools]
node = "26.5.1"
node = "26.12.1"
pnpm = "12.4.2"
Loading