5

My task is to secure a (previously HTTP) web service with HTTPS. From a now departed coworker I have inherited code that inserts an SSLEngine object between the TCP and HTTP layers in our existing server. As far as I know this code works correctly. I get the SSLEngine from SSLContext.createSSLEngine(), but how to produce an appropriate SSLContext confuses me.

SSLEngine itself has a beautiful conceptual introduction in its javadoc, but unfortunately is the part that I don't need to interface to myself. On the other hand SSLContext.init() is very sparsely documented and just say that I must pass "the sources of authentication keys" and "the sources of peer authentication trust decisions", and I have no idea what that is. The documentation for the types of these parameters (which would ordinarily my next try for understanding it) are generic to the point of not saying anything, and the class documentation for SSLContext is also uselessly brief.

I am provided with a bunch of ascii-armored .crt, .pem, and .key files that together enable Apache to serve HTTPS at the domain the Java server is eventually going to handle directly. I suppose I need to load them into either the SSLContext or the SSLEngine somehow, but am not sure whether SSLContext.init() is even the right place to to that (though there doesn't seem to be many other places it could be).

Which documentation should I start by reading to get a working understanding of how to do this?

My Google attempts produce lots of semi-undocumented example code of unknown quality and security, as well as some advanced walk-throughs such as "how to write your own key provider", but no overall conceptual introduction to the most basic use of the JRE classes.

Especially since this is security related, I have no use for copy-paste example code that I'll just whack on aimlessly until it seems to do more-or-less what I want. I need a high-level conceptual understanding of how the various pieces are actually supposed to fit together.

(Bonus points if the documentation is detailed enough to let me figure out how to do SSL client authorization in practice too -- but that is not immediately urgent).

hmakholm left over Monica
  • 23,074
  • 3
  • 51
  • 73
  • The SSLContext documentation indicates that all of the specified parameters to init() can be null. Have you tried just passing it nulls and seeing how it behaves? – Wug Aug 14 '12 at 19:15
  • Is your web service a JEE war? – artbristol Aug 14 '12 at 19:19
  • Ok one thing by one. SSLContext is a wrapper of a SSLContextSPI, e.g. it contains common methods, to use the Service Provider. A KeyManager gives the context it's own private keys. A TrustManager provides the Context with Public Keys it can Trust (e.g. you can use it to do client authentication) – John Smith Aug 14 '12 at 19:20
  • @Wug: Yes, I can pass null. But surely I will have to tell the JRE **somehow** where it is to find the certificate it should authorize itself to the client with. – hmakholm left over Monica Aug 14 '12 at 19:23
  • @artbristol: I don't think so. It is a stand-alone Java program (deployed as a plain old JAR) that binds a TCP socket and listens for incoming connections. It then parses HTTP messages by itself. – hmakholm left over Monica Aug 14 '12 at 19:25
  • 1
    @kw4nta: If `SSLContext` wraps an `SSLContextSPI`, then it does so privately. There are no public methods I can use to get at the latter, even if I knew what to do with it. – hmakholm left over Monica Aug 14 '12 at 19:27

2 Answers2

5

If you want detailed documenntation, take a look at the JSSE Reference Guide, more specifically its SSLContext section.

The default values (if you pass null to SSLContext.init(...)) are sensible by default, but you may want to understand what these default values are (see the Customization section).

There's no default for the keystore (only the truststore, which you'll almost certainly want to customise anyway if you want client certificate authentication).

Typically, you can initialise an SSLContext as follows:

KeyStore ks = KeyStore.getInstance(...); // Load the keystore
ks.load(...); // Load as required from the inputstream of your choice, for example.

KeyStore ts = KeyStore.getInstance(...); // Load the truststore
ts.load(...);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, <the key password>);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

Perhaps you may also be interested in this answer for the difference between keystore and truststore. In short, both are "keystores" in the storage sense, but the keystore is where you store your certificates (+chain) and associated private keys (i.e. the server cert and priv. key on a server and the client cert and priv. key on a client) and the truststore is where you store the CA certificates or the remote certificates (without private keys, because they're not your certs) that you're willing to trust, when the remote party presents its certificate.

Regarding what to do with your key/cert files, the easiest is certainly to use a keystore of type PCKS12, which you can build as described in this answer.

EDIT: (Following comments)

So is it correctly understood that I can get away with a null TrustManager if I'm a server and not yet doing SSL client authentication?

Yes.

But that if I were to do cliemt authentication I need to provide a TrustManager that contains the public keys of every client that should be able to connect?

Usually not. You'd provide a CA certificate for the CA that issued these client certificates. If you don't have this infrastructure (or if you're building it and don't have any client certificates yet), you should look into creating your own PKI, or perhaps buying client certificates from a well-known CA.

You could also build a TrustManager that accepts self-signed certificates (independently from any CA) and verifies them using the fingerprint from a pre-defined list, but this comes with a number of problems (in particular how the server asks for the right certificate), and you'd may end up replicating parts of what PKIs are for. That's not something I'd recommend unless you understand more about what you're doing.

That's a bit of a downer; I had hoped I could have waited until I got the request URL before I needed to retrieve a fingerprint for the authorized client and only then compare to the fingerprint the actual client authenticated with.

Here, you're talking about a completely different aspect. If you want to get the requested URL first, before requesting the certificate, you'll need to use re-negotiation. The HTTP layer would have to talk to the SSLEngine for ask it to trigger a new handshake (now set up to ask for a client certificate).

SSLEngine isn't the easy path to SSL/TLS in Java in general, but asynchronous renegotiation can become very tricky. Indeed, its semantics aren't quite clear at the application layer. You could very well trigger a re-negotiation after receiving an HTTP request, but be in the middle of sending a response at the same time (since you may have asynchronous request/response, possibly pipelined). Technically, the new handshake would affect both pipes.

Overall, renegotiation or not, that's quite independent of checking how you trust the client certificate. If you really want to go down the road of having your application layer (and not the SSL/TLS layer) do the certificate verification, you'd have to write an X509TrustManager that trusts anything (bypassing the verification at the SSL/TLS layer) and have your application get the cert from the SSLSession (peer certificate) and do the verification there. Overall, that's quite similar to accepting self-signed certs and verifying them more manually. It can be done, but you'd be stepping out of the PKI model, and you would need some custom code to do so (as opposed to just using the API as it's meant to be used by default).

If you're new to all this, I'd avoid this approach. Try perhaps to set up a test CA and learn how it works first. The whole issue about using a CA or doing manual fingerprint verification is ultimately more an administrative problem: it's defined by how you're going to distribute your certificates between the parties involved.

Also, what is <the key password> doing here? This is supposed to run on an unattended server in a hosting facility somewhere; we cannot wait for someone to come around to type in a password during startup (say, after a power outage).

You'll need to set a password. Most servers read it from a configuration file if needed (or worse, you could hard-code it).

Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • Thanks! So is it correctly understood that I can get away with a null TrustManager if I'm a server and not yet doing SSL client authentication? But that if I were to do cliemt authentication I need to provide a TrustManager that contains the public keys of every client that should be able to connect? That's a bit of a downer; I had hoped I could have waited until I got the request URL before I needed to retrieve a fingerprint for the authorized client and only then compare to the fingerprint the _actual_ client authenticated with. – hmakholm left over Monica Aug 14 '12 at 20:10
  • Also, what is `` doing here? This is supposed to run on an unattended server in a hosting facility somewhere; we cannot wait for someone to come around to type in a password during startup (say, after a power outage). – hmakholm left over Monica Aug 14 '12 at 20:12
  • The client auth issue is probably a matter for a separate question, but: What I had in mind was that our existing API/infrastructure for letting customers change their _passwords_ could be adapted into one where instead of a password they set a "fingerprint of the public key I want to give access to my account". Then when I get the URL and find out which account they want to access, I could check whether the certificate they _already_ used to handshake with had a fingerprint authorized for that account. That way the user wouldn't (?) have to pay a CA for the privilege of being our customers. – hmakholm left over Monica Aug 14 '12 at 21:26
  • You can't really use raw public keys directly with SSL/TLS (unlike SSH), they'd have to be in certificates, possibly self-signed (especially because the JSSE only supports X.509 certificates, like a lot of SSL stacks). Users would have to generate their own certs, which can be a bit tricky for most users. If you want to give users certificates for free, you could set up a free CA that lets them generate their keys within the browser, and issue a cert from your server. – Bruno Aug 14 '12 at 21:32
  • But the whole point of switching to SSL client authentication instead of RFC-2617-over-SSL would be that then the customer never needs to trust us with data that could be used to impersonate him. If it's us generating the key pairs (and wrapping them in certificates), then the exercise becomes rather pointless. I assumed that there would still be a public key somewhere inside the certificate that could be inspected and fingerprinted. (This is much more complicated than the GPG/PGP based system I would have built if only it was enterprisey enough for our target market). – hmakholm left over Monica Aug 14 '12 at 21:38
  • Yes, you can inspect the public key, but it has to be wrapped within an X.509 cert. With in-browser certificate application, the private key is generated within the user's browser and never leaves it. It sends a CSR to you, which you issue as a cert. You could also get the CSR by some other means if users don't trust the browser. You could choose to accept other certs too. (Note that this is not that much more complicated than using PGP certs: they're also certs, not keys, despite their common name.) – Bruno Aug 14 '12 at 21:42
  • It's moot anyway. We don't have anywhere near the legal and administrative resources necessary to launch a CA. Much cheaper for us to send each customer a check with instructions to order a certificate from VeriSign -- or (in practice) just stay with RFC-2617. – hmakholm left over Monica Aug 14 '12 at 21:57
  • I was just talking from a tech point of view regarding your own CA. If you were willing to accept raw public keys (wrapped in X.509 certs, self-signed or not), having a CA (your own or not) would require some terms and conditions indeed. Those need not be complicated. Deploying your own CA could be an easy way to have a registr. scheme as you mentioned (similar to GitHub's SSH public key registration), simply to make it convenient for the user; it doesn't have to be a full blow CA in legal terms (in that you don't want to guarantee more than required and/or it to be used by other services.) – Bruno Aug 14 '12 at 22:11
0

Answer Part 1:

You need to give a KeyManager to the SSLContext, so the SSLContext beeing the server is able to present itself to the client as whatever the KeyManager stores as Public and Private Key.

You need to give a TrustManager to the SSLContext to allow it to authenticate Clients.

EDIT: Answer Part 2:

All these Classes and Interfaces make it possible to use different Service Providers for any kind of thing (managing private keys, managing client authentication, the SSL Engine used and so on).

BUT sun provides it's own default implementations which are reasonably secure.

So: You would usually only dive this deep into the api, if you want to use something custom or not the default.

EDIT: Answer Part 3:

Usually you would use a SSLSocket and put over a plain Socket (or create the SSLSocket, so it creates the plain Socket as well).

You will use the SSLContext and SSLSession if you want to meddle with the ssl communication itself. In places where you would not use a Socket even.

John Smith
  • 2,282
  • 1
  • 14
  • 22
  • 1
    I'm asking about **what I should read** to gain a proper understanding, not a stream of unconnected factoids that each raise further questions -- such as: how do I get from my bunch of PEM files to a KeyManager? How do I get a TrustManager? What are the purposes of each? I **need** to know the high-level stuff, because I am not going to deploy a "security" solution that I don't understand completely. – hmakholm left over Monica Aug 14 '12 at 19:30
  • I was giving a short overview of what this low level API provides. And I was trying to communicate, that for your purpose you would use the SSLSocket and SSLSocketFactory API (which in turn might or might not use the API you want to know about). Then depending on the SSLSocketFactory implementation you choose, that is where you configure your trusts and keys and all. So I personally would dive in from that point. – John Smith Aug 14 '12 at 19:42
  • 1
    I am quite certain that we want SSLEngine rather than SSLSocket, because our existing implementation uses non-blocking I/O everywhere. And you haven't actually pointed to any documentation, even though I have explained twice (and now thrice) that **I need a high-level understanding**, rather than magic do-this-and-this code what I don't know what does or why it's necessary. – hmakholm left over Monica Aug 14 '12 at 19:47
  • What exactly do you mean by '*reasonably* secure'? Are we to infer that you are aware of known security vulnerabilities in the Sun implementation? And there is nothing here beyond your part 1 that actually answers the question. – user207421 Aug 14 '12 at 22:52