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.

I am entering a new project for work and I need to install some custom certificates in the JDK (11), which of course spells trouble in NixOS and led me here.

Now, I’m a beginner in NixOS and only have a rough idea what these certificates may be required for. This thread seems to contain all the necessary information to do what I need, but, as it is so often for newcomers, it lacks context that explains what to do with those snippets.

It would be tremendously helpful if somebody who truly understands what’s going on could take the time to compile this into a full tutorial that explains how do this start to end, start being “I got theses three certificates I need to install in a JDK (11) and a system that doesn’t yet have a JDK (except one bundled with Android Studio, which I think I can’t/won’t use)” and end being “sudo nixos-rebuild switch”. Ideally, some comments in the nix files would explain what’s happening and why.

In particular, I see all this nix code, but I have no idea where to place my certificates so they’ll be picked up by it. I’m sure I can figure it out. It would just be so helpful if this information was packaged in a way that wouldn’t have me guessing at first. I know, it’s extra work, but it would make entry into this world so much easier. If somebody actually takes this time, I suppose it would also be great to place this info on the Java page, which currently doesn’t have any useful info for JDK 11.

I’ve had ChatGPT compile a full tutorial from some of the provided posts and have it explain it. I did not get the chance to test it, yet. Maybe somebody could use this as a blueprint for a more detailed answer, but check that it’s actually correct and if perhaps there are better solutions that those suggested by the AI? My Chat with ChatGPT

Since your use case seems to be installing custom certificates that are project-specific rather than system-wide, setting JAVAX_NET_SSL_TRUSTSTORE or passing -Djavax.net.ssl.trustStore=... to the JVM command-line might actually be a suitable solution in your case? Or is that somehow not possible?

(as an aside, though I know it doesn’t help you much in the short term: my impression is that setting custom key/truststores at the JVM level is nowadays generally seen as dealing with this at the wrong granularity level: your application may want to use one set of key/truststores for one type of connection and another set for another type of connection. For that reason I think most client and server APIs/libraries now support passing in the key/truststores to be used for the connections managed by that API, in which case you could solve this at the application (configuration) level)