From Fedora Project Wiki
Line 12: Line 12:
  
 
The simplest one to test with is probably GNOME keyring. Obviously not everyone will be running GNOME for their day-to-day usage but it shouldn't be too hard to use GNOME keyring just for a simple test.
 
The simplest one to test with is probably GNOME keyring. Obviously not everyone will be running GNOME for their day-to-day usage but it shouldn't be too hard to use GNOME keyring just for a simple test.
 +
 +
== Generate a certificate ==
 +
 +
Useful certificates are actually signed by someone to vouch for the owner of the certificate. If you have one of those (and if you're a Fedora packager, you do) you can use it. Alternatively, just create your own for testing:
 +
 +
$ openssl req -x509 -days 3653 -new -nodes -out testkey.pem -keyout testkey.pem -subj /CN=testkey
  
 
== Import certificate ==
 
== Import certificate ==
Line 17: Line 23:
 
The seahorse GUI tool allows you to browse the contents of PKCS#11 tokens and import certificates and keys. If you simply run seahorse under GNOME you should see a 'Gnome2 Key Storage' token listed under the 'Certificates' heading. You can select the 'File'... 'Import' menu item to import a certificate from a file into the GNOME keyring (or indeed any other provider you choose to use).
 
The seahorse GUI tool allows you to browse the contents of PKCS#11 tokens and import certificates and keys. If you simply run seahorse under GNOME you should see a 'Gnome2 Key Storage' token listed under the 'Certificates' heading. You can select the 'File'... 'Import' menu item to import a certificate from a file into the GNOME keyring (or indeed any other provider you choose to use).
  
You can create a key for testing, as follows:
+
If you import the testkey.pem generated in the above example, give "testkey" as the label when prompted. If you import your Fedora certificate from ~/.fedora.cert then use your Fedora username (e.g. dwmw2). You are only being asked for the label for the private key, and life is easier if that matches the label of the cert (which is automatically taken from the subject of the cert).
$ openssl req -x509 -days 3653 -new -nodes -out testkey.pem -keyout testkey.pem -subj /CN=testkey
 
  
 
== Determine the PKCS#11 URI of your certificate ==
 
== Determine the PKCS#11 URI of your certificate ==
  
Unfortunately, seahorse doesn't show the PKCS#11 URI of the objects when you're browsing ''([https://bugzilla.gnome.org/show_bug.cgi?id=749071 bug #749071])''. So you'll want to use <code>p11tool</code> to list them and find the URI:
+
Unfortunately, seahorse doesn't show the PKCS#11 URI of the objects when you're browsing ''([https://bugzilla.gnome.org/show_bug.cgi?id=749071 GNOME bug #749071])''. So you'll want to use <code>p11tool</code> to list them and find the URI:
 
<pre>
 
<pre>
 
$ p11tool --list-certs --login pkcs11:token=Gnome2%20Key%20Storage
 
$ p11tool --list-certs --login pkcs11:token=Gnome2%20Key%20Storage
 
Object 0:
 
Object 0:
URL: pkcs11:model=1.0;manufacturer=Gnome%20Keyring;serial=1%3aUSER%3aDEFAULT;token=Gnome2%20Key%20Storage;id=%59%ae%17%70%af%e8%af%9f%5b%94%fb%c6%89%f6%f1%4c%11%5c%36%0e;object=Woodhouse%2c%20David;type=cert
+
URL: pkcs11:model=1.0;manufacturer=Gnome%20Keyring;serial=1%3aUSER%3aDEFAULT;token=Gnome2%20Key%20Storage;id=%f5%25%95%6c%95%9b%c3%b4%7f%19%b7%a1%0d%92%a8%b5%a3%57%4b%5f;object=testkey;type=cert
 
Type: X.509 Certificate
 
Type: X.509 Certificate
Label: Woodhouse, David
+
Label: testkey
ID: 59:ae:17:70:af:e8:af:9f:5b:94:fb:c6:89:f6:f1:4c:11:5c:36:0e
+
ID: f5:25:95:6c:95:9b:c3:b4:7f:19:b7:a1:0d:92:a8:b5:a3:57:4b:5f
 
</pre>
 
</pre>
  
The interesting part there is the URL. In fact a lot of the information there is redundant; all you probably need is the <code>token</code> and <code>id</code> parts:
+
The interesting part there is the URL. In fact a lot of the information there is redundant; all you need is enough match criteria to uniquely specify the object. So these are suitable URIs for referring to this certificate:
 +
 
 +
* <code>pkcs11:token=Gnome2%20Key%20Storage;object=testkey</code>
 +
* <code>pkcs11:token=Gnome2%20Key%20Storage;id=%f5%25%95%6c%95%9b%c3%b4%7f%19%b7%a1%0d%92%a8%b5%a3%57%4b%5f</code>
 +
* <code>pkcs11:serial=1:USER:DEFAULT;object=testkey</code>
 +
 
 +
In fact with some clients like GnuTLS you don't even need to specify the token, although it helps to speed things up by finding it more quickly. You could probably get away with:
  
<pre>pkcs11:token=Gnome2%20Key%20Storage;id=%59%ae%17%70%af%e8%af%9f%5b%94%fb%c6%89%f6%f1%4c%11%5c%36%0e</pre>
+
* <code>pkcs11:object=testkey</code>
  
 
== See if you can use it ==
 
== See if you can use it ==
Line 43: Line 54:
  
 
<pre>
 
<pre>
$ openconnect -c 'pkcs11:token=Gnome2%20Key%20Storage;id=%59%ae%17%70%af%e8%af%9f%5b%94%fb%c6%89%f6%f1%4c%11%5c%36%0e' https://auth.startssl.comPOST https://auth.startssl.com/
+
$ openconnect -c 'pkcs11:serial=1:USER:DEFAULT;object=testkey' https://auth.startssl.com
Attempting to connect to server 192.116.242.27:443
+
POST https://auth.startssl.com/
Using client certificate 'Woodhouse\, David'
+
Attempting to connect to server 104.192.110.222:443
SSL negotiation with auth.startssl.com
+
Using client certificate 'testkey'
SSL connection failure: A TLS fatal alert has been received.
+
...
Failed to open HTTPS connection to auth.startssl.com
 
Failed to obtain WebVPN cookie
 
 
</pre>
 
</pre>
  
Yes, it failed to actually make a VPN connection, but that's expected — that server isn't running an AnyConnect VPN service. The important part is that the VPN client '''was''' attempting to use the correct client certificate.
+
It'll fail to actually make a VPN connection, but that's expected — that server isn't running an AnyConnect VPN service. The important part is that the VPN client '''was''' attempting to use the correct client certificate.
  
Now let's pretend that I'm packaging curl. Curl uses a colon to separate the certificate and the password so we'll need to escape the colon in the URI...
+
Now let's pretend that I'm packaging curl. Note that curl expects to be given "CERTIFICATE:PASSWORD" in its -E option, so we need to escape any colons in the certificate URI:
  
 
<pre>
 
<pre>
$ curl -E 'pkcs11\:token=Gnome2%20Key%20Storage;id=%59%ae%17%70%af%e8%af%9f%5b%94%fb%c6%89%f6%f1%4c%11%5c%36%0e' -I -v https://auth.startssl.com* Rebuilt URL to: https://auth.startssl.com/
+
$ curl -E 'pkcs11\:serial=1\:USER\:DEFAULT;object=testkey' -I -v https://auth.startssl.com/
*  Trying 192.116.242.27...
+
*  Trying 104.192.110.222...
* Connected to auth.startssl.com (192.116.242.27) port 443 (#0)
+
* Connected to auth.startssl.com (104.192.110.222) port 443 (#0)
 
* Initializing NSS with certpath: sql:/etc/pki/nssdb
 
* Initializing NSS with certpath: sql:/etc/pki/nssdb
 
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
 
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
 
   CApath: none
 
   CApath: none
* NSS: client certificate not found: pkcs11:token=Gnome2%20Key%20Storage;id=%e6%34%78%b5%fd%86%77%ba%b9%86%95%c3%19%02%74%d1%f5%33%9a%97
+
* NSS: client certificate not found: pkcs11:serial=1:USER:DEFAULT;object=testkey
* NSS error -12227 (SSL_ERROR_HANDSHAKE_FAILURE_ALERT)
+
...
* SSL peer was unable to negotiate an acceptable set of security parameters.
 
* Closing connection 0
 
curl: (35) NSS: client certificate not found: pkcs11:token=Gnome2%20Key%20Storage;id=%e6%34%78%b5%fd%86%77%ba%b9%86%95%c3%19%02%74%d1%f5%33%9a%97
 
 
</pre>
 
</pre>
  
Oops, that 'client certificate not found' doesn't look good. If curl was built with GnuTLS instead of NSS that probably would have worked fine. As it is, let's file [https://bugzilla.redhat.com/show_bug.cgi?id=1219544 bug 1219544] against curl...
+
Oops, that 'client certificate not found' doesn't look good. If curl was built with GnuTLS instead of NSS that actually would have worked fine. As it is, let's file [https://bugzilla.redhat.com/show_bug.cgi?id=1219544 bug 1219544] against curl...
  
 
= Crypto library support =
 
= Crypto library support =
Line 76: Line 82:
 
== GnuTLS ==
 
== GnuTLS ==
  
If your package builds against GnuTLS, then in most cases it should Just Work. The common GnuTLS APIs for handling certificates and keys will support do the right thing.
+
If your package builds against GnuTLS, then in most cases it should Just Work. The common GnuTLS APIs for handling certificates and keys will silently do the right thing.
  
 
== OpenSSL ==
 
== OpenSSL ==
Line 84: Line 90:
 
== NSS ==
 
== NSS ==
  
Although NSS is entirely based around PKCS#11, NSS is ironically the most problematic of the major crypto libraries. It does not integrate properly with the platform and load the correct PKCS#11 providers ''([https://bugzilla.mozilla.org/show_bug.cgi?id=1161219 Mozilla bug 1161219])'', and it does not facilitate the use of RFC7512 PKCS#11 URIs ''([https://bugzilla.mozilla.org/show_bug.cgi?id=1162897 Mozilla bug 1162897])''.
+
Although NSS is entirely based around PKCS#11, NSS is ironically the most problematic of the major crypto libraries. It does not integrate properly with the platform and load the correct PKCS#11 providers ''([https://bugzilla.mozilla.org/show_bug.cgi?id=248722 Mozilla bug 248722])'', and it does not facilitate the use of RFC7512 PKCS#11 URIs ''([https://bugzilla.mozilla.org/show_bug.cgi?id=1162897 Mozilla bug 1162897])''.
  
 
These deficiencies in NSS can be worked around by manually loading the <code>p11-kit-proxy.so</code> module, and by using <code>libp11-kit</code> to parse URIs and manually iterate over the available tokens and search for objects.
 
These deficiencies in NSS can be worked around by manually loading the <code>p11-kit-proxy.so</code> module, and by using <code>libp11-kit</code> to parse URIs and manually iterate over the available tokens and search for objects.
  
 
Or it might be easier just to build against GnuTLS if your package has that option!
 
Or it might be easier just to build against GnuTLS if your package has that option!

Revision as of 13:50, 3 March 2016

Testing PKCS#11 support

The proposed packaging guidelines say that any program which can accept SSL certificates from a file should also allow them to come from a PKCS#11 token. This page exists to help packagers understand those guidelines and test their packages.

But I don't have any PKCS#11 hardware

You don't need hardware. There are plenty of PKCS#11 providers which are purely software. These include

  • NSS Certificate Database (Firefox, Evolution, Chrome)
  • GNOME keyring
  • SoftHSM

The simplest one to test with is probably GNOME keyring. Obviously not everyone will be running GNOME for their day-to-day usage but it shouldn't be too hard to use GNOME keyring just for a simple test.

Generate a certificate

Useful certificates are actually signed by someone to vouch for the owner of the certificate. If you have one of those (and if you're a Fedora packager, you do) you can use it. Alternatively, just create your own for testing:

$ openssl req -x509 -days 3653 -new -nodes -out testkey.pem -keyout testkey.pem -subj /CN=testkey

Import certificate

The seahorse GUI tool allows you to browse the contents of PKCS#11 tokens and import certificates and keys. If you simply run seahorse under GNOME you should see a 'Gnome2 Key Storage' token listed under the 'Certificates' heading. You can select the 'File'... 'Import' menu item to import a certificate from a file into the GNOME keyring (or indeed any other provider you choose to use).

If you import the testkey.pem generated in the above example, give "testkey" as the label when prompted. If you import your Fedora certificate from ~/.fedora.cert then use your Fedora username (e.g. dwmw2). You are only being asked for the label for the private key, and life is easier if that matches the label of the cert (which is automatically taken from the subject of the cert).

Determine the PKCS#11 URI of your certificate

Unfortunately, seahorse doesn't show the PKCS#11 URI of the objects when you're browsing (GNOME bug #749071). So you'll want to use p11tool to list them and find the URI:

$ p11tool --list-certs --login pkcs11:token=Gnome2%20Key%20Storage
Object 0:
	URL: pkcs11:model=1.0;manufacturer=Gnome%20Keyring;serial=1%3aUSER%3aDEFAULT;token=Gnome2%20Key%20Storage;id=%f5%25%95%6c%95%9b%c3%b4%7f%19%b7%a1%0d%92%a8%b5%a3%57%4b%5f;object=testkey;type=cert
	Type: X.509 Certificate
	Label: testkey
	ID: f5:25:95:6c:95:9b:c3:b4:7f:19:b7:a1:0d:92:a8:b5:a3:57:4b:5f

The interesting part there is the URL. In fact a lot of the information there is redundant; all you need is enough match criteria to uniquely specify the object. So these are suitable URIs for referring to this certificate:

  • pkcs11:token=Gnome2%20Key%20Storage;object=testkey
  • pkcs11:token=Gnome2%20Key%20Storage;id=%f5%25%95%6c%95%9b%c3%b4%7f%19%b7%a1%0d%92%a8%b5%a3%57%4b%5f
  • pkcs11:serial=1:USER:DEFAULT;object=testkey

In fact with some clients like GnuTLS you don't even need to specify the token, although it helps to speed things up by finding it more quickly. You could probably get away with:

  • pkcs11:object=testkey

See if you can use it

Now let's pretend I'm packaging the OpenConnect VPN client. It has fairly reasonable documentation (if I do say so myself) on how to use it with PKCS#11. It looks like it should comply with the Fedora guidelines and just accept a PKCS#11 URI on the command line with the -c option, in place of a filename.

Let's test...

$ openconnect -c 'pkcs11:serial=1:USER:DEFAULT;object=testkey' https://auth.startssl.com
POST https://auth.startssl.com/
Attempting to connect to server 104.192.110.222:443
Using client certificate 'testkey'
...

It'll fail to actually make a VPN connection, but that's expected — that server isn't running an AnyConnect VPN service. The important part is that the VPN client was attempting to use the correct client certificate.

Now let's pretend that I'm packaging curl. Note that curl expects to be given "CERTIFICATE:PASSWORD" in its -E option, so we need to escape any colons in the certificate URI:

$ curl -E 'pkcs11\:serial=1\:USER\:DEFAULT;object=testkey' -I -v https://auth.startssl.com/
*   Trying 104.192.110.222...
* Connected to auth.startssl.com (104.192.110.222) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* NSS: client certificate not found: pkcs11:serial=1:USER:DEFAULT;object=testkey
...

Oops, that 'client certificate not found' doesn't look good. If curl was built with GnuTLS instead of NSS that actually would have worked fine. As it is, let's file bug 1219544 against curl...

Crypto library support

GnuTLS

If your package builds against GnuTLS, then in most cases it should Just Work. The common GnuTLS APIs for handling certificates and keys will silently do the right thing.

OpenSSL

OpenSSL has no native support for PKCS#11, but there are a number of external tools which can make it work with PKCS#11. There are pkcs11-helper and libp11 helper libraries which can be used to add PKCs#11 support to an application which uses OpenSSL, but the simplest option is probably to use engine_pkcs11.

NSS

Although NSS is entirely based around PKCS#11, NSS is ironically the most problematic of the major crypto libraries. It does not integrate properly with the platform and load the correct PKCS#11 providers (Mozilla bug 248722), and it does not facilitate the use of RFC7512 PKCS#11 URIs (Mozilla bug 1162897).

These deficiencies in NSS can be worked around by manually loading the p11-kit-proxy.so module, and by using libp11-kit to parse URIs and manually iterate over the available tokens and search for objects.

Or it might be easier just to build against GnuTLS if your package has that option!