diff --git a/.bazelrc b/.bazelrc index baf0214..66aa14b 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,9 +10,16 @@ common --enable_bzlmod # https://bazelbuild.slack.com/archives/C014RARENH0/p1691158021917459?thread_ts=1691156601.420349&cid=C014RARENH0 common --check_direct_dependencies=off +# Enable platform specific options +build --enable_platform_specific_config # Use a hermetic Java version -build --java_runtime_version=remotejdk_17 +build --java_runtime_version=remotejdk_11 + +# Newer versions jdk creates collisions on /tmp +# See: https://github.com/bazelbuild/bazel/issues/3236 +# https://github.com/GoogleContainerTools/rules_distroless/actions/runs/7118944984/job/19382981899?pr=9#step:8:51 +common:linux --sandbox_tmpfs_path=/tmp # Load any settings specific to the current user. # .bazelrc.user should appear in .gitignore so that settings are not shared with team members diff --git a/distroless/private/BUILD.bazel b/distroless/private/BUILD.bazel index bbecf58..5e2ce65 100644 --- a/distroless/private/BUILD.bazel +++ b/distroless/private/BUILD.bazel @@ -2,9 +2,18 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") exports_files([ "cacerts.sh", - "java_keystore.sh", ]) +java_binary( + name = "keystore_binary", + srcs = ["JavaKeyStore.java"], + javacopts = [ + "-Xlint:-options", + ], + main_class = "JavaKeyStore", + visibility = ["//visibility:public"], +) + bzl_library( name = "cacerts", srcs = ["cacerts.bzl"], diff --git a/distroless/private/JavaKeyStore.java b/distroless/private/JavaKeyStore.java new file mode 100644 index 0000000..a2138e3 --- /dev/null +++ b/distroless/private/JavaKeyStore.java @@ -0,0 +1,117 @@ + +// Parts taken from https://github.com/openjdk/jdk17u-dev/blob/a028120220f6fd28e39fe0f6190eb1f5da6a788d/make/jdk/src/classes/build/tools/generatecacerts/GenerateCacerts.java +// https://github.com/GoogleContainerTools/distroless/tree/b1e2203eceb9cc91de0500d71c648e346e1d7b89/cacerts/jksutil +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map.Entry; + +import javax.security.auth.x500.X500Principal; + +/** + * Generate cacerts + */ +class JavaKeyStore { + + private static final int MAGIC = 0xfeedfeed; + private static final int VERSION = 0x02; + private static final int TRUSTED_CERT_TAG = 0x02; + private static final char[] PASSWORD = "changeit".toCharArray(); + private static final String SALT = "Mighty Aphrodite"; + + public static void main(String[] args) throws Exception { + try (FileOutputStream output = new FileOutputStream(args[0])) { + store(output, Arrays.copyOfRange(args, 1, args.length)); + } + } + + public static void store(OutputStream stream, String[] entries) + throws IOException, NoSuchAlgorithmException, CertificateException { + byte[] encoded; // the certificate encoding + CertificateFactory cf = CertificateFactory.getInstance("X509"); + + MessageDigest md = getPreKeyedHash(PASSWORD); + DataOutputStream dos = new DataOutputStream(new DigestOutputStream(stream, md)); + + HashMap certs = new HashMap(); + + for (String entry : entries) { + try (InputStream fis = Files.newInputStream(Path.of(entry))) { + for (Certificate rcert : cf.generateCertificates(fis)) { + X509Certificate cert = (X509Certificate) rcert; + String alias = cert.getSubjectX500Principal().getName(X500Principal.CANONICAL); + certs.put(alias, cert); + } + } + } + + dos.writeInt(MAGIC); + dos.writeInt(VERSION); + dos.writeInt(certs.size()); + + for (Entry entry : certs.entrySet()) { + + X509Certificate cert = entry.getValue(); + String alias = entry.getKey(); + + dos.writeInt(TRUSTED_CERT_TAG); + + // Write the alias + dos.writeUTF(alias); + + // Write the (entry creation) date, which is notBefore of the cert + dos.writeLong(cert.getNotBefore().getTime()); + + // Write the trusted certificate + encoded = cert.getEncoded(); + dos.writeUTF(cert.getType()); + dos.writeInt(encoded.length); + dos.write(encoded); + } + + /* + * Write the keyed hash which is used to detect tampering with + * the keystore (such as deleting or modifying key or + * certificate entries). + */ + byte[] digest = md.digest(); + + dos.write(digest); + dos.flush(); + } + + private static MessageDigest getPreKeyedHash(char[] password) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + + MessageDigest md = MessageDigest.getInstance("SHA"); + byte[] passwdBytes = convertToBytes(password); + md.update(passwdBytes); + Arrays.fill(passwdBytes, (byte) 0x00); + md.update(SALT.getBytes("UTF8")); + return md; + } + + private static byte[] convertToBytes(char[] password) { + int i, j; + byte[] passwdBytes = new byte[password.length * 2]; + for (i = 0, j = 0; i < password.length; i++) { + passwdBytes[j++] = (byte) (password[i] >> 8); + passwdBytes[j++] = (byte) password[i]; + } + return passwdBytes; + } +} \ No newline at end of file diff --git a/distroless/private/java_keystore.bzl b/distroless/private/java_keystore.bzl index aeaea97..9008829 100644 --- a/distroless/private/java_keystore.bzl +++ b/distroless/private/java_keystore.bzl @@ -7,34 +7,15 @@ _DOC = """Create a java keystore (database) of cryptographic keys, X.509 certifi Currently only public X.509 are supported as part of the PUBLIC API contract. """ -def _find_keytool(java): - for f in java.java_runtime.files.to_list(): - if f.basename == "keytool": - return f - fail("java toolchain does not contain `keytool`.") - def _java_keystore_impl(ctx): - jdk = ctx.toolchains["@bazel_tools//tools/jdk:toolchain_type"] - coreutils = ctx.toolchains["@aspect_bazel_lib//lib:coreutils_toolchain_type"] - bsdtar = ctx.toolchains[tar_lib.TOOLCHAIN_TYPE] - keytool = _find_keytool(jdk.java) - jks = ctx.actions.declare_file(ctx.attr.name + ".jks") args = ctx.actions.args() - - args.add(bsdtar.tarinfo.binary) - args.add(keytool) - args.add(coreutils.coreutils_info.bin) args.add(jks) args.add_all(ctx.files.certificates) ctx.actions.run( - executable = ctx.executable._java_keystore_sh, - tools = depset( - [keytool, coreutils.coreutils_info.bin], - transitive = [bsdtar.default.files], - ), + executable = ctx.executable._java_keystore, inputs = ctx.files.certificates, outputs = [jks], arguments = [args], @@ -55,11 +36,10 @@ def _java_keystore_impl(ctx): java_keystore = rule( doc = _DOC, attrs = { - "_java_keystore_sh": attr.label( - allow_single_file = True, + "_java_keystore": attr.label( executable = True, cfg = "exec", - default = ":java_keystore.sh", + default = ":keystore_binary", ), "certificates": attr.label_list( allow_files = True, @@ -70,7 +50,5 @@ java_keystore = rule( implementation = _java_keystore_impl, toolchains = [ tar_lib.TOOLCHAIN_TYPE, - "@bazel_tools//tools/jdk:toolchain_type", - "@aspect_bazel_lib//lib:coreutils_toolchain_type", ], ) diff --git a/distroless/private/java_keystore.sh b/distroless/private/java_keystore.sh deleted file mode 100755 index a709595..0000000 --- a/distroless/private/java_keystore.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -o pipefail -o errexit -o nounset - -readonly bsdtar="$1" -readonly keytool="$2" -readonly coreutils="$3" -readonly output="$4" -shift 4; - -tmp=$(mktemp -d) -while (( $# > 0 )); do - $coreutils csplit --quiet --elide-empty-files -f "$tmp/crt" -b "%02d_$#.crt" -k $1 '/-----BEGIN CERTIFICATE-----/' '{*}' - shift; -done - -certs=$(echo "$tmp"/* | tr " " "\n" | sort -n | tr "\n" " ") - -certopt="no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux,no_extensions" - -for f in $certs; do - subject=$(openssl x509 -in $f -noout -text -certopt $certopt | tr -d " " | tr -d '"') - subject="${subject/#"Subject:"}" - echo "$subject" - $keytool -importcert -keystore $output -storepass changeit -file $f -alias "${subject#"subject="}" -noprompt -done diff --git a/examples/java_keystore/BUILD.bazel b/examples/java_keystore/BUILD.bazel index c285a68..b7fd0ad 100644 --- a/examples/java_keystore/BUILD.bazel +++ b/examples/java_keystore/BUILD.bazel @@ -32,6 +32,6 @@ assert_tar_listing( ./etc/ssl time=1672560000.0 mode=755 gid=0 uid=0 type=dir ./etc/ssl/certs time=1672560000.0 mode=755 gid=0 uid=0 type=dir ./etc/ssl/certs/java time=1672560000.0 mode=755 gid=0 uid=0 type=dir -./etc/ssl/certs/java/cacerts nlink=0 time=1672560000.0 mode=755 gid=0 uid=0 type=file size=6214 cksum=53117108 sha1digest=c84a267b4b57f6e9c18773e26c78da66091c7ea8 +./etc/ssl/certs/java/cacerts nlink=0 time=1672560000.0 mode=755 gid=0 uid=0 type=file size=5349 cksum=3752477219 sha1digest=015078faa5537fcabb6c7e73fe2dedf8241b106d """, ) diff --git a/examples/java_keystore/expected.jks.output b/examples/java_keystore/expected.jks.output index 9739bfc..972b298 100644 --- a/examples/java_keystore/expected.jks.output +++ b/examples/java_keystore/expected.jks.output @@ -1,10 +1,10 @@ -Keystore type: PKCS12 +Keystore type: JKS Keystore provider: SUN Your keystore contains 5 entries -Alias name: c=us,o=amazon,cn=amazonrootca1 -Creation date: Dec 1, 2023 +Alias name: cn=amazon root ca 1,o=amazon,c=us +Creation date: May 26, 2015 Entry type: trustedCertEntry -----BEGIN CERTIFICATE----- @@ -33,8 +33,8 @@ rqXRfboQnoZsG4q5WTP468SQvvG5 ******************************************* -Alias name: c=us,o=digicertinc,ou=www.digicert.com,cn=digicertassuredidrootca -Creation date: Dec 1, 2023 +Alias name: cn=digicert assured id root ca,ou=www.digicert.com,o=digicert inc,c=us +Creation date: Nov 10, 2006 Entry type: trustedCertEntry -----BEGIN CERTIFICATE----- @@ -65,46 +65,8 @@ H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ******************************************* -Alias name: c=us,o=verisign,inc.,ou=verisigntrustnetwork,ou=(c)2008verisign,inc.-forauthorizeduseonly,cn=verisignuniversalrootcertificationauthority -Creation date: Dec 1, 2023 -Entry type: trustedCertEntry - ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - - -******************************************* -******************************************* - - -Alias name: ou=globalsignrootca-r2,o=globalsign,cn=globalsign -Creation date: Dec 1, 2023 +Alias name: cn=globalsign,o=globalsign,ou=globalsign root ca - r2 +Creation date: Dec 15, 2006 Entry type: trustedCertEntry -----BEGIN CERTIFICATE----- @@ -135,8 +97,8 @@ TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ******************************************* -Alias name: ou=globalsignrootca-r3,o=globalsign,cn=globalsign -Creation date: Dec 1, 2023 +Alias name: cn=globalsign,o=globalsign,ou=globalsign root ca - r3 +Creation date: Mar 18, 2009 Entry type: trustedCertEntry -----BEGIN CERTIFICATE----- @@ -166,3 +128,41 @@ WD9f ******************************************* +Alias name: cn=verisign universal root certification authority,ou=(c) 2008 verisign\, inc. - for authorized use only,ou=verisign trust network,o=verisign\, inc.,c=us +Creation date: Apr 2, 2008 +Entry type: trustedCertEntry + +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + + +******************************************* +******************************************* + +