Custom SSL certificates for JDK

I am using custom certificates (via security.pki.certificates in configuration.nix) but they don’t seem to be propagated to JDK. For instance I don’t see the certificates in /nix/store/yirggs7jnyxbhdmk342f6an4ifkgmbgv-openjdk-17.0.1+12/lib/openjdk/lib/security/cacerts and I am getting SSL errors from Java applications such as davmail. Is there a way to install the custom certificates in JDK?

The wiki page Java - NixOS Wiki hasn’t been updated recently but it does mention that OpenJDK has this script https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/compilers/openjdk/generate-cacerts.pl to convert the certificates to the JKS format that JDK needs but it is only called in JDK8 and only for the standard certificates in the cacerts package:

1 Like

The way security.pki works is by creating certificate bundles that are linked somewhere in /etc/, then it’s up to the program to use them.

The problem is I’m not sure you can configure openjdk to use an alternative path. For openjdk 8, the path is inside the package itself and is no good: it can’t be updated without rebuild the package (and everything depending on it).

If you know how to do this, I’ll be happy to try implement a solution in NixOS.

I gave a quick look at how Fedora solves this, but it doesn’t look applicable:

# Install cacerts symlink needed by some apps which hard-code the path
pushd $RPM_BUILD_ROOT%{_jvmdir}/%{sdkdir -- $suffix}/lib/security
  ln -sf /etc/pki/java/cacerts .
popd

We can’t just replace the file with a symlink because it would break on non-NixOS systems.

After some googling I found that JDKs > 8 have this global system property javax.net.ssl.trustStore to specify the location of an alternative trust store. Can it be used somehow to point to the location where security.pki.certificates &co collect the certificates? Where would global java properties go to in NixOS?

1 Like

It seems openjdk is already patched to read the default path from JAVAX_NET_SSL_TRUSTSTORE. The format of this newer keystore is PKCS12: I’m not sure we have tools to convert the bundle to this, I’ll look into this.

1 Like

I had the same issue and the way I solved it is via passing the path via CLI argument

Yes, a quick workaround is to generate the keystore with

nix-shell -p p11-kit --run 'trust extract --format=java-cacerts --purpose=server-auth cacerts'

and pass it from the command line.

2 Likes

Nice, I found the existing keystore cacerts from the output of my jdk (e.g. by running sbt 'eval System.getProperty("java.home")', copied it to the new location, added additional certificates, then set JAVAX_NET_SSL_TRUSTSTORE to point to this new cacerts keystore and all worked well.

2 Likes

Based on the previous answers, I’ve added the following snippet to my /etc/nixos/configuration.nix:

environment.variables.JAVAX_NET_SSL_TRUSTSTORE =
  let
    caBundle = config.environment.etc."ssl/certs/ca-bundle.crt".source;
    p11kit = pkgs.p11-kit.overrideAttrs (oldAttrs: {
      configureFlags = [
        "--with-trust-paths=${caBundle}"
      ];
    });
  in derivation {
    name = "java-cacerts";
    builder = pkgs.writeShellScript "java-cacerts-builder" ''
      ${p11kit.bin}/bin/trust \
        extract \
        --format=java-cacerts \
        --purpose=server-auth \
        $out
    '';
    system = builtins.currentSystem;
  };
4 Likes

Thank you, this worked like a charm! Why this is not a NixOS default?

Why this is not a NixOS default?

There’s simply not enough people that care about this. I had to do most of the work myself to get security.pki working for the majority of web browsers.

Anyway, I wasn’t aware of JAVAX_NET_SSL_TRUSTSTORE. An environment isn’t ideal in this case because you usually want that as a last resort to override a default.

Nonetheless, It means openjdk can be patched to look for our trust store by default.
It shouldn’t be too hard to do, but I don’t have much time to look into this now.

Thank you for your work, it is greatly appreciated.

I’d like to help with this by patching openjdk. What would be the first step? Open an issue in nixpkgs?

I’ve contributed some new packages, but never something like this.

I would do something like this:

  1. Start looking for references to JAVAX_NET_SSL_TRUSTSTORE in the sources.
    I suppose the implementation of this variable would be a good place to inject a /etc/ssl/certs/java-cacerts for NixOS.

  2. Change the cacert package to also generate such a file, using the trust extract command.

  3. Link the cacert output to /etc, in the security.pki module (nixos/modules/security/ca.nix).

If you get it working, open a PR and ping me. I’ll be glad to review it.

Thanks for sharing, this whole thread was a treasure trove. You snippet ends up containing exactly zero certificates. Any idea why that could be?

$ keytool -list -keystore $JAVAX_NET_SSL_TRUSTSTORE -storepass changeit
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 0 entries

vs.

$ keytool -cacerts -storepass changeit -list | head -n 8
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 89 entries

actalisauthenticationrootca [jdk], Jan 19, 2024, trustedCertEntry, 
Certificate fingerprint (SHA-256): 55:92:60:84:EC:96:3A:64:B9:6E:2A:BE:01:CE:0B:A8:6A:64:FB:FE:BC:C7:AA:B5:AF:C1:55:B3:7F:D7:60:66
addtrustexternalca [jdk], Jan 19, 2024, trustedCertEntry, 

I think the way p11-kit is build has changed and it no longer uses configureFlags. Don’t know how to do it with meson.

this is my current cacerts.nix for reference, maybe this works for you as well.

I was thinking it didn’t work and only realized today that i need to use the openjdk, as other jdk’s don’t contain the patch to read the truststore path from the env var.

# add custom ca certs
{ config, pkgs, system, lib, ... }:

let
  caBundle = config.environment.etc."ssl/certs/ca-certificates.crt".source;
  p11kit = pkgs.p11-kit.overrideAttrs (oldAttrs: {
    mesonFlags = [
      "--sysconfdir=/etc"
      (lib.mesonEnable "systemd" false)
      (lib.mesonOption "bashcompdir" "${placeholder "bin"}/share/bash-completion/completions")
      (lib.mesonOption "trust_paths" (lib.concatStringsSep ":" [
        "${caBundle}"
      ]))
    ];
  });
  javaCaCerts = derivation {
    name = "java-cacerts";
    builder = pkgs.writeShellScript "java-cacerts-builder" ''
      ${p11kit.bin}/bin/trust \
        extract \
        --format=java-cacerts \
        --purpose=server-auth \
        $out
    '';
    inherit system;
    outputs = [ "out" ];
  };
in {
  security = {
    pki = {
      installCACerts = true;

      # append trusted certificate authorities
      certificates = [
        #''
        #  NixOS.org
        #  =========
        #  -----BEGIN CERTIFICATE-----
        #  MIIGUDCCBTigAwIBAgIDD8KWMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ
        #  TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0
        # ...
        #  -----END CERTIFICATE-----
        #''
      ];
    };
  };

  environment.variables = {
    JAVAX_NET_SSL_TRUSTSTORE = javaCaCerts.outPath; # requires a patched version of openjdk (openjdk is already patched, see https://github.com/NixOS/nixpkgs/blob/1b64fc1287991a9cce717a01c1973ef86cb1af0b/pkgs/development/compilers/openjdk/read-truststore-from-env-jdk10.patch)
  };
}
3 Likes

Thanks a bunch. Just fyi security.pki.installCACerts is not an option that exists in nixpkgs (officially).

It’s just just internal so no need to set it yourself.