about summary refs log tree commit
diff options
context:
space:
mode:
authorSteve Hay <steve.m.hay@googlemail.com>2014-06-26 08:30:31 +0100
committerSteve Hay <steve.m.hay@googlemail.com>2014-06-26 08:30:31 +0100
commit6d3dbc8ef9f82e0991a68a6c37b5cda72c9c62c8 (patch)
tree3daaa2a1072294794c69b1fcd62fe3af8077e17e
parent4f43a25d533a2baa41bfaf0209f1831ba8ef210d (diff)
parent57610506fd43d0b47b5c5a962af803ba8bc1d00c (diff)
downloadperl-libnet-6d3dbc8ef9f82e0991a68a6c37b5cda72c9c62c8.tar.gz
Merge pull request #5 from noxxi/ipv6_ssl_ftp
added IPv6 and SSL support for Net::FTP
-rw-r--r--Changes4
-rw-r--r--MANIFEST1
-rw-r--r--lib/Net/FTP.pm312
-rw-r--r--lib/Net/FTP/dataconn.pm4
-rw-r--r--t/external/ftp-ssl.t173
5 files changed, 416 insertions, 78 deletions
diff --git a/Changes b/Changes
index 64d7f7c..158f515 100644
--- a/Changes
+++ b/Changes
@@ -16,8 +16,8 @@ libnet 1.28  -- TODO
     username.   If a plain text username is specified then use the first
     reported SMTP AUTH method supported, as usual.  [Ewen McNeill; resolves CPAN
     RT#58002]
-  * Add support for IPv6 and SSL to Net::SMTP and Net::POP3.  These features are
-    only available if the user has
+  * Add support for IPv6 and SSL to Net::SMTP, Net::POP3 and Net::FTP.  
+    These features are only available if the user has
 
       a recent IO::Socket::SSL for SSL support
       a recent IO::Socket::IP or an older IO::Socket::INET6 for IPv6 support
diff --git a/MANIFEST b/MANIFEST
index 8cca250..ad83dcc 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -30,6 +30,7 @@ t/critic.t
 t/datasend.t
 t/external/smtp-ssl.t
 t/external/pop3-ssl.t
+t/external/ftp-ssl.t
 t/ftp.t
 t/hostname.t
 t/libnet_t.pl
diff --git a/lib/Net/FTP.pm b/lib/Net/FTP.pm
index 302c81a..66befc8 100644
--- a/lib/Net/FTP.pm
+++ b/lib/Net/FTP.pm
@@ -25,7 +25,35 @@ use Socket 1.3;
 use Time::Local;
 
 our $VERSION = '2.80';
-our @ISA     = qw(Exporter Net::Cmd IO::Socket::INET);
+
+our $IOCLASS;
+BEGIN {
+  # Code for detecting if we can use SSL
+  my $ssl_class = eval {
+    require IO::Socket::SSL;
+    # first version with default CA on most platforms
+    IO::Socket::SSL->VERSION(1.994);
+  } && 'IO::Socket::SSL';
+
+  my $nossl_warn = !$ssl_class &&
+    'To use SSL please install IO::Socket::SSL with version>=1.994';
+
+  # Code for detecting if we can use IPv6
+  my $inet6_class = eval {
+    require IO::Socket::IP;
+    IO::Socket::IP->VERSION(0.20);
+  } && 'IO::Socket::IP' || eval {
+    require IO::Socket::INET6;
+    IO::Socket::INET6->VERSION(2.62);
+  } && 'IO::Socket::INET6';
+
+  sub can_ssl   { $ssl_class };
+  sub can_inet6 { $inet6_class };
+
+  $IOCLASS = $ssl_class || $inet6_class || 'IO::Socket::INET';
+}
+
+our @ISA = ('Exporter','Net::Cmd',$IOCLASS);
 
 use constant TELNET_IAC => 255;
 use constant TELNET_IP  => 244;
@@ -65,16 +93,32 @@ sub new {
     }
   }
 
+  my %tlsargs;
+  if (can_ssl()) {
+    # for name verification strip port from domain:port, ipv4:port, [ipv6]:port
+    (my $hostname = $host) =~s{(?<!:):\d+$}{};
+    %tlsargs = (
+      SSL_verifycn_scheme => 'ftp',
+      SSL_verifycn_name => $hostname,
+      # reuse SSL session of control connection in data connections
+      SSL_session_cache => Net::FTP::SSL_SingleSessionCache->new,
+    );
+    # user defined SSL arg
+    $tlsargs{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg);
+
+  } elsif ($arg{SSL}) {
+    croak("IO::Socket::SSL >= 1.944 needed for SSL support");
+  }
+
   my $ftp = $pkg->SUPER::new(
     PeerAddr  => $peer,
-    PeerPort  => $arg{Port} || 'ftp(21)',
+    PeerPort  => $arg{Port} || ($arg{SSL} ? 'ftps(990)' : 'ftp(21)'),
     LocalAddr => $arg{'LocalAddr'},
     Proto     => 'tcp',
-    Timeout   => defined $arg{Timeout}
-    ? $arg{Timeout}
-    : 120
-    )
-    or return;
+    Timeout   => defined $arg{Timeout} ? $arg{Timeout} : 120,
+    %tlsargs,
+    $arg{SSL} ? ():( SSL_startHandshake => 0 ),
+  ) or return;
 
   ${*$ftp}{'net_ftp_host'}    = $host;                             # Remote hostname
   ${*$ftp}{'net_ftp_type'}    = 'A';                               # ASCII/binary/etc mode
@@ -93,6 +137,12 @@ sub new {
     : defined $fire            ? $NetConfig{ftp_ext_passive}
     : $NetConfig{ftp_int_passive};    # Whew! :-)
 
+  ${*$ftp}{net_ftp_tlsargs} = \%tlsargs if %tlsargs;
+  if ($arg{SSL}) {
+    ${*$ftp}{net_ftp_tlsprot} = 'P';
+    ${*$ftp}{net_ftp_tlsdirect} = 1;
+  }
+
   $ftp->hash(exists $arg{Hash} ? $arg{Hash} : 0, 1024);
 
   $ftp->autoflush(1);
@@ -235,6 +285,35 @@ sub size {
 }
 
 
+sub starttls {
+  my $ftp = shift;
+  can_ssl() or croak("IO::Socket::SSL >= 1.944 needed for SSL support");
+  $ftp->is_SSL and croak("called starttls within SSL session");
+  $ftp->_AUTH('TLS') == CMD_OK or return;
+
+  $ftp->connect_SSL or return;
+  $ftp->prot('P');
+  return 1;
+}
+
+sub prot {
+  my ($ftp,$prot) = @_;
+  $prot eq 'C' or $prot eq 'P' or croak("prot must by C or P");
+  $ftp->_PBSZ(0) or return;
+  $ftp->_PROT($prot) or return;
+  ${*$ftp}{net_ftp_tlsprot} = $prot;
+  return 1;
+}
+
+sub stoptls {
+  my $ftp = shift;
+  $ftp->is_SSL or croak("called stoptls outside SSL session");
+  ${*$ftp}{net_ftp_tlsdirect} and croak("cannot stoptls direct SSL session");
+  $ftp->_CCC() or return;
+  $ftp->stop_SSL();
+  return 1;
+}
+
 sub login {
   my ($ftp, $user, $pass, $acct) = @_;
   my ($ok, $ruser, $fwtype);
@@ -787,38 +866,41 @@ sub _store_cmd {
 
 
 sub port {
-  @_ == 1 || @_ == 2 or croak 'usage: $ftp->port([PORT])';
-
-  my ($ftp, $port) = @_;
-  my $ok;
-
-  delete ${*$ftp}{'net_ftp_intern_port'};
-
-  unless (defined $port) {
+    @_ == 1 || @_ == 2 or croak 'usage: $self->port([PORT])';
+    return _eprt('PORT',@_);
+}
 
-    # create a Listen socket at same address as the command socket
+sub eprt {
+  @_ == 1 || @_ == 2 or croak 'usage: $self->eprt([PORT])';
+  return _eprt('EPRT',@_);
+}
 
-    ${*$ftp}{'net_ftp_listen'} ||= IO::Socket::INET->new(
-      Listen    => 5,
-      Proto     => 'tcp',
+sub _eprt {
+  my ($cmd,$ftp,$port) = @_;
+  delete ${*$ftp}{net_ftp_intern_port};
+  unless ($port) {
+    my $listen = ${*$ftp}{net_ftp_listen} ||= $IOCLASS->new(
+      Listen    => 1,
       Timeout   => $ftp->timeout,
       LocalAddr => $ftp->sockhost,
+      can_ssl() ? (
+        %{ ${*$ftp}{net_ftp_tlsargs} },
+        SSL_startHandshake => 0,
+      ):(),
     );
-
-    my $listen = ${*$ftp}{'net_ftp_listen'};
-
-    my ($myport, @myaddr) = ($listen->sockport, split(/\./, $listen->sockhost));
-
-    $port = join(',', @myaddr, $myport >> 8, $myport & 0xff);
-
-    ${*$ftp}{'net_ftp_intern_port'} = 1;
+    ${*$ftp}{net_ftp_intern_port} = 1;
+    my $fam = ($listen->sockdomain == AF_INET) ? 1:2;
+    if ( $cmd eq 'EPRT' || $fam == 2 ) {
+      $port = "|$fam|".$listen->sockhost."|".$listen->sockport."|";
+      $cmd = 'EPRT';
+    } else {
+      my $p = $listen->sockport;
+      $port = join(',',split(m{\.},$listen->sockhost),$p >> 8,$p & 0xff);
+    }
   }
-
-  $ok = $ftp->_PORT($port);
-
-  ${*$ftp}{'net_ftp_port'} = $port;
-
-  $ok;
+  my $ok = $cmd eq 'EPRT' ? $ftp->_EPRT($port) : $ftp->_PORT($port);
+  ${*$ftp}{net_ftp_port} = $port if $ok;
+  return $ok;
 }
 
 
@@ -827,14 +909,27 @@ sub dir { shift->_list_cmd("LIST", @_); }
 
 
 sub pasv {
-  @_ == 1 or croak 'usage: $ftp->pasv()';
-
   my $ftp = shift;
+  @_ and croak 'usage: $ftp->port()';
+  return $ftp->epsv if $ftp->sockdomain != AF_INET;
+  delete ${*$ftp}{net_ftp_intern_port};
+
+  if ( $ftp->_PASV &&
+    $ftp->message =~ m{(\d+,\d+,\d+,\d+),(\d+),(\d+)} ) {
+    my $port = 256 * $2 + $3;
+    ( my $ip = $1 ) =~s{,}{.}g;
+    return ${*$ftp}{net_ftp_pasv} = [ $ip,$port ];
+  }
+  return;
+}
 
-  delete ${*$ftp}{'net_ftp_intern_port'};
+sub epsv {
+  my $ftp = shift;
+  @_ and croak 'usage: $ftp->epsv()';
+  delete ${*$ftp}{net_ftp_intern_port};
 
-  $ftp->_PASV && $ftp->message =~ /(\d+(,\d+)+)/
-    ? ${*$ftp}{'net_ftp_pasv'} = $1
+  $ftp->_EPSV && $ftp->message =~ m{\(([\x33-\x7e])\1\1(\d+)\1\)}
+    ? ${*$ftp}{net_ftp_pasv} = [ $ftp->peerhost, $2 ]
     : undef;
 }
 
@@ -918,41 +1013,51 @@ sub _extract_path {
 
 
 sub _dataconn {
-  my $ftp  = shift;
-  my $data = undef;
-  my $pkg  = "Net::FTP::" . $ftp->type;
-
-  eval "require " . $pkg; ## no critic (BuiltinFunctions::ProhibitStringyEval)
-
+  my $ftp = shift;
+  my $pkg = "Net::FTP::" . $ftp->type;
+  eval "require " . $pkg ## no critic (BuiltinFunctions::ProhibitStringyEval)
+    or croak("cannot load $pkg required for type ".$ftp->type);
   $pkg =~ s/ /_/g;
-
-  delete ${*$ftp}{'net_ftp_dataconn'};
-
-  if (defined ${*$ftp}{'net_ftp_pasv'}) {
-    my @port = map { 0 + $_ } split(/,/, ${*$ftp}{'net_ftp_pasv'});
-
-    $data = $pkg->new(
-      PeerAddr  => join(".", @port[0 .. 3]),
-      PeerPort  => $port[4] * 256 + $port[5],
-      LocalAddr => ${*$ftp}{'net_ftp_localaddr'},
-      Proto     => 'tcp',
-      Timeout   => $ftp->timeout
-    );
-  }
-  elsif (defined ${*$ftp}{'net_ftp_listen'}) {
-    $data = ${*$ftp}{'net_ftp_listen'}->accept($pkg);
-    close(delete ${*$ftp}{'net_ftp_listen'});
+  delete ${*$ftp}{net_ftp_dataconn};
+
+  my $conn;
+  my $pasv = ${*$ftp}{net_ftp_pasv};
+  if ($pasv) {
+    $conn = $pkg->new(
+      PeerAddr  => $pasv->[0],
+      PeerPort  => $pasv->[1],
+      LocalAddr => ${*$ftp}{net_ftp_localaddr},
+      Timeout   => $ftp->timeout,
+      can_ssl() ? (
+        SSL_startHandshake => 0,
+        $ftp->is_SSL ? (
+          SSL_reuse_ctx => $ftp,
+          SSL_verifycn_name => ${*$ftp}{net_ftp_tlsargs}{SSL_verifycn_name},
+        ) :( %{${*$ftp}{net_ftp_tlsargs}} ),
+      ):(),
+    ) or return;
+  } elsif (my $listen =  delete ${*$ftp}{net_ftp_listen}) {
+    $conn = $listen->accept($pkg) or return;
+    $conn->timeout($ftp->timeout);
+    close($listen);
+  } else {
+    croak("no listener in active mode");
   }
 
-  if ($data) {
-    ${*$data} = "";
-    $data->timeout($ftp->timeout);
-    ${*$ftp}{'net_ftp_dataconn'} = $data;
-    ${*$data}{'net_ftp_cmd'}     = $ftp;
-    ${*$data}{'net_ftp_blksize'} = ${*$ftp}{'net_ftp_blksize'};
+  if (( ${*$ftp}{net_ftp_tlsprot} || '') eq 'P') {
+    if ($conn->connect_SSL) {
+      # SSL handshake ok
+    } else {
+      carp("failed to ssl upgrade dataconn: $IO::Socket::SSL::SSL_ERROR");
+      return;
+    }
   }
 
-  $data;
+  ${*$ftp}{net_ftp_dataconn} = $conn;
+  ${*$conn} = "";
+  ${*$conn}{net_ftp_cmd} = $ftp;
+  ${*$conn}{net_ftp_blksize} = ${*$ftp}{net_ftp_blksize};
+  return $conn;
 }
 
 
@@ -1009,10 +1114,7 @@ sub _data_cmd {
     && !defined ${*$ftp}{'net_ftp_pasv'}
     && !defined ${*$ftp}{'net_ftp_port'})
   {
-    my $data = undef;
-
     return unless defined $ftp->pasv;
-    $data = $ftp->_dataconn() or return;
 
     if ($where and !$ftp->_REST($where)) {
       my ($status, $message) = ($ftp->status, $ftp->message);
@@ -1021,13 +1123,17 @@ sub _data_cmd {
       return;
     }
 
+    # first send command, then open data connection
+    # otherwise the peer might not do a full accept (with SSL
+    # handshake if PROT P)
     $ftp->command($cmd, @_);
+    my $data = $ftp->_dataconn();
     if (CMD_INFO == $ftp->response()) {
       $data->reading
-        if $cmd =~ /RETR|LIST|NLST/;
+        if $data && $cmd =~ /RETR|LIST|NLST/;
       return $data;
     }
-    $data->_close;
+    $data->_close if $data;
 
     return;
   }
@@ -1206,7 +1312,7 @@ sub cmd { shift->command(@_)->response() }
 
 ########################################
 #
-# RFC959 commands
+# RFC959 + RFC2428 + RFC4217 commands
 #
 
 
@@ -1230,6 +1336,11 @@ sub _SIZE { shift->command("SIZE", @_)->response() == CMD_OK }
 sub _HELP { shift->command("HELP", @_)->response() == CMD_OK }
 sub _STAT { shift->command("STAT", @_)->response() == CMD_OK }
 sub _FEAT { shift->command("FEAT", @_)->response() == CMD_OK }
+sub _PBSZ { shift->command("PBSZ", @_)->response() == CMD_OK }
+sub _PROT { shift->command("PROT", @_)->response() == CMD_OK }
+sub _CCC  { shift->command("CCC", @_)->response() == CMD_OK }
+sub _EPRT { shift->command("EPRT", @_)->response() == CMD_OK }
+sub _EPSV { shift->command("EPSV", @_)->response() == CMD_OK }
 sub _APPE { shift->command("APPE", @_)->response() == CMD_INFO }
 sub _LIST { shift->command("LIST", @_)->response() == CMD_INFO }
 sub _NLST { shift->command("NLST", @_)->response() == CMD_INFO }
@@ -1261,6 +1372,26 @@ sub _SYST { shift->unsupported(@_) }
 sub _STRU { shift->unsupported(@_) }
 sub _REIN { shift->unsupported(@_) }
 
+{
+  # Session Cache with single entry
+  # used to make sure that we reuse same session for control and data channels
+  package Net::FTP::SSL_SingleSessionCache;
+  sub new { my $x; return bless \$x,shift }
+  sub add_session {
+    my ($cache,$key,$session) = @_;
+    Net::SSLeay::SESSION_free($$cache) if $$cache;
+    $$cache = $session;
+  }
+  sub get_session {
+    my $cache = shift;
+    return $$cache
+  }
+  sub DESTROY {
+    my $cache = shift;
+    Net::SSLeay::SESSION_free($$cache) if $$cache;
+  }
+}
+
 1;
 
 __END__
@@ -1364,6 +1495,13 @@ transfers. (defaults to 10240)
 B<Port> - The port number to connect to on the remote machine for the
 FTP connection
 
+B<SSL> - If the connection should be done from start with SSL, contrary to later
+upgrade with C<starttls>.
+
+B<SSL_*> - SSL arguments which will be applied when upgrading the control or
+data connection to SSL. You can use SSL arguments as documented in
+L<IO::Socket::SSL>, but it will usually use the right arguments already.
+
 B<Timeout> - Set a timeout value in seconds (defaults to 120)
 
 B<Debug> - debug level (see the debug method in L<Net::Cmd>)
@@ -1420,6 +1558,19 @@ will be used for password.
 If the connection is via a firewall then the C<authorize> method will
 be called with no arguments.
 
+=item starttls
+
+Upgrade existing plain connection to SSL.
+The SSL arguments have to be given in C<new> already because they are needed for
+data connections too.
+
+=item stoptls
+
+Downgrade existing SSL connection back to plain.
+This is needed to work with some FTP helpers at firewalls, which need to see the
+PORT and PASV commands and responses to dynamically open the necessary ports.
+In this case C<starttls> is usually only done to protect the authorization.
+
 =item host ()
 
 Returns the value used by the constructor, and passed to IO::Socket::INET,
@@ -1729,6 +1880,14 @@ Returns most significant digit of the response code.
 B<WARNING> This call should only be used on commands that do not require
 data connections. Misuse of this method can hang the connection.
 
+=item can_inet6 ()
+
+Returns whether we can use IPv6.
+
+=item can_ssl ()
+
+Returns whether we can use SSL.
+
 =back
 
 =head1 THE dataconn CLASS
@@ -1799,9 +1958,12 @@ Steve Hay E<lt>F<shay@cpan.org>E<gt> is now maintaining libnet as of version
 
 L<Net::Netrc>
 L<Net::Cmd>
+L<IO::Socket::SSL>
 
-ftp(1), ftpd(8), RFC 959
+ftp(1), ftpd(8), RFC 959, RFC 2428, RFC 4217
 http://www.ietf.org/rfc/rfc959.txt
+http://www.ietf.org/rfc/rfc2428.txt
+http://www.ietf.org/rfc/rfc4217.txt
 
 =head1 USE EXAMPLES
 
diff --git a/lib/Net/FTP/dataconn.pm b/lib/Net/FTP/dataconn.pm
index 3719a27..c9c3c14 100644
--- a/lib/Net/FTP/dataconn.pm
+++ b/lib/Net/FTP/dataconn.pm
@@ -14,7 +14,9 @@ use Errno;
 use Net::Cmd;
 
 our $VERSION = '0.13';
-our @ISA     = qw(IO::Socket::INET);
+
+$Net::FTP::IOCLASS or die "please load Net::FTP before Net::FTP::dataconn";
+our @ISA = $Net::FTP::IOCLASS;
 
 sub reading {
   my $data = shift;
diff --git a/t/external/ftp-ssl.t b/t/external/ftp-ssl.t
new file mode 100644
index 0000000..3036630
--- /dev/null
+++ b/t/external/ftp-ssl.t
@@ -0,0 +1,173 @@
+#!perl
+
+use 5.008001;
+
+use strict;
+use warnings;
+
+use Net::FTP;
+use Test::More;
+use File::Temp;
+use IO::Socket::INET;
+
+my $server = 'test.rebex.net';
+my $debug = 0;
+
+plan skip_all => "no SSL support" if ! Net::FTP->can_ssl;
+require IO::Socket::SSL;
+
+
+# first try to connect w/o ftp
+# plain
+diag( "connect inet to $server:21" );
+IO::Socket::INET->new( "$server:21" ) or do {
+    plan skip_all => "$server:21 not reachable";
+};
+
+# ssl to the right host
+diag( "connect inet to $server:990" );
+my $sock = IO::Socket::INET->new( "$server:990") or do {
+    plan skip_all => "$server:990 not reachable";
+};
+
+# now we need CAs
+my $cafh = File::Temp->new( UNLINK => 0, SUFFIX => '.crt' );
+my %sslargs = ( SSL_ca_file => $cafh->filename );
+print $cafh <DATA>;
+close($cafh);
+
+diag( "upgrade to ssl $server:990" );
+IO::Socket::SSL->start_SSL($sock,
+    SSL_verify_mode => 1,
+    SSL_verifycn_name => $server,
+    SSL_verifycn_scheme => 'ftp',
+    %sslargs,
+) or do {
+    plan skip_all => "$server:990 not upgradable to SSL: ".
+        $IO::Socket::SSL::SSL_ERROR;
+};
+
+plan tests => 9;
+
+# first direct SSL
+diag( "connect ftp over ssl to $server" );
+my $ftp = Net::FTP->new($server,
+    SSL => 1,
+    %sslargs,
+    Debug => $debug,
+    Passive => 1,
+);
+ok($ftp,"ftp ssl connect $server");
+$ftp->login("anonymous",'net-sslglue-ftp@test.perl')
+    or die "login to $server failed";
+diag("logged in");
+# check that we can talk on connection
+ok(~~$ftp->ls,"directory listing protected");
+$ftp->prot('C');
+ok(~~$ftp->ls,"directory listing clear");
+
+# then TLS upgrade inside plain connection
+$ftp = Net::FTP->new($server,
+    Passive => 1,
+    Debug => $debug,
+    %sslargs
+);
+ok($ftp,"ftp plain connect $server");
+my $ok = $ftp->starttls;
+ok($ok,"ssl upgrade");
+$ftp->login("anonymous",'net-sslglue-ftp@test.perl')
+    or die "login to $server failed";
+diag("logged in");
+# check that we can talk on connection
+ok(~~$ftp->ls,"directory listing protected");
+$ftp->prot('C');
+ok(~~$ftp->ls,"directory listing clear");
+$ok = $ftp->stoptls;
+ok($ok,"ssl downgrade");
+ok(~~$ftp->ls,"directory listing after downgrade");
+
+
+__DATA__
+# Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Class 2 Primary Intermediate Server CA
+# Issuer:  C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority
+-----BEGIN CERTIFICATE-----
+MIIGNDCCBBygAwIBAgIBGjANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NzA5WhcNMTcxMDI0MjA1NzA5WjCB
+jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
+IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
+YXJ0Q29tIENsYXNzIDIgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4k85L6GMmoWtCA4IPlfyiAEh
+G5SpbOK426oZGEY6UqH1D/RujOqWjJaHeRNAUS8i8gyLhw9l33F0NENVsTUJm9m8
+H/rrQtCXQHK3Q5Y9upadXVACHJuRjZzArNe7LxfXyz6CnXPrB0KSss1ks3RVG7RL
+hiEs93iHMuAW5Nq9TJXqpAp+tgoNLorPVavD5d1Bik7mb2VsskDPF125w2oLJxGE
+d2H2wnztwI14FBiZgZl1Y7foU9O6YekO+qIw80aiuckfbIBaQKwn7UhHM7BUxkYa
+8zVhwQIpkFR+ZE3EMFICgtffziFuGJHXuKuMJxe18KMBL47SLoc6PbQpZ4rEAwID
+AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFBHbI0X9VMxqcW+EigPXvvcBLyaGMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
+L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
+YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
+dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
+c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
+BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
+BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
+LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAnQfh7pB2MWcWRXCMy4SLS1doRKWJwfJ+
+yyiL9edwd9W29AshYKWhdHMkIoDW2LqNomJdCTVCKfs5Y0ULpLA4Gmj0lRPM4EOU
+7Os5GuxXKdmZbfWEzY5zrsncavqenRZkkwjHHMKJVJ53gJD2uSl26xNnSFn4Ljox
+uMnTiOVfTtIZPUOO15L/zzi24VuKUx3OrLR2L9j3QGPV7mnzRX2gYsFhw3XtsntN
+rCEnME5ZRmqTF8rIOS0Bc2Vb6UGbERecyMhK76F2YC2uk/8M1TMTn08Tzt2G8fz4
+NVQVqFvnhX76Nwn/i7gxSZ4Nbt600hItuO3Iw/G2QqBMl3nf/sOjn6H0bSyEd6Si
+BeEX/zHdmvO4esNSwhERt1Axin/M51qJzPeGmmGSTy+UtpjHeOBiS0N9PN7WmrQQ
+oUCcSyrcuNDUnv3xhHgbDlePaVRCaHvqoO91DweijHOZq1X1BwnSrzgDapADDC+P
+4uhDwjHpb62H5Y29TiyJS1HmnExUdsASgVOb7KD8LJzaGJVuHjgmQid4YAjff20y
+6NjAbx/rJnWfk/x7G/41kNxTowemP4NVCitOYoIlzmYwXSzg+RkbdbmdmFamgyd6
+0Y+NWZP8P3PXLrQsldiL98l+x/ydrHIEH9LMF/TtNGCbnkqXBP7dcg5XVFEGcE3v
+qhykguAzx/Q=
+-----END CERTIFICATE-----
+# Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority
+# Issuer:  C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9
+MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
+U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
+cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
+pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
+OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
+Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
+Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
+HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
+Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
+Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
+26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
+AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC
+ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w
+ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk
+aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0
+YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg
+c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93
+d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG
+CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF
+wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS
+Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst
+0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc
+pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl
+CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF
+P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK
+1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm
+KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ
+8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm
+fyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----