YubiKey, for multi-factor authentication on Linux.

YubiKey Authentication With pam_pkcs11.so

YubiKey and PKCS #11

YubiKey cryptographic device
Amazon B0BVNPWPCN

A YubiKey is a small hardware authentication device that plugs into a USB port. Several models are available, for US$ 20–70.

Public Key Cryptography Standard or PKCS #11 defines a generic programming interface for software to interact with cryptographic tokens.

The pam_pkcs11.so library module has been the common method for specifying YubiKey or Smart Card authentication through the PAM mechanism on Linux.

While pam_pkcs11.so has been dropped from Red Hat and Oracle Linux major release 8, it's still available in Debian 11 and there are many legacy RHEL 7 and Oracle Linux 7 systems out there.

Let's see how to set up YubiKey authentication with the pam_pkcs11.so PAM library.

YubiKey authentication device plugged into a laptop.

What Are We Testing?

We are checking to see whether the user posesses a YubiKey containing a certificate for them. The certificate must list their user name as the Canonical Name or CN, and it must be signed by a trusted Certificate Authority or CA.

Real-world
PIN study

The user must enter the device PIN, so you could argue that this accomplishes multi-factor authentication on its own — the device is the something you have while the PIN is something you know. The PIN, however, is a maximum of six digits, with a much smaller search space than a typical password or pass phrase.

We create and operate the CA operation, so we trust it on a human level as a credential issuer. We can validate certificates issued by the CA by using database files stored under /etc/pki/nssdb on the system.

Overview

We need to establish the CA, create the user keys and certificate, load data into the YubiKey device, and record the CA's certificate in the system's database. Here are the steps in more detail:

1 Generate the CA key pairs. On Linux the file names don't matter. However, some systematic naming convention will make things much easier for you to keep track of what is in which file. I will put the CA's RSA and EC key pairs into files RootCA_RSA.key and RootCA_EC.key, respectively.

2 Create self-signed certificates from the CA public keys. I will store these in RootCA_RSA.pem and RootCA_EC.pem.

3 Create user key pairs. I will store these in username_RSA.key and username_EC.key, with username changed to their actual user name.

4 Create Certificate Signing Requests or CSRs for the user public keys. I will store these in username_RSA.csr and username_EC.csr.

5 Create certificates for the user public keys, signing them with the CA key. I will store these in username_RSA.crt and username_EC.crt.

6 Create PFX (or PKCS #12) files for the user identities. Each includes a public key and its corresponding private key. I will store these in username_RSA.pfx and username_EC.pfx.

7 Load a PFX key into each YubiKey device.

8 Configure the operating system to map Linux user names to CN strings.

9 Store the CA self-signed certificates in databases in /etc/pki/nssdb/.

10 Configure PAM to use pam_pkcs11.so for user authentication.

YubiKey authentication device.

1: Generate CA key pairs

These keys will be read by the YubiKey device, so we are limited to RSA 1024-bit, RSA 2048-bit, or ECC secp256r1 (also known as NIST P-256) keys. We will call this "the Root CA", although you very likely already have a PKI or Public Key Infrastructure operation in place. The name makes sense though, as the CA will be our root of trust for user authentication. And so it would be a good idea to include the -aes256 option to encrypt and thus password-protect the keys.

Let's generate both RSA 2048 and ECC P-256 key pairs. Without the named_curve option for the ECC keys, the elliptic curves will be explicitly defined. Many tools require the names, rather than definitions, including ykman and certutil. We will use the first to load and check the YubiKey, and the second to install the certificates on the server.

See the manual page for the individual openssl commands for further details. In this case, the genpkey manual page.

# openssl genpkey -algorithm RSA \
		-pkeyopt rsa_keygen_bits:2048 \
		-aes256 \
		-out RootCA_RSA.key
.....................+++++
.....................................................................+++++
Enter PEM pass phrase: ØØØØØØØØØØØØØØØ
Verifying - Enter PEM pass phrase: ØØØØØØØØØØØØØØØ
# openssl genpkey -algorithm EC \
		-pkeyopt ec_paramgen_curve:P-256 \
		-pkeyopt ec_param_enc:named_curve \
		-aes256 \
		-out RootCA_EC.key
Enter PEM pass phrase: ØØØØØØØØØØØØØØØ
Verifying - Enter PEM pass phrase: ØØØØØØØØØØØØØØØ
# ls -l RootCA_*
-rw------- 1 root root  399 Apr 25 05:53 RootCA_EC.key
-rw------- 1 root root 1874 Apr 25 05:53 RootCA_RSA.key
# head -7 RootCA_*
==> RootCA_EC.key <==
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhIGkdgUnNpGQICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEGTb0I49WVBNwduOG3NYcs0EgZCv
KthpVHx+/8OkIEN8cseccV2/gf3w5qf0DQ4kv1QOqhAGU8Frpxzo5aNjp5BF/95m
GlhsFiefzMD5OOX4rcVkrlgvfsKOezUHzseSA+iUt7rosNwASJOyaVlxaH62yvPw
31CLSh1QHQghDBF4JTaMl2fsVhjskxxN94GhSDhkpFanVCromsMCH+gZD/goIGA=
-----END ENCRYPTED PRIVATE KEY-----

==> RootCA_RSA.key <==
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIzxyLRv9ZKvsCAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAEhtSomyXixnoYqEsuO6LKBIIE
0GTKM352addByzrsbCqKIg0z4LicmQ/IesrRsiJE3K0nCk4epHKBJLhlNixg+94O
58D+wsB62dqR1nu6ujEGKA30CRLZqLBOAde1fu9DZpLgdYkh6lWHuT5t8bzYi86z
0qkNcntp3SDcf7pMkW/OWTvJSSOXIn6jJs3gPB4k8zoDNFDDwoMFddlDDbkZM6pk
Ud9IVnWW0WP+ZUfFdr/nAHlHpF+rN7SEH85tHNKjNXKuC/N1KTlDfSGj1F6xQwHC

The resulting *.key files say that they contain private keys. Actually they contain both the private and public key. They contain an asymmetric key pair, and have been encrypted with the symmetric AES-256 cipher using a key derived from the pass phrase.

2: Create self-signed CA certificates

We are going to create a self-signed certificate. It means that the CA is saying "I exist and the following is my public key. Believe this because I signed it using the corresponding private key and so it must be true."

By itself, that's meaningless. We must simply trust that the CA's public key really is what it claims to be, that the cryptography is strong enough, and (toughest of all) that the people operating the CA are skilled, careful, and utterly trustworthy on matters of personal identity.

We could do the signing in an interactive fashion. We're asking for a certificate good for the next 365 days, you can of course change that. If you don't specify that, the default is only 30 days! Here we go with an interactive run:

# openssl req -x509 -new -key RootCA_RSA.key \
		-days 365 -out RootCA_RSA.pem
Enter pass phrase for RootCA_RSA.key: ØØØØØØØØØØØØØØØ
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:US
State or Province Name (full name) []:Indiana
Locality Name (eg, city) [Default City]:West Lafayette
Organization Name (eg, company) [Default Company Ltd]:Cromwell International
Organizational Unit Name (eg, section) []:HQ
Common Name (e.g. server FQDN or YOUR name) []:pki.cromwell-intl.com
Email Address []:
# file RootCA_RSA.pem
RootCA_RSA.pem: PEM certificate
# head RootCA_RSA.pem
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIURskiIsoYK0xJ26tujrjovAssB1kwDQYJKoZIhvcNAQEL
BQAwgYYxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdJbmRpYW5hMRcwFQYDVQQHDA5X
ZXN0IExhZmF5ZXR0ZTEfMB0GA1UECgwWQ3JvbXdlbGwgSW50ZXJuYXRpb25hbDEL
MAkGA1UECwwCSFExHjAcBgNVBAMMFXBraS5jcm9td2VsbC1pbnRsLmNvbTAeFw0y
MTAzMjYxMjEzMzRaFw0yMjAzMjYxMjEzMzRaMIGGMQswCQYDVQQGEwJVUzEQMA4G
A1UECAwHSW5kaWFuYTEXMBUGA1UEBwwOV2VzdCBMYWZheWV0dGUxHzAdBgNVBAoM
FkNyb213ZWxsIEludGVybmF0aW9uYWwxCzAJBgNVBAsMAkhRMR4wHAYDVQQDDBVw
a2kuY3JvbXdlbGwtaW50bC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDMztNo49FqdmkFK2rs5ABiSvQXw8zJ6Gua1jNQBoUn+qo1d7vL4jvRPlhZ

You might want to create a script to do this, with everything but the pass phrase automated. If so, you must use one -subj option with a very long string. If you use multiple -subj options, each with a part of the subject definition, then only the last one is used.

# openssl req -x509 -new -key RootCA_RSA.key \
	-subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=HQ/CN=pki.cromwell-intl.com/" \
	-days 365 -out RootCA_RSA.pem
Enter pass phrase for RootCA_RSA.key: ØØØØØØØØØØØØØØØ
# openssl req -x509 -new -key RootCA_EC.key \
	-subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=HQ/CN=pki.cromwell-intl.com/" \
	-days 365 -out RootCA_EC.pem
Enter pass phrase for RootCA_EC.key: ØØØØØØØØØØØØØØØ
# ls -l RootCA_*
-rw------- 1 cromwell cromwell  399 Apr 25 05:53 RootCA_EC.key
-rw-rw-r-- 1 cromwell cromwell  887 Apr 25 05:53 RootCA_EC.pem
-rw------- 1 cromwell cromwell 1874 Apr 25 05:53 RootCA_RSA.key
-rw-rw-r-- 1 cromwell cromwell 1424 Apr 25 05:53 RootCA_RSA.pem

You can examine the details. The -noout option means "Don't include the encoded certificate, the literal content of the certificate file."

# openssl x509 -in RootCA_RSA.pem -text -noout | less
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            4a:d7:51:58:41:07:d4:73:18:2d:34:3a:69:ee:8d:d7:a1:06:24:55
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = pki.cromwell-intl.com
        Validity
            Not Before: Apr 25 05:53:04 2024 GMT
            Not After : Apr 25 05:53:04 2025 GMT
        Subject: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = pki.cromwell-intl.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:cc:ce:d3:68:e3:d1:6a:76:69:05:2b:6a:ec:e4:
                    00:62:4a:f4:17:c3:cc:c9:e8:6b:9a:d6:33:50:06:
                    85:27:fa:aa:35:77:bb:cb:e2:3b:d1:3e:58:59:83:
                    7e:7a:dd:78:85:c2:8b:ca:76:48:45:0c:0e:97:02:
                    0b:6d:b5:a0:20:10:7e:24:97:0e:2d:8d:fb:38:42:
                    62:b2:19:f8:5a:9d:51:d1:a0:72:1d:8c:0e:4d:17:
                    51:97:8a:70:03:0d:c8:15:85:dc:05:6e:d8:ee:d6:
                    99:d8:bd:ab:ec:38:52:95:71:2a:b8:0b:08:8f:67:
                    d9:c2:81:9c:5d:fb:80:d9:6e:cc:4e:a4:f8:f4:78:
                    5e:19:1f:a4:6a:66:a4:44:ce:f3:1f:77:d4:ae:87:
                    fe:c7:31:d3:90:98:76:30:ba:76:ba:85:e7:cc:ba:
                    86:92:e0:44:2e:d6:fa:18:72:1c:08:fb:37:bb:d7:
                    4b:1f:2f:fe:41:31:dd:62:ab:16:f4:3b:9e:c5:80:
                    60:4d:47:89:fb:4d:00:ae:ba:bc:d9:b7:1e:1e:75:
                    0e:9f:27:1c:6a:0b:2b:e8:3b:77:33:c4:01:1e:b0:
                    b9:fb:b4:9a:d5:22:f2:ad:48:f3:7a:40:dd:db:10:
                    74:95:55:aa:45:64:c3:15:48:57:05:b5:c8:8c:da:
                    ff:2d
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                0F:0D:DA:A4:AA:5E:4A:23:61:A1:19:3D:EF:10:49:6B:40:1C:A4:E3
            X509v3 Authority Key Identifier: 
                keyid:0F:0D:DA:A4:AA:5E:4A:23:61:A1:19:3D:EF:10:49:6B:40:1C:A4:E3

            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
         aa:12:9c:ea:47:ce:7e:34:f4:8b:83:e8:65:70:04:bb:35:04:
         36:9f:15:ba:51:d6:40:a0:df:64:28:89:5c:af:69:cf:f4:fd:
         af:f1:1d:37:f5:3b:14:a1:38:49:2c:fb:7f:c5:ae:c6:60:ea:
         1d:ae:8f:1a:da:25:68:e9:42:11:30:bc:4c:c3:b2:b6:c1:d7:
         2c:d5:31:9d:62:19:ba:11:4d:15:2f:dd:08:01:db:f9:eb:c3:
         75:8d:66:bb:6a:46:6c:46:7d:13:35:3a:cd:8e:db:2a:46:d3:
         95:92:ba:2d:c0:dc:ba:77:ec:7d:2f:3f:1f:c8:92:e9:9c:56:
         75:c1:14:9b:f7:71:95:f0:e7:29:e9:ee:98:28:85:8c:f6:62:
         f9:9d:f6:a7:e7:db:81:ff:ad:74:6b:b4:4c:a2:de:6f:09:21:
         98:c5:70:73:b7:fd:7f:14:a6:60:88:68:d1:77:4f:0f:91:1e:
         98:89:32:26:26:d7:4b:3f:5e:02:fc:1c:87:df:c2:61:cd:17:
         28:b6:f7:2e:8f:90:eb:5b:38:b8:b9:ca:b3:67:da:bb:49:ed:
         c8:c2:d4:39:03:26:30:ff:25:1a:5b:3a:a2:e2:33:aa:86:1f:
         cf:30:26:81:21:a7:ae:49:ef:fc:4e:1f:ce:82:2f:c7:1a:a4:
         51:33:02:a7

# openssl x509 -in RootCA_EC.pem -text -noout | less
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            70:08:e6:71:d8:b7:ac:80:a6:e9:60:0e:73:8c:ad:5b:90:13:a2:0a
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = pki.cromwell-intl.com
        Validity
            Not Before: Apr 25 05:53:04 2024 GMT
            Not After : Apr 25 05:53:04 2025 GMT
        Subject: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = pki.cromwell-intl.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:8b:13:bf:d6:a9:8b:75:6d:39:92:4b:fd:cd:ac:
                    6d:84:c8:a3:1f:69:2c:c4:98:80:6f:39:8a:69:d7:
                    c7:ea:55:ec:39:e0:85:ca:ea:8c:6a:5e:50:eb:32:
                    22:fe:6d:64:69:d6:60:e7:02:cb:0f:4d:c2:5d:e6:
                    72:29:48:0f:8b
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                82:83:91:AD:11:30:F9:FA:BE:7F:9F:99:2A:30:88:C3:CB:D2:FC:8E
            X509v3 Authority Key Identifier: 
                keyid:82:83:91:AD:11:30:F9:FA:BE:7F:9F:99:2A:30:88:C3:CB:D2:FC:8E

            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:21:00:92:72:25:c0:60:32:df:eb:95:55:b6:f0:8a:
         1c:22:24:ab:55:a3:ab:8c:a4:de:b3:4b:55:07:3c:39:06:92:
         78:02:20:5b:6a:b3:e6:71:c0:97:1d:e5:2f:7f:50:8e:2e:67:
         92:62:9d:bb:5f:1d:33:74:99:14:96:cc:32:1f:c1:45:84

Advanced topic: If you want to include CSP and CRL fields, use ca instead of x509 to create the certificate. See the openssl-ca manual page for details.

3: Create user key pairs

An Intro to
Elliptic-Curve
Cryptography

Now we need to generate a key pair for each user. These keys aren't actually used by the YubiKey device, so we aren't limited to the small set of asymmetric ciphers supported by the device. The YubiKey only reads the certificate "wrapper", it doesn't process the public key within it. We could generate a larger RSA key pair, or use a different curve.

On a RHEL or Oracle Linux system we have limited choices as they have built openssl to only support FIPS 140-2 algorithms. On Oracle Linux 8 I see:

# openssl version
OpenSSL 1.1.1g-fips  21 Apr 2020
# openssl ecparam -list_curves
  secp224r1 : NIST/SECG curve over a 224 bit prime field
  secp256k1 : SECG curve over a 256 bit prime field
  secp384r1 : NIST/SECG curve over a 384 bit prime field
  secp521r1 : NIST/SECG curve over a 521 bit prime field
  prime256v1: X9.62/SECG curve over a 256 bit prime field 
NIST: Digital Signature Standard (DSS) Certicom/SECG: SEC 2, Recommended Elliptic Curve Domain Parameters

There are many standards defining elliptic curves, each with their own nomenclature. The five above are what OpenSSL calls them.

prime256v1 is what U.S. NIST calls P-256, and it's also called secp256r1. We will see below that we can use either the prime256v1 or P-256 parameter (but not secp256r1) with the openssl command.

secp224r1, secp256r1 (a.k.a. P-256 and prime256v1), secp384r1, and secp521r1 have what NIST says are randomly chosen parameters, hence the "r" in their name.

secp256k1 is a Koblitz curve. It instead has parameters that exchange a few bits' worth of security for a significant computational speed advantage. That's just a few bits out of 256, so secp256k1 and secp256r1 should have comparable security. That is, as long as there has been no funny business with the supposedly "random" parameter selection of all the secp*r1 curves.

Government
backdoors

In 2013 The New York Times reported that NSA had influenced NIST to include a deliberate weakness the the Dual_EC_DRBG random bit stream generator and its associated elliptic curve.

Once that came out, many cryptographers seem to feel that while there's no specific reason to distrust NIST-defined elliptic curve algorithms, we certainly can't have absolute confidence in them.

In other words, just how "random" are those supposedly random curve parameters? Might they look random but actually embody a backdoor? We have no way of knowing.

SafeCurves

The best reference I know of is the SafeCurves project. They describe security problems with both secp256k1 and secp256r1.

On a Mint Linux or Debian system we have a much broader list of choices.

# openssl version
OpenSSL 1.1.1f-fips  31 Mar 2020
# openssl ecparam -list_curves
  secp112r1 : SECG/WTLS curve over a 112 bit prime field
  secp112r2 : SECG curve over a 112 bit prime field
  secp128r1 : SECG curve over a 128 bit prime field
  secp128r2 : SECG curve over a 128 bit prime field
  secp160k1 : SECG curve over a 160 bit prime field
  secp160r1 : SECG curve over a 160 bit prime field
  secp160r2 : SECG/WTLS curve over a 160 bit prime field
  secp192k1 : SECG curve over a 192 bit prime field
  secp224k1 : SECG curve over a 224 bit prime field
  secp224r1 : NIST/SECG curve over a 224 bit prime field
  secp256k1 : SECG curve over a 256 bit prime field
  secp384r1 : NIST/SECG curve over a 384 bit prime field
  secp521r1 : NIST/SECG curve over a 521 bit prime field
  prime192v1: NIST/X9.62/SECG curve over a 192 bit prime field
  prime192v2: X9.62 curve over a 192 bit prime field
  prime192v3: X9.62 curve over a 192 bit prime field
  prime239v1: X9.62 curve over a 239 bit prime field
  prime239v2: X9.62 curve over a 239 bit prime field
  prime239v3: X9.62 curve over a 239 bit prime field
  prime256v1: X9.62/SECG curve over a 256 bit prime field
  sect113r1 : SECG curve over a 113 bit binary field
  sect113r2 : SECG curve over a 113 bit binary field
  sect131r1 : SECG/WTLS curve over a 131 bit binary field
  sect131r2 : SECG curve over a 131 bit binary field
  sect163k1 : NIST/SECG/WTLS curve over a 163 bit binary field
  sect163r1 : SECG curve over a 163 bit binary field
  sect163r2 : NIST/SECG curve over a 163 bit binary field
  sect193r1 : SECG curve over a 193 bit binary field
  sect193r2 : SECG curve over a 193 bit binary field
  sect233k1 : NIST/SECG/WTLS curve over a 233 bit binary field
  sect233r1 : NIST/SECG/WTLS curve over a 233 bit binary field
  sect239k1 : SECG curve over a 239 bit binary field
  sect283k1 : NIST/SECG curve over a 283 bit binary field
  sect283r1 : NIST/SECG curve over a 283 bit binary field
  sect409k1 : NIST/SECG curve over a 409 bit binary field
  sect409r1 : NIST/SECG curve over a 409 bit binary field
  sect571k1 : NIST/SECG curve over a 571 bit binary field
  sect571r1 : NIST/SECG curve over a 571 bit binary field
  c2pnb163v1: X9.62 curve over a 163 bit binary field
  c2pnb163v2: X9.62 curve over a 163 bit binary field
  c2pnb163v3: X9.62 curve over a 163 bit binary field
  c2pnb176v1: X9.62 curve over a 176 bit binary field
  c2tnb191v1: X9.62 curve over a 191 bit binary field
  c2tnb191v2: X9.62 curve over a 191 bit binary field
  c2tnb191v3: X9.62 curve over a 191 bit binary field
  c2pnb208w1: X9.62 curve over a 208 bit binary field
  c2tnb239v1: X9.62 curve over a 239 bit binary field
  c2tnb239v2: X9.62 curve over a 239 bit binary field
  c2tnb239v3: X9.62 curve over a 239 bit binary field
  c2pnb272w1: X9.62 curve over a 272 bit binary field
  c2pnb304w1: X9.62 curve over a 304 bit binary field
  c2tnb359v1: X9.62 curve over a 359 bit binary field
  c2pnb368w1: X9.62 curve over a 368 bit binary field
  c2tnb431r1: X9.62 curve over a 431 bit binary field
  wap-wsg-idm-ecid-wtls1: WTLS curve over a 113 bit binary field
  wap-wsg-idm-ecid-wtls3: NIST/SECG/WTLS curve over a 163 bit binary field
  wap-wsg-idm-ecid-wtls4: SECG curve over a 113 bit binary field
  wap-wsg-idm-ecid-wtls5: X9.62 curve over a 163 bit binary field
  wap-wsg-idm-ecid-wtls6: SECG/WTLS curve over a 112 bit prime field
  wap-wsg-idm-ecid-wtls7: SECG/WTLS curve over a 160 bit prime field
  wap-wsg-idm-ecid-wtls8: WTLS curve over a 112 bit prime field
  wap-wsg-idm-ecid-wtls9: WTLS curve over a 160 bit prime field
  wap-wsg-idm-ecid-wtls10: NIST/SECG/WTLS curve over a 233 bit binary field
  wap-wsg-idm-ecid-wtls11: NIST/SECG/WTLS curve over a 233 bit binary field
  wap-wsg-idm-ecid-wtls12: WTLS curve over a 224 bit prime field
  Oakley-EC2N-3: 
	IPSec/IKE/Oakley curve #3 over a 155 bit binary field.
	Not suitable for ECDSA.
	Questionable extension field!
  Oakley-EC2N-4: 
	IPSec/IKE/Oakley curve #4 over a 185 bit binary field.
	Not suitable for ECDSA.
	Questionable extension field!
  brainpoolP160r1: RFC 5639 curve over a 160 bit prime field
  brainpoolP160t1: RFC 5639 curve over a 160 bit prime field
  brainpoolP192r1: RFC 5639 curve over a 192 bit prime field
  brainpoolP192t1: RFC 5639 curve over a 192 bit prime field
  brainpoolP224r1: RFC 5639 curve over a 224 bit prime field
  brainpoolP224t1: RFC 5639 curve over a 224 bit prime field
  brainpoolP256r1: RFC 5639 curve over a 256 bit prime field
  brainpoolP256t1: RFC 5639 curve over a 256 bit prime field
  brainpoolP320r1: RFC 5639 curve over a 320 bit prime field
  brainpoolP320t1: RFC 5639 curve over a 320 bit prime field
  brainpoolP384r1: RFC 5639 curve over a 384 bit prime field
  brainpoolP384t1: RFC 5639 curve over a 384 bit prime field
  brainpoolP512r1: RFC 5639 curve over a 512 bit prime field
  brainpoolP512t1: RFC 5639 curve over a 512 bit prime field
  SM2       : SM2 curve over a 256 bit prime field

Let's create RSA 4096 and secp384r1 key pairs for the user bob. The file names are, of course, completely arbitrary. But organized naming may make it easier to keep track of what is in which file.

# openssl genpkey -algorithm RSA \
		-pkeyopt rsa_keygen_bits:4096 \
		-out bob_RSA.key
........................................................................++++
......++++
# openssl genpkey -algorithm EC \
		-pkeyopt ec_paramgen_curve:secp384r1 \
		-pkeyopt ec_param_enc:named_curve \
		-out bob_EC.key
# ls -l bob_*
-rw------- 1 cromwell cromwell  306 Apr 25 05:53 bob_EC.key
-rw------- 1 cromwell cromwell 3272 Apr 25 05:53 bob_RSA.key

4: Create CSRs for the user public keys

The users have key pairs, we need to create certificates with the public keys. But in order to do that, we must first create Certificate Signing Requests or CSRs.

The Canonical Name or CN must be their username. The file name doesn't matter but the CN does. The pam_pkcs11.so module enforces the rule that the CN must match the user name.

# openssl req -new -key bob_RSA.key \
	-subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=HQ/CN=bob/" \
	-out bob_RSA.csr
# openssl req -new -key bob_EC.key \
	-subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=HQ/CN=bob/" \
	-out bob_EC.csr
# ls -l bob_*
-rw-rw-r-- 1 cromwell cromwell  570 Apr 25 05:53 bob_EC.csr
-rw------- 1 cromwell cromwell  306 Apr 25 05:53 bob_EC.key
-rw-rw-r-- 1 cromwell cromwell 1716 Apr 25 05:53 bob_RSA.csr
-rw------- 1 cromwell cromwell 3272 Apr 25 05:53 bob_RSA.key

5: Create certificates for the user public keys

Now we can use those CSRs to generate the certificates. Here I'm using the same CA RSA key to sign both of the user's RSA and ECC public keys.

# openssl x509 -req -in bob_RSA.csr \
		-CA RootCA_RSA.pem -CAkey RootCA_RSA.key \
		-CAcreateserial -days 365 -out bob_RSA.crt
Signature ok
subject=C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = bob
Getting CA Private Key
Enter pass phrase for RootCA_RSA.key: ØØØØØØØØØØØØØØØ
# openssl x509 -req -in bob_EC.csr \
		-CA RootCA_RSA.pem -CAkey RootCA_RSA.key \
		-CAcreateserial -days 365 -out bob_EC.crt
Signature ok
subject=C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = bob
Getting CA Private Key
Enter pass phrase for RootCA_RSA.key: ØØØØØØØØØØØØØØØ
# ls -l bob_*
-rw-rw-r-- 1 cromwell cromwell 1042 Apr 25 05:53 bob_EC.crt
-rw-rw-r-- 1 cromwell cromwell  570 Apr 25 05:53 bob_EC.csr
-rw------- 1 cromwell cromwell  306 Apr 25 05:53 bob_EC.key
-rw-rw-r-- 1 cromwell cromwell 1623 Apr 25 05:53 bob_RSA.crt
-rw-rw-r-- 1 cromwell cromwell 1716 Apr 25 05:53 bob_RSA.csr
-rw------- 1 cromwell cromwell 3272 Apr 25 05:53 bob_RSA.key

Again, you can examine the results. I have highlighted some critical parts.

# openssl x509 -in bob_RSA.crt -text -noout | less
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            4e:bf:dd:b3:6f:d5:e5:0d:47:44:02:71:1b:68:f2:7f:3e:c4:56:d4
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = pki.cromwell-intl.com
        Validity
            Not Before: Apr 25 05:53:04 2024 GMT
            Not After : Apr 25 05:53:04 2025 GMT
        Subject: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = bob
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (4096 bit)
                Modulus:
                    00:9a:52:cc:bb:e0:89:cc:fe:68:ac:76:a3:5d:84:
                    a6:3c:52:e6:40:77:f8:43:2e:ce:b9:3d:14:4d:0a:
                    64:27:0f:c9:4d:15:d6:32:de:e5:7d:2c:03:b4:83:
                    da:29:e3:17:05:49:a3:98:81:bd:e5:35:5d:d2:1c:
                    d5:c6:12:16:87:ff:7e:d1:a6:03:12:a7:c1:3d:5a:
                    2a:44:3e:eb:fb:42:6d:da:8f:ce:d2:d9:c2:2a:1b:
                    34:ce:e9:6d:db:34:5e:32:82:48:87:56:33:ef:0c:
                    4f:98:67:53:60:77:3f:3d:26:9f:0f:f1:7f:ca:22:
                    47:17:7b:de:9a:3b:98:b1:ac:85:6d:b0:ee:75:b9:
                    45:4f:3e:86:2e:8a:18:81:0e:d0:00:d0:e0:c0:8c:
                    13:6c:1f:b0:f8:18:e3:0b:b2:21:bd:1c:33:90:64:
                    ce:d6:7f:cc:ab:a4:5d:08:83:67:5d:06:db:52:eb:
                    86:c2:66:df:12:d6:04:c0:07:41:6e:c5:1b:df:39:
                    4b:dc:48:ad:43:29:4b:ef:f5:dd:c7:c6:79:25:d4:
                    97:34:b9:38:57:18:e0:70:fd:80:5f:64:66:20:51:
                    33:08:09:a9:34:ed:cb:a6:4e:3c:88:b6:51:51:2b:
                    9d:7c:5b:3c:5f:21:56:46:62:72:95:0b:17:29:79:
                    3f:d2:22:23:60:3c:c1:8f:a8:a0:3e:f3:ae:12:81:
                    c5:d7:14:2a:23:8c:39:63:5a:a1:b1:dc:83:64:20:
                    ac:f3:20:24:74:23:26:bf:35:d7:5c:ef:bb:f5:11:
                    12:97:74:95:0d:5f:ea:fd:74:a6:f9:4c:2b:20:d7:
                    1f:50:9e:af:2d:0b:78:1e:d6:97:46:69:46:13:06:
                    fc:7e:36:36:5c:84:83:69:db:45:fd:1f:a2:ef:cd:
                    c1:73:dd:15:c1:95:c2:1a:c6:c3:b6:44:af:20:7b:
                    9c:3a:2f:0c:eb:c8:2e:98:8a:a5:6f:5f:1e:d3:42:
                    a2:70:e2:8e:68:af:e5:fd:c8:2a:5f:1c:79:8e:8a:
                    67:69:8d:4f:87:2e:d1:aa:da:3d:88:4f:f7:bb:7b:
                    66:25:fa:a3:ae:d7:33:e6:fa:f1:61:05:60:d2:a8:
                    ec:1b:8d:28:7d:f4:3f:aa:19:d8:ce:29:a4:ca:2c:
                    25:e6:1a:13:04:e8:8e:38:a2:88:de:e2:1b:dd:91:
                    1a:92:e9:8d:45:64:b5:2c:9d:ca:ab:3c:69:74:31:
                    fc:7f:8b:97:97:99:d8:26:3c:da:1c:1e:0b:ba:97:
                    51:09:34:fb:a4:b1:cc:81:c7:2f:9e:de:f7:3c:65:
                    56:42:88:c2:43:d7:b1:a0:72:7e:99:73:63:ec:f2:
                    d5:f2:05
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         6b:e6:3b:d6:3a:78:11:fd:13:44:70:bc:e2:3d:f4:60:86:1e:
         2a:8c:2c:56:10:a4:4f:ff:39:0f:8d:38:dc:5f:66:6a:32:ca:
         84:e0:20:6b:2f:a4:1e:9e:37:64:c2:9e:c8:65:a7:4d:d4:10:
         d6:ea:8c:16:fe:91:f5:d9:c2:ff:05:95:e5:7a:84:2c:48:04:
         de:60:dd:60:12:72:92:b2:4c:f2:01:5b:8b:3d:38:57:fd:5d:
         cd:d4:2d:38:9e:37:5c:8a:8a:40:42:4c:0d:f6:97:33:90:55:
         1a:46:28:f6:c3:59:3b:fa:dd:0b:71:0f:19:ed:0f:9b:8b:2e:
         f0:a2:e5:5e:cd:de:28:29:82:1f:ff:68:e9:af:67:39:b5:f7:
         30:85:da:26:6c:2e:1e:d3:d0:7e:f2:d0:0e:23:49:01:ef:4f:
         ac:a4:fb:48:f0:8b:3d:6e:e8:ad:4b:56:31:85:d9:8b:39:a8:
         9f:d7:fe:18:48:6a:53:e4:50:0b:62:8c:2c:ef:c4:5a:45:08:
         4d:2b:49:dd:1c:6e:9c:01:bf:2f:d0:99:94:74:bd:26:e0:c4:
         a6:59:aa:a0:52:ba:2a:ec:24:2f:7a:36:4c:e4:39:b4:3c:f4:
         07:9e:87:32:c6:a5:8b:1d:52:73:cc:45:1b:ce:ee:af:1a:c4:
         44:b7:f6:c1

# openssl x509 -in bob_EC.crt -text -noout | less
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            4e:bf:dd:b3:6f:d5:e5:0d:47:44:02:71:1b:68:f2:7f:3e:c4:56:d5
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = pki.cromwell-intl.com
        Validity
            Not Before: Apr 25 05:53:04 2024 GMT
            Not After : Apr 25 05:53:04 2025 GMT
        Subject: C = US, ST = Indiana, L = West Lafayette, O = Cromwell International, OU = HQ, CN = bob
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:2c:e4:bc:27:a7:7b:9b:7f:f9:6b:b0:b1:db:fe:
                    8a:d3:7e:bc:a4:63:8c:55:94:6b:e8:e5:b8:13:4e:
                    ef:00:86:d7:41:9a:d2:6d:b6:b6:3c:8e:e8:c7:6a:
                    80:1e:ed:08:c7:ad:8a:9a:23:7b:22:a3:6f:ef:7d:
                    93:c0:39:57:7a:f8:b7:8b:33:d5:9c:cb:73:0b:12:
                    de:3f:6d:5e:79:37:e8:c0:fa:87:b8:4a:18:4d:08:
                    82:f8:71:85:27:0d:b5
                ASN1 OID: secp384r1
                NIST CURVE: P-384
    Signature Algorithm: sha256WithRSAEncryption
         02:7c:96:37:6d:24:17:e4:20:ce:78:ed:a8:f9:c0:82:af:4d:
         b1:7c:f9:a7:24:46:7f:88:2b:04:b6:f6:9e:78:36:1c:c3:88:
         1a:6e:ca:83:79:09:95:d1:94:bf:4d:9f:2a:57:84:11:8e:52:
         e4:40:5b:84:1f:16:34:86:80:3d:7a:15:3c:86:35:91:37:96:
         b3:b2:2b:49:7d:76:50:d5:c7:bf:ab:d0:f3:2e:fe:fc:92:58:
         e2:3f:bd:bf:27:69:6a:23:11:7e:cb:30:79:00:d8:ee:d7:a6:
         46:22:48:cc:93:3c:55:3d:67:bf:bf:71:df:a8:4a:1a:b2:01:
         a5:96:52:78:2c:c9:d0:eb:ba:73:e4:12:b5:77:bc:58:c4:8d:
         12:e8:0e:9f:c6:00:e1:86:02:8b:6e:c3:37:c5:1e:e7:31:bb:
         f7:d5:89:19:fb:b8:4e:c0:6c:10:bc:c0:ae:56:fb:35:75:6d:
         7a:d9:f5:b8:55:c4:0a:49:f4:29:ae:04:2b:19:1d:e8:67:fa:
         97:0a:c7:35:c3:5c:79:78:5a:0c:25:89:54:0a:14:23:11:59:
         9d:d8:f7:0c:a4:e9:45:27:d2:2f:71:8d:d0:b8:b6:57:7c:04:
         66:4f:29:cc:76:22:f0:27:23:b8:18:61:d9:1f:84:37:57:7b:
         6f:12:14:2a

6: Create PFX / PKCS #12 files for the user identities

The PFX or PKCS #12 file contains both the user's private key and their public key within the certificate just created by the CA.

We do an "export" operation here to create the file, which we later "import" into the YubiKey. You will be asked for a "Export Password", you can enter the same string twice to protect the PFX file and the user's private key which it contains. Or if you simply press Enter twice, there will be no such password.

# openssl pkcs12 -export \
		-in bob_RSA.crt \
		-inkey bob_RSA.key \
		-out bob_RSA.pfx
Enter Export Password: ØØØØØØØØØØØØ
Verifying - Enter Export Password: ØØØØØØØØØØØØ
# openssl pkcs12 -export \
		-in bob_EC.crt \
		-inkey bob_EC.key \
		-out bob_EC.pfx
Enter Export Password: ØØØØØØØØØØØØ
Verifying - Enter Export Password: ØØØØØØØØØØØØ
# ls -l bob_*
-rw-rw-r-- 1 cromwell cromwell 1042 Apr 25 05:53 bob_EC.crt
-rw-rw-r-- 1 cromwell cromwell  570 Apr 25 05:53 bob_EC.csr
-rw------- 1 cromwell cromwell  306 Apr 25 05:53 bob_EC.key
-rw------- 1 cromwell cromwell 1298 Apr 25 05:53 bob_EC.pfx
-rw-rw-r-- 1 cromwell cromwell 1623 Apr 25 05:53 bob_RSA.crt
-rw-rw-r-- 1 cromwell cromwell 1716 Apr 25 05:53 bob_RSA.csr
-rw------- 1 cromwell cromwell 3272 Apr 25 05:53 bob_RSA.key
-rw------- 1 cromwell cromwell 3909 Apr 25 05:53 bob_RSA.pfx

And now we're done with the openssl commands!

7: Load PFX files into YubiKey devices

We select one of the user's PFX files and load it into slot 9A of the YubiKey.

First, let's make sure the YubiKey is detected.

# lsusb
Bus 001 Device 002: ID 1050:0407 Yubico.com Yubikey 4/5 OTP+U2F+CCID
Bus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

ykman is fussy about locale settings. If you set LC_ALL=C so that commands like ls won't ignore case when sorting words, ykman won't like that. My solution is to set LC_ALL=en_US.utf8 or LC_ALL=en_CA.utf8 or LC_ALL=fr_CA.utf8 or whatever is appropriate. Then make an alias for ls in your ~/.bashrc file:
alias ls='LC_ALL=C ls --color==auto'

Now we'll see if there's anything already loaded into the device.

We will use the ykman command from the yubikey-manager package. It does not come with a manual. Yubico has the missing ykman manual.

You can run ykman -h to see a list of applications.

Then ykman piv -h for a list of piv commands.

Then ykman piv import-certificate -h for details on the import-certificate command, and so on.

Let's see what, if anything, is stored in the PIV slots:

# ykman piv info
PIV version: 4.3.7
PIN tries remaining: 3
CHUID:	3019d4e739da739ced39ce739d836858210842108421c84210c3eb34109f8401173d5f226c14c650c5efa2731f350832303330303130313e00fe00
CCC: 	f015a000000116ff0217b5e9fa5cc2252ff4070b8ac930f10121f20121f300f40100f50110f600f700fa00fb00fc00fd00fe00

If there was something shown in PIV slot 9A, we could delete it. However, unmentioned means empty. This device is ready for us to load a certificate. Let's do that, and then check what happened.

# ykman piv import-certificate 9a bob_EC.pfx
Enter a management key [blank to use default key]: 
Enter password to decrypt certificate:  ØØØØØØØØØØØØ
# ykman piv info
PIV version: 4.3.7
PIN tries remaining: 3
CHUID:	3019d4e739da739ced39ce739d836858210842108421c84210c3eb3410358a9d4cb4d43e2606ed79d43b6bcd9e350832303330303130313e00fe00
CCC: 	f015a000000116ff0217b5e9fa5cc2252ff4070b8ac930f10121f20121f300f40100f50110f600f700fa00fb00fc00fd00fe00
Slot 9a:
	Algorithm:	ECCP384
	Subject CN:	bob
	Issuer CN:	pki.cromwell-intl.com
	Serial:		449580035331568342259526991763565597446497392341
	Fingerprint:	abfd843520daf2bba712e9b180f8769c521edabb8fb8ddffd5ed9719e0d0cc60
	Not before:	Apr 25 05:53:04 2024 GMT
	Not after:	Apr 25 05:53:04 2025 GMT

The YubiKey has interpreted the certificate "wrapper", the X.509 data structure, and found the subject's key algorithm, the subject CN, the issuer CN (the CA), and dates of inception and expiration. It outputs the serial number in base 10, not the base 16 used by openssl. The YubiKey reports an ECCP384 (P-384 or secp384r1) elliptic curve key, although it can't use that key.

8: Configure mappings between user names and CN strings

Now we get into Linux server administration. We need to create /etc/pam_pkcs11/cn_map to tell the pam_pkcs11.so PAM module how to map from certificate CN field to Linux user name.

The user name and CN string don't have to be identical. But if they aren't, it will be very difficult to keep track of how your CN strings and user names match up.

We'll use the "minimum UID of an ordinary user" concept defined in /etc/login.defs to decide whether to configure the user. If you want to include root, then you'll manually add a line after running this script:

#!/bin/bash

UID_MIN=$(awk '/\<UID_MIN\>/ {print $2}' /etc/login.defs)
rm -f /etc/pam_pkcs11/cn_map
for login in $( awk -F: -v u=$UID_MIN '$3 >= u {print $1}' /etc/passwd )
do
	echo " adding $login"
	echo "$login -> $login" >> /etc/pam_pkcs11/cn_map
done

9: Store the CA certificates in system databases

We will use the certutil command, see its manual page for details.

# certutil -A -d dbm:/etc/pki/nssdb -n "Linux Root CA RSA" -t "CT,C,C" -i RootCA_RSA.pem 
# certutil -A -d dbm:/etc/pki/nssdb -n "Linux Root CA EC"  -t "CT,C,C" -i RootCA_EC.pem 
# certutil -A -d sql:/etc/pki/nssdb -n "Linux Root CA RSA" -t "CT,C,C" -i RootCA_RSA.pem 
# certutil -A -d sql:/etc/pki/nssdb -n "Linux Root CA EC"  -t "CT,C,C" -i RootCA_EC.pem 
-A Add a certificate to a database.
-d location Use this database.

Specifying it as sql:directory uses the newer SQLite database in the files cert9.db, key4.db, and pkcs11.txt.

Specifying it as dbm:directory uses the legacy database in the files cert8.db, key3.db, and secmod.db.

SQLite is better for reporting, but some authentication tasks need the legacy database.
-n nickname Assign this nickname to the certificate.
-t attributes Specify the trust attributes.
-i filename Read the certificate from this file.

Verify that the certificates are in the database, we can find them by nickname, and we can validate them.

# certutil -L -d sql:/etc/pki/nssdb

Certificate Nickname                                         Trust Attributes
                                                             SSL,S/MIME,JAR/XPI

Linux Root CA RSA                                            CT,C,C
Linux Root CA EC                                             CT,C,C
# certutil -O -d sql:/etc/pki/nssdb -n "Linux Root CA RSA"
"Linux Root CA RSA" [CN=factory.mfew.army.mil,OU=HQ,O=Cromwell Intl,L=West Lafayette,ST=Indiana,C=US]

# certutil -V -d sql:/etc/pki/nssdb -n "Linux Root CA RSA" -u A
certutil: certificate is valid
# certutil -O -d sql:/etc/pki/nssdb -n "Linux Root CA EC"
"Linux Root CA EC" [CN=factory.mfew.army.mil,OU=HQ,O=Cromwell Intl,L=West Lafayette,ST=Indiana,C=US]

# certutil -V -d sql:/etc/pki/nssdb -n "Linux Root CA EC" -u  A
certutil: certificate is valid

That much should be adequate, but just in case:

# mkdir /etc/pam_pkcs11/cacerts
# cp -a RootCA_*pem /etc/pam_pkcs11/cacerts
# ./cacertdir_rehash /etc/pam_pkcs11/cacerts
‘fc8c95cc.0’ -> ‘RootCA_EC.pem’
‘fc8c95cc.1’ -> ‘RootCA_RSA.pem’
# ls -l /etc/pam_pkcs11/cacerts/
total 8
lrwxrwxrwx. 1 root root   13 Mar 26 15:04 fc8c95cc.0 -> RootCA_EC.pem
lrwxrwxrwx. 1 root root   14 Mar 26 15:04 fc8c95cc.1 -> RootCA_RSA.pem
-r--r--r--. 1 root root  843 Jun  6  2020 RootCA_EC.pem
-r--r--r--. 1 root root 1379 Jun  6  2020 RootCA_RSA.pem

The functional part of the cacertdir_rehash script looks like this:

#!/bin/bash

# Actual script starts with tests: called with one
# parameter, the name of an existing directory.

cd $1
# Remove existing symbolic links.
find * -type l -exec rm {} \;
for i in *
do
    if [ -r $i -a ! -d $i ]
    then
	hash=$(openssl x509 -hash -noout -in $i 2> /dev/null)
	if [ -z "$hash" ]
	then
	    continue
	fi
	suffix=0
	while [ -e $hash.$suffix ]
	do
	    suffix=$((suffix + 1))
	done
	ln -sfv $i $hash.$suffix
    fi
done

The file /etc/pam_pkcs11/pam_pkcs11.conf sets a cert_policy in three stanzas:

[... lines deleted ...]
    cert_policy = ca, certificate;
[... lines deleted ...]
    cert_policy = ca, certificate;
[... lines deleted ...]
    cert_policy = ca, certificate;
[... lines deleted ...]

Option ca verifies the validity of the certificate in the device, as belonging to the user and signed by the trusted Root CA.

pam_pkcs11.c
source code
pam_pkcs11.c
documentation

Option certificate attempts to digitally sign a random string using the user's private key embedded in the *.pfx file stored in the device. Reading the debug output (shown toward the bottom of this page) and following the source code, the ca verification of the certificate clearly succeeds first, and then it's a failure to create a new signature of a random string.

As we're already doing two-factor authentication, I haven't pursued this further. I simply remove the certificate option.

Meanwhile the US DoD STIG requires the ocsp_on option. So:

# sed -i 's/cert_policy =.*/cert_policy = ca, ocsp_on/' /etc/pam_pkcs11/pam_pkcs11.conf
# grep cert_policy /etc/pam_pkcs11/pam_pkcs11.conf
    cert_policy = ca, ocsp_on
    cert_policy = ca, ocsp_on
    cert_policy = ca, ocsp_on

While we must not use the authconfig or authconfig-gtk program, in order to keep things in sync we should also:

# sed -i 's/SMARTCARD=no/SMARTCARD=yes/' /etc/sysconfig/authconfig

Let's make sure that the SELinux security labeling is OK:

# restorecon -r -v /etc

Finally, we need to make sure the pcscd daemon is running, to detect that the YubiKey has been plugged in.

# systemctl enable pcscd
# systemctl restart pcscd

10: Configure PAM to authenticate users with pam_pkcs11.so

On RHEL and Oracle Linux and their derivatives, the file /etc/pam.d/system-auth is included in many PAM service files. On Oracle Linux and RHEL 7 and 8 it contains the following:

#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authselect is run.
auth        required      pam_env.so
auth        sufficient    pam_unix.so try_first_pass nullok
auth        required      pam_deny.so

account     required      pam_unix.so

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password    sufficient    pam_unix.so try_first_pass use_authtok nullok sha512 shadow
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
-session     optional      pam_systemd.so
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so

To require both YubiKey and password, simply insert a line. Add the word debug to the end of the line to see a lot of debug output. Once you have things working, you will want to remove the debug option.

#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authselect is run.
auth        required      pam_env.so
auth        required      pam_pkcs11.so wait_for_card debug
auth        sufficient    pam_unix.so try_first_pass nullok
auth        required      pam_deny.so

account     required      pam_unix.so

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password    sufficient    pam_unix.so try_first_pass use_authtok nullok sha512 shadow
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
-session     optional      pam_systemd.so
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so

To require the YubiKey only for text console login, graphical display manager login, and various screensavers, insert a pam_succeed_if.so line. The success=N means "If this succeeds, skip over the next N rules." If you want to require the YubiKey for su, sudo, or other PAM-aware services, just add them to the list.

#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authselect is run.
auth        required      pam_env.so
auth [success=1 default=ignore] pam_succeed_if.so service notin login:gdm:xdm:kdm:xscreensaver:gnome-screensaver:kscreensaver quiet use_uid
auth        required      pam_pkcs11.so wait_for_card debug
auth        sufficient    pam_unix.so try_first_pass nullok
auth        required      pam_deny.so

account     required      pam_unix.so

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password    sufficient    pam_unix.so try_first_pass use_authtok nullok sha512 shadow
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
-session     optional      pam_systemd.so
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so

On Debian and Ubuntu, you will do similar things in the /etc/pam.d/common-auth file.

Linux, PAM,
and Compliance

See my page on compliance through Linux PAM to see what else you must add with pam_faillock.so, pam_lastlog.so, pam_pwhistory.so, pam_pwquality.so, and pam_sss.so to meet the U.S. DoD STIG requirements.

Example output

With the debug option on the pam_pkcs11.so module I see the following on the screen.

Red Hat Enterprise Linux Server 7
Kernel 3.10.0 on an x86_64

localhost login: bob
DEBUG:pam_config.c:230: Using config file /etc/pam_pkcs11/pam_pkcs11.conf
DEBUG:pkcs11_lib.c:182: Initializing NSS ...
DEBUG:pkcs11_lib.c:192: Initializing NSS ... database=/etc/pki/nssdb
DEBUG:pkcs11_lib.c:212: ... NSS Complete
DEBUG:pam_pkcs11.c:272: Is it a screen saver?
DEBUG:pam_pkcs11.c:287: explicit username = [bob]
DEBUG:pam_pkcs11.c:315: loading pkcs #11 module...
DEBUG:pkcs11_lib.c:237: Looking up module in list
DEBUG:pkcs11_lib.c:240: modlist = 0x2384ef0 next = 0x238f260

DEBUG:pkcs11_lib.c:241: dllName= <null>

DEBUG:pkcs11_lib.c:240: modlist= 0x238f260 next = 0x0

DEBUG:pkcs11_lib.c:241: dllName= libcoolkeypk11.so

DEBUG:pam_pkcs11.c:324: Initializing pkcs #11 module...
Found the Smart card.
Welcome bob!
Smart card PIN: ######
DEBUG:pkcs11_lib.c:761: cert 0: found (bob:PIV ID Certificate), "CN=bob,OU=HQ,O=Cromwell International,L=West Lafayette,ST=Indiana,C=US"
DEBUG:mapper_mgr.c:172: Retrieveing mapper module list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'cn'
DEBUG:mapper_mgr.c:197: Inserting mapper [cn] into list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'uid'
DEBUG:mapper_mgr.c:197: Inserting mapper [uid] into list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'pwent'
DEBUG:mapper_mgr.c:197: Inserting mapper [pwent] into list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'null'
DEBUG:mapper_mgr.c:197: Inserting mapper [null] into list
DEBUG:pam_pkcs11.c:494: verifying the certificate #1
DEBUG:cert_vfy.c:34: Verifying Cert: bob:PIV ID Certificate (CN=bob,OU=HQ,O=Cromwell International,L=West Lafayette,ST=Indiana,C=US)
DEBUG:mapper_mgr.c:300: Mapper module cn match() returns 1
DEBUG:pam_pkcs11.c:547: certificate is valid and matches the user
DEBUG:pam_pkcs11.c:610: Skipping signature check
DEBUG:pam_pkcs11.c:679: Releasing pkcs #11 module...
DEBUG:pam_pkcs11.c:702: authentication succeeded
Password: ØØØØØØØØØØØØØØØ
DEBUG:pam_pkcs11.c:695:pam_sm_setcred() called
Last login: Thu Apr 25 05:53:04 UTC 2024 on tty3
[bob@localhost ~]$

If I had left the cert_policy field in /etc/pam_pkcs11/pam_pkcs11.conf reading:
cert_policy = ca, signature
then I instead get the following output. Bold lines are different.

Red Hat Enterprise Linux Server 7
Kernel 3.10.0 on an x86_64

localhost login: bob
DEBUG:pam_config.c:230: Using config file /etc/pam_pkcs11/pam_pkcs11.conf
DEBUG:pkcs11_lib.c:182: Initializing NSS ...
DEBUG:pkcs11_lib.c:192: Initializing NSS ... database=/etc/pki/nssdb
DEBUG:pkcs11_lib.c:212: ... NSS Complete
DEBUG:pam_pkcs11.c:272: Is it a screen saver?
DEBUG:pam_pkcs11.c:287: explicit username = [bob]
DEBUG:pam_pkcs11.c:315: loading pkcs #11 module...
DEBUG:pkcs11_lib.c:237: Looking up module in list
DEBUG:pkcs11_lib.c:240: modlist = 0x2384ef0 next = 0x238f260

DEBUG:pkcs11_lib.c:241: dllName= <null>

DEBUG:pkcs11_lib.c:240: modlist= 0x238f260 next = 0x0

DEBUG:pkcs11_lib.c:241: dllName= libcoolkeypk11.so

DEBUG:pam_pkcs11.c:324: Initializing pkcs #11 module...
Found the Smart card.
Welcome bob!
Smart card PIN: ######
DEBUG:pkcs11_lib.c:761: cert 0: found (bob:PIV ID Certificate), "CN=bob,OU=HQ,O=Cromwell International,L=West Lafayette,ST=Indiana,C=US"
DEBUG:mapper_mgr.c:172: Retrieveing mapper module list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'cn'
DEBUG:mapper_mgr.c:197: Inserting mapper [cn] into list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'uid'
DEBUG:mapper_mgr.c:197: Inserting mapper [uid] into list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'pwent'
DEBUG:mapper_mgr.c:197: Inserting mapper [pwent] into list
DEBUG:mapper_mgr.c:73: Loading static module for mapper 'null'
DEBUG:mapper_mgr.c:197: Inserting mapper [null] into list
DEBUG:pam_pkcs11.c:494: verifying the certificate #1
DEBUG:cert_vfy.c:34: Verifying Cert: bob:PIV ID Certificate (CN=bob,OU=HQ,O=Cromwell International,L=West Lafayette,ST=Indiana,C=US)
DEBUG:mapper_mgr.c:300: Mapper module cn match() returns 1
DEBUG:pam_pkcs11.c:547: certificate is valid and matches the user
DEBUG:pam_pkcs11.c:831: Signature failed: (null)
ERROR:pam_pkcs11.c:589: sign_value() failed:
DEBUG:mapper_mgr.c:214: unloading mapper module list
DEBUG:mapper_mgr.c:137: calling mapper_module_end() cn
DEBUG:mapper_mgr.c:148: Module cn is static: don't remove
DEBUG:mapper_mgr.c:137: calling mapper_module_end() uid
DEBUG:mapper_mgr.c:148: Module uid is static: don't remove
DEBUG:mapper_mgr.c:137: calling mapper_module_end() pwent
DEBUG:mapper_mgr.c:148: Module pwent is static: don't remove
DEBUG:mapper_mgr.c:137: calling mapper_module_end() null
DEBUG:mapper_mgr.c:148: Module null is static: don't remove
Login incorrect

localhost login: