[UPDATED 2022.07.06]
I already covered how baffling smart cards hardware and standards can be:
But now that you - and presumably I - are no longer in a state of utter helplessness when confronted by smart cards mumbo jumbo, let’s see what clever things we can do with them. Coming to mind:
Two-factor authentication: PAM, Kerberos, SSH, VPN, web, etc.
E-mail: signature and encryption
Now, each of those buzzwords may be familiar to the Linux layman. But - oh dear! - what intricacies, pitfalls, frustrations and sense of despair they entice when smart cards come into play!
Here be dragons!
For the sake of the examples below, we’ll assume the following:
a PKI smart card from Aventra (MyEID 4.0.0)
used with two PIN codes
one for each purpose: login (authentication) and e-mail
along a card reader from HID Global (Omnikey 3x21 or 5422)
And for the sake of completeness, we’ll use:
a smart card-generated login keypair
(you loose your card, you’re out, you dumbass!)
an externally-generated (and backed-up!) e-mail keypair (better still be able to decrypt those e-mails, though, huh?!)
Although all necessary packages are readily available in Debian/Stretch, our chosen use case and hardware requires that we use packages from Debian/Buster:
for the Aventra MyEID (4.0.0) smart card (using multiple PINs):
opensc >= 0.18.0
for the HID Omnikey 5422 card reader:
libccid >= 1.4.27
The first step is to initalize the smart card and create the necessary PKCS#15 structure, along the PIN codes - aka. tokens/slots - for our declared purposes.
Install the required Debian packages:
apt-get install opensc pcscd
PKCS#15 initialization:
# Note: *user* PIN/PUK will be created separately (see below)
pkcs15-init --create-pkcs15 --pin 0000 --puk 0000
pkcs15-tool --dump
PINs/PUKs (tokens/slots) creation:
pkcs15-init --store-pin --auth-id 1 --label "Login"
pkcs15-init --store-pin --auth-id 2 --label "Email"
pkcs15-tool --list-pins
Next, we need to generate/import the required PKI material in each token/slot.
Login keypair generation:
pkcs15-init --generate-key rsa/2048 --id 1 --auth-id 1 \
--label "username@example.org" \
--key-usage digitalSignature,keyAgreement,keyEncipherment
pkcs15-tool --list-keys
pkcs15-tool --list-public-keys
Login certificate import (after having its CSR properly signed; see the OpenSSL section further below):
pkcs15-init --store-certificate /path/to/cert.pem --id 1 --cert-label "username@example.org"
pkcs15-init --delete-object pubkey --id 1
pkcs15-tool --list-certificates
E-mail key/certificate import (externally-generated and exported into a PKCS#12 bundle; see the OpenSSL section further below):
pkcs15-init --store-private-key /path/to/file.p12 --format pkcs12 --id 2 --auth-id 2 \
--cert-label "full.name@example.org" \
--key-usage digitalSignature,keyEncipherment,dataEncipherment \
--ignore-ca-certificates
pkcs15-tool --list-keys
pkcs15-tool --list-certificates
Now that we’re done with the smart card itself, we can “finalize” its preparation:
pkcs15-init --finalize
The exact meaning of this step depends on the actual smart card vendor.
Using smart cards for our purposes will in all cases be achieved thanks to the PKCS#11 software API/stack.
Install the required Debian packages:
apt-get install opensc-pkcs11
Harden the default OpenSC configuration, in /etc/opensc/opensc.conf
:
app default {
# Disable swap memory usage
paranoid_memory = true;
reader_driver pcsc {
# Disable pinpad (required by PKCS#11 'atomic = true'; see below)
# (and QUIRK for HID Omnikey 3x21 with OpenSC < 0.18)
enable_pinpad = false;
}
}
app opensc-pkcs11 {
pkcs11 {
# Lock card once logged-in (implied by 'atomic = true')
lock_login = true;
# Atomic (stateful) transactions
atomic = true;
# Make sure PIN unblocking is achieved with proper tool (not via PKCS#11)
user_pin_unblock_style = none;
}
}
And test its functioning:
pkcs11-tool --list-token-slots # -> "Login (MyEID)" and "Email (MyEID)"
pkcs11-tool --test --login --token-label "Login (MyEID)"
pkcs11-tool --test --login --token-label "Email (MyEID)"
WARNINGS:
Some libraries and utilities may require you to provide so-called PKCS#11 URI as private key or certificate parameters. Those URIs are standardized by the RFC 7512 and look like:
# WARNING: labels MUST be URL-encoded
# ... private key
PKCS11_RFC7512_URI='pkcs11:type=private;token=Login%20%28MyEID%29;object=username%40example.org'
# ... certificate
PKCS11_RFC7512_URI='pkcs11:type=cert;token=Login%20%28MyEID%29;object=username%40example.org'
PKCS#11 support is implemented in Mozilla Thunderbird, Firefox and Google
Chromium thanks to the libnss3
Network Security Services (NSS) library:
Add OpenSC driver to NSSDB:
modutil -dbdir sql:${HOME}/.pki/nssdb/ -add OpenSC -libfile /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
modutil -dbdir sql:${HOME}/.pki/nssdb/ -list OpenSC
certutil -d sql:${HOME}/.pki/nssdb/ -L -h 'Login (MyEID)'
certutil -d sql:${HOME}/.pki/nssdb/ -L -h 'Email (MyEID)'
NOTE: For Thunderbird and Firefox, you may need to specify the path to your
profile instead of the home directory ~/.pki/nssdb
location. Look for
a pkcs11.txt
file, where modutil
will actually have added its magic.
WARNING: For Thunderbird, make sure the emailAddress
attribute is included
in the certificate DN
(as opposed to the
RFC 3850, ยง.3
recommendation) or it will complain about not being able to find the certificate
at the moment the message is sent (albeit being correctly configured in the
Account Settings
> Security
section).
OpenSSL shall be required to generate the Certificate Signing Request (CSR) corresponding to our login key, as well as issuing the CA-signed X.509 certificates for both login and email purposes. Generally speaking, this implies:
Install the required Debian packages:
apt-get install libengine-pkcs11-openssl1.1
Create the PKCS#11-friendly openssl.conf
:
## OpenSSL Configuration for SC <-> PKCS#11
## REF: https://www.openssl.org/docs/man1.1.0/apps/config.html
## Initialization
openssl_conf = SSL_Configuration
[ SSL_Configuration ]
engines = SSL_Engines
## Engines
[ SSL_Engines ]
pkcs11 = ENG_PKCS11
# PKCS#11
# REF: https://github.com/OpenSC/libp11
[ ENG_PKCS11 ]
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/libpkcs11.so
MODULE_PATH = /usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so
Check the engine is properly configured
OPENSSL_CONF=/path/to/openssl.conf openssl engine -t
# [output]
(pkcs11) pkcs11 engine
[ available ]
Create a CSR from a smart card private key:
openssl req -new -engine pkcs11 -keyform engine -key "<rfc7512-uri>" ...
Create a self-signed certificate from a smart card private key:
openssl req -new -x509 -engine pkcs11 -keyform engine -key "<rfc7512-uri>" ...
CA-signing a certificate, with the CA private key stored on a smart card:
openssl ca -engine pkcs11 -keyform engine -keyfile "<rfc7512-uri>" ...
OpenSSL being the horrifying piece of software that it is (at least to my simple self), I invite you to discover OpenSSL-Easy, my humble and certainly poor attempt at making one’s OpenSSL life easier:
cURL relies on OpenSSL engine to perform its PKCS#11 magic. Once the engine installed (see above), it is just a matter of using the proper PKCS#11 URIs as private key and certificate arguments:
# Check the PKCS#11 engine integration
OPENSSL_CONF=./openssl.conf curl --engine list
# [output]
Build-time engines:
pkcs11
# TLS client authentication
curl \
--key 'pkcs11:type=private;token=Login%20%28MyEID%29;object=username%40example.org' \
--cert 'pkcs11:type=cert;token=Login%20%28MyEID%29;object=username%40example.org' \
https://authenticate.example.org
Using the smart card along SSH for RSA-based authentication is straight-forward, except for one BIG gotcha (see the WARNINGS further below).
Export the public key to your SSH authorized keys:
pkcs15-tool --read-ssh-key 1 | tee -a ~/.ssh/authorized_keys2
Login using your smart card:
ssh -I /usr/lib/x86_64-linux-gnu/onepin-opensc-pkcs11.so ...
Optionally temporarily storing your key in the SSH agent:
ssh-add -s /usr/lib/x86_64-linux-gnu/onepin-opensc-pkcs11.so -t 1h
And correctly removing it from the SSH agent once done with it:
ssh-add -e /usr/lib/x86_64-linux-gnu/onepin-opensc-pkcs11.so
WARNINGS:
onepin-opensc-pkcs11.so
library to prevent the SSH
agent from attempting to unlock all tokens/slots with the same PIN and
eventually locking those tokens/slots that do not match.
This is also the reason why one MUST create the login token/slot as the
first one on the smart card.Using public key material instead of passwords for Kerberos authentication is known as PKINIT:
Setting it up is rather straight-forward, albeit not devoid of gotchas (see the WARNINGS further below).
Install the required Debian packages (on both servers and clients):
apt-get install krb5-pkinit
Generating the required servers certificates implies delving into OpenSSL;
once again, one’s life may be easier thanks to OpenSSL-Easy:
OpenSSL-Easy (Authentication section)
Debug the Kerberos-specific ASN.1 Subject Alternative Name (SAN) with:
openssl asn1parse -dump -strictpem -in /path/to/cert.pem
Configure the Kerberos Key Distribution Center (KDC) servers;
in /etc/krb5kdc/kdc.conf
:
[realms]
EXAMPLE.ORG = {
pkinit_anchors = FILE:/etc/ssl/local/localsite-auth-ca.pem
pkinit_identity = FILE:/etc/krb5kdc/kdc.example.org-cert.pem,/etc/krb5kdc/kdc.example.org-key.pem
}
Configure the Kerberos clients; in /etc/krb5.conf
:
[realms]
EXAMPLE.ORG = {
pkinit_anchors = FILE:/etc/ssl/local/localsite-auth-ca.pem
pkinit_identities = PKCS11:module_name=/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
pkinit_cert_match = <EKU>pkinit
}
WARNINGS:
MIT Kerberos V does not allow to enforce PKINIT; if it fails, it will automatically revert to password-based authentication
If one is willing to enforce smart card-based authentication, one should look into using the PAM PKCS#11 module instead of PKINIT (see section below)
Using the smart card for authenticating on a Linux box implies adding the PKCS#11
module - pam_pkcs11
- to the PAM stack. Again, nothing particularly complicated,
except a few gotchas (see the WARNINGS further below).
Install the required Debian packages:
apt-get install libpam-pkcs11
Configure the PAM PKCS#11 module; in /etc/pam_pkcs11/pam_pkcs11.conf
:
pam_pkcs11 {
debug = false;
# No need for password (use the smart card PIN instead)
nullok = true;
use_first_pass = false;
try_first_pass = false;
use_authtok = false;
# PKCS#11 module
use_pkcs11_module = opensc;
pkcs11_module opensc {
description = "OpenSC PKCS#11 Module";
token_type = "SmartCard";
module = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so;
slot_description = "none";
support_threads = false;
ca_dir = /etc/ssl/local/localsite-auth-ca.pem;
crl_dir = /etc/ssl/local/localsite-auth-crl.pem;
#cert_policy = ca,signature,crl_auto; # WARNING! BUG!
cert_policy = ca,signature;
}
# Mappers
use_mappers = cn, null;
# ... CN (is username)
mapper cn {
debug = false;
module = internal;
ignorecase = false;
mapfile = "none";
}
# ... catch all
mapper null {
debug = false;
module = internal ;
default_match = false;
}
}
Configure the PAM authentication stack, in /etc/pam.d/common-auth
:
# User authentication
# ... local account
auth [success=ok default=1] pam_localuser.so
auth [success=done default=die] pam_unix.so
# ... PKCS#11
# NB: pam_pkcs11 will automatically prompt "SmartCard authentication starts"
auth [success=1 default=ignore] pam_succeed_if.so quiet use_uid service notin login:lightdm
auth [success=ok default=die] pam_pkcs11.so
# ... Kerberos/LDAP
auth optional pam_echo.so Password authentication starts
auth [success=2 default=ignore] pam_krb5.so minimum_uid=1000
auth [success=1 default=die] pam_ldap.so use_first_pass
# ... auth. failed
auth requisite pam_deny.so
# ... auth. success
auth required pam_permit.so
WARNINGS:
If MIT Kerberos V is configured for PKINIT (in /etc/krb5.conf
), pam_krb5
will attempt PKINIT authentication, when password-based fails, even if
try_pkinit
is not specified
At the time of writing, PAM PKCS#11 module will segfault if any form of CRL check is attempted
The smart card PIN code will be stored by pam_pkcs11
and used as “password”
by subsequent modules (e.g. pam_gnome_keyring
); this can lead to akward - if
not unsecure - results; thus our mandating password (Kerberos/LDAP)
authentication once PKCS#11’s succeeded
(see this as “two+”-factor authentication)
Using the smart card for (client) authentication on the Apache web server in a LDAP-centric environment is far - very far! - from trivial.
I invite you to read the article dedicated on the subject:
[UPDATED 2021.10.01] Issues reported below are solved as per Debian 11 (Bullseye)
Naively, one would think that using the smart card with OpenVPN would be as simple as:
Recovering the PKCS#11 ID(s) for use by OpenVPN:
openvpn --show-pkcs11-ids /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
And adding the relevant PKCS#11 stanza to OpenVPN (client) configuration:
pkcs11-providers /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
pkcs11-id '<ID>'
Poor you! Nope! Not that easy! Well… Yes… That easy, provided you take into account the following well-(and-longime)-known bugs:
Shortly put, you will just need to:
retrieve OpenVPN and PKCS#11 helper source packages
apply the patches mentioned in the two above bug reports
make sure to disable PKCS#11 helper (multi-)threading
build the corresponding new Debian packages
and install them
… what!?!…
GnuPG is expected to be natively used along ad-hoc OpenPGP cards, totally different beasts from the PKCS#15 and X.509-oriented smart cards we’re now growing accustomed to.
Howevever, GnuPG can be made to work with those, again via the PKCS#11 interface and some additional trickery:
Install the required Debian package:
apt-get install gnupg-pkcs11-scd
Configure the GnuPG Agent, in ~/.gnupg/gpg-agent.conf
:
# GnuPG PKCS#11 Smart Card Daemon (SCD)
scdaemon-program scdaemon-program /usr/bin/gnupg-pkcs11-scd
Configure the GnuPG PKCS#11 Smart Card Daemon (SCD), in ~/.gnupg/gnupg-pkcs11-scd.conf
:
# Pin cache period [seconds]
pin-cache 10
# PKCS#11 provider
providers opensc
provider-opensc-library /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
# GnuPG integration
emulate-openpgp
openpgp-auth <login-hash>
openpgp-sign <email-hash>
openpgp-encr <email-hash>
Where the <login-hash>
and <email-hash>
are recovered with:
echo 'SCD LEARN' | gpg-agent --server gpg-connect-agent 2>/dev/null | grep KEYPAIRINFO
Reload the GnuPG Agent and verify everything is ready:
echo 'RELOADAGENT' | gpg-agent --server gpg-connect-agent
gpg --card-status # expect some ouput incl. login/email hashes
We can therefrom coerce GnuPG into being more X.509 (and S/MIME) friendly:
Import our CA root (and intermediate) certificate(s):
cat CA-cert.pem | gpgsm --import
Import our smart cards certificates:
pkcs15-tool --read-certificate 1 | gpgsm --import
pkcs15-tool --read-certificate 2 | gpgsm --import
gpgsm --list-keys
And associate the corresponding (smart card) private keys:
gpgsm --learn-card
gpgsm --list-secret-keys
(TODO: unravel the misteries of using the smart card with gpg
itself)
WARNINGS:
Python requests is nowadays the most ubiquitous library for doing HTTP(S).
However, PKCS#11 isn’t natively supported and requires leveraging the
M2Crypto library to hook the necessary
gear work in, by replacing the https://
adapter (handler) by the
M2HttpsAdapter
found in:
Which is as simple as it gets:
from requests import Session
from m2requests import M2HttpsAdapter
request = Session()
request.mount("https://", M2HttpsAdapter())
# PKCS#11 URI; REF: https://tools.ietf.org/html/rfc7512
request.cert=("pkcs11:type=cert;...", "pkcs11:type=private;...")
request.get("https://...")
Should you need to encrypt/decrypt data using your SmartCard:
# Retrieve the SmartCard's public key
pkcs11-tool -r --id 01 --type cert \
| openssl x509 -inform DER -noout -pubkey \
> pub.pem
# Encrypt the data
cat data \
| openssl rsautl -encrypt -pubin -inkey pub.pem \
| base64 \
| tee data.enc.b64
# Decrypt the data
pkcs11-tool -m RSA-PKCS --decrypt --id 01 -i <(base64 -d data.enc.b64)
If you run into troubles, you can easily debug PKCS#11 interactions thanks to
OpenSC’s OPENSC_DEBUG
or PKCS11SPY
environment variables:
[UPDATED 2021.10.01] This issue is solved as per Debian 11 (Bullseye)
Probabilistic Signature Scheme (PSS), now creeps in TLS connections relying on OpenSSL 1.1.1 or above. This may prevent you to use your credentials on websites that moved on to TLS 1.3 or with OpenVPN (which stubbornly refuses to stick to TLS 1.2 and not use PSS), unless you use:
OpenSC 0.20.0 or above
LibP11 0.4.8 or above (<-> OpenSSL engine)
PKCS#11 Helper patch (forecoming 1.26.0 release <-> OpenVPN)
[UPDATED 2022.07.06; Thanks to Salvatore Stanzione for the heads up]
Keen eyes might have spotted the entirety of this gibberish revolves around OpenSC and its “native”
libraries (opensc-pkcs11.so
and siblings).
One shall bear in mind some SmartCards might actually not be (entirely/properly) supported by
OpenSC and might require their own, vendor/product-specific PKCS#11 library (to be used in place
of the opensc-pkcs11.so
one in above examples).
Smart card?!? Easy!!!