Nix Signing Keys¶
Why Signing Keys?¶
Every Nix build produces one or more store paths. A store path can be a
single file or a directory tree. Each store path gets a name on the format
/nix/store/<hash>-<name>
. For example, if I build the hello
package defined
in revision f945939fd679284d736112d3d5410eb867f3b31c
of
nixpkgs, it will produce the store path
/nix/store/ki1s92r0524hj416rh20v0msxmi4pjvn-hello-2.12.1
.
The hash inside the store path (ki1s92r0524hj416rh20v0msxmi4pjvn
in the above
example) is based on the store paths of the inputs that are required to build
the package, and on the Nix expression that defines the package. Notably, the
store path hash does not depend on the actual contents of the build output.
This means that Nix is able to calculate the store path of a build without
actually performing the build. This is a very powerful concept, because it
allows Nix to perform substitution of store paths. Once Nix knows it needs a
certain store path, instead of actually building that store path, it can ask a
substituter if it already has the path available. If so, Nix will simply
download the contents of the store path and unpack it into its own
/nix/store
.
The most well-known Nix substituter is cache.nixos.org, and it contains prebuilt outputs of almost all packages defined in nixpkgs. This substituter is used by default by Nix.
The fact that store paths are based solely on build inputs (input addressed) also has a disadvantage. There is no way to verify that the store path contents you download from a substituter actually was produced by the same Nix expressions you used when calculating the store path hash. You simply must trust that the substituter ran the build in an acceptable way, and not just stuffed the store path full with malware. This trust is what the Nix signing keys formalises.
When a Nix build is done, Nix can sign it. When doing so it will combine the store path hash with a hash of the actual output, and sign that with an asymmetric cryptograhic key producing a signature. When downloading a store path that has a signature, Nix can check the contents of the build and verify if its signature is valid using the public part of the signing key.
Nix has a
trusted-public-keys
setting that can be set to a list of signing keys that you trust. By default it
is set to the signing key of cache.nixos.org
. Nix will only download store
paths if they have signatures signed by a key listed in the
trusted-public-keys
setting.
Local and Remote Builds¶
You can set up Nix to sign the builds that you run on your own computer, but this does not happen by default and is usually not done. In the local Nix database on your computer, Nix will simply mark any build performed locally as trusted. The same happens with builds that you have performed on a remote builder and then fetched to your local computer. This works fine as long as you don't intend to let other Nix machines use your Nix store for substitution. However, this sort of mixed trust model with locally built store paths and signed store paths is not ideal and can sometimes lead to confusion. You might run into cases where Nix complains about missing signatures when you copy store paths between machines, depending on how you do the copy.
Signing Keys in nixbuild.net¶
In nixbuild.net, every store path must have a signature, there is no concept of "locally trusted" paths. However, to make nixbuild.net behave like an ordinary remote builder and not force users to explicitly configure signing keys, each account on nixbuild.net will get an automatically generated signing key. Every build performed on nixbuild.net will be signed with this key, and every store path uploaded to nixbuild.net will be signed with it. The key will also, implicitly, be part of the trusted-public-keys setting. All of this means that you can use nixbuild.net as a normal remote builder and not worry about signing keys and signatures.
However, if you need more control over signing keys, nixbuild.net has plenty to offer.
You can configure the trusted-public-keys setting just like you can in Nix. But nixbuild.net takes this setting one step further. You will never see any messages of store paths that are missing signatures in nixbuild.net. Instead, the set of trusted public key is what defines your Nix store. If a path is not signed by a trusted public key, it will simply not be visible in your store. This means that you can have different settings for different SSH keys or SSH sessions and they will have completely different views of your store within nixbuild.net.
The set of trusted public keys are available in the authorization context. This allows you to create access tokens with a policy that only allows a certain set of public keys to be trusted. In other words, it will allow users of your account to set the trusted-public-keys setting as they like, but if the resulting list of keys is not a subset of what the access token policy defines, access to nixbuild.net will be denied.
If you want to control what signing key that is used for your builds and
uploads on nixbuild.net, you can set the keys using the
signing-key-for-builds and
signing-key-for-uploads settings. As most
other settings, you can set this differently depending on which SSH key is
used, or even per SSH session. If you set the signing-key
setting using the
SSH session you also avoid having the private signing key stored anywhere in
nixbuild.net. It will only be kept in memory for the duration of your SSH
session and used for signing builds, then discarded.
The reason for why you would want to use different signing keys for builds and uploads is a matter of trust within your nixbuild.net account. When a build runs on nixbuild.net, it is executed in a sandbox that gives higher isolation guarantees than the standard Nix sandbox. You can therefore be sure that the output of the build corresponds to the actual derivation (and hence the output store paths) that triggered the build. However, for uploaded store paths you don't have the same guarantees on how they were built.
You could for example let all developers in your team use your nixbuild.net
account with the default auto-generated signing keys, and then setup your CI to
either use a different upload signing key or even not configure an upload
signing key at all. That way, all builds produced by your CI would exclude any
uploads your developers have performed (as long as those uploads are not also
signed by another trusted key like the cache.nixos.org
key), but still
include any builds that the developers triggered in nixbuild.net. Or your CI
could use a completely different build signing key that only is stored in your
CI.