Offline KSK on YubiKey to DNSSEC sign BIND zones

Sat 18 April 2020
By mute

BIND9 has native pkcs11 support which is usually available in separate packages. But that does not work for smartcard. Your HSM would need to support every crypto function. We want to use OpenSSL engine to do everything except the few things that the smartcard holds secret inside (signing things with a key that can't come out). So don't bother with the bind pkcs11 packages.

This took too long to figure out because of a bug in libp11. I guess other software doesn't check whether the key has the private flag set. I would keep getting something like:

dnssec-signzone: fatal: cannot sign zone with non-private dnskey Kexample.test.+005+38895

This needs libp11 >= 0.4.11 as noted here: https://gitlab.isc.org/isc-projects/bind9/-/wikis/BIND-9-PKCS11

Incorporating that change is what gave me the problems I think. Here are some options once you have a pkcs11.so from new libp11. Somewhat in increasing betterness..

  1. Overwrite the system one. This probably won't survive if your distro patches something else in the file. I also don't like overwriting package controlled files. But Debian 10 has it in /usr/lib/x86_64-linux-gnu/engines-1.1 and CentOS 7 has it in /usr/lib64/engines-1.1 if you'd like to try that.

  2. Just call the engine something else and put it in the engine dir. That requires editing PKCS11_ENGINE_ID in libp11/src/eng_front.c before you compile. Here I just add the short commit ID to the end. You should use vi and an easier name.

    mute@local:~/dns/libp11$ version=$(git log --pretty=format:'%h' -n 1); printf %s\\n "/define PKCS11_ENGINE_ID/s/pkcs11/pkcs11-$version" w q | ed -s src/eng_front.c
    #define PKCS11_ENGINE_ID "pkcs11-4084f83"
    ... make and sudo ...
    root@local:/usr/lib/x86_64-linux-gnu/engines-1.1# cp /home/mute/dns/libp11/src/.libs/pkcs11.so pkcs11-4084f83.so
    root@local:/usr/lib/x86_64-linux-gnu/engines-1.1# openssl engine pkcs11-4084f83 -c
    (pkcs11-4084f83) pkcs11 engine
     [RSA, rsaEncryption, id-ecPublicKey]
    
  3. Edit system openssl.cnf to redefine the pkcs11 engine. Probably a great solution... Details in libp11 README: https://github.com/OpenSC/libp11/blob/master/README.md

  4. Copy the system openssl.cnf to somewhere else and export OPENSSL_CONF to point to it. Very minimal one just for dnssec-signzone:

    openssl_conf = bind_conf
    [bind_conf]
    engines = engine_sect
    [engine_sect]
    pkcs11 = pkcs11_sect
    [pkcs11_sect]
    dynamic_path = /home/mute/dns/libp11/src/.libs/pkcs11.so
    MODULE_PATH = /usr/lib/x86_64-linux-gnu/p11-kit-proxy.so
    init = 0
    
  5. export OPENSSL_ENGINES=/home/mute/dns/libp11/src/.libs and put your newer library there. This is what I'm going with.

No matter what, you will still need the openssl.conf entry with a MODULE_PATH if you don't configure libp11 with ./configure --with-pkcs11-module=p11-kit-proxy

Now that that is fixed .. The overall method to store KSK offline, and ZSK in file on system is as so:

  1. Find your YubiKey token ID. You may see it twice if you have loaded their driver. Using the entire string is not necessary. You can get away with "pkcs11:token=ksk" (using opensc driver) or "pkcs11:token=YubiKey%20PIV%20%237792984" if you have loaded libykcs11.so. "ksk" is just the CN of my first cert so your token may be different.

    mute@local:~$ p11tool --list-token-urls
    pkcs11:model=p11-kit-trust;manufacturer=PKCS%2311%20Kit;serial=1;token=System%20Trust
    pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=dc6e8bb5b47e7aa8;token=ksk
    pkcs11:model=YubiKey%20YK4;manufacturer=Yubico%20%28www.yubico.com%29;serial=7792984;token=YubiKey%20PIV%20%237792984
    
  2. Find the key you wish to use. I used the PIV AUTH (9a, id=%01) in opensc driver, but CARD AUTH (9e, id=%04) seems to work as well if you've loaded ykcs11. You will want to pass the --login parameter when asking about privkeys otherwise you won't get good results. For dnssec-signzone it MUST have the CKA_PRIVATE flag.

    mute@local:~$ p11tool --list-privkeys "pkcs11:token=ksk"
    Object 0:
            URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=dc6e8bb5b47e7aa8;token=ksk;id=%04;object=CARD%20AUTH%20key;type=private
    Token 'ksk' with URL 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=dc6e8bb5b47e7aa8;token=ksk' requires user PIN
    Enter PIN:
            Type: Private key (RSA-2048)
            Label: CARD AUTH key
            Flags: CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
            ID: 04
    
    mute@local:~$ p11tool --list-privkeys "pkcs11:token=ksk" --login
    Token 'ksk' with URL 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=dc6e8bb5b47e7aa8;token=ksk' requires user PIN
    Enter PIN:
    Object 0:
            URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=dc6e8bb5b47e7aa8;token=ksk;id=%01;object=PIV%20AUTH%20key;type=private
            Type: Private key (RSA-2048)
            Label: PIV AUTH key
            Flags: CKA_WRAP/UNWRAP; CKA_PRIVATE; CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
            ID: 01
    
    Object 1:
            URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=dc6e8bb5b47e7aa8;token=ksk;id=%04;object=CARD%20AUTH%20key;type=private
            Type: Private key (RSA-2048)
            Label: CARD AUTH key
            Flags: CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
            ID: 04
    
    mute@local:~/dns$ p11tool --list-privkeys "pkcs11:token=YubiKey%20PIV%20%237792984"
    No matching objects found
    mute@local:~/dns$ p11tool --list-privkeys "pkcs11:token=YubiKey%20PIV%20%237792984" --login
    Token 'YubiKey PIV #7792984' with URL 'pkcs11:model=YubiKey%20YK4;manufacturer=Yubico%20%28www.yubico.com%29;serial=7792984;token=YubiKey%20PIV%20%237792984' requires user PIN
    Enter PIN:
    Object 0:
            URL: pkcs11:model=YubiKey%20YK4;manufacturer=Yubico%20%28www.yubico.com%29;serial=7792984;token=YubiKey%20PIV%20%237792984;id=%01;object=Private%20key%20for%20PIV%20Authentication;type=private
            Type: Private key (RSA-2048)
            Label: Private key for PIV Authentication
            Flags: CKA_PRIVATE; CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
            ID: 01
    
    Object 1:
            URL: pkcs11:model=YubiKey%20YK4;manufacturer=Yubico%20%28www.yubico.com%29;serial=7792984;token=YubiKey%20PIV%20%237792984;id=%04;object=Private%20key%20for%20Card%20Authentication;type=private
            Type: Private key (RSA-2048)
            Label: Private key for Card Authentication
            Flags: CKA_PRIVATE; CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
            ID: 04
    
    Object 2:
            URL: pkcs11:model=YubiKey%20YK4;manufacturer=Yubico%20%28www.yubico.com%29;serial=7792984;token=YubiKey%20PIV%20%237792984;id=%19;object=Private%20key%20for%20PIV%20Attestation;type=private        Type: Private key (RSA-2048)
            Label: Private key for PIV Attestation
            Flags: CKA_PRIVATE; CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
            ID: 19
    
  3. Create the KSK key from the label, generate ZSK to file, and sign the zone. I am not going to show both URL anymore, just the built-in opensc one (it's shorter, and built-in, and works).

    mute@local:~/dns$ dnssec-keyfromlabel -E pkcs11-4084f83 -a rsasha256 -l 'pkcs11:token=ksk;id=%01' -f KSK example.org
    Enter PKCS#11 token PIN for ksk:
    Kexample.org.+008+65005
    mute@local:~/dns$ dnssec-keygen -E pkcs11-4084f83 -a rsasha256 -b 1024 example.org
    Generating key pair....+++++ .....+++++
    Enter PKCS#11 token PIN for ksk:
    Kexample.org.+008+10734
    mute@local:~/dns$ OPENSSL_CONF=openssl.conf dnssec-signzone -E pkcs11-4084f83 -S example.org
    Enter PKCS#11 token PIN for ksk:
    Fetching ZSK 10734/RSASHA256 from key repository.
    Fetching KSK 65005/RSASHA256 from key repository.
    Verifying the zone using the following algorithms: RSASHA256.
    Zone fully signed:
    Algorithm: RSASHA256: KSKs: 1 active, 0 stand-by, 0 revoked
                          ZSKs: 1 active, 0 stand-by, 0 revoked
    example.org.signed
    

And that's it. Really just the 2 issues that made this so hard was (1) making sure fixed pkcs11 engine was loaded and (2) making sure I logged in to find private keys. Two days wasted!

Comments