kairos
kairos copied to clipboard
feat: Add PKCS#11 support for platform key(s)
Is your feature request related to a problem? Please describe.
When using auroraboot to build a UKI we should be able to pass a URI for a PKCS#11 device that is holding the RSA private key.
Describe the solution you'd like
Add support to go-ukify and auroraboot to pass in a URI, passcode and/or module link to allow for the private key (primarily db.key) to be stored on a hardware key instead of plaintext on the filesystem.
Describe alternatives you've considered
Keep using a more insecure option of keys in plaintext on the filesystem.
Additional context
For auroraboot something like changing/adding:
&cli.StringFlag{
Name: "sb-key-uri",
Value: "",
Usage: "Override SBKey with a PKCS#11 URI (e.g. 'pkcs11:manufacturer=piv_II;id=%02'). If empty, the default file-based key will be used.",
},
&cli.StringFlag{
Name: "pkcs11-pin",
Value: "",
Usage: "PIN for the PKCS#11 token (YubiKey); required when using a pkcs11 URI",
},
&cli.StringFlag{
Name: "pkcs11-path",
Value: "/usr/lib/libykcs11.so",
Usage: "Path to the PKCS#11 module (default: /usr/lib/libykcs11.so)",
},
...
// Check if the keys directory contains the required files
requiredFiles := []string{"db.der", "db.auth", "KEK.der", "KEK.auth", "PK.der", "PK.auth", "tpm2-pcr-private.pem"}
// If no SBKey override is provided, then require "db.key".
if ctx.String("sb-key-uri") == "" {
requiredFiles = append(requiredFiles, "db.key")
}
...
var sbKey string
if ctx.String("sb-key-uri") != "" {
// Use the provided PKCS#11 URI.
sbKey = ctx.String("sb-key-uri")
} else {
// Otherwise use the default file-based SBKey.
sbKey = filepath.Join(ctx.String("keys"), "db.key")
}
builder := &uki.Builder{
Arch: config.Arch,
Version: kairosVersion,
SdStubPath: stub,
KernelPath: filepath.Join(artifactsTempDir, "vmlinuz"),
InitrdPath: filepath.Join(artifactsTempDir, "initrd"),
Cmdline: entry.Cmdline,
OsRelease: filepath.Join(sourceDir, "etc/os-release"),
OutUKIPath: entry.FileName + ".efi",
PCRKey: filepath.Join(ctx.String("keys"), "tpm2-pcr-private.pem"),
SBKey: sbKey,
SBCert: filepath.Join(ctx.String("keys"), "db.pem"),
SdBootPath: systemdBoot,
OutSdBootPath: outputSystemdBootEfi,
Splash: ctx.String("splash"),
PKCS11Pin: ctx.String("pkcs11-pin"),
PKCS11Path: ctx.String("pkcs11-path"),
}
and adding support for github.com/ThalesIgnite/crypto11 to go-ukifiy and the handlers to support the auroraboot parts above.
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.
nitrokeys seems to cover this and they are pretty cheap for testing https://www.nitrokey.com/products/nitrokeys
I got one for myself to substitute my 2 yubikeys so I migth be able to test this when it arrives.
I just bought some https://www.smartcard-hsm.com/ from the US disti last week, they arrive tomorrow.
You can probably get them from: https://www.cardomatic.de/en/c/smartcard-hsm
The banner on the Nitrokey HSM 2 site said wait till August.
The regular NitroKey can only hold a few keys, the HSM version can hold 10's of them. It also lets you setup security zones and have a few HSMs all hold they same copies of keys without they plaintext ever being exposed. This is what I plan to use for production. I can write a blog on it when we get things moving here.
a thing that its clear is that most of the docs and libs out there are for the ?HSM versions of all this keys, so make sure that we get one. The one mentioned by @bencorrado seems cheap enough that we can get one to add the support in there.
Also this: have a few HSMs all hold they same copies of keys its very interesting becuase it means the whole team could have a a hardware key that contains the "kairos official key" or whatever so we dont have to rely on a single person for this. Still the more people that has it the more chances to lose it soooo...
also having a look at the smartcard-hsm, they seem to cover also our use cases
Ok, I was able to sign an efi file with just the nitrokey start which is very cheap (27 EUR + shipping)
Remember that default PINs for the card are 123456 for User and 12345678 for Admin
Here are the steps
Configure GPG to use PC/SC
Create or edit ~/.gnupg/scdaemon.conf to prevent conflicts between GPG and PKCS#11 tools:
disable-ccid
And then restart GPG:
gpgconf --kill scdaemon
gpgconf --kill gpg-agent
Otherwise you might get a forbidden access to the card
Generate the keys inside the card
$ gpg --card-edit
inside gpg:
$ admin
$ generate
That should generate the key in the hardware key
Check the card status
$ gpg --card-status
Reader ...........: Nitrokey Nitrokey Start (FSIJ-1.2.19-C5B562D9) 00 00
Application ID ...: D276000124010200FFFEC5B562D90000
Application type .: OpenPGP
Version ..........: 2.0
Manufacturer .....: unmanaged S/N range
Serial number ....: C5B562D9
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 3
KDF setting ......: off
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: 843D 24A5 C992 3E97 6E6E 080E B479 5DD1 D89D 7EDE
created ....: 2025-06-26 10:36:55
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]
Check slot 1 (slot 1 is where Nitrokey stores the sign keys)
$ pkcs11-tool --module opensc-pkcs11.so --slot 1 -l --list-objects
Logging in to "OpenPGP card (User PIN (sig))".
Please enter User PIN:
Private Key Object; RSA
label: Signature key
ID: 01
Usage: sign, signRecover, non-repudiation
Access: sensitive, always sensitive, never extractable, local
uri: pkcs11:model=PKCS%2315%20emulated;manufacturer=OpenPGP%20project;serial=fffec5b562d9;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29;id=%01;object=Signature%20key;type=private
Public Key Object; RSA 2048 bits
label: Signature key
ID: 01
Usage: verify, verifyRecover
Access: none
uri: pkcs11:model=PKCS%2315%20emulated;manufacturer=OpenPGP%20project;serial=fffec5b562d9;token=OpenPGP%20card%20%28User%20PIN%20%28sig%29%29;id=%01;object=Signature%20key;type=public
Profile object 3983474672
profile_id: CKP_PUBLIC_CERTIFICATES_TOKEN (4)
The 01 ID KEY is the one we will use for signing
Create openssl-pkcs11-provider.cnf file
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
base = base_sect
pkcs11 = pkcs11_sect
[default_sect]
activate = 1
[base_sect]
activate = 1
[pkcs11_sect]
activate = 1
pkcs11-module-path = /usr/lib/pkcs11/opensc-pkcs11.so # Change to the path of yours, can differ by OS
Note that the pkcs11-module-path is specific to the distro and even to the arch. For example on Fedora ARM64 the correct path is /usr/lib64/pkcs11/opensc-pkcs11.so
Verify provider activation
$ OPENSSL_CONF=openssl-pkcs11-provider.cnf openssl list -providers
Providers:
base
name: OpenSSL Base Provider
version: 3.5.0
status: active
default
name: OpenSSL Default Provider
version: 3.5.0
status: active
pkcs11
name: PKCS#11 Provider
version: 3.4.1
status: active
You should see the pkcs11 provider active
Generate a CSR
$ OPENSSL_CONF=openssl-pkcs11-provider.cnf openssl req \
-new \
-key "pkcs11:slot-id=1;id=%01" \
-sha256 -out nitrokey.csr.pem \
-subj "/CN=SecureBoot Key/"
HINT: You can append ;pin-value=<YOUR USER PIN> to the pkcs11 url to not have to write it. If you dont add it, openssl will ask you for it
Self-sign certificate
$ OPENSSL_CONF=openssl-pkcs11-provider.cnf openssl x509 \
-req -days 3650 \
-in nitrokey.csr.pem \
-signkey "pkcs11:slot-id=1;id=%01" \
-out nitrokey.crt.pem
HINT: You can append ;pin-value=<YOUR USER PIN> to the pkcs11 url to not have to write it. If you dont add it, openssl will ask you for it
Sign your EFI binary
OPENSSL_CONF=openssl-pkcs11-provider.cnf /usr/lib/systemd/systemd-sbsign sign \
--private-key="pkcs11:slot-id=1;id=%01" \
--private-key-source=provider:pkcs11 \
--certificate=nitrokey.crt.pem \
--output=OUTPUT.efi \
INPUT.efi
Troubleshooting
If card is blocked or device errors (CKR_DEVICE_ERROR):
Restart GPG and PC/SC services explicitly:
gpgconf --kill scdaemon
gpgconf --kill gpg-agent
sudo systemctl restart pcscd
Physically unplug and reinsert your Nitrokey. Retry explicitly checking the key availability:
pkcs11-tool --slot 1 -l --list-objects
This is now only missing surfacing on AuroraBoot with a new release and documentation
This just needs QA, but it's not part of a release yet, however AuroraBoot 0.9.0 contains the necessary changes to test this.