SSL Client Certificate authentication
Certificates are just the public key with some added context. The name of the key, signatures, usage guidelines, etc. SSL/TLS depends on that context. Otherwise the host you're connecting to won't know whether the key genuinely belongs to you or not. The SSL trust mechanism is built on the concept of explicitly trusted certificates and then the implicitly trusted certificates that they sign.
But if you're not using SSL or TLS, then encryption can still happen without it, but trust has to happen a different way. SSH is a good example of this: SSH still uses public and private keys, but since there is no hierarchy of signatures, the added information that certificates provide wouldn't be useful, so the key is sent bare. Instead, the client asks you whether or not to trust the server's public key the first time it connects, and every time thereafter checks to see if the public key matches what it saw last time. This can happen because SSH does not use SSL or TLS for its encryption.
But if you're using the SSL protocol, the protocol dictates that the public key must be presented with the added context of the certificate. That is, the certificate is the container that the public key must be placed in for SSL to use it.
If you have the private key, it's trivial to create a self-signed certificate using a tool like OpenSSL.
SSL/TLS supports client authentication with a certificate. What really happens internally is that:
- The server requests a "client certificate" through a
CertificateRequest
message. - The client sends its certificate as a
Certificate
message, and also computes a signature (using its private key) over all preceding messages in the handshake; the signature is sent as aCertificateVerify
message. - The server somehow obtains the client public key (normally by decoding the client certificate as a X.509 certificate, validating it in the X.509 way, and then extracting the public key from it) and uses it to verify the signature from the client.
However, in the protocol itself, certificates are exchanged as opaque chunks of bytes; each "certificate" is sent with a 3-byte header which specifies its length. Thus, if you control both server and client code, then it is completely up to you to decide how these chunks of bytes are to be interpreted. You need not send an X.509 certificate. You could send an empty message, if you wish.
The important points remain:
For authentication to make sense, the server must know with some guarantee the client public key. The signature demonstrates that the client controls the private key, but this makes any good only if the corresponding public key is unambiguously attached to a known client identity.
The key pair must be of a type suitable for signatures (i.e. RSA or DSA, not Diffie-Hellman).
Existing SSL implementations might insist on at least respecting the X.509 format, in which case you have to wrap your public key in some sort of self-signed X.509 certificate, which is relatively easy programmatically with some libraries like OpenSSL.
There is a standard for replacing X.509 certificates in SSL with OpenPGP public keys, demonstrating the format agility inherent to SSL.
(This is based on my answer to the same question on SO. It's often better to ask on only one SE site.)
The TLS protocol only allows for certificates to be exchanged, not raw public keys. Even "PGP keys" (if you wanted to replace X.509 with OpenPGP for authentication in TLS, which is much less supported) are in fact certificates (they're the signed combination of a public key and a set of identifiers and attributes).
As Thomas says, you could possibly define your own type of certificate and make them be a plain public key (although I'm not sure they could still be called "certificates" technically).
From a practical point of view, you should be able to achieve what you need using existing SSL/TLS stacks, and tweaking the way they deal with the verification X.509 certificates, with caution. Indeed, most SSL/TLS stacks expose the verification of X.509 certificates through their APIs, but few allow for other types of certificates (e.g. OpenPGP) or would let you customise their code easily. Customisation for non-X.509 certificates could be quite difficult, might results in additional bugs if you're new to SSL/TLS implementation and would also be difficult to deploy in practice, since all the potential clients would also need to support your customisations.
You can perform client authentication using self-signed client X.509 certificates and rely on their public keys (but you will need to verify this public key against something your server already knows, such as a known list). You need to understand the security implications for implementing this first. This is not quite the same problem as self-signed server certificates. (I'd suggest keeping a more traditional approach to verifying the server certificate.)
You can make the client send a self-signed certificate (possibly using certain tricks regarding the CA list advertised by the server) and either perform the verification in the TLS stack or later in the application.
Note that many application containers (e.g. Tomcat/Jetty in Java) expect the SSL/TLS layer to verify the client certificate. Hence, if you skip the authentication there (and prefer to do it later on within the container or as part of the application), many application frameworks will be confused. You need to be quite careful to make sure that authentication is actually performed somewhere before performing any action that requires authentication in your application.
For example, it can be OK to have a trust manager that lets any client certificate through in Tomcat/Jetty, but you can't rely on the javax.servlet.request.X509Certificate
request attribute to have been verified in any way (which most frameworks would otherwise expect). You'd need to implement some verification logic within your application: for example, having a filter before any authenticated feature that compares the public key in this client certificate with your known list of public keys (or however you want to match the public key to an identifier). Alternatively, you can also perform this verification within your custom trust manager (in Java), this will require less work within the applications.
You could do something similar in an Apache Httpd + PHP setup (for example), using SSLVerifyClient optional_no_ca
. Again, your PHP application could not rely on the certificate having been verified, so you would have to implement so verification there too.
Don't do any of this unless you understand at which stage the certificate information you get has been verified.