perlsslssl-certificateio-socket-ssl

How to verify self-signed certificate?


I use Net::Jabber::Client to send messages via XMPP.

The server I am connecting to uses self-signed certificate:

DEBUG: .../IO/Socket/SSL.pm:2853: new ctx 45728400
DEBUG: .../IO/Socket/SSL.pm:1540: start handshake
DEBUG: .../IO/Socket/SSL.pm:717: ssl handshake not started
DEBUG: .../IO/Socket/SSL.pm:750: using SNI with hostname my.host.name
DEBUG: .../IO/Socket/SSL.pm:785: request OCSP stapling
DEBUG: .../IO/Socket/SSL.pm:806: set socket to non-blocking to enforce timeout=10
DEBUG: .../IO/Socket/SSL.pm:819: call Net::SSLeay::connect
DEBUG: .../IO/Socket/SSL.pm:822: done Net::SSLeay::connect -> -1
DEBUG: .../IO/Socket/SSL.pm:832: ssl handshake in progress
DEBUG: .../IO/Socket/SSL.pm:842: waiting for fd to become ready: SSL wants a read first
DEBUG: .../IO/Socket/SSL.pm:862: socket ready, retrying connect
DEBUG: .../IO/Socket/SSL.pm:819: call Net::SSLeay::connect
DEBUG: .../IO/Socket/SSL.pm:2754: did not get stapled OCSP response
DEBUG: .../IO/Socket/SSL.pm:2707: ok=0 [0] /CN=my.host.name/CN=my.host.name
DEBUG: .../IO/Socket/SSL.pm:822: done Net::SSLeay::connect -> -1
DEBUG: .../IO/Socket/SSL.pm:825: SSL connect attempt failed

DEBUG: .../IO/Socket/SSL.pm:825: local error: SSL connect attempt failed error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed
DEBUG: .../IO/Socket/SSL.pm:828: fatal SSL error: SSL connect attempt failed error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed
DEBUG: .../IO/Socket/SSL.pm:1963: downgrading SSL only, not closing socket
DEBUG: .../IO/Socket/SSL.pm:2875: free ctx 45728400 open=
DEBUG: .../IO/Socket/SSL.pm:2879: free ctx 45728400 callback
DEBUG: .../IO/Socket/SSL.pm:2886: OK free ctx 45728400

I have found that I can pass SSL_fingerprint and/or SSL_verifycn_name to pass verification of self-signed certificate.

To make it just work I hack this

my %ssl_params = (
    SSL_verify_mode => $self->{SIDS}->{newconnection}->{ssl_verify},
    SSL_hostname    => 'my.host.name',
    SSL_verifycn_name => 'my.host.name',
);

without success =(

I try to use ->get_fingerprint to obtain finger print and pass it to SSL_fingerprint parameter, but:

IO::Socket::SSL->start_SSL(
    $self->{SIDS}->{$sid}->{sock},
    $self->{SIDS}->{$sid}->{ssl_params}
) or die "$IO::Socket::SSL::SSL_ERROR";

fail with error:

SSL connect attempt failed error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed at

Which option to pass to IO::Socket::SSL to verify self-signed certificate?


Solution

  • Using the fingerprint is probably the easiest way to verify a self-signed certificate which is in your own control. When using SSL_fingerprint it will not care about any other kind of validations, i.e. not check the name, revocation, expiration etc anymore - so if you want to have checks for this too you should not use SSL_fingerprint.

    Getting the fingerprint of a site can be done by connecting to the site once without validation (since you have no trust in the certificate yet) and getting the fingerprint or by getting the fingerprint from the certificate directly.

    To get the fingerprint by asking the server, assuming that the connection is not intercepted so that you get the correct fingerprint:

    use IO::Socket::SSL;
    print IO::Socket::SSL->new(
        PeerHost => 'example.com:443',
        # switch off validation since the certificate is not trusted yet
        SSL_verify_mode => SSL_VERIFY_NONE,
    )->get_fingerprint,"\n";
    

    This currently gives sha256$642de54d84c30494157f53f657bf9f89b4ea6c8b16351fd7ec258d556f821040 which can be directly used as argument for SSL_fingerprint.

    If you have instead the certificate for the site already you can compute the fingerprint directly on it:

    # get the certificate
    $ openssl s_client -connect example.com:443 -servername example.com 
    ...
    -----BEGIN CERTIFICATE-----
    MIIF8jCCBNqgAwIBAgIQDmTF+8I2reFLFyrrQceMsDANBgkqhkiG9w0BAQsFADBw
    ...
    -----END CERTIFICATE-----
    
    # take this PEM certificate and get fingerprint
    $ openssl x509 -fingerprint -sha256 -noout -in cert.pem
    SHA256 Fingerprint=64:2D:E5:4D:84:C3:04:94:15:7F:53:F6:57:BF:9F:89:B4:EA:6C:8B:16:35:1F:D7:EC:25:8D:55:6F:82:10:40
    

    The shown fingerprint is practically the same as before, only written in a different way. By removing all ':' (which are only there for readability) one gets 642DE5....1040 and by prefixing it with the used hash algorithm sha256 one gets something to be usable in SSL_fingerprint: sha256$642DE5...1040.

    To use the fingerprint then to connect to the site:

    my $cl = IO::Socket::SSL->new(
        PeerHost => 'example.com:443',
        SSL_fingerprint => 'sha256$642DE5...1040'
    );