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 From 91e495708137fd2b9e4f66d01285b53b1569a26a Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Fri, 8 Oct 2021 01:20:51 +0000 Subject: cacert: extract certdata.txt from main package This allows users to specify custom CAs without needing to download the entirety of the NSS source code - just certdata.txt, which should end up in cache.nixos.org. --- pkgs/data/misc/cacert/default.nix | 55 +++++++++++++++++++++++++++------------ pkgs/data/misc/cacert/update.sh | 4 +-- 2 files changed, 40 insertions(+), 19 deletions(-) (limited to 'pkgs/data/misc') diff --git a/pkgs/data/misc/cacert/default.nix b/pkgs/data/misc/cacert/default.nix index da781f310f4c1..49645ee800838 100644 --- a/pkgs/data/misc/cacert/default.nix +++ b/pkgs/data/misc/cacert/default.nix @@ -2,12 +2,14 @@ , stdenv , writeText , fetchurl -, nss , buildcatrust , blacklist ? [] , extraCertificateFiles ? [] , extraCertificateStrings ? [] +# Used by update.sh +, nssOverride ? null + # Used for tests only , runCommand , cacert @@ -17,24 +19,49 @@ let blocklist = writeText "cacert-blocklist.txt" (lib.concatStringsSep "\n" blacklist); extraCertificatesBundle = writeText "cacert-extra-certificates-bundle.crt" (lib.concatStringsSep "\n\n" extraCertificateStrings); + + srcVersion = "3.71"; + version = if nssOverride != null then nssOverride.version else srcVersion; + meta = with lib; { + homepage = "https://curl.haxx.se/docs/caextract.html"; + description = "A bundle of X.509 certificates of public Certificate Authorities (CA)"; + platforms = platforms.all; + maintainers = with maintainers; [ andir fpletz lukegb ]; + license = licenses.mpl20; + }; + certdata = stdenv.mkDerivation { + pname = "nss-cacert-certdata"; + inherit version; + + src = if nssOverride != null then nssOverride.src else fetchurl { + url = "mirror://mozilla/security/nss/releases/NSS_${lib.replaceStrings ["."] ["_"] version}_RTM/src/nss-${version}.tar.gz"; + sha256 = "0ly2l3dv6z5hlxs72h5x6796ni3x1bq60saavaf42ddgv4ax7b4r"; + }; + + dontBuild = true; + + installPhase = '' + runHook preInstall + + mkdir $out + cp nss/lib/ckfw/builtins/certdata.txt $out + + runHook postInstall + ''; + + inherit meta; + }; in stdenv.mkDerivation rec { pname = "nss-cacert"; - version = "3.71"; + inherit version; - src = fetchurl { - url = "mirror://mozilla/security/nss/releases/NSS_${lib.replaceStrings ["."] ["_"] version}_RTM/src/nss-${version}.tar.gz"; - sha256 = "0ly2l3dv6z5hlxs72h5x6796ni3x1bq60saavaf42ddgv4ax7b4r"; - }; + src = certdata; outputs = [ "out" "unbundled" "p11kit" ]; nativeBuildInputs = [ buildcatrust ]; - configurePhase = '' - ln -s nss/lib/ckfw/builtins/certdata.txt - ''; - buildPhase = '' mkdir unbundled buildcatrust \ @@ -176,11 +203,5 @@ stdenv.mkDerivation rec { }; }; - meta = with lib; { - homepage = "https://curl.haxx.se/docs/caextract.html"; - description = "A bundle of X.509 certificates of public Certificate Authorities (CA)"; - platforms = platforms.all; - maintainers = with maintainers; [ andir fpletz lukegb ]; - license = licenses.mpl20; - }; + inherit meta; } diff --git a/pkgs/data/misc/cacert/update.sh b/pkgs/data/misc/cacert/update.sh index 1c286dc6206f7..72d581b9650fa 100755 --- a/pkgs/data/misc/cacert/update.sh +++ b/pkgs/data/misc/cacert/update.sh @@ -28,7 +28,7 @@ BASEDIR="$(dirname "$0")/../../../.." CURRENT_PATH=$(nix-build --no-out-link -A cacert.out) -PATCHED_PATH=$(nix-build --no-out-link -E "with import $BASEDIR {}; let nss_pkg = pkgs.nss_latest or pkgs.nss; in (cacert.overrideAttrs (_: { inherit (nss_pkg) src version; })).out") +PATCHED_PATH=$(nix-build --no-out-link -E "with import $BASEDIR {}; let nss_pkg = pkgs.nss_latest or pkgs.nss; in (cacert.override { nssOverride = nss_pkg; }).out") # Check the hash of the etc subfolder # We can't check the entire output as that contains the nix-support folder @@ -38,5 +38,5 @@ PATCHED_HASH=$(nix-hash "$PATCHED_PATH/etc") if [[ "$CURRENT_HASH" != "$PATCHED_HASH" ]]; then NSS_VERSION=$(nix-instantiate --json --eval -E "with import $BASEDIR {}; nss.version" | jq -r .) - update-source-version cacert "$NSS_VERSION" + update-source-version --version-key=srcVersion cacert.src "$NSS_VERSION" fi -- cgit 1.4.1