Trusting CA certificates in Guix System
I’m going to be honest. It took me frankly an embarassing amount of time to figure out how I could trust custom CA certificates in Guix System. I’ve been using it as my main operating system for almost a year at this point, and I only figured this out recently.
The core of my problem was (apart from not being very good at Scheme or Guix) that
this wasn’t clearly documented anywhere. Mainstream GNU/Linux distros have a clear
way to do this. Just place the PEM encoded files under /usr/local/share/ca-certificates
or /etc/pki/trust/anchors, or something similar, then run update-ca-certificates.
The certificates will then be added to the system wide trust bundle, usually somewhere
under /etc/ssl/certs.
Even Nix, which Guix is originally forked from, has a short and very clear article about
how to do this.
Guix just has this article: X.509 Certificates. Nowhere does it say how do actually ensure certificates end up in the systems trust bundle. It does mention the nss-certs package, which Guix installs by default to provide CA certs from Mozilla.
Being quite new to Scheme and Guix, I got way to hung up on this package.
For some reason I was under the impressions that this package took care of actually generating
the trust bundle at /etc/ssl/certs/ca-certificates.crt, which is does not.
Turns out, I didn’t need to care about nss-certs at all.
If we look at that file, it should be obvious.
$ ls -la /etc/ssl/certs/ca-certificates.crt
lrwxrwxrwx 1 root root 99 1 jan 1970 /etc/ssl/certs/ca-certificates.crt -> /gnu/store/l3y2wisv02pq1qkh6j101crr8bjs0xzr-ca-certificate-bundle/etc/ssl/certs/ca-certificates.crt
It’s not even part of the nss-certs /gnu/store/ item!
Then I found another package, le-certs, a package for installing root and intermediate CA
certs from Let’s Encrypt, and at this point it became quite clear that I’d been an idiot.
Turns out, you can just write a package that ensures some certificates files gets placed
under /etc/ssl/certs and create the appropriate hash symlinks with c_rehash, then Guix
will make sure those certs end up in the system trust bundle.
You can create a package definition anywhere, maybe just in your dotfiles repository, but I opted to create an actual channel since I wanted this package to me installed on both my PC’s, and on my virtual machines (managed via a different central repository).
For reference, here is my Guix channel: https://git.sr.ht/~gunnargrop/guix-channel.
And here is the tree output:
.
├── COPYING
├── gunnargrop
│ ├── auxillary
│ │ └── ca-certs
│ │ ├── gunnar-grop-ca.pem
│ │ └── home-internal-ca.pem
│ ├── packages
│ │ └── pki.scm
│ └── services
│ └── virtualization.scm
└── README.md
Here is what (gunnargrop packages pki) looks like:
(define-module (gunnargrop packages pki)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (guix build-system trivial)
#:use-module (guix packages)
#:use-module (guix gexp)
#:use-module (guix utils)
#:use-module (gnu packages tls)
#:use-module (gnu packages perl))
(define-public grop-certs
(package
(name "grop-certs")
(version "1")
(source #f)
(build-system trivial-build-system)
(inputs
`(("gunnar-grop-root"
,(local-file "../auxillary/ca-certs/gunnar-grop-ca.pem"))
("home-internal-root"
,(local-file "../auxillary/ca-certs/home-internal-ca.pem"))))
(native-inputs
(list openssl perl))
(arguments
(list
#:modules '((guix build utils))
#:builder
#~(begin
(use-modules (guix build utils))
(let ((out (string-append #$output "/etc/ssl/certs"))
(openssl
(assoc-ref %build-inputs "openssl"))
(perl
(assoc-ref %build-inputs "perl"))
(gunnar-grop-root (assoc-ref %build-inputs "gunnar-grop-root"))
(home-internal-root (assoc-ref %build-inputs "home-internal-root")))
(mkdir-p out)
(for-each
(lambda (cert)
(copy-file cert (string-append out "/"
(strip-store-file-name cert))))
(list gunnar-grop-root
home-internal-root))
;; Create hash symlinks suitable for OpenSSL ('SSL_CERT_DIR' and
;; similar.)
(chdir out)
(invoke (string-append perl "/bin/perl")
(string-append openssl "/bin/c_rehash")
".")))))
(home-page "https://grop.dev")
(synopsis "Gunnar Grop CA certificates")
(description
"This package provides a certificate store containing my own
root and intermediate CA certificates.")
(license license:public-domain)))
If you look at the le-certs package you can see that mine is basically a straight copy. The main difference being that my inputs (.pem files) come from local files, instead of being downloaded from the internet.
Anyway, after adding my own channel to /.config/guix/channels.scm I just added grop-certs
to my systems packages definition:
(use-modules (gunnargrop packages pki))
; ...
(packages (append (list curl
grop-certs)
%base-packages))
And voilà! After a system reconfigure my CA certificates are present in the system
trust bundle, and I can finally use curl without specifying CA files.
I hope whoever reads this finds it helpful, as I know I struggled with this particular part of Guix for a long time.