<keygen>
has been deprecated a few days ago, and the issue has been taken up by @timbl on the Technical Architecture Group as it removes a useful tool in the asymmetric public key cryptography available in browsers.
One reason given for deprecating that recurs often is that keygen
uses MD5, as for example argued by Ryan Sleevi in his mail on blink-dev on the intent to deprecate keygen
- itself is problematically and incompatibly insecure - requiring the use of MD5 in a signing algorithm as part of the SPKAC generated. This can't easily be changed w/o breaking compatibility with UAs.
What is the problem with MD5? Well it was shown to make it possible to create collisions, thereby allowing a major attack vector on the whole Certificate Authority System which is currently central behind web security. This was presented in a very good paper "MD5 considered harmful today" at the Chaos Communication Congress in Berlin in 2008 by a number of researchers of which Jacob Appelbaum ( aka. @ioerror ) . ( That was the time when Julian Assange was working freely on Wikileaks, so you can even hear him ask a question at the end )
The slides are here:
https://www.trailofbits.com/resources/creating_a_rogue_ca_cert_slides.pdf
The video is here:
http://chaosradio.ccc.de/25c3_m4v_3023.html
In short they were able to create a fake Certificate Authority (CA) because the CA signed its certificates with MD5 and they were able to create hash collisions, to use the certificate signed by the CA
and change some information in it to produce their own top level certificate, with which
they could create a certificate for any site they wished to! ( Pretty awesomely bad - though they did this carefully to avoid misuse ). This is why projects such as IETFs DANE, DNSSEC, and many other improvements to the internet infrastructure are vital.
This was 7 years ago, so all of this should be fixed by now. There should be no CA signing
Server Certificates with MD5 anymore.
Great. But that has nothing to do with what is going on with <keygen>
. The problem
may well be that the documentation of <keygen>
is misleading here. The WHATWG documentation on keygen currently states:
If the keytype attribute is in the RSA state: Generate an RSA key pair using the settings given by the user, if appropriate, using the md5WithRSAEncryption RSA signature algorithm (the signature algorithm with MD5 and the RSA encryption algorithm) referenced in section 2.2.1 ("RSA Signature Algorithm") of RFC 3279, and defined in RFC 3447. [RFC3279] [RFC3447]
By whether or not keygen
wraps the key and signs it with MD5 is of not much importance, since this is the keyrequest we are speaking of here, not the generated certificate!
To summarise how the keygen is actually used:
- The browser creates a public/private key, saves the private key in the secure keychain
- and sends the public key in an spkac format in a normal form request to the server ( best of course to do this over https of course )
- which on receipt of the certificate request and verification of the data, uses that to create a Client Certificate using any signature algorithm it wants for the creation of the certificate ( And so it SHOULD NOT USE MD5: see CCC talk above )
- which it returns using one of the x509 mime types available to it,
Here is an illustration of the flow that we use in the WebID-TLS spec to illustrate this:
To see some real code implementing this I point you to my ClientCertificateApp.scala code that receives a certificate Request, and either returns an error or a certificate.
The key parts of the code are extracted below:
def generate = Action { implicit request =>
certForm.bindFromRequest.fold(
errors => BadRequest(html.webid.cert.genericCertCreator(errors)),
certreq => {
Result(
//https://developer.mozilla.org/en-US/docs/NSS_Certificate_Download_Specification
header = ResponseHeader(200, Map("Content-Type" -> "application/x-x509-user-cert")),
body = Enumerator(certreq.certificate.getEncoded)
)
}
)
}
CertForm
just takes the data from the html form (verifies all fields are ok) and generates a CertReq
object. ( or it can also take a CertReq
object and generate a form, so that errors can be shown to the user )
val certForm = Form(
mapping(
"CN" -> email,
"webids" -> list(of[Option[URI]]).
transform[List[URI]](_.flatten,_.map(e=>Some(e))).
verifying("require at least one WebID", _.size > 0),
"spkac" -> of(spkacFormatter),
"years" -> number(min=1,max=20)
)((CN, webids, pubkey,years) => CertReq(CN,webids,pubkey,tenMinutesAgo,yearsFromNow(years)))
((req: CertReq) => Some(req.cn,req.webids,null,2))
)
The spkacFormatter
just returns a public key. ( It plays around with testing the challenge, but I am not sure what that is for - would like to know ).
Anyway as I wrote above: if successful the generate method returns an encoded certificate with the right mime type. And as you can see we create a certificate with SHA1withRSA
val sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA")
val digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId)
val rsaParams = CertReq.issuerKey.getPrivate match {
case k: RSAPrivateCrtKey =>
new RSAPrivateCrtKeyParameters(
k.getModulus(), k.getPublicExponent(), k.getPrivateExponent(),
k.getPrimeP(), k.getPrimeQ(), k.getPrimeExponentP(), k.getPrimeExponentQ(),
k.getCrtCoefficient());
case k: RSAPrivateKey =>
new RSAKeyParameters(true, k.getModulus(), k.getPrivateExponent());
}
val sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(rsaParams);
x509Builder.build(sigGen)
So the MD5
plays no serious role in all this.
This should not be a big surprise. The only thing of value sent to the server is the public key. It sends
back a certificate based on that public key ( and other information it may have on the user ). But the only one to be able to use that certificate is the person owning the private key.
Now my code could presumably be improved in many places I doubt not. But this should show how
<keygen>
is actually used. After all remember that <keygen>
was added 10 years after it appeared in browsers, and that there was not that much discussion about the documentation when it was added.