|
|
 |
 |
 |
 |
TCL(Tool Command Language) Scripting
|
 |
 |
 |
 |
 |
 |
 |
 |
TLS Verification
I've recently added TLS support to an application of mine (communication between Tcl programs of my own), and would like to add certificate validation. Actually, I've already played around with it, and I've gotten it to work: Create CA. Create two key/cert pairs, signed by CA. Both clients use -cafile $ca_cert -certfile $my_cert -keyfile $my_key, where my_cert and my_key are unique per client. The good news is, it works just fine, specifying -request 1 -require 0 on both ends, and even -request 1 -require 1 on both ends. With -require 1, it properly rejects self-signed client certs, and certs signed by a different CA. Great! However, I'd like to accept connections regardless of whether or not the remote side has a valid, signed key.cert, BUT I would still like to know whether or not the cert would have authenticated (so I can make decisions about how to indicate this). First, and easiest option, would be if there is a way to extract this information once the connection is established. Somehow I doubt it, but it's worth asking. The second option would be to write my own callback for the -commmand option. I've played with this a little bit. The callback gets called twice, once for the ca_cert, and then once for the peer's client cert. Unfortunately, ::tls::callback does not provide much in the way of hints as to how to duplicate the built-in functionality: "verify" { foreach {chan depth cert rc err} $args {;} array set c $cert if {$rc != "1"} { log 1 "TLS/$chan: verify/$depth: Bad Cert: $err (rc = $rc)" } else { log 2 "TLS/$chan: verify/$depth: $c(subject)" } return $rc } What I find is that I'll get $rc==0 and an $err set for a self-signed cert, but only an $rc==1 for a cert signed by a different CA. So, if I see $rc!=1, I can do something like [set ::AUTH($chan) 0] Additionally, I can check the valid before/after dates and compare them with today's date. I presume that, in order to validate that the peer's cert is signed by the CA, I have to capture information across calls to the callback function, saving information about the CA ($c(serial)==-1 -- or the cert file whose subject is its signer, or, is the CA cert always the first call to the callback with "verify"?), and the peer's cert, and somehow verify that the CA signed the peer's cert. But other than the sha hashes, there doesn't seem to be much information available about the certificates themselves. Any clues as to how to go about doing this? Are there any other certificate validation routines I should be performing? Also, I presume if I wanted to add revocation list support, it would involve retrieving it from the machine that hosts the CA. Thanks. -- MKS
Melissa Schrumpf wrote: > I presume that, in order to validate that the peer's cert is signed by > the CA, I have to capture information across calls to the callback > function, saving information about the CA ($c(serial)==-1 -- or the cert > file whose subject is its signer, or, is the CA cert always the first > call to the callback with "verify"?), and the peer's cert, and somehow > verify that the CA signed the peer's cert. But other than the sha > hashes, there doesn't seem to be much information available about the > certificates themselves. > Any clues as to how to go about doing this?
To know what is the CA and what is the subject, examine the depth parameter. According to http://www.flightlab.com/~joe/gutter/doc/tls-1.5/tls.htm the depth is always 0 for subjects, and greater values up the certificate chain (for complex reasons, the "CA" might not be at 1; I sincerely hope you're not that situation though). I suspect that the chain will always be verified from root to tip; the other way round would be idiotic! (And yes, you should definitely check the validity period, though what you do on a failed check is up to you.) > Are there any other certificate validation routines I should be > performing? > Also, I presume if I wanted to add revocation list support, it would > involve retrieving it from the machine that hosts the CA.
There are two options: check the CRL or use OCSP. I won't discuss the privacy issues involved, but I will mention that CRL distribution doesn't scale all that well. CRLs are not necessarily distributed from the CA system itself (that might actually be not online at all in a high-grade CA!) but should instead be present in the CA certificate as a non-critical extension (IIRC; I don't do this very often!) It is probably a good design decision to cache the CRLs for some period of time to reduce transfer costs. Alas, the TLS extension does not expose access to certificate fields it does not know about, even through OIDs; this makes handling CRLs much more difficult than it ought to be, though when you've got a relatively closed situation (only one CA you trust, and hence only one CRL) this isn't a problem in practice. FWIW, I recommend only trusting a very limited set of CAs anyway if at all possible. It massively simplifies your higher-level trust policy management (i.e. what you do after you decide that, yes, you do know who is talking to you...) Donal (I've never implemented OCSP, so don't know tips/tricks there).
Donal K. Fellows wrote: > > I presume that, in order to validate that the peer's cert is signed by > > the CA, I have to capture information across calls to the callback > > function, saving information about the CA ($c(serial)==-1 -- or the cert > > file whose subject is its signer, or, is the CA cert always the first > > call to the callback with "verify"?), and the peer's cert, and somehow > > verify that the CA signed the peer's cert. But other than the sha > > hashes, there doesn't seem to be much information available about the > > certificates themselves. > > Any clues as to how to go about doing this? > To know what is the CA and what is the subject, examine the depth > parameter. According to > http://www.flightlab.com/~joe/gutter/doc/tls-1.5/tls.htm > the depth is always 0 for subjects, and greater values up the > certificate chain (for complex reasons, the "CA" might not be at 1; I > sincerely hope you're not that situation though). I suspect that the > chain will always be verified from root to tip; the other way round > would be idiotic! (And yes, you should definitely check the validity > period, though what you do on a failed check is up to you.)
Yes, well, the more I think about this, the less I care for it. How does one write a certificate chain verification for -command? a. One hopes that TLS provides some mechanism for determining whether or not the chain is valid from within the default callback, without substituting a different function. b. One hacks TLS to provide this functionality. c. One hopes that TLS exposes the callbacks to perform these operations on the data. d. One Hacks TLS to provide this functionality. e. One hopes that TLS exposes the raw data, so that one may rewrite all of the validation routines in pure TCL... All of these are maintenance nightmares. Unless someone knowledgeable about the innards of TLS can shed some light on the subject--hopefully to the effect that the functionality is already present, just not very well-documented, it seems that, aside from logging, the -callback option is largely useless. The easiest solution, is to have each peer run TWO tls::socket servers, one configured -request 1 -require 1, the other not, and to instruct each peer, upon connection attempt, to try connecting to the more secure server socket, and, upon failure, try the second. Actually, since certificates are being verified on both ends, and peer A may authenticate properly to peer B without peer B providing a proper certificate, it seems that four separate connection attempts will need to be made: A and B both -require 1 if failed, try both of the following: A -req 1, B -req 0 Zero or one of these options will succeed. A -req 0, B -req 1 If zero, proceed to: A and B both -req 0 Terribly inelegant. But it doesn't require re-implementation of SSL, or branching TLS. -- MKS
Melissa Schrumpf wrote: > Yes, well, the more I think about this, the less I care for it. How > does one write a certificate chain verification for -command?
Depends on what you mean by "verification". :-) > a. One hopes that TLS provides some mechanism for determining whether or > not the chain is valid from within the default callback, without > substituting a different function.
If the chain is not valid (i.e. if the certificate signing checks fail) the callback will be indicating an error for sure. I think basic validity is always automatically checked for you. > e. One hopes that TLS exposes the raw data, so that one may rewrite all > of the validation routines in pure TCL...
All the routines? Or just those to get information from the certificate in order to make authorization decisions? > Unless someone knowledgeable about the innards of TLS can shed some > light on the subject--hopefully to the effect that the functionality is > already present, just not very well-documented, it seems that, aside > from logging, the -callback option is largely useless.
I suspect that the real problem is that the author of TLS only really thought about the basic cases of dealing with either connecting to a public website or acting as a server with a private CA. The few extra features needed to support richer stuff (either access to the other fields of the cert or, more likely, just a way to get the raw cert so that you can pick it apart with ASN.1 tools) just aren't there. > The easiest solution, is to have each peer run TWO tls::socket servers, > one configured -request 1 -require 1, the other not, and to instruct > each peer, upon connection attempt, to try connecting to the more secure > server socket, and, upon failure, try the second.
That's a bad idea. That's a very very bad idea. > Actually, since certificates are being verified on both ends, and peer A > may authenticate properly to peer B without peer B providing a proper > certificate, it seems that four separate connection attempts will need > to be made:
The server should always present a certificate, and that certificate must be in a chain from some trust root (e.g. a CA) that the client trusts, since that's pretty close to being the only way to establish trust anyway. Any client that doesn't require the server to authenticate is asking for massive trouble, since it will be totally open to man-in-the-middle attacks. Donal.
Donal K. Fellows wrote: > Melissa Schrumpf wrote: > > Yes, well, the more I think about this, the less I care for it. How > > does one write a certificate chain verification for -command? > Depends on what you mean by "verification". :-)
Well, yes, it does. And for anyone using TLS--or any other utility that provides as simple, built-in method of "verifying" a certificate--I highly recommend running a series of penetration tests against it to see what slips through and what doesn't. For example, TLS does NOT verify that the cerificate subject CN matches the host from which the certificate is received. I can understand why, but it's something additional I wanted to add. > > a. One hopes that TLS provides some mechanism for determining whether or > > not the chain is valid from within the default callback, without > > substituting a different function. > If the chain is not valid (i.e. if the certificate signing checks fail) > the callback will be indicating an error for sure. I think basic > validity is always automatically checked for you.
Yes, it is. In a fit of stupidity, my last round of testing before that message, I was looking at the error code in the control window, rather than the experiment window. That is, I had two tclsh sessions running, connecting to each other, and each doing cert verification on the other, both when connecting and being connected to. After the first test (self-signed), I started looking at the result code in the console with the invalid cert, and, lo and behold, all of the verifications were passing. I believe this is what's referred to as a "duh" moment. Anyhow, I was wrong. All of the "normal" validations are performed at all times. So, since my search for example code for a -command callback proc was more or less fruitless (I believe I found one example that always returned 1), I will now post mine here, with an explanation inline: tls::init -certfile $files(my_cert) \ -keyfile $files(my_key) \ -ssl2 1 -ssl3 1 -tls1 0 -require 0 -request 1 \ -cafile $files(ca) # This is largely based on ::tls::callback proc tlsCbfn {option args} { switch -- $option { "error" { return 1 } "verify" { foreach {chan depth cert rc err} $args {;} array set c $cert if {![info exists ::AUTH($chan,authcode)]} { if {$rc==1} { set ::AUTH($chan,authcode) 1 set ::AUTH($chan,autherr) "" } else { set ::AUTH($chan,authcode) $rc set ::AUTH($chan,autherr) $err } } else { if {$rc!=1} { set ::AUTH($chan,authcode) $rc set ::AUTH($chan,autherr) $err } } # The information I'm interested in is whether or not the # cert validated. I include the error message in case the # application wants to take different actions on different # errors (for example, accept an expired cert with a warning, # but reject one for which the chain does not validate. # TLS does not verify that the peer certificate is for # the host to whom we are connected: if {(($depth==0) && ($::AUTH($chan,authcode)==1))} { set subl [split $c(subject) "/"] foreach item $subl { set iteml [split $item "="] if {[lindex $iteml 0]=="CN"} { set certcn [lindex $iteml 1] set certhost [lindex [split $certcn "."] 0] set peerinfo [fconfigure $chan -peername] set peercn [lindex $peerinfo 1] set peerhost [lindex [split $peercn "."] 0] if {$peercn==$peerhost} { # need full cn set mycn [lindex [fconfigure $chan -sockname] 1] set mydomainl [lrange [split $mycn "."] 1 end] set peercnl [concat $peercn $mydomainl] set peercn [join $peercnl "."] } # on some networks -peername host will only # be the hostname, not the full CN. # whether it is the "right" thing to do # to accept these connections is left as an # exercise for the reader. I decided to allow # it here. But then, I'm doing this on an intranet. # I doubt I'd allow it in the wild. if {$certcn!=$peercn} { set ::AUTH($chan,authcode) 0 set ::AUTH($chan,autherr) "CN does not match host" } } } } # always accept, even if rc is not 1 # application connection handler will determine what to do return 1 } "info" { # For tracing # upvar #0 ::tls::$chan cb # set cb($major) $minor # NOTE: I have no idea what this is supposed to do. # It was in ::tls::callback, but if left in, it causes even # a valid certificate chain to fail to connect. Removing it # does not seem to inhibit TLS's ability to detect: # self-signed certificates # expired certificates # certificates that are not yet valid # certificates signed by a different CA # therefore, out it goes. return 1 } default { return -code error "bad option \"$option\":\ must be one of error, info, or verify" } } }
# It's a good idea to unset any ::AUTH($chan,*) entries when they # are no longer needed -- either after the connection has been # established, and the authcode and autherr have been checked, or # in the callback for cleanup/closing the socket. Not doing so # is bad bad bad, and should only be done in short-lived applications # that exit after one connection. I hope that helps someone in the future. > The server should always present a certificate, and that certificate > must be in a chain from some trust root (e.g. a CA) that the client > trusts, since that's pretty close to being the only way to establish > trust anyway. Any client that doesn't require the server to authenticate > is asking for massive trouble, since it will be totally open to > man-in-the-middle attacks.
Very true. Which is why I'm using certificates on both sides. In terms of most transactions, "Client" and "Server" are imaginary naming conventions that stem from one of three notions: 1. That which listens on a socket is the "Server." That which initiates the connection is the "Client." 2. "My computer is more important than your computer." 3. "I have a static IP address and a proper DNS entry, you don't." The only one that really matters, in a technical sense, is #1. The others are conventions of the "read-only" internet, brought to you by big important companies everywhere. *Sigh* Remember when the 'net was ALL peer-to-peer? -- MKS
|
 |
 |
 |
 |
|