[前][次][番号順一覧][スレッド一覧]

ruby-changes:2779

From: ko1@a...
Date: 17 Dec 2007 21:26:11 +0900
Subject: [ruby-changes:2779] gotoyuzo - Ruby:r14270 (trunk): * ext/openssl/ossl_ssl.c (ossl_sslctx_set_ssl_version):

gotoyuzo	2007-12-17 21:18:28 +0900 (Mon, 17 Dec 2007)

  New Revision: 14270

  Modified files:
    trunk/ext/openssl/lib/openssl/ssl.rb
    trunk/ext/openssl/ossl_ssl.c
    trunk/test/openssl/test_ssl.rb

  Log:
    * ext/openssl/ossl_ssl.c (ossl_sslctx_set_ssl_version):
      new method OpenSSL::SSL::SSLContext#ssl_version to wrap
      SSL_CTX_set_ssl_version.
    
    * ext/openssl/ossl_ssl.c (ossl_ssl_get_verify_result):
      new method OpenSSL::SSL::SSLSocket#verify_result to wrap
      SSL_get_verrify_result.
    
    * ext/openssl/lib/openssl/ssl.rb (OpenSSL::SSL::SSLContext.build):
      new method to build OpenSSL::SSL::SSLContext with Hash parameters.
      this method provides safety default parameters than SSLContext.new.
    
    * ext/openssl/lib/openssl/ssl.rb (OpenSSL::SSL.verify_cetificate_identity):
      new module function: pull out identity verification process
      from OpenSSL::SSL::SSLSocket#post_connection_check.


  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/ext/openssl/ossl_ssl.c?r1=14270&r2=14269
  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/test/openssl/test_ssl.rb?r1=14270&r2=14269
  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/ext/openssl/lib/openssl/ssl.rb?r1=14270&r2=14269

Index: ext/openssl/ossl_ssl.c
===================================================================
--- ext/openssl/ossl_ssl.c	(revision 14269)
+++ ext/openssl/ossl_ssl.c	(revision 14270)
@@ -141,31 +141,14 @@
     return Data_Wrap_Struct(klass, 0, ossl_sslctx_free, ctx);
 }
 
-/*
- * call-seq:
- *    SSLContext.new => ctx
- *    SSLContext.new(:TLSv1) => ctx
- *    SSLContext.new("SSLv23_client") => ctx
- *
- * You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS
- */
 static VALUE
-ossl_sslctx_initialize(int argc, VALUE *argv, VALUE self)
+ossl_sslctx_set_ssl_version(VALUE self, VALUE ssl_method)
 {
-    VALUE ssl_method;
     SSL_METHOD *method = NULL;
-    SSL_CTX *ctx;
+    const char *s;
     int i;
-    const char *s;
 
-    for(i = 0; i < numberof(ossl_sslctx_attrs); i++){
-	char buf[32];
-	snprintf(buf, sizeof(buf), "@%s", ossl_sslctx_attrs[i]);
-	rb_iv_set(self, buf, Qnil);
-    }
-    if (rb_scan_args(argc, argv, "01", &ssl_method) == 0){
-        return self;
-    }
+    SSL_CTX *ctx;
     if(TYPE(ssl_method) == T_SYMBOL)
 	s = rb_id2name(SYM2ID(ssl_method));
     else
@@ -184,6 +167,33 @@
         ossl_raise(eSSLError, "SSL_CTX_set_ssl_version:");
     }
 
+    return ssl_method;
+}
+
+/*
+ * call-seq:
+ *    SSLContext.new => ctx
+ *    SSLContext.new(:TLSv1) => ctx
+ *    SSLContext.new("SSLv23_client") => ctx
+ *
+ * You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS
+ */
+static VALUE
+ossl_sslctx_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE ssl_method;
+    int i;
+
+    for(i = 0; i < numberof(ossl_sslctx_attrs); i++){
+	char buf[32];
+	snprintf(buf, sizeof(buf), "@%s", ossl_sslctx_attrs[i]);
+	rb_iv_set(self, buf, Qnil);
+    }
+    if (rb_scan_args(argc, argv, "01", &ssl_method) == 0){
+        return self;
+    }
+    ossl_sslctx_set_ssl_version(self, ssl_method);
+
     return self;
 }
 
@@ -1299,7 +1309,20 @@
     return arg1;
 }
 
+static VALUE
+ossl_ssl_get_verify_result(VALUE self)
+{
+    SSL *ssl;
 
+    Data_Get_Struct(self, SSL, ssl);
+    if (!ssl) {
+        rb_warning("SSL session is not started yet.");
+        return Qnil;
+    }
+
+    return INT2FIX(SSL_get_verify_result(ssl));
+}
+
 void
 Init_ossl_ssl()
 {
@@ -1327,15 +1350,17 @@
      *
      * The following attributes are available but don't show up in rdoc.
      * All attributes must be set before calling SSLSocket.new(io, ctx).
-     * * cert, key, client_ca, ca_file, ca_path, timeout, verify_mode, verify_depth
-     * * client_cert_cb, tmp_dh_callback, session_id_context,
-     * * session_add_cb, session_new_cb, session_remove_cb
+     * * ssl_version, cert, key, client_ca, ca_file, ca_path, timeout,
+     * * verify_mode, verify_depth client_cert_cb, tmp_dh_callback,
+     * * session_id_context, session_add_cb, session_new_cb, session_remove_cb
      */
     cSSLContext = rb_define_class_under(mSSL, "SSLContext", rb_cObject);
     rb_define_alloc_func(cSSLContext, ossl_sslctx_s_alloc);
     for(i = 0; i < numberof(ossl_sslctx_attrs); i++)
         rb_attr(cSSLContext, rb_intern(ossl_sslctx_attrs[i]), 1, 1, Qfalse);
+    rb_define_alias(cSSLContext, "ssl_timeout", "timeout");
     rb_define_method(cSSLContext, "initialize",  ossl_sslctx_initialize, -1);
+    rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1);
     rb_define_method(cSSLContext, "ciphers",     ossl_sslctx_get_ciphers, 0);
     rb_define_method(cSSLContext, "ciphers=",    ossl_sslctx_set_ciphers, 1);
 
@@ -1392,6 +1417,7 @@
     rb_define_method(cSSLSocket, "pending",    ossl_ssl_pending, 0);
     rb_define_method(cSSLSocket, "session_reused?",    ossl_ssl_session_reused, 0);
     rb_define_method(cSSLSocket, "session=",    ossl_ssl_set_session, 1);
+    rb_define_method(cSSLSocket, "verify_result", ossl_ssl_get_verify_result, 0);
 
 #define ossl_ssl_def_const(x) rb_define_const(mSSL, #x, INT2FIX(SSL_##x))
 
Index: ext/openssl/lib/openssl/ssl.rb
===================================================================
--- ext/openssl/lib/openssl/ssl.rb	(revision 14269)
+++ ext/openssl/lib/openssl/ssl.rb	(revision 14270)
@@ -20,6 +20,31 @@
 
 module OpenSSL
   module SSL
+    class SSLContext
+      class <<self
+        def build(params={})
+          default_params = {
+            :ssl_version => "SSLv23",
+            :verify_mode => OpenSSL::SSL::VERIFY_PEER,
+            :ciphers => "ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW",
+            :options => OpenSSL::SSL::OP_ALL,
+          }
+          params = default_params.merge(params)
+          ctx = new()
+          params.each{|name, value| ctx.__send__("#{name}=", value) }
+          ctx.verify_mode ||= OpenSSL::SSL::VERIFY_NONE
+          if ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
+            unless ctx.ca_file or ctx.ca_path or
+                     ctx.cert_store or ctx.verify_callback
+              ctx.cert_store = OpenSSL::X509::Store.new
+              ctx.cert_store.set_default_paths
+            end
+          end
+          return ctx
+        end
+      end
+    end
+
     module SocketForwarder
       def addr
         to_io.addr
@@ -59,36 +84,43 @@
       end
     end
 
+    def verify_certificate_identity(cert, hostname)
+      should_verify_common_name = true
+      cert.extensions.each{|ext|
+        next if ext.oid != "subjectAltName"
+        ext.value.split(/,\s+/).each{|general_name|
+          if /\ADNS:(.*)/ =~ general_name
+            should_verify_common_name = false
+            reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
+            return true if /\A#{reg}\z/i =~ hostname
+          elsif /\AIP Address:(.*)/ =~ general_name
+            should_verify_common_name = false
+            return true if $1 == hostname
+          end
+        }
+      }
+      if should_verify_common_name
+        cert.subject.to_a.each{|oid, value|
+          if oid == "CN"
+            reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
+            return true if /\A#{reg}\z/i =~ hostname
+          end
+        }
+      end
+      return false
+    end
+    module_function :verify_certificate_identity
+
     class SSLSocket
       include Buffering
       include SocketForwarder
       include Nonblock
 
       def post_connection_check(hostname)
-        check_common_name = true
-        cert = peer_cert
-        cert.extensions.each{|ext|
-          next if ext.oid != "subjectAltName"
-          ext.value.split(/,\s+/).each{|general_name|
-            if /\ADNS:(.*)/ =~ general_name
-              check_common_name = false
-              reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
-              return true if /\A#{reg}\z/i =~ hostname
-            elsif /\AIP Address:(.*)/ =~ general_name
-              check_common_name = false
-              return true if $1 == hostname
-            end
-          }
-        }
-        if check_common_name
-          cert.subject.to_a.each{|oid, value|
-            if oid == "CN"
-              reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
-              return true if /\A#{reg}\z/i =~ hostname
-            end
-          }
+        unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
+          raise SSLError, "hostname was not match with the server certificate"
         end
-        raise SSLError, "hostname was not match with the server certificate"
+        return true
       end
 
       def session
Index: test/openssl/test_ssl.rb
===================================================================
--- test/openssl/test_ssl.rb	(revision 14269)
+++ test/openssl/test_ssl.rb	(revision 14270)
@@ -94,15 +94,15 @@
   end
 
   def test_connect_and_close
-    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|s, p|
-      sock = TCPSocket.new("127.0.0.1", p)
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock)
       assert(ssl.connect)
       ssl.close
       assert(!sock.closed?)
       sock.close
 
-      sock = TCPSocket.new("127.0.0.1", p)
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock)
       ssl.sync_close = true  # !!
       assert(ssl.connect)
@@ -112,8 +112,8 @@
   end
 
   def test_read_and_write
-    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|s, p|
-      sock = TCPSocket.new("127.0.0.1", p)
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock)
       ssl.sync_close = true
       ssl.connect
@@ -162,9 +162,9 @@
 
   def test_client_auth
     vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
-    start_server(PORT, vflag, true){|s, p|
+    start_server(PORT, vflag, true){|server, port|
       assert_raises(OpenSSL::SSL::SSLError){
-        sock = TCPSocket.new("127.0.0.1", p)
+        sock = TCPSocket.new("127.0.0.1", port)
         ssl = OpenSSL::SSL::SSLSocket.new(sock)
         ssl.connect
       }
@@ -172,7 +172,7 @@
       ctx = OpenSSL::SSL::SSLContext.new
       ctx.key = @cli_key
       ctx.cert = @cli_cert
-      sock = TCPSocket.new("127.0.0.1", p)
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
       ssl.sync_close = true
       ssl.connect
@@ -186,7 +186,7 @@
         called = true
         [@cli_cert, @cli_key]
       }
-      sock = TCPSocket.new("127.0.0.1", p)
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
       ssl.sync_close = true
       ssl.connect
@@ -198,8 +198,8 @@
   end
 
   def test_starttls
-    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, false){|s, p|
-      sock = TCPSocket.new("127.0.0.1", p)
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, false){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock)
       ssl.sync_close = true
       str = "x" * 1000 + "\n"
@@ -222,10 +222,10 @@
 
   def test_parallel
     GC.start
-    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|s, p|
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
       ssls = []
       10.times{
-        sock = TCPSocket.new("127.0.0.1", p)
+        sock = TCPSocket.new("127.0.0.1", port)
         ssl = OpenSSL::SSL::SSLSocket.new(sock)
         ssl.connect
         ssl.sync_close = true
@@ -242,17 +242,72 @@
     }
   end
 
+  def test_verify_result
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.build
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
+      assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
+
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.build(
+        :verify_callback => Proc.new do |preverify_ok, store_ctx|
+          store_ctx.error = OpenSSL::X509::V_OK
+          true
+        end
+      )
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      ssl.connect
+      assert_equal(OpenSSL::X509::V_OK, ssl.verify_result)
+
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.build(
+        :verify_callback => Proc.new do |preverify_ok, store_ctx|
+          store_ctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
+          false
+        end
+      )
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
+      assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, ssl.verify_result)
+    }
+  end
+
+  def test_sslctx_build
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.build
+      assert_equal(OpenSSL::SSL::VERIFY_PEER, ctx.verify_mode)
+      assert_equal(OpenSSL::SSL::OP_ALL, ctx.options)
+      ciphers = ctx.ciphers
+      ciphers_versions = ciphers.collect{|_, v, _, _| v }
+      ciphers_names = ciphers.collect{|v, _, _, _| v }
+      assert(ciphers_names.all?{|v| /ADH/ !~ v })
+      assert(ciphers_versions.all?{|v| /SSLv2/ !~ v })
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
+      assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
+    }
+  end
+
   def test_post_connection_check
     sslerr = OpenSSL::SSL::SSLError
 
-    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|s, p|
-      sock = TCPSocket.new("127.0.0.1", p)
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock)
       ssl.connect
       assert_raises(sslerr){ssl.post_connection_check("localhost.localdomain")}
       assert_raises(sslerr){ssl.post_connection_check("127.0.0.1")}
       assert(ssl.post_connection_check("localhost"))
       assert_raises(sslerr){ssl.post_connection_check("foo.example.com")}
+
+      cert = ssl.peer_cert
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+      assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
     }
 
     now = Time.now
@@ -263,14 +318,20 @@
     ]
     @svr_cert = issue_cert(@svr, @svr_key, 4, now, now+1800, exts,
                            @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new)
-    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|s, p|
-      sock = TCPSocket.new("127.0.0.1", p)
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock)
       ssl.connect
       assert(ssl.post_connection_check("localhost.localdomain"))
       assert(ssl.post_connection_check("127.0.0.1"))
       assert_raises(sslerr){ssl.post_connection_check("localhost")}
       assert_raises(sslerr){ssl.post_connection_check("foo.example.com")}
+
+      cert = ssl.peer_cert
+      assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+      assert(OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
     }
 
     now = Time.now
@@ -280,14 +341,19 @@
     ]
     @svr_cert = issue_cert(@svr, @svr_key, 5, now, now+1800, exts,
                            @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new)
-    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|s, p|
-      sock = TCPSocket.new("127.0.0.1", p)
+    start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
       ssl = OpenSSL::SSL::SSLSocket.new(sock)
       ssl.connect
       assert(ssl.post_connection_check("localhost.localdomain"))
       assert_raises(sslerr){ssl.post_connection_check("127.0.0.1")}
       assert_raises(sslerr){ssl.post_connection_check("localhost")}
       assert_raises(sslerr){ssl.post_connection_check("foo.example.com")}
+      cert = ssl.peer_cert
+      assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+      assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
     }
   end
 end

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml

[前][次][番号順一覧][スレッド一覧]