Discussion:
IO::Socket::SSL and certificate-based authentication
Andrew Beverley
2015-08-19 14:34:44 UTC
Permalink
Dear all,

I'm writing a small client/server application that communicates over SSL. I've
successfully created a simple test program, whereby a client can read data from
the server.

I'm considering doing certificate-based authentication, but am not sure if/how
this is possible using IO::Socket::SSL. I've generated self-signed certificates
for the client and server, both signed by the same self-generated CA certificate.
This works fine.

However, how can I ensure that only certificates signed by that particular CA
certificate are allowed to connect?

(hopefully I've got the terminology correct and it makes sense).


This is the server program:

use strict;
use warnings;

use IO::Socket::SSL;

my $server = IO::Socket::SSL->new(
LocalPort => 8080,
Listen => 10,
SSL_cert_file => 'cert.pem', # Contains both CA and client cert
SSL_key_file => 'mykey.key',
);

while (1) {
my $client = $server->accept;
print $client "Random data\n";
}

$server->close();


And the client program:


use strict;
use warnings;

use IO::Socket::SSL;

my $client = IO::Socket::SSL->new(
SSL_ca_file => 'ca-cert.pem',
PeerHost => "testhost.com",
PeerPort => "8080",
);

my $serverdata = <$client>;
print "Message from Server : $serverdata \n";
$client->close();
Ashley Hindmarsh
2015-08-19 18:30:46 UTC
Permalink
This is (to coin a phrase) "arse about face".

The client is the one with the client cert - this will often also contain
the private key.
The server has access to the CA used to generate the client cert, but also
the server cert (otherwise how can the client trust the server?).
Once the server has authenticated the client cert, you can trust the
subject (and any assertions) listed in the cert.

Any reason you're not using a web server which has ssl support (such as
Apache)? There are a number of other factors to consider, such as the key
exchange schemes.

Ash
Post by Andrew Beverley
Dear all,
I'm writing a small client/server application that communicates over SSL. I've
successfully created a simple test program, whereby a client can read data from
the server.
I'm considering doing certificate-based authentication, but am not sure if/how
this is possible using IO::Socket::SSL. I've generated self-signed certificates
for the client and server, both signed by the same self-generated CA certificate.
This works fine.
However, how can I ensure that only certificates signed by that particular CA
certificate are allowed to connect?
(hopefully I've got the terminology correct and it makes sense).
use strict;
use warnings;
use IO::Socket::SSL;
my $server = IO::Socket::SSL->new(
LocalPort => 8080,
Listen => 10,
SSL_cert_file => 'cert.pem', # Contains both CA and client cert
SSL_key_file => 'mykey.key',
);
while (1) {
my $client = $server->accept;
print $client "Random data\n";
}
$server->close();
use strict;
use warnings;
use IO::Socket::SSL;
my $client = IO::Socket::SSL->new(
SSL_ca_file => 'ca-cert.pem',
PeerHost => "testhost.com",
PeerPort => "8080",
);
my $serverdata = <$client>;
print "Message from Server : $serverdata \n";
$client->close();
Andrew Beverley
2015-08-20 08:46:02 UTC
Permalink
Post by Ashley Hindmarsh
This is (to coin a phrase) "arse about face".
The client is the one with the client cert - this will often also contain
the private key.
The server has access to the CA used to generate the client cert, but also
the server cert (otherwise how can the client trust the server?).
Once the server has authenticated the client cert, you can trust the
subject (and any assertions) listed in the cert.
Thanks for the reply, that makes a lot of sense. I've added in the client key, but
I can't make the server reject the client if I then remove it. I now have:

my $server = IO::Socket::SSL->new(
LocalPort => 8080,
Listen => 10,
SSL_ca_file => '/etc/ssl/certs/my-ca-cert.pem',
SSL_cert_file => '/etc/ssl/certs/host.example.com.pem',
SSL_key_file => '/etc/ssl/private/host.example.com.key',
SSL_verify_mode => SSL_VERIFY_PEER,
);

my $client = IO::Socket::SSL->new(
SSL_ca_file => '/etc/ssl/certs/my-ca-cert.pem',
SSL_cert_file => '/etc/ssl/certs/my-client-cert.pem',
SSL_key_file => '/etc/ssl/private/my-client-key.key',
PeerHost => "host.example.com",
PeerPort => "8080",
SSL_use_cert => 1,
)

I was hoping that SSL_VERIFY_PEER (or SSL_VERIFY_FAIL_IF_NO_PEER_CERT) will ensure
that the client is authorized, but I think it may only be checking the public
certificate. If I remove the key from the client, everything still works.
Post by Ashley Hindmarsh
Any reason you're not using a web server which has ssl support (such as
Apache)?
I didn't really want to have to rely on a full-blown web server, as the "server"
is actually a small agent that I want to install onto lots of servers. I was
therefore trying to keep it as small and lightweight as possible.

Thanks,

Andy
Alistair McGlinchy
2015-09-04 13:42:04 UTC
Permalink
Andrew,

Did you have any success at this project? I have been trying to reproduce
your solution and I think I may have run into the same problems you did.

From what I can tell SSL_VERIFY_PEER alone means - If you don't give me a
client certificate that's OK, but if you do I will check it and kill the
socket. Whereas SSL_VERIFY_FAIL_IF_NO_PEER_CERT ensures the socket is
killed if no client certificate is provided. This much is working for me at
least.

What is not working for me is actually accepting the client certificates.
If I add in a SSL_verify_callback I can check the error messages, but they
are somewhat oblique.
error:00000013:lib(0):func(0):reason(19)

Referring to this. https://www.openssl.org/docs/manmaster/apps/verify.html
suggests my problem (and perhaps yours) is that the server thinks the cert
is self signed. All root CAs are self signed, so I think the issue is more
likely that the server is not trusting the client ca I specify.

I am pretty sure that the problem is with the server configuration as
wireshark produces identical traces with Firefox, s_client and my own perl
IO::Socket::SSL.

Do you have any tips as to how to get this to work?

FWIW my server script currently sits at:

use strict;
use warnings;

use IO::Socket::SSL;
#$IO::Socket::SSL::DEBUG = 4;

my $server = IO::Socket::SSL->new(
SSL_server => 1,
SSL_hostname => 'my.example.com',
SSL_ca_file => 'serverca.crt',
SSL_client_ca_file => 'clientca.crt',
SSL_check_crl => 0,
LocalPort => 8443,
Listen => 10,
SSL_cert_file => 'server.crt',
SSL_key_file => 'server.key',
SSL_verify_mode => SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
SSL_verify_callback => \&check_cb
) or die $SSL_ERROR;

sub check_cb {
warn map{"\t$_\n"} @_;
return 1;
}

while (1) {
my $client = $server->accept;
if ($client) {
warn "We got a socket\n";
print $client "Hello at ". localtime(). "\n";
$client->close();
} else {
warn "Connection failed: error=$!, ssl_error=$SSL_ERROR\n";
}
}

$server->close();
Post by Andrew Beverley
Post by Ashley Hindmarsh
This is (to coin a phrase) "arse about face".
The client is the one with the client cert - this will often also contain
the private key.
The server has access to the CA used to generate the client cert, but
also
Post by Ashley Hindmarsh
the server cert (otherwise how can the client trust the server?).
Once the server has authenticated the client cert, you can trust the
subject (and any assertions) listed in the cert.
Thanks for the reply, that makes a lot of sense. I've added in the client key, but
my $server = IO::Socket::SSL->new(
LocalPort => 8080,
Listen => 10,
SSL_ca_file => '/etc/ssl/certs/my-ca-cert.pem',
SSL_cert_file => '/etc/ssl/certs/host.example.com.pem',
SSL_key_file => '/etc/ssl/private/host.example.com.key',
SSL_verify_mode => SSL_VERIFY_PEER,
);
my $client = IO::Socket::SSL->new(
SSL_ca_file => '/etc/ssl/certs/my-ca-cert.pem',
SSL_cert_file => '/etc/ssl/certs/my-client-cert.pem',
SSL_key_file => '/etc/ssl/private/my-client-key.key',
PeerHost => "host.example.com",
PeerPort => "8080",
SSL_use_cert => 1,
)
I was hoping that SSL_VERIFY_PEER (or SSL_VERIFY_FAIL_IF_NO_PEER_CERT) will ensure
that the client is authorized, but I think it may only be checking the public
certificate. If I remove the key from the client, everything still works.
Post by Ashley Hindmarsh
Any reason you're not using a web server which has ssl support (such as
Apache)?
I didn't really want to have to rely on a full-blown web server, as the "server"
is actually a small agent that I want to install onto lots of servers. I was
therefore trying to keep it as small and lightweight as possible.
Thanks,
Andy
Andrew Beverley
2015-09-04 18:31:05 UTC
Permalink
Post by Alistair McGlinchy
Did you have any success at this project?
No, not yet. I've put it out to a well-known Perl consultancy, but I
haven't had a chance to get back to their initial response yet.
Post by Alistair McGlinchy
From what I can tell SSL_VERIFY_PEER alone means - If you don't give
me a client certificate that's OK, but if you do I will check it and
kill the socket. Whereas SSL_VERIFY_FAIL_IF_NO_PEER_CERT ensures the
socket is killed if no client certificate is provided. This much is
working for me at least.
Thanks for that. I never did work out the difference between those 2
options!
Post by Alistair McGlinchy
What is not working for me is actually accepting the client
certificates.
I think I had a slightly different problem. For me, it was *not*
accepting certain certificates. No matter what I put on the server, it
always accepted the client certificate, as long as it validated. I was
trying to only accept certain signers (basically my locally generated
CA).
Post by Alistair McGlinchy
If I add in a SSL_verify_callback I can check the error messages,
but they are somewhat oblique.
error:00000013:lib(0):func(0):reason(19)
Thanks, I wondered whether to play with the callback function, but never
got that far.
Post by Alistair McGlinchy
Referring to this.
https://www.openssl.org/docs/manmaster/apps/verify.html
suggests my problem (and perhaps yours) is that the server thinks the
cert is self signed. All root CAs are self signed, so I think the
issue is more likely that the server is not trusting the client ca I
specify.
I'm not sure that's exactly the same problem I was having. I had no
problem getting the server to accept the certificate, the problem was
getting it to only accept some certificates (I wanted to use this to be
my authentication mechanism).
Post by Alistair McGlinchy
I am pretty sure that the problem is with the server configuration as
wireshark produces identical traces with Firefox, s_client and my own
perl IO::Socket::SSL.
Do you have any tips as to how to get this to work?
I'm not sure about your particular problem, but what I will say is that
I did have some problems initially, and it was to do with having a
missing intermediate certificate (I had the root CA and my domain's
certificate, but was missing the one in the middle from my certificate
provider).

Andy

Loading...