about summary refs log tree commit diff
path: root/pkgs/data/misc/cacert/default.nix
blob: be45a9c4646788ce7dd775e530d4a4e0e95e06cb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
{ lib
, stdenv
, writeText
, fetchFromGitHub
, buildcatrust
, blacklist ? []
, extraCertificateFiles ? []
, extraCertificateStrings ? []

# Used by update.sh
, nssOverride ? null

# Used for tests only
, runCommand
, cacert
, openssl
}:

let
  blocklist = writeText "cacert-blocklist.txt" (lib.concatStringsSep "\n" blacklist);
  extraCertificatesBundle = writeText "cacert-extra-certificates-bundle.crt" (lib.concatStringsSep "\n\n" extraCertificateStrings);

  srcVersion = "3.101.1";
  version = if nssOverride != null then nssOverride.version else srcVersion;
  meta = with lib; {
    homepage = "https://curl.haxx.se/docs/caextract.html";
    description = "Bundle of X.509 certificates of public Certificate Authorities (CA)";
    platforms = platforms.all;
    maintainers = with maintainers; [ fpletz lukegb ];
    license = licenses.mpl20;
  };
  certdata = stdenv.mkDerivation {
    pname = "nss-cacert-certdata";
    inherit version;

    src = if nssOverride != null then nssOverride.src else fetchFromGitHub {
      owner = "nss-dev";
      repo = "nss";
      rev = "NSS_${lib.replaceStrings ["."] ["_"] version}_RTM";
      hash = "sha256-KcRiOUbdFnH618MFM6uxmRn+/Jn4QMHtv1BELXrCAX4=";
    };

    dontBuild = true;

    installPhase = ''
      runHook preInstall

      mkdir $out
      cp lib/ckfw/builtins/certdata.txt $out

      runHook postInstall
    '';

    inherit meta;
  };
in
stdenv.mkDerivation rec {
  pname = "nss-cacert";
  inherit version;

  src = certdata;

  outputs = [ "out" "unbundled" "p11kit" ];

  nativeBuildInputs = [ buildcatrust ];

  buildPhase = ''
    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_standard_bundle_output ca-no-trust-rules-bundle.crt \
      --ca_unpacked_output unbundled \
      --p11kit_output ca-bundle.trust.p11-kit
  '';

  installPhase = ''
    install -D -t "$out/etc/ssl/certs" ca-bundle.crt

    # install standard PEM compatible bundle
    install -D -t "$out/etc/ssl/certs" ca-no-trust-rules-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
    install -D -t "$unbundled/etc/ssl/certs" unbundled/*.crt
  '';

  setupHook = ./setup-hook.sh;

  passthru = {
    updateScript = ./update.sh;
    tests = let
      isTrusted = ''
        isTrusted() {
          # isTrusted <unbundled-dir> <ca name> <ca sha256 fingerprint>
          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
              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"
      '';
    };
  };

  inherit meta;
}