类 OpenSSL::SSL::SSLSocket

属性

context[R]

此连接中使用的 SSLContext 对象。

hostname[R]
io[R]

底层的 IO 对象。

sync_close[RW]

当 SSL/TLS 连接关闭时,是否也关闭底层套接字。默认值为 false

to_io[R]

底层的 IO 对象。

公共类方法

new(io) → aSSLSocket 点击切换源代码
new(io, ctx) → aSSLSocket

io 创建一个新的 SSL 套接字,io 必须是真正的 IO 对象(而不是响应读/写操作的类似 IO 的对象)。

如果提供了 ctx,则 SSL 套接字的初始参数将从上下文获取。

OpenSSL::Buffering 模块提供了额外的 IO 方法。

此方法将冻结 SSLContext(如果提供了)。但是,在冻结的 SSLContext 中仍然允许会话管理。

static VALUE
ossl_ssl_initialize(int argc, VALUE *argv, VALUE self)
{
    VALUE io, v_ctx, verify_cb;
    SSL *ssl;
    SSL_CTX *ctx;

    TypedData_Get_Struct(self, SSL, &ossl_ssl_type, ssl);
    if (ssl)
        ossl_raise(eSSLError, "SSL already initialized");

    if (rb_scan_args(argc, argv, "11", &io, &v_ctx) == 1)
        v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0);

    GetSSLCTX(v_ctx, ctx);
    rb_ivar_set(self, id_i_context, v_ctx);
    ossl_sslctx_setup(v_ctx);

    if (rb_respond_to(io, rb_intern("nonblock=")))
        rb_funcall(io, rb_intern("nonblock="), 1, Qtrue);
    Check_Type(io, T_FILE);
    rb_ivar_set(self, id_i_io, io);

    ssl = SSL_new(ctx);
    if (!ssl)
        ossl_raise(eSSLError, NULL);
    RTYPEDDATA_DATA(self) = ssl;

    SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self);
    SSL_set_info_callback(ssl, ssl_info_cb);
    verify_cb = rb_attr_get(v_ctx, id_i_verify_callback);
    // We don't need to trigger a write barrier because it's already
    // an instance variable of this object.
    SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)verify_cb);

    rb_call_super(0, NULL);

    return self;
}

私有类方法

open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil) 点击切换源代码

创建一个新的 SSLSocket 实例。remotehost_ 和 remoteport_ 用于打开 TCPSocket。如果指定了 localhost_ 和 localport_,则在本地端使用这些参数建立连接。如果提供了 context,则 SSL Sockets 的初始参数将从上下文中获取。

示例

sock = OpenSSL::SSL::SSLSocket.open('localhost', 443)
sock.connect # Initiates a connection to localhost:443

使用 SSLContext

ctx = OpenSSL::SSL::SSLContext.new
sock = OpenSSL::SSL::SSLSocket.open('localhost', 443, context: ctx)
sock.connect # Initiates a connection to localhost:443 with SSLContext
# File openssl/lib/openssl/ssl.rb, line 470
def open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil)
  sock = ::TCPSocket.open(remote_host, remote_port, local_host, local_port)
  if context.nil?
    return OpenSSL::SSL::SSLSocket.new(sock)
  else
    return OpenSSL::SSL::SSLSocket.new(sock, context)
  end
end

公共实例方法

accept → self 点击切换源代码

等待 SSL/TLS 客户端发起握手。

static VALUE
ossl_ssl_accept(VALUE self)
{
    ossl_ssl_setup(self);

    return ossl_start_ssl(self, SSL_accept, "SSL_accept", Qfalse);
}
accept_nonblock([options]) → self 点击切换源代码

以非阻塞方式作为服务器发起 SSL/TLS 握手。

# emulates blocking accept
begin
  ssl.accept_nonblock
rescue IO::WaitReadable
  IO.select([s2])
  retry
rescue IO::WaitWritable
  IO.select(nil, [s2])
  retry
end

通过将关键字参数 exception 指定为 false,您可以指示 accept_nonblock 不应引发 IO::WaitReadableIO::WaitWritable 异常,而是返回符号 :wait_readable:wait_writable

static VALUE
ossl_ssl_accept_nonblock(int argc, VALUE *argv, VALUE self)
{
    VALUE opts;

    rb_scan_args(argc, argv, "0:", &opts);
    ossl_ssl_setup(self);

    return ossl_start_ssl(self, SSL_accept, "SSL_accept", opts);
}
alpn_protocol → String | nil 点击切换源代码

返回在握手过程中服务器最终选择的 ALPN 协议字符串。

static VALUE
ossl_ssl_alpn_protocol(VALUE self)
{
    SSL *ssl;
    const unsigned char *out;
    unsigned int outlen;

    GetSSL(self, ssl);

    SSL_get0_alpn_selected(ssl, &out, &outlen);
    if (!outlen)
        return Qnil;
    else
        return rb_str_new((const char *) out, outlen);
}
cert → cert or nil 点击切换源代码

此套接字端点的 X509 证书。

static VALUE
ossl_ssl_get_cert(VALUE self)
{
    SSL *ssl;
    X509 *cert = NULL;

    GetSSL(self, ssl);

    /*
     * Is this OpenSSL bug? Should add a ref?
     * TODO: Ask for.
     */
    cert = SSL_get_certificate(ssl); /* NO DUPs => DON'T FREE. */

    if (!cert) {
        return Qnil;
    }
    return ossl_x509_new(cert);
}
cipher → nil or [name, version, bits, alg_bits] 点击切换源代码

返回当前会话中实际使用的密码套件,如果尚未建立会话,则返回 nil。

static VALUE
ossl_ssl_get_cipher(VALUE self)
{
    SSL *ssl;
    const SSL_CIPHER *cipher;

    GetSSL(self, ssl);
    cipher = SSL_get_current_cipher(ssl);
    return cipher ? ossl_ssl_cipher_to_ary(cipher) : Qnil;
}
client_ca → [x509name, ...] 点击切换源代码

返回客户端 CA 列表。请注意,与 SSLContext#client_ca= 相比,它不返回 X509::Certificate 数组,而是返回 CA 主体识别名称的 X509::Name 实例。

在服务器模式下,返回由 SSLContext#client_ca= 设置的列表。在客户端模式下,返回服务器发送的客户端 CA 列表。

static VALUE
ossl_ssl_get_client_ca_list(VALUE self)
{
    SSL *ssl;
    STACK_OF(X509_NAME) *ca;

    GetSSL(self, ssl);

    ca = SSL_get_client_CA_list(ssl);
    return ossl_x509name_sk2ary(ca);
}
connect → self 点击切换源代码

与服务器发起 SSL/TLS 握手。

static VALUE
ossl_ssl_connect(VALUE self)
{
    ossl_ssl_setup(self);

    return ossl_start_ssl(self, SSL_connect, "SSL_connect", Qfalse);
}
connect_nonblock([options]) → self 点击切换源代码

以非阻塞方式启动 SSL/TLS 握手作为客户端。

# emulates blocking connect
begin
  ssl.connect_nonblock
rescue IO::WaitReadable
  IO.select([s2])
  retry
rescue IO::WaitWritable
  IO.select(nil, [s2])
  retry
end

通过将关键字参数 exception 指定为 false,您可以指示 connect_nonblock 不应引发 IO::WaitReadableIO::WaitWritable 异常,而是返回符号 :wait_readable:wait_writable

static VALUE
ossl_ssl_connect_nonblock(int argc, VALUE *argv, VALUE self)
{
    VALUE opts;
    rb_scan_args(argc, argv, "0:", &opts);

    ossl_ssl_setup(self);

    return ossl_start_ssl(self, SSL_connect, "SSL_connect", opts);
}
export_keying_material(label, length) → String 点击切换源代码

根据 RFC 5705 启用共享会话密钥材料的使用。

static VALUE
ossl_ssl_export_keying_material(int argc, VALUE *argv, VALUE self)
{
    SSL *ssl;
    VALUE str;
    VALUE label;
    VALUE length;
    VALUE context;
    unsigned char *p;
    size_t len;
    int use_ctx = 0;
    unsigned char *ctx = NULL;
    size_t ctx_len = 0;
    int ret;

    rb_scan_args(argc, argv, "21", &label, &length, &context);
    StringValue(label);

    GetSSL(self, ssl);

    len = (size_t)NUM2LONG(length);
    str = rb_str_new(0, len);
    p = (unsigned char *)RSTRING_PTR(str);
    if (!NIL_P(context)) {
        use_ctx = 1;
        StringValue(context);
        ctx = (unsigned char *)RSTRING_PTR(context);
        ctx_len = RSTRING_LEN(context);
    }
    ret = SSL_export_keying_material(ssl, p, len, (char *)RSTRING_PTR(label),
                                     RSTRING_LENINT(label), ctx, ctx_len, use_ctx);
    if (ret == 0 || ret == -1) {
        ossl_raise(eSSLError, "SSL_export_keying_material");
    }
    return str;
}
finished_message → "finished message" 点击切换源代码

返回最后发送的 Finished 消息。

static VALUE
ossl_ssl_get_finished(VALUE self)
{
    SSL *ssl;
    char sizer[1], *buf;
    size_t len;

    GetSSL(self, ssl);

    len = SSL_get_finished(ssl, sizer, 0);
    if (len == 0)
        return Qnil;

    buf = ALLOCA_N(char, len);
    SSL_get_finished(ssl, buf, len);
    return rb_str_new(buf, len);
}
hostname = hostname → hostname 点击切换源代码

设置用于 SNI 的服务器主机名。这需要在调用 SSLSocket#connect 之前设置。

static VALUE
ossl_ssl_set_hostname(VALUE self, VALUE arg)
{
    SSL *ssl;
    char *hostname = NULL;

    GetSSL(self, ssl);

    if (!NIL_P(arg))
        hostname = StringValueCStr(arg);

    if (!SSL_set_tlsext_host_name(ssl, hostname))
        ossl_raise(eSSLError, NULL);

    /* for SSLSocket#hostname */
    rb_ivar_set(self, id_i_hostname, arg);

    return arg;
}
npn_protocol → String | nil 点击切换源代码

返回客户端在握手过程中最终选择的协议字符串。

static VALUE
ossl_ssl_npn_protocol(VALUE self)
{
    SSL *ssl;
    const unsigned char *out;
    unsigned int outlen;

    GetSSL(self, ssl);

    SSL_get0_next_proto_negotiated(ssl, &out, &outlen);
    if (!outlen)
        return Qnil;
    else
        return rb_str_new((const char *) out, outlen);
}
peer_cert → cert or nil 点击切换源代码

此套接字对端的 X509 证书。

static VALUE
ossl_ssl_get_peer_cert(VALUE self)
{
    SSL *ssl;
    X509 *cert = NULL;
    VALUE obj;

    GetSSL(self, ssl);

    cert = SSL_get_peer_certificate(ssl); /* Adds a ref => Safe to FREE. */

    if (!cert) {
        return Qnil;
    }
    obj = ossl_x509_new(cert);
    X509_free(cert);

    return obj;
}
peer_cert_chain → [cert, ...] or nil 点击切换源代码

此套接字对端的 X509 证书链。

static VALUE
ossl_ssl_get_peer_cert_chain(VALUE self)
{
    SSL *ssl;
    STACK_OF(X509) *chain;
    X509 *cert;
    VALUE ary;
    int i, num;

    GetSSL(self, ssl);

    chain = SSL_get_peer_cert_chain(ssl);
    if(!chain) return Qnil;
    num = sk_X509_num(chain);
    ary = rb_ary_new2(num);
    for (i = 0; i < num; i++){
        cert = sk_X509_value(chain, i);
        rb_ary_push(ary, ossl_x509_new(cert));
    }

    return ary;
}
peer_finished_message → "peer finished message" 点击切换源代码

返回最后接收到的 Finished 消息。

static VALUE
ossl_ssl_get_peer_finished(VALUE self)
{
    SSL *ssl;
    char sizer[1], *buf;
    size_t len;

    GetSSL(self, ssl);

    len = SSL_get_peer_finished(ssl, sizer, 0);
    if (len == 0)
        return Qnil;

    buf = ALLOCA_N(char, len);
    SSL_get_peer_finished(ssl, buf, len);
    return rb_str_new(buf, len);
}
pending → Integer 点击切换源代码

立即可用于读取的字节数。

static VALUE
ossl_ssl_pending(VALUE self)
{
    SSL *ssl;

    GetSSL(self, ssl);

    return INT2NUM(SSL_pending(ssl));
}
post_connection_check(hostname) → true 点击切换源代码

根据 RFC 6125 执行主机名验证。

必须在调用 connect 后调用此方法,以确保已验证远程对端的主机名。

# File openssl/lib/openssl/ssl.rb, line 397
def post_connection_check(hostname)
  if peer_cert.nil?
    msg = "Peer verification enabled, but no certificate received."
    if using_anon_cipher?
      msg += " Anonymous cipher suite #{cipher[0]} was negotiated. " \
             "Anonymous suites must be disabled to use peer verification."
    end
    raise SSLError, msg
  end

  unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
    raise SSLError, "hostname \"#{hostname}\" does not match the server certificate"
  end
  return true
end
session → aSession 点击切换源代码

返回当前使用的 SSLSession 对象,如果会话未建立则返回 nil。

# File openssl/lib/openssl/ssl.rb, line 418
def session
  SSL::Session.new(self)
rescue SSL::Session::SessionError
  nil
end
session = session → session 点击切换源代码

设置连接建立时使用的 Session

static VALUE
ossl_ssl_set_session(VALUE self, VALUE arg1)
{
    SSL *ssl;
    SSL_SESSION *sess;

    GetSSL(self, ssl);
    GetSSLSession(arg1, sess);

    if (SSL_set_session(ssl, sess) != 1)
        ossl_raise(eSSLError, "SSL_set_session");

    return arg1;
}
session_reused? → true | false 点击切换源代码

如果在握手过程中协商了重用会话,则返回 true

static VALUE
ossl_ssl_session_reused(VALUE self)
{
    SSL *ssl;

    GetSSL(self, ssl);

    return SSL_session_reused(ssl) ? Qtrue : Qfalse;
}
ssl_version → String 点击切换源代码

返回一个字符串,表示为连接协商的 SSL/TLS 版本,例如“TLSv1.2”。

static VALUE
ossl_ssl_get_version(VALUE self)
{
    SSL *ssl;

    GetSSL(self, ssl);

    return rb_str_new2(SSL_get_version(ssl));
}
state → string 点击切换源代码

当前连接状态的描述。仅用于诊断目的。

static VALUE
ossl_ssl_get_state(VALUE self)
{
    SSL *ssl;
    VALUE ret;

    GetSSL(self, ssl);

    ret = rb_str_new2(SSL_state_string(ssl));
    if (ruby_verbose) {
        rb_str_cat2(ret, ": ");
        rb_str_cat2(ret, SSL_state_string_long(ssl));
    }
    return ret;
}
sysclose → nil 点击切换源代码

向对等方发送“关闭通知”,并尝试优雅地关闭 SSL 连接。

如果 sync_close 设置为 true,则也会关闭底层的 IO

# File openssl/lib/openssl/ssl.rb, line 384
def sysclose
  return if closed?
  stop
  io.close if sync_close
end
sysread(length) → string 点击切换源代码
sysread(length, buffer) → buffer

SSL 连接读取 length 字节。如果提供了预先分配的 buffer,则数据将写入其中。

static VALUE
ossl_ssl_read(int argc, VALUE *argv, VALUE self)
{
    return ossl_ssl_read_internal(argc, argv, self, 0);
}
syswrite(string) → Integer 点击切换源代码

string 写入 SSL 连接。

static VALUE
ossl_ssl_write(VALUE self, VALUE str)
{
    return ossl_ssl_write_internal(self, str, Qfalse);
}
tmp_key → PKey or nil 点击切换源代码

在正向保密密码的情况下返回使用的临时密钥。

static VALUE
ossl_ssl_tmp_key(VALUE self)
{
    SSL *ssl;
    EVP_PKEY *key;

    GetSSL(self, ssl);
    if (!SSL_get_server_tmp_key(ssl, &key))
        return Qnil;
    return ossl_pkey_new(key);
}
verify_result → Integer 点击切换源代码

返回对等方证书验证的结果。有关错误值和描述,请参阅 verify(1)。

如果未提供对等方证书,则返回 X509_V_OK。

static VALUE
ossl_ssl_get_verify_result(VALUE self)
{
    SSL *ssl;

    GetSSL(self, ssl);

    return LONG2NUM(SSL_get_verify_result(ssl));
}

私有实例方法

client_cert_cb() 点击切换源代码
# File openssl/lib/openssl/ssl.rb, line 432
def client_cert_cb
  @context.client_cert_cb
end
session_get_cb() 点击切换源代码
# File openssl/lib/openssl/ssl.rb, line 444
def session_get_cb
  @context.session_get_cb
end
session_new_cb() 点击切换源代码
# File openssl/lib/openssl/ssl.rb, line 440
def session_new_cb
  @context.session_new_cb
end
stop → nil 点击切换源代码

向对等方发送“关闭通知”,并尝试优雅地关闭 SSL 连接。

static VALUE
ossl_ssl_stop(VALUE self)
{
    SSL *ssl;
    int ret;

    GetSSL(self, ssl);
    if (!ssl_started(ssl))
        return Qnil;
    ret = SSL_shutdown(ssl);
    if (ret == 1) /* Have already received close_notify */
        return Qnil;
    if (ret == 0) /* Sent close_notify, but we don't wait for reply */
        return Qnil;

    /*
     * XXX: Something happened. Possibly it failed because the underlying socket
     * is not writable/readable, since it is in non-blocking mode. We should do
     * some proper error handling using SSL_get_error() and maybe retry, but we
     * can't block here. Give up for now.
     */
    ossl_clear_error();
    return Qnil;
}
sysread_nonblock(length) → string 点击切换源代码
sysread_nonblock(length, buffer) → buffer
sysread_nonblock(length[, buffer [, opts]) → buffer

非阻塞版本的 sysread。如果读取会阻塞,则会引发 SSLError。如果传递了“exception: false”,则此方法返回一个符号::wait_readable、:wait_writable 或 nil,而不是引发异常。

SSL 连接读取 length 字节。如果提供了预先分配的 buffer,则数据将写入其中。

static VALUE
ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self)
{
    return ossl_ssl_read_internal(argc, argv, self, 1);
}
syswrite_nonblock(string) → Integer 点击切换源代码

以非阻塞方式将string写入 SSL 连接。如果写入会阻塞,则会引发 SSLError

static VALUE
ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self)
{
    VALUE str, opts;

    rb_scan_args(argc, argv, "1:", &str, &opts);

    return ossl_ssl_write_internal(self, str, opts);
}
tmp_dh_callback() 点击切换源代码
# File openssl/lib/openssl/ssl.rb, line 436
def tmp_dh_callback
  @context.tmp_dh_callback || OpenSSL::SSL::SSLContext::DEFAULT_TMP_DH_CALLBACK
end
using_anon_cipher?() 点击切换源代码
# File openssl/lib/openssl/ssl.rb, line 426
def using_anon_cipher?
  ctx = OpenSSL::SSL::SSLContext.new
  ctx.ciphers = "aNULL"
  ctx.ciphers.include?(cipher)
end