Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

TLS Verification

59 views
Skip to first unread message

Melissa Schrumpf

unread,
May 24, 2007, 9:22:11 PM5/24/07
to
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

Donal K. Fellows

unread,
May 25, 2007, 4:25:38 AM5/25/07
to
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).

Melissa Schrumpf

unread,
May 25, 2007, 8:16:19 AM5/25/07
to
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

Donal K. Fellows

unread,
May 25, 2007, 10:22:30 AM5/25/07
to
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.

Melissa Schrumpf

unread,
May 28, 2007, 11:01:09 AM5/28/07
to
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

0 new messages