celluloid / reel

UNMAINTAINED: See celluloid/celluloid#779 - Celluloid::IO-powered web server
https://celluloid.io
MIT License
595 stars 87 forks source link

HTTPS client certificate validation (mutual authentication) #94

Open tarcieri opened 11 years ago

tarcieri commented 11 years ago

Reel does not presently support the ability to validate HTTPS client certificates or pass them through the request object. This would be useful for letting clients authenticate cryptographically.

A strawman API:

ssl_options = {
  cert: File.read("server.crt"),
  key: File.read("server.key"),
  client_certs: {
    verify: true,
    max_depth: 1,
    ca_cert: File.read("clientca.crt")
  }
}

valid_user_subject = "/C=US/ST=California/L=San Francisco/O=World YOLO Federation/OU=YOLO Computing/CN=YOLO Crypto/emailAddress=yolocrypto@gmail.com"

Reel::SSLServer.run("127.0.0.1", 3000, ssl_options) do |connection|
  connection.each_request do |request|
    if request.certificate.subject == valid_user_subject
      request.respond :ok, "hello, world!"
    else
      request.respond :forbidden, "go away"
    end
  end
end
Asmod4n commented 11 years ago

+1 could this also work when Reel is sitting behind a reverse proxy?

tarcieri commented 11 years ago

It would work out-of-the-box through a TCP proxy, of course.

If you want the proxy (i.e. firewall/load balancer/page cache) to actually take selective action based on the traffic, it will have to terminate TLS and establish its own TLS session with Reel. In that case, Reel could use client certificate validation to determine it's talking to the proxy/firewall (and ONLY the proxy/firewall)

halorgium commented 11 years ago

@tarcieri does this relate to CIF? I'm a little bit concerned about the string representation for the subject.

tarcieri commented 11 years ago

@halorgium

does this relate to CIF?

no

I'm a little bit concerned about the string representation for the subject.

Why? If the SSL implementation is doing its job, it won't pass through the subject unless it comes from a trusted cert.

How else should the subject be specified?

halorgium commented 11 years ago

@tarcieri I would have thought the subject would be a structured object. perhaps the user would check which org the subject was associated with.

tarcieri commented 11 years ago

@halorgium the nice thing about subject strings, IMO, is that they can be treated as a "username" (i.e. all of the qualifiers make subject strings unique to a particular user). This is how MySQL's REQUIRE SUBJECT works for example (not that MySQL is a good example of how to do anything ;)

halorgium commented 11 years ago

@tarcieri how does one disambiguate a subject with a CN of halorgium/O=hax?

tarcieri commented 11 years ago

Disambiguate it from what?

halorgium commented 11 years ago

Compare

CN=halorgium/O=hax/O=celluloid

and

CN=halorgium/O=celluloid
tarcieri commented 11 years ago

So you're saying you have two different certificates with two different subjects, and you want them to be treated as the same because they both contain "halorgium"?

halorgium commented 11 years ago

No, which organization is the first one associated with?

tarcieri commented 11 years ago

These are exactly the sorts of problems you'd run into if you actually parsed the subject line ;) Offhand, I don't know, nor do I know if it's valid for an X.509 subject to belong to multiple organizations.

If you were to parse the subject line, how would you handle that particular case?

halorgium commented 11 years ago

Two scenarios exist, which is the problem.

tarcieri commented 11 years ago

Okay, to back up a bit and spell this out a little more precisely:

CN=YOLO Crypto, O=World YOLO Federation, OU=YOLO Computing, ST=CA, C=US, L=Laguna Beach, emailAddress=yolocrypto@gmail.com
/C=US/ST=California/L=San Francisco/O=World YOLO Federation/OU=YOLO Computing/CN=YOLO Crypto/emailAddress=yolocrypto@gmail.com

The DirName representation is the one I am suggesting passing through (just by convention), but arguably you should also be able to get both the Distinguished Name and the parts of the subject as a data structure (and arguably the raw ASN.1 too, although ick)

I think having access to some distinguished name of the certificate and being to deal with that more or less opaquely provides the easiest API for this sort of thing.

tarcieri commented 11 years ago

Talking this over with @emboss it sounds like the simplest and most flexible thing to do here is to pass through the entire client certificate in some format. I agree with this.

The next question is what format: DER? PEM? Both? Something else? (e.g. a certificate object)

tarcieri commented 11 years ago

Okay, talking with @emboss some more we thing we should just pass through the PEM string

emboss commented 11 years ago

Discussed this a bit with @tarcieri, here's the summary. DNs, although they're supposed to be unique, are quite error-prone when used standalone to reference certificates. The standardization attempts for mapping object identifiers (OIDs) of a DN to their string representation are not exhaustive in general, so different libraries may interpret different OIDs in different ways. "emailAddress" is such a field, for example it's parsed and encoded as different strings by OpenSSL, Java or C#. That's why I would say that forwarding the whole certificate is much safer, individual libraries can then filter whatever they need.

DER vs. PEM - I'd chose PEM over DER for the single reason that a DER binary string can cause problems with the default UTF-8 encoding in Ruby 2.0. Otherwise they should be fairly equivalent, but in general it is easier to process PEM because of its Base64 encoding.

tarcieri commented 11 years ago

@seancribbs what do you think about exposing PEM encoded certificates (if present) as part of Webmachine::Requests?