Search code examples
perlsslopenssllwp

How can I share OpenSSL sessions between Perl processes?


I'm using Perl to connect to some (very) low-powered hardware devices with TLS. The first handshake can take around 10-15 seconds to complete! Reusing the session (from the same Perl process) is much faster but when the next job comes around to connect to the same device the new process has to establish a new session with the same delay. I'd like to share a session cache between my processes but I'm running into problems (and segfaults!).

I have a test script (connecting to openssl s_server -www) with a IO::Socket::SSL::Session_Cache wrapper that uses Sereal to write the cache object out to disk. Despite finding the existing sessions in the cache, it does not reuse them and sometimes segfaults when trying to add additional entries to the cache.

use 5.20.1; use warnings;

use LWP::UserAgent;
use IO::Socket::SSL;
# $IO::Socket::SSL::DEBUG = 2;

request_with_new_ua();
request_with_new_ua();
request_with_new_ua();

sub request_with_new_ua {
   say "make request";
   my $ua = LWP::UserAgent->new;
   $ua->ssl_opts(
      verify_hostname => 0,
      SSL_session_cache => Inline::SessionStore->new,
   );
   process_response($ua->get('https://localhost:4433'));
}

sub process_response {
   my $res = shift;
   say "> $_" for grep /Session|Master/, split /\n/, $res->as_string;
}

BEGIN {
   package Inline::SessionStore;

   use 5.20.1; use warnings;
   use Moo;
   use experimental qw(signatures);
   use Sereal::Encoder;
   use Sereal::Decoder;
   use Try::Tiny;
   use Path::Tiny;

   has session_cache => ( is => 'rw' );

   my $encoder = Sereal::Encoder->new;
   my $decoder = Sereal::Decoder->new;
   my $file = path('/tmp/ssl-session-cache');

   sub get_session ($self, $key) {
      say "get session $key";
      my $cache;
      try {
         $cache = $decoder->decode($file->slurp_raw);
         say "got cache from file, ".ref $cache;
      } catch {
         say $_ unless /No such file/;
         $cache = IO::Socket::SSL::Session_Cache->new(128);
         say "made new cache";
      };
      $self->session_cache($cache);

      my $ret = $cache->get_session($key);
      say "found session $ret" if $ret;
      return $ret;
   }

   sub add_session {
      my $self = shift;
      say"add session " . join ' - ', @_;
      my $session = $self->session_cache->add_session(@_);

      $file->spew_raw($encoder->encode($self->session_cache));
      return $session;
   }

   sub del_session {
      my $self = shift;
      say "del session " . join ' - ', @_;
      $self->session_cache->del_session(@_);

      $file->spew_raw($encoder->encode($self->session_cache));
   }

   1;
}

And output:

 rm -f /tmp/ssl-session-cache && perl wes.swp/workbench.pl
make request
get session localhost:4433
made new cache
add session localhost:4433 - 23864624
> SSL-Session:
>     Session-ID: 
>     Session-ID-ctx: 01000000
>     Master-Key: DDF335492BFE2A7BA7674A656E72005865859D89249D597302F338D01C5776E2C94B61E6BCBED6114DFDA5AAEECD22EA
make request
get session localhost:4433
got cache from file, IO::Socket::SSL::Session_Cache
found session 23864624
add session localhost:4433 - 23864624  # trying to re-add the session??
> SSL-Session:
>     Session-ID: 
>     Session-ID-ctx: 01000000
>     Master-Key: 4FE17B7FE9B4DE0A711C659FC333F686AD41840740B9D10E67A972D5A27D1720F0470329DA63DE65C1B023A1E2F0CC89
make request
get session localhost:4433
got cache from file, IO::Socket::SSL::Session_Cache
found session 23864624
add session localhost:4433 - 23864624
> SSL-Session:
>     Session-ID: 
>     Session-ID-ctx: 01000000
>     Master-Key: C76C52E5ECC13B0BB4FA887B381779B6F686A73DDFBEA06B33336537EC6AE39290370C07505BCD8B552CA874CD6E4089

I feel like I'm close to getting this to work but I'm missing something.


Solution

  • I don't think there is a way to use IO::Socket::SSL/Net::SSLeay or Crypt::SSLeay (which are the newer and older SSL backend for LWP) between processes.

    The session cache you are trying to use in your code references SESSION objects which are internal to the OpenSSL library. Serializing the cache at the Perl level will not serialize the parts from inside the OpenSSL library but will just include the pointers to the internal structures. Since these pointers are only valid for the current state of the process, deserializing this again inside a different process or process state will thus result in dangling pointers pointing into nowhere in the best case or to some other data in the worst case and will thus result in segmentation faults or corruption of internal data.

    In Net::SSLeay there are i2d_SSL_SESSION and d2i_SSL_SESSION functions which could in theory be used to properly serialize a SESSION object. But I doubt that there are usable in the current implementation.