From 906f44cef305d59b743758bfb30678433130a5de Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Sat, 12 Jun 2021 19:14:37 +0000 Subject: cacert: port to use buildcatrust This introduces the ability to have additional certificates in the trust store using an override, similar to how the blacklist is done. If the certificates are provided in OpenSSL TRUSTED CERTIFICATE form, then those trust bits will be respected. It also adds a p11-kit compatible trust store output. --- pkgs/data/misc/cacert/default.nix | 215 +++++++++++++++++++------------ pkgs/data/misc/cacert/test-cert-file.crt | 13 ++ 2 files changed, 148 insertions(+), 80 deletions(-) create mode 100644 pkgs/data/misc/cacert/test-cert-file.crt (limited to 'pkgs/data/misc') diff --git a/pkgs/data/misc/cacert/default.nix b/pkgs/data/misc/cacert/default.nix index 404fba91d9d8f..da781f310f4c1 100644 --- a/pkgs/data/misc/cacert/default.nix +++ b/pkgs/data/misc/cacert/default.nix @@ -1,27 +1,23 @@ { lib , stdenv +, writeText , fetchurl , nss -, python3 -, blacklist ? [ ] +, buildcatrust +, blacklist ? [] +, extraCertificateFiles ? [] +, extraCertificateStrings ? [] - # Used for tests only +# Used for tests only , runCommand , cacert , openssl }: let - certdata2pem = fetchurl { - name = "certdata2pem.py"; - urls = [ - "https://salsa.debian.org/debian/ca-certificates/raw/debian/20170717/mozilla/certdata2pem.py" - "https://git.launchpad.net/ubuntu/+source/ca-certificates/plain/mozilla/certdata2pem.py?id=47e49e1e0a8a1ca74deda27f88fe181191562957" - ]; - sha256 = "1d4q27j1gss0186a5m8bs5dk786w07ccyq0qi6xmd2zr1a8q16wy"; - }; + blocklist = writeText "cacert-blocklist.txt" (lib.concatStringsSep "\n" blacklist); + extraCertificatesBundle = writeText "cacert-extra-certificates-bundle.crt" (lib.concatStringsSep "\n\n" extraCertificateStrings); in - stdenv.mkDerivation rec { pname = "nss-cacert"; version = "3.71"; @@ -31,93 +27,152 @@ stdenv.mkDerivation rec { sha256 = "0ly2l3dv6z5hlxs72h5x6796ni3x1bq60saavaf42ddgv4ax7b4r"; }; - outputs = [ "out" "unbundled" ]; + outputs = [ "out" "unbundled" "p11kit" ]; - nativeBuildInputs = [ python3 ]; + nativeBuildInputs = [ buildcatrust ]; configurePhase = '' ln -s nss/lib/ckfw/builtins/certdata.txt - - cat << EOF > blacklist.txt - ${lib.concatStringsSep "\n" (map (c: ''"${c}"'') blacklist)} - EOF - - # copy from the store, otherwise python will scan it for imports - cat "${certdata2pem}" > certdata2pem.py ''; buildPhase = '' - python certdata2pem.py | grep -vE '^(!|UNTRUSTED)' - - for cert in *.crt; do - echo $cert | cut -d. -f1 | sed -e 's,_, ,g' >> ca-bundle.crt - cat $cert >> ca-bundle.crt - echo >> ca-bundle.crt - done + mkdir unbundled + buildcatrust \ + --certdata_input certdata.txt \ + --ca_bundle_input "${extraCertificatesBundle}" ${lib.escapeShellArgs (map (arg: "${arg}") extraCertificateFiles)} \ + --blocklist "${blocklist}" \ + --ca_bundle_output ca-bundle.crt \ + --ca_unpacked_output unbundled \ + --p11kit_output ca-bundle.trust.p11-kit ''; installPhase = '' - mkdir -pv $out/etc/ssl/certs - cp -v ca-bundle.crt $out/etc/ssl/certs + install -D -t "$out/etc/ssl/certs" ca-bundle.crt + + # install p11-kit specific output to p11kit output + install -D -t "$p11kit/etc/ssl/trust-source" ca-bundle.trust.p11-kit + # install individual certs in unbundled output - mkdir -pv $unbundled/etc/ssl/certs - cp -v *.crt $unbundled/etc/ssl/certs - rm $unbundled/etc/ssl/certs/ca-bundle.crt # not wanted in unbundled + install -D -t "$unbundled/etc/ssl/certs" unbundled/*.crt ''; setupHook = ./setup-hook.sh; passthru = { updateScript = ./update.sh; - tests = { - # Test that building this derivation with a blacklist works, and that UTF-8 is supported. - blacklist-utf8 = - let - blacklistCAToFingerprint = { - # "blacklist" uses the CA name from the NSS bundle, but we check for presence using the SHA256 fingerprint. - "CFCA EV ROOT" = "5C:C3:D7:8E:4E:1D:5E:45:54:7A:04:E6:87:3E:64:F9:0C:F9:53:6D:1C:CC:2E:F8:00:F3:55:C4:C5:FD:70:FD"; - "NetLock Arany (Class Gold) Főtanúsítvány" = "6C:61:DA:C3:A2:DE:F0:31:50:6B:E0:36:D2:A6:FE:40:19:94:FB:D1:3D:F9:C8:D4:66:59:92:74:C4:46:EC:98"; - }; - mapBlacklist = f: lib.concatStringsSep "\n" (lib.mapAttrsToList f blacklistCAToFingerprint); - in - runCommand "verify-the-cacert-filter-output" - { - cacert = cacert.unbundled; - cacertWithExcludes = (cacert.override { - blacklist = builtins.attrNames blacklistCAToFingerprint; - }).unbundled; - - nativeBuildInputs = [ openssl ]; - } '' - isPresent() { - # isPresent - for f in $1/etc/ssl/certs/*.crt; do - fingerprint="$(openssl x509 -in "$f" -noout -fingerprint -sha256 | cut -f2 -d=)" - if [[ "x$fingerprint" == "x$3" ]]; then - return 0 + tests = let + isTrusted = '' + isTrusted() { + # isTrusted + for f in $1/etc/ssl/certs/*.crt; do + if ! [[ -s "$f" ]]; then continue; fi + fingerprint="$(openssl x509 -in "$f" -noout -fingerprint -sha256 | cut -f2 -d=)" + if [[ "x$fingerprint" == "x$3" ]]; then + # If the certificate is treated as rejected for TLS Web Server, then we consider it untrusted. + if openssl x509 -in "$f" -noout -text | grep -q '^Rejected Uses:'; then + if openssl x509 -in "$f" -noout -text | grep -A1 '^Rejected Uses:' | grep -q 'TLS Web Server'; then + return 1 + fi fi - done - return 1 - } - - # Ensure that each certificate is in the main "cacert". - ${mapBlacklist (caName: caFingerprint: '' - isPresent "$cacert" "${caName}" "${caFingerprint}" || ({ - echo "CA fingerprint ${caFingerprint} (${caName}) is missing from the CA bundle. Consider picking a different CA for the blacklist test." >&2 - exit 1 - }) - '')} - - # Ensure that each certificate is NOT in the "cacertWithExcludes". - ${mapBlacklist (caName: caFingerprint: '' - isPresent "$cacertWithExcludes" "${caName}" "${caFingerprint}" && ({ - echo "CA fingerprint ${caFingerprint} (${caName}) is present in the cacertWithExcludes bundle." >&2 - exit 1 - }) - '')} - - touch $out + return 0 + fi + done + return 1 + } + ''; + in { + # Test that building this derivation with a blacklist works, and that UTF-8 is supported. + blacklist-utf8 = let + blacklistCAToFingerprint = { + # "blacklist" uses the CA name from the NSS bundle, but we check for presence using the SHA256 fingerprint. + "CFCA EV ROOT" = "5C:C3:D7:8E:4E:1D:5E:45:54:7A:04:E6:87:3E:64:F9:0C:F9:53:6D:1C:CC:2E:F8:00:F3:55:C4:C5:FD:70:FD"; + "NetLock Arany (Class Gold) Főtanúsítvány" = "6C:61:DA:C3:A2:DE:F0:31:50:6B:E0:36:D2:A6:FE:40:19:94:FB:D1:3D:F9:C8:D4:66:59:92:74:C4:46:EC:98"; + }; + mapBlacklist = f: lib.concatStringsSep "\n" (lib.mapAttrsToList f blacklistCAToFingerprint); + in runCommand "verify-the-cacert-filter-output" { + cacert = cacert.unbundled; + cacertWithExcludes = (cacert.override { + blacklist = builtins.attrNames blacklistCAToFingerprint; + }).unbundled; + + nativeBuildInputs = [ openssl ]; + } '' + ${isTrusted} + + # Ensure that each certificate is in the main "cacert". + ${mapBlacklist (caName: caFingerprint: '' + isTrusted "$cacert" "${caName}" "${caFingerprint}" || ({ + echo "CA fingerprint ${caFingerprint} (${caName}) is missing from the CA bundle. Consider picking a different CA for the blacklist test." >&2 + exit 1 + }) + '')} + + # Ensure that each certificate is NOT in the "cacertWithExcludes". + ${mapBlacklist (caName: caFingerprint: '' + isTrusted "$cacertWithExcludes" "${caName}" "${caFingerprint}" && ({ + echo "CA fingerprint ${caFingerprint} (${caName}) is present in the cacertWithExcludes bundle." >&2 + exit 1 + }) + '')} + + touch "$out" + ''; + + # Test that we can add additional certificates to the store, and have them be trusted. + extra-certificates = let + extraCertificateStr = '' + -----BEGIN CERTIFICATE----- + MIIB5DCCAWqgAwIBAgIUItvsAYEIdYDkOIo5sdDYMcUaNuIwCgYIKoZIzj0EAwIw + KTEnMCUGA1UEAwweTml4T1MgY2FjZXJ0IGV4dHJhIGNlcnRpZmljYXRlMB4XDTIx + MDYxMjE5MDQzMFoXDTIyMDYxMjE5MDQzMFowKTEnMCUGA1UEAwweTml4T1MgY2Fj + ZXJ0IGV4dHJhIGNlcnRpZmljYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuP8y + lAm6ZyQt9v/P6gTlV/a9R+D61WjucW04kaegOhg8csiluimYodiSv0Pbgymu+Zxm + A3Bz9QGmytaYTiJ16083rJkwwIhqoYl7kWsLzreSTaLz87KH+rdeol59+H0Oo1Mw + UTAdBgNVHQ4EFgQUCxuHfvqI4YVU5M+A0+aKvd1LrdswHwYDVR0jBBgwFoAUCxuH + fvqI4YVU5M+A0+aKvd1LrdswDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNo + ADBlAjEArgxgjdNmRlSEuai0dzlktmBEDZKy2Iiul+ttSoce9ohfEVYESwO602HW + keVvI56vAjBCro3dc3m2TuktiKO6lQV56PUEyxko4H/sR5pnHlduCGRDlFzQKXf/ + pMMmtj7cVb8= + -----END CERTIFICATE----- ''; + extraCertificateFile = ./test-cert-file.crt; + extraCertificatesToFingerprint = { + # String above + "NixOS cacert extra certificate string" = "A3:20:D0:84:96:97:25:FF:98:B8:A9:6D:A3:7C:89:95:6E:7A:77:21:92:F3:33:E9:31:AF:5E:03:CE:A9:E5:EE"; + + # File + "NixOS cacert extra certificate file" = "88:B8:BE:A7:57:AC:F1:FE:D6:98:8B:50:E0:BD:0A:AE:88:C7:DF:70:26:E1:67:5E:F5:F6:91:27:FF:02:D4:A5"; + }; + mapExtra = f: lib.concatStringsSep "\n" (lib.mapAttrsToList f extraCertificatesToFingerprint); + in runCommand "verify-the-cacert-extra-output" { + cacert = cacert.unbundled; + cacertWithExtras = (cacert.override { + extraCertificateStrings = [ extraCertificateStr ]; + extraCertificateFiles = [ extraCertificateFile ]; + }).unbundled; + + nativeBuildInputs = [ openssl ]; + } '' + ${isTrusted} + + # Ensure that the extra certificate is not in the main "cacert". + ${mapExtra (extraName: extraFingerprint: '' + isTrusted "$cacert" "${extraName}" "${extraFingerprint}" && ({ + echo "'extra' CA fingerprint ${extraFingerprint} (${extraName}) is present in the main CA bundle." >&2 + exit 1 + }) + '')} + + # Ensure that the extra certificates ARE in the "cacertWithExtras". + ${mapExtra (extraName: extraFingerprint: '' + isTrusted "$cacertWithExtras" "${extraName}" "${extraFingerprint}" || ({ + echo "CA fingerprint ${extraFingerprint} (${extraName}) is not present in the cacertWithExtras bundle." >&2 + exit 1 + }) + '')} + + touch "$out" + ''; }; }; diff --git a/pkgs/data/misc/cacert/test-cert-file.crt b/pkgs/data/misc/cacert/test-cert-file.crt new file mode 100644 index 0000000000000..095f38817d20f --- /dev/null +++ b/pkgs/data/misc/cacert/test-cert-file.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7TCCAXSgAwIBAgIUFJB0STXn22fIEDjpncEt++IdFeMwCgYIKoZIzj0EAwIw +LjEsMCoGA1UEAwwjTml4T1MgY2FjZXJ0IGV4dHJhIGNlcnRpZmljYXRlIGZpbGUw +HhcNMjEwNjEyMTkxODA4WhcNMjIwNjEyMTkxODA4WjAuMSwwKgYDVQQDDCNOaXhP +UyBjYWNlcnQgZXh0cmEgY2VydGlmaWNhdGUgZmlsZTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMifTLM5K5xd+guGdKE1+NR7wnEJbxw5INzuMrkg/7jgEIQil4+L2YOF +kU1gxcM80Ot8tQAG5OcSvX1DF6CxunpoCT+hnHqyfqoWFvl89i1BUKjyWCQ5WXEe +nSkuJUmYC6NTMFEwHQYDVR0OBBYEFBE2kNis1ri4fweyNVRmvje83gFQMB8GA1Ud +IwQYMBaAFBE2kNis1ri4fweyNVRmvje83gFQMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDZwAwZAIwUZf1qaSb4cezulV+4B4FoJHY2B/nRVIi/rFD8634YEDT +vcg6dmCi/AqLEzJn7uFMAjBVTu4EVC/mtQCGESFChMeb04fsuhXgttWSwWliVPEG +jkG7u0UNNGaU8dvrjpqRRmA= +-----END CERTIFICATE----- -- cgit 1.4.1