<keygen>
element
This page presents some tools for dealing with websites
designed for use with the obsolete
HTML <keygen>
form element. The tools consist
of a Javascript bookmarklet that transforms the page to fill in
an answer to the form element, plus some offline Python scripts
that generate the answer in the correct format.
These tools are early alpha, not very tested, and not streamlined for general use. If you can’t make sense of the explanations on this page, they may not be for you.
Before about 2016, forms in HTML used to be able to contain an
input element called <keygen>
. When a form
containing this element was submitted to a web server, the
browser would generate an RSA public/private key pair, and
upload the public key to the web server as part of the form
submission. The private key would be retained in the browser’s
local store of cryptographic material.
This system was used by (at least) certification authorities. If
you were applying for a certificate, the CA’s website might
present you with an application form including
a <keygen>
, together with further form fields
for all the rest of the details. You’d submit the whole form in
one go, and once the CA had done all its checking and
validation, it would deliver you a certificate matching the
public key you’d uploaded. Your browser would receive that
certificate and combine it with the private key that it
(hopefully) still had in store.
This system was originally designed (I think) for applications in which the browser itself was going to end up using the private key and certificate, i.e. TLS client certificates. But it was often reused for applications which have nothing to do with that, in which case, after the browser had combined the private key with the certificate, you’d then export the result into a file containing both the private key and the certificate, which you could transfer into whatever application ultimately needed to sign things (a web server, or Authenticode, or whatever).
That was an undesirable way of doing things for many reasons:
In 2016 the <keygen>
element was deprecated,
and later, removed from the HTML standard. Modern browsers have
also dropped support for it – for example, it was removed in
Firefox 69, released in 2019.
Unfortunately, as of 2021, some CAs still depend on it
for (e.g.) Authenticode, and haven’t updated their procedures to
allow certificate applications to be made any other way! Such a
CA might suggest, for example, that you use an older browser
such as IE11 to work around the lack
of <keygen>
in anything up to date. That
means trusting an obsolete browser, out of security
support, with a private key of high value.
If you’d rather not run that risk, here’s an alternative.
The tools here allow you to use a modern browser, and still
successfully submit a form containing
a <keygen>
, by pretending the
browser supports it. This is done by running a Javascript
bookmarklet after the page has loaded, which finds
the <keygen>
and replaces it with a
simple <input type="hidden">
containing the
same response data that a real <keygen>
would
have sent.
That response data is known as an SPKAC: “Subject Public Key And
Challenge”. It’s a blob of encoded data that includes not only
the public key, but a signature made by that key on a
challenge string embedded in the <keygen>
itself. For example, the server might present a web page
including something like <keygen name="pubkey"
challenge="12345678">
, and then the browser would have
to submit a signature covering the string “12345678”.
(The purpose of the challenge string is that it can be different every time, so that the web server can be sure that the person submitting the public key really did have access to the matching private key right now, and didn’t prepare the submission in advance and then leave it somewhere an attacker could substitute a different public key. But not every website will care about making sure of this, so you may find that the challenge is just the empty string.)
This means that you can’t generate the correct SPKAC to respond
to a <keygen>
, without being able to use the
private key. So the JS bookmarklet can’t do the whole job,
because you wouldn’t want that bookmarklet to have direct access
to your real private key. (Not even if you trust me
completely – the bookmarklet runs in the context of an untrusted
web page which certainly shouldn’t be able to get hold
of the private key, even by accident.)
So, instead, you have to run a tool outside your browser which
generates the SPKAC. That tool needs to be able to use the
private key (one way or another), and also needs to know the
challenge string from the <keygen>
element.
Multiple SPKAC generation tools are provided, depending on where your private key lives:
gen_ossl_spkac.py
uses the
Unix openssl
command-line utility to generate the
signature. You can use this if you just want to keep the
private key on disk, in PEM format.gen_p11_spkac.py
uses the
Unix pkcs11-tool
command-line utility to generate
a signature from a key stored on a hardware device, provided
that device comes with a PKCS#11 library. For example, this
script can generate an SPKAC from a key stored on a Yubikey,
using the library in the ykcs11
Debian
package.gen_gnupg_spkac.py
uses GnuPG to generate the
signature, from any RSA public key that GnuPG knows about.So the expected workflow would be something like this:
<keygen>
.<keygen>
on the page, extracts the
challenge string, and puts up a prompt like this:
./gen_ossl_spkac.py --challenge="12345678" mykey.pemIf that tool successfully finds and uses your key, it will write a pile of base64 data to the terminal, looking something like this:
./gen_p11_spkac.py --challenge="12345678" /usr/lib/x86_64-linux-gnu/libykcs11.so 1
./gen_gnupg_spkac.py --challenge="12345678" FB8E30B448D4C53E3552E86385116503A8DF59A1
MIICSDCCATAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa2clucat33ysjIWMsicHc j0mnhLsGkF2F4rkY0qeMIzzecnuZsQWcTgI8EFOD+yLsEENYdV4RBF1cTqcOmmQG2jaAcgwqV9Lf nQjgwooMmt45dBPqV/9DtAh2Ah+KMGdKg0hQo3bfppKCgKq+RCFIuAX+6ldgn2KdH6YuN/e3ykn1 Khcke49LQnA03QU/Y/VtLEG/XuPYR4SezwtrDF7zIGGFumg3y8gnjSuia4LH8cZ8vJqfkJtE4ebT 7GedIRT8XHr8Sjp1zjWy9JuI6ijd5Kpvbar/ngmT52sk/yEnqHn2tZFlSmv5+W9qiCGsUEmm9UPi Aoe3qQGzUdxfunsnAgMBAAEWCDEyMzQ1Njc4MA0GCSqGSIb3DQEBBAUAA4IBAQBpVXbsUAxHZTKo XV1/uMpcVI3OOWa0/f0iDbDva5oI0Oigtb2+jgt+m2XRZiZnS3ynoTj35qP1ZMUaZuj0ml29PqWQ giw02Mr6fS1jxMehD8PIrjKyqNvCFWVerbgVmMdO+lyG2KUU70Ec85yZmzhdO7jgQtWmp75VJqMD Hay+5oFgHAwgfkn9ubghs4cff9VkWI1nDfyEiyLDG5WLAdpftJbu+rwjiFrnAlcL2riB598FW+5Z NjkrrQO5M/juBXs+WdyLzSP3xWRFMRuRLn1hBZBQ0j4XLjcl+gLgn1obQ9bDye3cJeMD++okRgyn j0tUnKbOXBDJlc63IO9CcyxP
Here’s some more discussion of each SPKAC generation script, and a fully worked example of generating a key and then generating an SPKAC from it. Click to expand whichever one you need to know more about.
gen_ossl_spkac.py
The OpenSSL library comes with a command-line tool
called openssl
, which can generate RSA keys. You’ll
have to have this installed anyway, because it’s the same tool
that gen_ossl_spkac.py
will use to generate the
signature in the SPKAC.
To generate a new 3072-bit RSA key (for example) and save it unencrypted to a file on disk, run a command like this:
openssl genrsa -out mykey.pem 3072
If you want to encrypt the key as well, add a cipher option.
OpenSSL supports a lot of these (try openssl help
to see the full list). An example might be:
openssl genrsa -aes-256-cbc -out mykey.pem 3072
The resulting PEM file will be mostly base64-encoded, and
start with a line like “-----BEGIN RSA PRIVATE
KEY-----
”. If you’ve encrypted the key, then immediately
after that header you can also expect to see some headers
describing the encryption, so that the file might start like
this:
-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,17BA61F07B6EDEA0FE081901296A715D
This PEM format for private keys is the same one that OpenSSH
has historically used for SSH private keys. So an alternative
way to generate a compatible key is to
use ssh-keygen
, although with modern versions you
have to use the -m pem
option to stop it from
writing out OpenSSH’s newer and more SSH-specific file format.
For example, you might replace that generation command with:
ssh-keygen -t rsa -b 3072 -m pem -f mykey.pem
(Of course, if you prefer, you could also use any other SSH key generator that can output in PEM format!)
Once you’ve generated your key, all the SPKAC generation tool
needs to know is the challenge string from
the <keygen>
, and where to find the PEM file.
So you might run, for example:
./gen_ossl_spkac.py --challenge="12345678" mykey.pem
If the key is encrypted, you’ll be prompted for its
passphrase twice (because the script has to run two
separate openssl
subcommands, and the second one
can’t reuse the decrypted key from the first). After that, the
SPKAC should be written to your terminal.
gen_p11_spkac.py
Many hardware cryptography devices provide a driver in the
form of a .so
shared library that speaks the
PKCS#11 API. pkcs11-tool
will talk to those,
generate keys, and make signatures.
For example, if you have a Yubikey, then installing the
Debian (or Ubuntu) package ykcs11
will provide a
file called libykcs11.so
in a subdirectory
of /usr/lib
. On a typical x86-64 Linux system the
exact location will
be /usr/lib/x86_64-linux-gnu/libykcs11.so
.
To generate a key using pkcs11-tool
, you can run
a command such as
pkcs11-tool --module /usr/lib/x86_64-linux-gnu/libykcs11.so --login --login-type so --keypairgen --id 1 --key-type rsa:2048
This will prompt you for the “SO PIN”. (“SO” stands for
“Security Officer” and is the PKCS#11 term for “administrator”:
typically a device like this has more than one PIN unlocking
different levels of access.) On a Yubikey, for example, this is
the thing that ykman
thinks of as the “Management
Key”.
If that command completes successfully, then the hardware
token will now hold a public and private key object under the
specified id (in this case, “1”). The token won’t let you
extract the private key (that’s most of the point!), but further
commands with pkcs11-tool
will let you extract the
public key, or ask the token to sign things for you. And other
software can talk to the same library to generate signatures in
the context where you’ll eventually want to use the key.
The SPKAC generation tool needs to know the name of the
PKCS#11 driver library, and the id of the key object you just
generated (the same as you gave to the --id
option
in the command above). And, of course, the challenge string from
the <keygen>
:
./gen_p11_spkac.py --challenge="12345678" /usr/lib/x86_64-linux-gnu/libykcs11.so 1
This time, you’ll likely be prompted for a less privileged PIN to log in to the card with, like the “User PIN”. Enter that in order to authorise the script to generate a signature from the key, and your base64-encoded SPKAC will be printed to the terminal.
gen_gnupg_spkac.py
Perhaps you already have a key pair stored in GnuPG and you want to use that.
You can generate an SPKAC from a GnuPG RSA key by providing
the key fingerprint (or its shorter 16-digit id) and the
challenge string from the <keygen>
:
./gen_gnupg_spkac.py --challenge="12345678" FB8E30B448D4C53E3552E86385116503A8DF59A1
If this requires a key passphrase, the GnuPG agent should prompt you out of band for it with a GUI dialog box, in the same way as a normal GPG signing operation would.
If the key you want to use is a subkey of some other key, you
can find out its fingerprint using “gpg --list-keys
--with-subkey-fingerprint
”.
GnuPG is able to store private keys either in normal disk files or on hardware devices. In principle, this tool should work the same way in both cases, because you ask the GnuPG agent to make a signature in the same way regardless. However, you may find that this doesn’t work for a key on a hardware device, because the SPKAC requires a signature on an MD5 hash, and some PGP-compatible hardware devices will only generate signatures on hashes they approve of. For example, the OpenPGP application on a Yubikey will fail in this way, even though the same signature technique is quite happy to sign SHA-1 or SHA-256 hashes. (And even though the same Yubikey can sign MD5 hashes with a key you can use through its PKCS#11 mode!)
The Javascript bookmarklet is right here. Right-click and “Bookmark Link”: keygen-fake
To get the Python scripts that generate an SPKAC to use as the response data:
git clone https://git.tartarus.org/simon/keygen-fake.git
This is not polished software. It has lots of limitations, and it hasn’t had a lot of testing. Among the limitations:
<keygen>
should be able
to support more than one public-key algorithm. But these tools
only handle RSA.ykcs11
. I have no idea what other PKCS#11
drivers it might or might not work with, or how to fix it if
it doesn’t, or how to test it.<keygen>
, and try to
stop you from submitting the form in some way. You might have
to work around this by additional messing around with the
page. For example, when I used this on a live website, I had
to use the Firefox console to delete an onclick
handler on the form’s “Submit” button, which had been
installed at page load time based on my browser
identification, and was preventing me from actually clicking
the button. I have no way to automate this step for you, or
even to predict how it will work on some other website, so
you’ll just have to figure it out for whatever page you’re
presented with.
Send feedback to
anakin@pobox.com
.
I don’t expect to have a lot of time to polish this code, so if you want it improved, be prepared to help. Patches welcome!