# Smart Card / PKCS#11 support

OpenConnect supports the use of X.509 certificates and keys from smart cards (as well as software storage such as GNOME Keyring and SoftHSM) by means of the PKCS#11 standard. Objects from PKCS#11 tokens are specified by a PKCS#11 URI according to RFC 7512.

In order to use a certificate or key with OpenConnect, you must provide a PKCS#11 URI which identifies it sufficiently. That can be as simple as the following example:

- `openconnect -c pkcs11:id=%01 vpn.example.com`

However, if you're now looking blankly at a USB crypto device and wondering what PKCS#11 URI to use, the following documentation should hopefully assist you in working it out.

## Identifying the token

In order to use a PKCS#11 token with OpenConnect, first it must be installed appropriately in the system's p11-kit configuration. You shouldn't need to worry about this; it should automatically be the case for properly packaged software on any modern operating system.

Typically, the smart card support is likely to be provided by OpenSC and a distribution's packaging of OpenSC should automatically have registered the OpenSC module with p11-kit by creating a file such as `/usr/share/p11-kit/modules/opensc.module`.

In order to query the available PKCS#11 modules, and the certificates stored therein, the best tool to use is the p11tool distributed with GnuTLS. In Fedora it's in the `gnutls-utils` package.

First identify the PKCS#11 modules which are available by using the `--list-tokens` option:

- `p11tool --list-tokens`

This should produce output including something like the following:

    Token 7:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29
    	Label: PIV_II (PIV Card Holder pin)
    	Type: Hardware token
    	Manufacturer: piv_II
    	Model: PKCS#15 emulated
    	Serial: 108421384210c3f5

This example shows the relatively common PIV SmartCard, in this case in a Yubikey NEO device.

## Locating the certificate

Having established that the token is present and registered correctly with p11-kit, the next step is to identify the URI of the certificate you wish to use. You will note that the above output of `p11tool --list-tokens` gave a PKCS#11 URI for each token. With that, we can now query the objects available within a specific token, using the `--list-all-certs` option. We can cut and paste the PKCS#11 URI for the token, but be careful to put it within quotes because it contains semicolons:

- `p11tool --list-all-certs 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29'`

Note that the PKCS#11 URI specifies a list of attributes which must match. Some of these match criteria may be redundant — in this case we've asked it to list the certificates in a token which has a model of "PKCS#15 emulated" and a manufacturer of "piv_II" and serial number 108421384210c3f5 and token label "PIV_II (PIV Card Holder pin)". Since any one of those criteria would probably be sufficient to uniquely identify this token from the other configured tokens in our system, a simpler command line would also work. For example:

- `p11tool --list-all-certs pkcs11:manufacturer=piv_II`

The output of either such command should look something like this:

    Object 0:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%01;object=Certificate%20for%20PIV%20Authentication;object-type=cert
    	Type: X.509 Certificate
    	Label: Certificate for PIV Authentication
    	ID: 01

    Object 1:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%02;object=Certificate%20for%20Digital%20Signature;object-type=cert
    	Type: X.509 Certificate
    	Label: Certificate for Digital Signature
    	ID: 02

    Object 2:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%03;object=Certificate%20for%20Key%20Management;object-type=cert
    	Type: X.509 Certificate
    	Label: Certificate for Key Management
    	ID: 03

    Object 3:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%04;object=Certificate%20for%20Card%20Authentication;object-type=cert
    	Type: X.509 Certificate
    	Label: Certificate for Card Authentication
    	ID: 04

This device has four certificates installed; the URL for each one is given in the output. (Choosing between the certificates on a given device, if there is more than one, is left as an exercise for the user. You may need to try each one.)

Some devices may not even permit you to list the certificates without logging in. In that case add `--login` to the `p11tool` command line above, and provide the PIN when requested

For OpenConnect 7.01 we should be able to use the URI seen here in its entirety, and the software will be cunning enough to find the corresponding key:

- `openconnect -c 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%01;object=Certificate%20for%20PIV%20Authentication;object-type=cert' vpn.example.com`

Older versions, however, may require a little help...

## Helping OpenConnect find the key

If no explicit `-k` argument is given to specify the key, OpenConnect will use the contents of the `-c` argument as the basis for finding both certificate and key.

It will sensibly add `object-type=cert` or `object-type=private` for itself, according to which object it is trying to locate each time. But in version 7.00 and earlier, it would not do that if the URI you provide already contained any `object-type=` element. So the first thing you need to do with older versions of OpenConnect is trim that part of the URI. So the above example might now be:

- `openconnect -c 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%01;object=Certificate%20for%20PIV%20Authentication' vpn.example.com`

Additionally, it can sometimes be the case that although the ID (`id=`) for a certificate should match the ID of its matching key, the label (`object=`) might not match. Newer versions of OpenConnect (7.01+), on failing to find a key, will strip the label from the search URI and add the ID of the certificate that was found (even if no ID was part of the original search terms provided with the `-c` option). But older versions don't.

So it can be useful also to remove the `object=` part of the URI and leave only the `id=` attribute to specify the individual object, so that you're giving search criteria which are true for both the certificate and the key:

- `openconnect -c 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%01' vpn.example.com`

And while we're at it, that's still a massively redundant way of specifying which token to look in, so we can cut that down as we did before just to make it less unwieldy:

- `openconnect -c 'pkcs11:manufacturer=piv_II;id=%01' vpn.example.com`

## Searching for the key manually

If the heuristics for finding the key don't work, you can always provide an explicit PKCS#11 URI for the key with the `-k` option. You can look for them by using the `--list-privkeys` option to `p11tool`. You will almost certainly want to use the `--login` option too:

- `p11tool --list-privkeys --login pkcs11:manufacturer=piv_II`

    Token 'PIV_II (PIV Card Holder pin)' with URL 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29' requires user PIN
    Enter PIN:
    Object 0:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%01;object=PIV%20AUTH%20key;object-type=private
    	Type: Private key
    	Label: PIV AUTH key
    	Flags: CKA_WRAP/UNWRAP; CKA_PRIVATE; CKA_SENSITIVE;
    	ID: 01

    Object 1:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%02;object=SIGN%20key;object-type=private
    	Type: Private key
    	Label: SIGN key
    	Flags: CKA_PRIVATE; CKA_SENSITIVE;
    	ID: 02

    Object 2:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%03;object=KEY%20MAN%20key;object-type=private
    	Type: Private key
    	Label: KEY MAN key
    	Flags: CKA_WRAP/UNWRAP; CKA_PRIVATE; CKA_SENSITIVE;
    	ID: 03

    Object 3:
    	URL: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%04;object=CARD%20AUTH%20key;object-type=private
    	Type: Private key
    	Label: CARD AUTH key
    	Flags: CKA_SENSITIVE;
    	ID: 04

Here's the full longhand specification of both certificate and key:

- `openconnect -c 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%01;object=Certificate%20for%20PIV%20Authentication;object-type=cert' -k 'pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=108421384210c3f5;token=PIV_II%20%28PIV%20Card%20Holder%20pin%29;id=%01;object=PIV%20AUTH%20key;object-type=private' vpn.example.com`

OpenConnect doesn't care; you can use certificate and key from entirely different hardware tokens if you want to. Or one from a file. Or a key from a TPM and a certificate from a PKCS#11 hardware token. Or all kinds of bizarre combinations. But if it's a sensible combination on a sanely configured PKCS#11 token, and OpenConnect can't infer the key location from the certificate, then please send us an email and we'll try to fix it.