How do you manage custom CA certificates?

I started using Guix System a few weeks ago, and thus far I’m really impressed. There’s just one thing I just haven’t been able to wrap my head around: how do I trust custom CA certificates?

I read through the Guix documentation on X.509 Certificates but I feel like it isn’t very through.

I have a PEM encoded CA certificate that I want to trust (preferably on the entire system) so that I can curl/wget/whatever without having to specify any environment variables or flags. I guess I should have to modify the nss-certs package to do this?

How do you handle this? I’d love to know.

You may need to write a service for your certificate, e.g. to extend the nss-certs.

I’ve created a package to install custom certificates system-wide. I first created my own channel to host the package definition. It has the following directory structure,

vec
├── packages
│ ├── aux-files
│ │ ├── Issuing-CA3.1.pem
│ │ └── Root_CA_2.0.pem
│ └── custom-certs.scm
└── packages.scm

The top level directory name was chosen arbitrarily, but the rest follow Guix system conventions. Inside the packages.scm file is just a helper function,

(define-module (vec packages)
  #:use-module (guix packages)
  #:use-module (guix diagnostics)
  #:use-module (guix i18n)
  #:use-module (srfi srfi-26)
  #:export (search-auxiliary-file
            %auxiliary-files-path))

(define %auxiliary-files-path
  (make-parameter
   (map (cut string-append <> "/vec/packages/aux-files")
        %load-path)))

(define (search-auxiliary-file file-name)
  "Search the auxiliary FILE-NAME. Return #f if not found."
  (search-path (%auxiliary-files-path) file-name))

and inside custom-certs.scm is the package definition.

(define-module (vec packages custom-certs)
  #:use-module (guix gexp)
  #:use-module (guix packages)
  #:use-module (guix build-system trivial)
  #:use-module ((guix licenses) #:prefix license:)
  #:use-module (gnu packages perl)
  #:use-module (gnu packages tls)
  #:use-module (vec packages))


(define nonfree(
  (@@ (guix licenses) license) "Nonfree"
           "."
           "This a nonfree license."))

(define-public custom-certs
  (package
    (name "custom-certs")
    (version "3.1")
    (source #f)
    (build-system trivial-build-system)
    (arguments
     '(#:modules ((guix build utils))
       #:builder
       (begin
         (use-modules (guix build utils))
         (let ((root-cert (assoc-ref %build-inputs "root-cert"))
               (issuing-cert (assoc-ref %build-inputs "issuing-cert"))
               (out (string-append (assoc-ref %outputs "out") "/etc/ssl/certs"))
               (openssl (assoc-ref %build-inputs "openssl"))
               (perl (assoc-ref %build-inputs "perl")))
           (mkdir-p out)
           (for-each
             (lambda (cert)
               (copy-file cert (string-append out "/" (strip-store-file-name cert))))
             (list root-cert issuing-cert))

           ;; Create hash symlinks suitable for OpenSSL ('SSL_CERT_DIR' and
           ;; similar.)
           (chdir (string-append %output "/etc/ssl/certs"))
           (invoke (string-append perl "/bin/perl")
                   (string-append openssl "/bin/c_rehash")
                   ".")))))
    (native-inputs
      (list openssl perl)) ;for 'c_rehash'
    (inputs
      `(("root-cert", (search-auxiliary-file "Root_CA_2.0.pem"))
        ("issuing-cert", (search-auxiliary-file "Issuing-CA3.1.pem"))))
    (synopsis "Custome Certificates")
    (description "This package provides certificates for an internal network.")
    (home-page "https://www.example.com")
    (license nonfree)))

I marked this as non-free since it is for my company’s internal network, choose whichever license you want for yours. Also, the certs must be in .pem format for Guix’s bundling step to work.

After adding the channel, this package can be included to your system config, or user package list just like any other package.

See nss-certs and nss-certs-for-test in ./gnu/packages/nss.scm… or just run guix edit nss-certs to jump to it with $EDITOR.

the nss-certs package inherits its source from nss. it gets pulled from https://ftp.mozilla.org/pub/mozilla.org/security/nss/... and is validated by hash.

@spk44’s suggestion is in the right direction. Some versioning on the (source ...) that the .pem files are fetched from would be essential later on.

I think the files just need to be in /etc/ssl/certs and $SSL_CERT_* vars need to be get set in the system profile. idk i could be wrong.

;; maybe not typically necessary
(native-search-paths
      (list $SSL_CERT_DIR
            $SSL_CERT_FILE))

idk authoritatively. it depends on:

  • how you want to inject the trust: system-wide, for a user’s profile, for an application/browser/container.
  • whether you care about certificate bundles ending up in /gnu/store on build machines
  • how you want to manage/rotate/revoke trust later. in some cases, resetting trust could require restarting services/apps.

in the past, i think i tried using the extra-special-file which works for a personal system definition… but probably a bad approach.

for managing private x509 trust, i imagine this beats ansible & other config tools by a pretty wide margin.

For actual secrets management, it would be worth checking out sops-nix, EmergentMind/nix-config and EmergentMind/nix-secrets-reference.

There’s also fishinthecalculator/sops-guix, which i had always wanted to use.