Blob Blame History Raw
From 24fa6df5e1910269d86cb97bb5cef464fc3111ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Wed, 19 Mar 2014 12:54:22 +0100
Subject: [PATCH] Added support for ECDH key exchange with key SSL_ecdh_curve
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is port of following upstream commit to 1.94 version:

commit e067e09bf0c6b5844693169af75ec93b22bfa660
Author: Steffen Ullrich <Steffen_Ullrich@genua.de>
Date:   Fri Oct 11 18:50:01 2013 +0200

    1.955 - added support for ECDH key exchange with key SSL_ecdh_curve

Signed-off-by: Petr Písař <ppisar@redhat.com>
---
 MANIFEST  |  1 +
 SSL.pm    | 28 +++++++++++++++++++++++
 t/ecdhe.t | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+)
 create mode 100644 t/ecdhe.t

diff --git a/MANIFEST b/MANIFEST
index afed6c6..3d97511 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -50,6 +50,7 @@ t/auto_verify_hostname.t
 t/verify_hostname.t
 t/sni.t
 t/mitm.t
+t/ecdhe.t
 util/export_certs.pl
 META.yml                                 Module YAML meta-data (added by MakeMaker)
 META.json                                Module JSON meta-data (added by MakeMaker)
diff --git a/SSL.pm b/SSL.pm
index bcf098f..ad18f7a 100644
--- a/lib/IO/Socket/SSL.pm
+++ b/lib/IO/Socket/SSL.pm
@@ -1756,6 +1756,25 @@ sub new {
 	    Net::SSLeay::DH_free( $dh );
 	    $rv || return IO::Socket::SSL->error( "Failed to set DH from $f" );
 	}
+
+	if ( my $curve = $arg_hash->{SSL_ecdh_curve} ) {
+	    return IO::Socket::SSL->error(
+		"ECDH curve needs Net::SSLeay>=1.56 and OpenSSL>=1.0")
+		if ! defined( &Net::SSLeay::CTX_set_tmp_ecdh );
+	    if ( $curve !~ /^\d+$/ ) {
+		# name of curve, find NID
+		$curve = Net::SSLeay::OBJ_txt2nid($curve) 
+		    || return IO::Socket::SSL->error(
+		    "cannot find NID for curve name '$curve'");
+	    }
+	    my $ecdh = Net::SSLeay::EC_KEY_new_by_curve_name($curve) or 
+		return IO::Socket::SSL->error(
+		"cannot create curve for NID $curve");
+	    Net::SSLeay::CTX_set_tmp_ecdh($ctx,$ecdh) or
+		return IO::Socket::SSL->error(
+		"failed to set ECDH curve context");
+	    Net::SSLeay::EC_KEY_free($ecdh);
+	}
     }
 
     my $verify_cb = $arg_hash->{SSL_verify_callback};
@@ -2158,11 +2177,20 @@ C<SSL_key> option.
 
 If you want Diffie-Hellman key exchange you need to supply a suitable file here
 or use the SSL_dh parameter. See dhparam command in openssl for more information.
+To create a server which provides perfect forward secrecy you need to either
+give the DH parameters or (better, because faster) the ECDH curve.
 
 =item SSL_dh
 
 Like SSL_dh_file, but instead of giving a file you use a preloaded or generated DH*.
 
+=item SSL_ecdh_curve
+
+If you want Elliptic Curve Diffie-Hellmann key exchange you need to supply the
+OID or NID of a suitable curve (like 'prime256v1') here.
+To create a server which provides perfect forward secrecy you need to either
+give the DH parameters or (better, because faster) the ECDH curve.
+
 =item SSL_passwd_cb
 
 If your private key is encrypted, you might not want the default password prompt from
diff --git a/t/ecdhe.t b/t/ecdhe.t
new file mode 100644
index 0000000..40aed59
--- /dev/null
+++ b/t/ecdhe.t
@@ -0,0 +1,78 @@
+#!perl
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl t/ecdhe.t'
+
+use strict;
+use warnings;
+use Net::SSLeay;
+use Socket;
+use IO::Socket::SSL;
+
+if ( grep { $^O =~m{$_} } qw( MacOS VOS vmesa riscos amigaos ) ) {
+    print "1..0 # Skipped: fork not implemented on this platform\n";
+    exit
+}
+
+if ( ! defined &Net::SSLeay::CTX_set_tmp_ecdh ) {
+    print "1..0 # Skipped: no support for ecdh with this openssl/Net::SSLeay\n";
+    exit
+}
+
+$|=1;
+print "1..4\n";
+
+# first create simple ssl-server
+my $ID = 'server';
+my $addr = '127.0.0.1';
+my $server = IO::Socket::SSL->new(
+    LocalAddr => $addr,
+    Listen => 2,
+    ReuseAddr => 1,
+    SSL_cert_file => "certs/server-cert.pem",
+    SSL_key_file  => "certs/server-key.pem",
+    SSL_ecdh_curve => 'prime256v1',
+) || do {
+    notok($!);
+    exit
+};
+ok("Server Initialization");
+
+# add server port to addr
+$addr.= ':'.(sockaddr_in( getsockname( $server )))[0];
+
+my $pid = fork();
+if ( !defined $pid ) {
+    die $!; # fork failed
+
+} elsif ( !$pid ) {    ###### Client
+
+    $ID = 'client';
+    close($server);
+    my $to_server = IO::Socket::SSL->new(
+	PeerAddr => $addr,
+	SSL_verify_mode => 0 ) || do {
+	notok( "connect failed: $SSL_ERROR" );
+	exit
+    };
+    ok( "client connected" );
+
+    my $cipher = $to_server->get_cipher();
+    if ( $cipher !~m/^ECDHE-/ ) {
+	notok("bad key exchange: $cipher");
+	exit;
+    }
+    ok("ecdh key exchange: $cipher");
+
+} else {                ###### Server
+
+    my $to_client = $server->accept || do {
+	notok( "accept failed: $SSL_ERROR" );
+	kill(9,$pid);
+	exit;
+    };
+    ok( "Server accepted" );
+    wait;
+}
+
+sub ok { print "ok # [$ID] @_\n"; }
+sub notok { print "not ok # [$ID] @_\n"; }
-- 
1.8.5.3