类 OpenSSL::SSL::SSLContext

一个 SSLContext 用于设置有关证书、算法、验证、会话缓存等的各种选项。 SSLContext 用于创建 SSLSocket.

所有属性必须在创建 SSLSocket 之前设置,因为 SSLContext 之后将被冻结。

常量

DH_ffdhe2048
方法

可用的 SSL/TLS 方法列表。此常量仅为向后兼容性提供。

METHODS_MAP
SESSION_CACHE_BOTH

客户端和服务器会话都将添加到会话缓存中

SESSION_CACHE_CLIENT

客户端会话将添加到会话缓存中

SESSION_CACHE_NO_AUTO_CLEAR

通常,会话缓存会每 255 个连接检查一次过期会话。由于这可能会导致无法控制的延迟,因此可以禁用自动刷新,并可以显式调用 flush_sessions

SESSION_CACHE_NO_INTERNAL

同时启用 SESSION_CACHE_NO_INTERNAL_LOOKUPSESSION_CACHE_NO_INTERNAL_STORE.

SESSION_CACHE_NO_INTERNAL_LOOKUP

即使会话在内部缓存中,也始终执行会话的外部查找。

此标志对客户端没有影响

SESSION_CACHE_NO_INTERNAL_STORE

从不自动将会话存储在内部存储中。

SESSION_CACHE_OFF

客户端或服务器没有会话缓存

SESSION_CACHE_SERVER

服务器会话将添加到会话缓存中

属性

alpn_protocols[RW]

一个字符串可枚举对象。每个字符串代表一个协议,作为应用程序层协议协商(ALPN)支持协议列表进行广告。支持在 OpenSSL 1.0.2 及更高版本中。对服务器端没有影响。如果未显式设置,则 ALPN 扩展不会包含在握手过程中。

示例

ctx.alpn_protocols = ["http/1.1", "spdy/2", "h2"]
alpn_select_cb[RW]

当服务器需要从客户端发送的列表中选择协议时,在服务器端调用的回调函数。支持在 OpenSSL 1.0.2 及更高版本中。回调函数必须返回客户端广告的协议之一。如果没有任何可接受的协议,则在回调函数中引发错误会导致握手失败。未显式设置此回调函数意味着不支持服务器上的 ALPN 扩展 - 客户端广告的任何协议都将被忽略。

示例

ctx.alpn_select_cb = lambda do |protocols|
  # inspect the protocols and select one
  protocols.first
end
ca_file[RW]

包含 PEM 格式 CA 证书的文件路径。

ca_path[RW]

包含 PEM 格式 CA 证书的目录路径。

通过主题的 X509 名称的哈希值查找文件。

cert[RW]

上下文证书

certkeyextra_chain_cert 属性已弃用。建议使用 add_certificate 代替。

cert_store[RW]

用于证书验证的 OpenSSL::X509::Store

client_ca[RW]

将发送给客户端的证书或证书数组。

client_cert_cb[RW]

当服务器请求客户端证书且未设置证书时调用的回调函数。

回调函数使用 Session 调用,并且必须返回一个包含 OpenSSL::X509::CertificateOpenSSL::PKey 的数组。如果返回任何其他值,则握手将暂停。

extra_chain_cert[RW]

一个额外的 X509 证书数组,将被添加到证书链中。

certkeyextra_chain_cert 属性已弃用。建议使用 add_certificate 代替。

key[RW]

上下文私钥

certkeyextra_chain_cert 属性已弃用。建议使用 add_certificate 代替。

keylog_cb[RW]

当 TLS 密钥材料生成或接收时调用的回调,以便应用程序可以将此密钥材料存储以供调试目的。

该回调将使用一个 SSLSocket 和一个字符串调用,该字符串包含 NSS 用于其 SSLKEYLOGFILE 调试输出的格式的密钥材料。

它仅与 OpenSSL >= 1.1.1 兼容。即使 LibreSSL 从 v3.4.2 开始实现 SSL_CTX_set_keylog_callback(),它也不会执行任何操作(参见 github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6)。

示例

context.keylog_cb = proc do |_sock, line|
  File.open('ssl_keylog_file', "a") do |f|
    f.write("#{line}\n")
  end
end
npn_protocols[RW]

一个字符串的可枚举集合。每个字符串代表一个协议,将被广告为支持的协议列表,用于下一协议协商。在 OpenSSL 1.0.1 及更高版本中受支持。对客户端没有影响。如果未明确设置,服务器在握手时不会发送 NPN 扩展。

示例

ctx.npn_protocols = ["http/1.1", "spdy/2"]
npn_select_cb[RW]

当客户端需要从服务器发送的列表中选择协议时,在客户端调用的回调。在 OpenSSL 1.0.1 及更高版本中受支持。客户端必须从服务器广告的协议中选择一个协议。如果没有任何协议可以接受,在回调中引发错误将导致握手失败。未明确设置此回调意味着不支持客户端上的 NPN 扩展 - 服务器广告的任何协议都将被忽略。

示例

ctx.npn_select_cb = lambda do |protocols|
  # inspect the protocols and select one
  protocols.first
end
renegotiation_cb[RW]

每当在已建立的连接上启动新的握手时调用的回调。可用于完全禁用重新协商。

该回调将使用活动的 SSLSocket 调用。回调的返回值将被忽略。正常返回表示“批准”重新协商,并将继续该过程。要禁止重新协商并取消该过程,请在回调中引发异常。

禁用客户端重新协商

在运行服务器时,通常希望完全禁用客户端重新协商。您可以使用以下回调来实现此功能

ctx.renegotiation_cb = lambda do |ssl|
  raise RuntimeError, "Client renegotiation disabled"
end
servername_cb[RW]

在连接时调用的回调,用于区分多个服务器名称。

回调使用一个 SSLSocket 和一个服务器名称进行调用。回调必须返回一个 SSLContext 用于服务器名称或 nil。

session_get_cb[RW]

在服务器上调用一个回调,当客户端提出一个会话但服务器的内部缓存中找不到该会话时。

回调使用 SSLSocket 和会话 ID 进行调用。回调可以从外部缓存中返回一个 Session

session_id_context[RW]

设置可以重用会话的上下文。这允许区分多个应用程序的会话,例如,通过名称。

session_new_cb[RW]

在协商新会话时调用的回调。

回调使用一个 SSLSocket 进行调用。如果返回 false,则会话将从内部缓存中删除。

session_remove_cb[RW]

在会话从内部缓存中删除时调用的回调。

回调使用一个 SSLContext 和一个 Session 进行调用。

重要说明:目前无法在多线程应用程序中安全地使用它。回调是在全局锁内调用的,它可能会随机导致 Ruby 线程切换时死锁。

ssl_timeout[RW]

最大会话生存时间(以秒为单位)。

timeout[RW]

最大会话生存时间(以秒为单位)。

tmp_dh_callback[RW]

在需要 DH 参数进行短暂 DH 密钥交换时调用的回调。

回调使用 SSLSocket、一个指示使用导出密码的标志和所需的密钥长度进行调用。

回调必须返回一个 OpenSSL::PKey::DH 实例,其密钥长度正确。

在 3.0 版本中已弃用。 请使用 tmp_dh= 代替。

verify_callback[RW]

用于额外证书验证的回调函数。该回调函数会针对链中的每个证书调用。

回调函数会传入两个值。preverify_ok 指示验证是否通过(true)或未通过(false)。store_context 是一个 OpenSSL::X509::StoreContext,包含用于证书验证的上下文。

如果回调函数返回 false,则链验证会立即停止,并发送 bad_certificate 警告。

verify_depth[RW]

验证证书链时要遍历的 CA 证书数量。

verify_hostname[RW]

是否检查服务器证书是否对主机名有效。

为了使此功能生效,verify_mode 必须设置为 VERIFY_PEER,并且服务器主机名必须通过 OpenSSL::SSL::SSLSocket#hostname= 提供。

verify_mode[RW]

Session 验证模式。

有效模式为 VERIFY_NONE、VERIFY_PEER、VERIFY_CLIENT_ONCE、VERIFY_FAIL_IF_NO_PEER_CERT,这些模式在 OpenSSL::SSL 中定义。

默认模式为 VERIFY_NONE,它不会执行任何验证。

有关详细信息,请参阅 SSL_CTX_set_verify(3)。

公共类方法

new → ctx 点击以切换源代码
new(:TLSv1) → ctx
new("SSLv23") → ctx

创建一个新的 SSL 上下文。

如果提供了参数,则 ssl_version= 会使用该值进行调用。请注意,此形式已弃用。新的应用程序应根据需要使用 min_version=max_version=

# File openssl/lib/openssl/ssl.rb, line 127
def initialize(version = nil)
  self.options |= OpenSSL::SSL::OP_ALL
  self.ssl_version = version if version
  self.verify_mode = OpenSSL::SSL::VERIFY_NONE
  self.verify_hostname = false
end

公共实例方法

add_certificate(certificate, pkey [, extra_certs]) → self 点击以切换源代码

向上下文添加证书。pkey 必须是与 certificate 相对应的私钥。

可以通过重复调用此方法添加具有不同公钥类型的多个证书,OpenSSL 会在握手过程中选择最合适的证书。

cert=key=extra_chain_cert= 是用于设置证书的旧访问器方法,它们会在内部调用此方法。

参数

certificate

证书。一个 OpenSSL::X509::Certificate 实例。

pkey

证书的私钥。一个 OpenSSL::PKey::PKey 实例。

extra_certs

可选。一个 OpenSSL::X509::Certificate 数组。当发送证书链时,由该参数指定的证书将按照数组中的顺序,在证书之后发送。

示例

rsa_cert = OpenSSL::X509::Certificate.new(...)
rsa_pkey = OpenSSL::PKey.read(...)
ca_intermediate_cert = OpenSSL::X509::Certificate.new(...)
ctx.add_certificate(rsa_cert, rsa_pkey, [ca_intermediate_cert])

ecdsa_cert = ...
ecdsa_pkey = ...
another_ca_cert = ...
ctx.add_certificate(ecdsa_cert, ecdsa_pkey, [another_ca_cert])
static VALUE
ossl_sslctx_add_certificate(int argc, VALUE *argv, VALUE self)
{
    VALUE cert, key, extra_chain_ary;
    SSL_CTX *ctx;
    X509 *x509;
    STACK_OF(X509) *extra_chain = NULL;
    EVP_PKEY *pkey, *pub_pkey;

    GetSSLCTX(self, ctx);
    rb_scan_args(argc, argv, "21", &cert, &key, &extra_chain_ary);
    rb_check_frozen(self);
    x509 = GetX509CertPtr(cert);
    pkey = GetPrivPKeyPtr(key);

    /*
     * The reference counter is bumped, and decremented immediately.
     * X509_get0_pubkey() is only available in OpenSSL >= 1.1.0.
     */
    pub_pkey = X509_get_pubkey(x509);
    EVP_PKEY_free(pub_pkey);
    if (!pub_pkey)
        rb_raise(rb_eArgError, "certificate does not contain public key");
    if (EVP_PKEY_eq(pub_pkey, pkey) != 1)
        rb_raise(rb_eArgError, "public key mismatch");

    if (argc >= 3)
        extra_chain = ossl_x509_ary2sk(extra_chain_ary);

    if (!SSL_CTX_use_certificate(ctx, x509)) {
        sk_X509_pop_free(extra_chain, X509_free);
        ossl_raise(eSSLError, "SSL_CTX_use_certificate");
    }
    if (!SSL_CTX_use_PrivateKey(ctx, pkey)) {
        sk_X509_pop_free(extra_chain, X509_free);
        ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey");
    }
    if (extra_chain && !SSL_CTX_set0_chain(ctx, extra_chain)) {
        sk_X509_pop_free(extra_chain, X509_free);
        ossl_raise(eSSLError, "SSL_CTX_set0_chain");
    }
    return self;
}
ciphers → [[name, version, bits, alg_bits], ...] click to toggle source

此上下文配置的密码套件列表。

static VALUE
ossl_sslctx_get_ciphers(VALUE self)
{
    SSL_CTX *ctx;
    STACK_OF(SSL_CIPHER) *ciphers;
    const SSL_CIPHER *cipher;
    VALUE ary;
    int i, num;

    GetSSLCTX(self, ctx);
    ciphers = SSL_CTX_get_ciphers(ctx);
    if (!ciphers)
        return rb_ary_new();

    num = sk_SSL_CIPHER_num(ciphers);
    ary = rb_ary_new2(num);
    for(i = 0; i < num; i++){
        cipher = sk_SSL_CIPHER_value(ciphers, i);
        rb_ary_push(ary, ossl_ssl_cipher_to_ary(cipher));
    }
    return ary;
}
ciphers = "cipher1:cipher2:..." click to toggle source
ciphers = [name, ...]
ciphers = [[name, version, bits, alg_bits], ...]

设置此上下文可用的密码套件列表。请注意,在服务器上下文中,某些密码套件需要相应的证书。例如,只有在 RSA 证书可用时才能选择 RSA 密码套件。

static VALUE
ossl_sslctx_set_ciphers(VALUE self, VALUE v)
{
    SSL_CTX *ctx;
    VALUE str;

    rb_check_frozen(self);
    if (NIL_P(v))
        return v;

    str = build_cipher_string(v);

    GetSSLCTX(self, ctx);
    if (!SSL_CTX_set_cipher_list(ctx, StringValueCStr(str)))
        ossl_raise(eSSLError, "SSL_CTX_set_cipher_list");

    return v;
}
ciphersuites = "cipher1:cipher2:..." click to toggle source
ciphersuites = [name, ...]
ciphersuites = [[name, version, bits, alg_bits], ...]

设置此上下文可用的 TLSv1.3 密码套件列表。

static VALUE
ossl_sslctx_set_ciphersuites(VALUE self, VALUE v)
{
    SSL_CTX *ctx;
    VALUE str;

    rb_check_frozen(self);
    if (NIL_P(v))
        return v;

    str = build_cipher_string(v);

    GetSSLCTX(self, ctx);
    if (!SSL_CTX_set_ciphersuites(ctx, StringValueCStr(str)))
        ossl_raise(eSSLError, "SSL_CTX_set_ciphersuites");

    return v;
}
ecdh_curves = curve_list → curve_list click to toggle source

设置此上下文的“支持的椭圆曲线”列表。

对于 TLS 客户端,该列表直接用于支持的椭圆曲线扩展。对于服务器,该列表由 OpenSSL 用于确定共享曲线的集合。 OpenSSL 将从中选择最合适的曲线。

示例

ctx1 = OpenSSL::SSL::SSLContext.new
ctx1.ecdh_curves = "X25519:P-256:P-224"
svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx1)
Thread.new { svr.accept }

ctx2 = OpenSSL::SSL::SSLContext.new
ctx2.ecdh_curves = "P-256"
cli = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx2)
cli.connect

p cli.tmp_key.group.curve_name
# => "prime256v1" (is an alias for NIST P-256)
static VALUE
ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg)
{
    SSL_CTX *ctx;

    rb_check_frozen(self);
    GetSSLCTX(self, ctx);
    StringValueCStr(arg);

    if (!SSL_CTX_set1_curves_list(ctx, RSTRING_PTR(arg)))
        ossl_raise(eSSLError, NULL);
    return arg;
}
enable_fallback_scsv() → nil click to toggle source

为此上下文激活 TLS_FALLBACK_SCSV。参见 RFC 7507。

static VALUE
ossl_sslctx_enable_fallback_scsv(VALUE self)
{
    SSL_CTX *ctx;

    GetSSLCTX(self, ctx);
    SSL_CTX_set_mode(ctx, SSL_MODE_SEND_FALLBACK_SCSV);

    return Qnil;
}
flush_sessions(time) → self click to toggle source

删除内部缓存中在时间之前过期的会话。

static VALUE
ossl_sslctx_flush_sessions(int argc, VALUE *argv, VALUE self)
{
    VALUE arg1;
    SSL_CTX *ctx;
    time_t tm = 0;

    rb_scan_args(argc, argv, "01", &arg1);

    GetSSLCTX(self, ctx);

    if (NIL_P(arg1)) {
        tm = time(0);
    } else if (rb_obj_is_instance_of(arg1, rb_cTime)) {
        tm = NUM2LONG(rb_funcall(arg1, rb_intern("to_i"), 0));
    } else {
        ossl_raise(rb_eArgError, "arg must be Time or nil");
    }

    SSL_CTX_flush_sessions(ctx, (long)tm);

    return self;
}
freeze()

此方法在创建新的 SSLSocket 时会自动调用。但是,它不是线程安全的,必须在多线程程序中创建 SSLSocket 对象之前调用。

别名:setup
max_version = OpenSSL::SSL::TLS1_2_VERSION click to toggle source
max_version = :TLS1_2
max_version = nil

设置支持的 SSL/TLS 协议版本的上限。有关可能的值,请参见 min_version=

# File openssl/lib/openssl/ssl.rb, line 190
def max_version=(version)
  set_minmax_proto_version(@min_proto_version ||= nil, version)
  @max_proto_version = version
end
min_version = OpenSSL::SSL::TLS1_2_VERSION 点击切换源代码
min_version = :TLS1_2
min_version = nil

设置支持的 SSL/TLS 协议版本的下限。版本可以通过名为 OpenSSL::SSL::*_VERSION 的整数常量、符号或 nil 指定,nil 表示“任何版本”。

请注意,在调用 min_version=max_version= 之后,不要通过 options= 覆盖 OpenSSL::SSL::OP_NO_{SSL,TLS}v* 选项。

示例

ctx = OpenSSL::SSL::SSLContext.new
ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION
ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION

sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)
sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2
# File openssl/lib/openssl/ssl.rb, line 178
def min_version=(version)
  set_minmax_proto_version(version, @max_proto_version ||= nil)
  @min_proto_version = version
end
options() 点击切换源代码

获取各种 OpenSSL 选项。

static VALUE
ossl_sslctx_get_options(VALUE self)
{
    SSL_CTX *ctx;
    GetSSLCTX(self, ctx);
    /*
     * Do explicit cast because SSL_CTX_get_options() returned (signed) long in
     * OpenSSL before 1.1.0.
     */
    return ULONG2NUM((unsigned long)SSL_CTX_get_options(ctx));
}
options=(p1) 点击切换源代码

设置各种 OpenSSL 选项。

static VALUE
ossl_sslctx_set_options(VALUE self, VALUE options)
{
    SSL_CTX *ctx;

    rb_check_frozen(self);
    GetSSLCTX(self, ctx);

    SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx));

    if (NIL_P(options)) {
        SSL_CTX_set_options(ctx, SSL_OP_ALL);
    } else {
        SSL_CTX_set_options(ctx, NUM2ULONG(options));
    }

    return self;
}
security_level → Integer 点击切换源代码

返回上下文的安全级别。

另请参见 OpenSSL::SSL::SSLContext#security_level=

static VALUE
ossl_sslctx_get_security_level(VALUE self)
{
    SSL_CTX *ctx;

    GetSSLCTX(self, ctx);

#if defined(HAVE_SSL_CTX_GET_SECURITY_LEVEL)
    return INT2NUM(SSL_CTX_get_security_level(ctx));
#else
    (void)ctx;
    return INT2FIX(0);
#endif
}
security_level = integer 点击切换源代码

设置上下文的安全级别。 OpenSSL 根据级别限制参数。“参数”包括:密码套件、曲线、密钥大小、证书签名算法、协议版本等等。例如,级别 1 拒绝提供低于 80 位安全性的参数,例如使用 MD5 进行 MAC 或 RSA 密钥短于 1024 位的密码套件。

请注意,尝试设置安全级别不足的此类参数也会被阻止。您需要先降低级别。

此功能在 OpenSSL < 1.1.0 中不受支持,将级别设置为 0 以外的值将引发 NotImplementedError。级别 0 表示允许所有操作,与 OpenSSL 的先前版本的行为相同。

有关详细信息,请参见 SSL_CTX_set_security_level(3) 的手册页。

static VALUE
ossl_sslctx_set_security_level(VALUE self, VALUE value)
{
    SSL_CTX *ctx;

    rb_check_frozen(self);
    GetSSLCTX(self, ctx);

#if defined(HAVE_SSL_CTX_GET_SECURITY_LEVEL)
    SSL_CTX_set_security_level(ctx, NUM2INT(value));
#else
    (void)ctx;
    if (NUM2INT(value) != 0)
        ossl_raise(rb_eNotImpError, "setting security level to other than 0 is "
                   "not supported in this version of OpenSSL");
#endif

    return value;
}
session_add(session) → true | false 点击切换源代码

session 添加到会话缓存。

static VALUE
ossl_sslctx_session_add(VALUE self, VALUE arg)
{
    SSL_CTX *ctx;
    SSL_SESSION *sess;

    GetSSLCTX(self, ctx);
    GetSSLSession(arg, sess);

    return SSL_CTX_add_session(ctx, sess) == 1 ? Qtrue : Qfalse;
}
session_cache_mode → Integer 点击切换源代码

当前会话缓存模式。

static VALUE
ossl_sslctx_get_session_cache_mode(VALUE self)
{
    SSL_CTX *ctx;

    GetSSLCTX(self, ctx);

    return LONG2NUM(SSL_CTX_get_session_cache_mode(ctx));
}
session_cache_mode=(integer) → Integer 点击切换源代码

设置 SSL 会话缓存模式。按位或运算所需 SESSION_CACHE_* 常量以设置。有关详细信息,请参见 SSL_CTX_set_session_cache_mode(3)。

static VALUE
ossl_sslctx_set_session_cache_mode(VALUE self, VALUE arg)
{
    SSL_CTX *ctx;

    GetSSLCTX(self, ctx);

    SSL_CTX_set_session_cache_mode(ctx, NUM2LONG(arg));

    return arg;
}
session_cache_size → Integer 点击切换源代码

返回当前会话缓存大小。零表示无限缓存大小。

static VALUE
ossl_sslctx_get_session_cache_size(VALUE self)
{
    SSL_CTX *ctx;

    GetSSLCTX(self, ctx);

    return LONG2NUM(SSL_CTX_sess_get_cache_size(ctx));
}
session_cache_size=(integer) → Integer 点击切换源代码

设置会话缓存大小。返回之前有效的会话缓存大小。零表示无限会话缓存大小。

static VALUE
ossl_sslctx_set_session_cache_size(VALUE self, VALUE arg)
{
    SSL_CTX *ctx;

    GetSSLCTX(self, ctx);

    SSL_CTX_sess_set_cache_size(ctx, NUM2LONG(arg));

    return arg;
}
session_cache_stats → Hash 点击切换源代码

返回一个包含以下键的哈希表:

:accept

服务器模式下启动的 SSL/TLS 握手次数

:accept_good

服务器模式下建立的 SSL/TLS 会话数

:accept_renegotiate

服务器模式下启动重新协商的次数

:cache_full

由于缓存溢出而删除的会话数

:cache_hits

成功重用连接的次数

:cache_misses

客户端提出的但在缓存中找不到的会话数

:cache_num

内部会话缓存中的会话数

:cb_hits

从服务器模式下的外部缓存中检索到的会话数

:connect

客户端模式下启动的 SSL/TLS 握手次数

:connect_good

客户端模式下建立的 SSL/TLS 会话数

:connect_renegotiate

客户端模式下启动重新协商的次数

:timeouts

客户端提出的但在缓存中找到但由于超时而过期的会话数

static VALUE
ossl_sslctx_get_session_cache_stats(VALUE self)
{
    SSL_CTX *ctx;
    VALUE hash;

    GetSSLCTX(self, ctx);

    hash = rb_hash_new();
    rb_hash_aset(hash, ID2SYM(rb_intern("cache_num")), LONG2NUM(SSL_CTX_sess_number(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("connect")), LONG2NUM(SSL_CTX_sess_connect(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("connect_good")), LONG2NUM(SSL_CTX_sess_connect_good(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("connect_renegotiate")), LONG2NUM(SSL_CTX_sess_connect_renegotiate(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("accept")), LONG2NUM(SSL_CTX_sess_accept(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("accept_good")), LONG2NUM(SSL_CTX_sess_accept_good(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("accept_renegotiate")), LONG2NUM(SSL_CTX_sess_accept_renegotiate(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("cache_hits")), LONG2NUM(SSL_CTX_sess_hits(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("cb_hits")), LONG2NUM(SSL_CTX_sess_cb_hits(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("cache_misses")), LONG2NUM(SSL_CTX_sess_misses(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("cache_full")), LONG2NUM(SSL_CTX_sess_cache_full(ctx)));
    rb_hash_aset(hash, ID2SYM(rb_intern("timeouts")), LONG2NUM(SSL_CTX_sess_timeouts(ctx)));

    return hash;
}
session_remove(session) → true | false 点击切换源代码

从会话缓存中删除session

static VALUE
ossl_sslctx_session_remove(VALUE self, VALUE arg)
{
    SSL_CTX *ctx;
    SSL_SESSION *sess;

    GetSSLCTX(self, ctx);
    GetSSLSession(arg, sess);

    return SSL_CTX_remove_session(ctx, sess) == 1 ? Qtrue : Qfalse;
}
set_params(params = {}) → params 点击切换源代码

设置更合理的默认值,针对与 HTTP 类似的协议进行优化。

如果给定一个哈希表params,则参数将被它覆盖。params中的键必须是SSLContext上的赋值方法。

如果verify_mode不是 VERIFY_NONE 且ca_fileca_pathcert_store未设置,则使用系统默认证书存储。

# File openssl/lib/openssl/ssl.rb, line 146
def set_params(params={})
  params = DEFAULT_PARAMS.merge(params)
  self.options = params.delete(:options) # set before min_version/max_version
  params.each{|name, value| self.__send__("#{name}=", value) }
  if self.verify_mode != OpenSSL::SSL::VERIFY_NONE
    unless self.ca_file or self.ca_path or self.cert_store
      self.cert_store = DEFAULT_CERT_STORE
    end
  end
  return params
end
setup → Qtrue # 第一次 点击切换源代码
setup → nil # 之后

此方法在创建新的 SSLSocket 时会自动调用。但是,它不是线程安全的,必须在多线程程序中创建 SSLSocket 对象之前调用。

static VALUE
ossl_sslctx_setup(VALUE self)
{
    SSL_CTX *ctx;
    X509 *cert = NULL, *client_ca = NULL;
    EVP_PKEY *key = NULL;
    char *ca_path = NULL, *ca_file = NULL;
    int verify_mode;
    long i;
    VALUE val;

    if(OBJ_FROZEN(self)) return Qnil;
    GetSSLCTX(self, ctx);

#if !defined(OPENSSL_NO_DH)
    SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback);
#endif

#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
    SSL_CTX_set_post_handshake_auth(ctx, 1);
#endif

    val = rb_attr_get(self, id_i_cert_store);
    if (!NIL_P(val)) {
        X509_STORE *store = GetX509StorePtr(val); /* NO NEED TO DUP */
        SSL_CTX_set_cert_store(ctx, store);
        X509_STORE_up_ref(store);
    }

    val = rb_attr_get(self, id_i_extra_chain_cert);
    if(!NIL_P(val)){
        rb_block_call(val, rb_intern("each"), 0, 0, ossl_sslctx_add_extra_chain_cert_i, self);
    }

    /* private key may be bundled in certificate file. */
    val = rb_attr_get(self, id_i_cert);
    cert = NIL_P(val) ? NULL : GetX509CertPtr(val); /* NO DUP NEEDED */
    val = rb_attr_get(self, id_i_key);
    key = NIL_P(val) ? NULL : GetPrivPKeyPtr(val); /* NO DUP NEEDED */
    if (cert && key) {
        if (!SSL_CTX_use_certificate(ctx, cert)) {
            /* Adds a ref => Safe to FREE */
            ossl_raise(eSSLError, "SSL_CTX_use_certificate");
        }
        if (!SSL_CTX_use_PrivateKey(ctx, key)) {
            /* Adds a ref => Safe to FREE */
            ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey");
        }
        if (!SSL_CTX_check_private_key(ctx)) {
            ossl_raise(eSSLError, "SSL_CTX_check_private_key");
        }
    }

    val = rb_attr_get(self, id_i_client_ca);
    if(!NIL_P(val)){
        if (RB_TYPE_P(val, T_ARRAY)) {
            for(i = 0; i < RARRAY_LEN(val); i++){
                client_ca = GetX509CertPtr(RARRAY_AREF(val, i));
                if (!SSL_CTX_add_client_CA(ctx, client_ca)){
                    /* Copies X509_NAME => FREE it. */
                    ossl_raise(eSSLError, "SSL_CTX_add_client_CA");
                }
            }
        }
        else{
            client_ca = GetX509CertPtr(val); /* NO DUP NEEDED. */
            if (!SSL_CTX_add_client_CA(ctx, client_ca)){
                /* Copies X509_NAME => FREE it. */
                ossl_raise(eSSLError, "SSL_CTX_add_client_CA");
            }
        }
    }

    val = rb_attr_get(self, id_i_ca_file);
    ca_file = NIL_P(val) ? NULL : StringValueCStr(val);
    val = rb_attr_get(self, id_i_ca_path);
    ca_path = NIL_P(val) ? NULL : StringValueCStr(val);
#ifdef HAVE_SSL_CTX_LOAD_VERIFY_FILE
    if (ca_file && !SSL_CTX_load_verify_file(ctx, ca_file))
        ossl_raise(eSSLError, "SSL_CTX_load_verify_file");
    if (ca_path && !SSL_CTX_load_verify_dir(ctx, ca_path))
        ossl_raise(eSSLError, "SSL_CTX_load_verify_dir");
#else
    if (ca_file || ca_path) {
        if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_path))
            ossl_raise(eSSLError, "SSL_CTX_load_verify_locations");
    }
#endif

    val = rb_attr_get(self, id_i_verify_mode);
    verify_mode = NIL_P(val) ? SSL_VERIFY_NONE : NUM2INT(val);
    SSL_CTX_set_verify(ctx, verify_mode, ossl_ssl_verify_callback);
    if (RTEST(rb_attr_get(self, id_i_client_cert_cb)))
        SSL_CTX_set_client_cert_cb(ctx, ossl_client_cert_cb);

    val = rb_attr_get(self, id_i_timeout);
    if(!NIL_P(val)) SSL_CTX_set_timeout(ctx, NUM2LONG(val));

    val = rb_attr_get(self, id_i_verify_depth);
    if(!NIL_P(val)) SSL_CTX_set_verify_depth(ctx, NUM2INT(val));

#ifdef OSSL_USE_NEXTPROTONEG
    val = rb_attr_get(self, id_i_npn_protocols);
    if (!NIL_P(val)) {
        VALUE encoded = ssl_encode_npn_protocols(val);
        rb_ivar_set(self, id_npn_protocols_encoded, encoded);
        SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self);
        OSSL_Debug("SSL NPN advertise callback added");
    }
    if (RTEST(rb_attr_get(self, id_i_npn_select_cb))) {
        SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self);
        OSSL_Debug("SSL NPN select callback added");
    }
#endif

    val = rb_attr_get(self, id_i_alpn_protocols);
    if (!NIL_P(val)) {
        VALUE rprotos = ssl_encode_npn_protocols(val);

        /* returns 0 on success */
        if (SSL_CTX_set_alpn_protos(ctx, (unsigned char *)RSTRING_PTR(rprotos),
                                    RSTRING_LENINT(rprotos)))
            ossl_raise(eSSLError, "SSL_CTX_set_alpn_protos");
        OSSL_Debug("SSL ALPN values added");
    }
    if (RTEST(rb_attr_get(self, id_i_alpn_select_cb))) {
        SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self);
        OSSL_Debug("SSL ALPN select callback added");
    }

    rb_obj_freeze(self);

    val = rb_attr_get(self, id_i_session_id_context);
    if (!NIL_P(val)){
        StringValue(val);
        if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val),
                                            RSTRING_LENINT(val))){
            ossl_raise(eSSLError, "SSL_CTX_set_session_id_context");
        }
    }

    if (RTEST(rb_attr_get(self, id_i_session_get_cb))) {
        SSL_CTX_sess_set_get_cb(ctx, ossl_sslctx_session_get_cb);
        OSSL_Debug("SSL SESSION get callback added");
    }
    if (RTEST(rb_attr_get(self, id_i_session_new_cb))) {
        SSL_CTX_sess_set_new_cb(ctx, ossl_sslctx_session_new_cb);
        OSSL_Debug("SSL SESSION new callback added");
    }
    if (RTEST(rb_attr_get(self, id_i_session_remove_cb))) {
        SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb);
        OSSL_Debug("SSL SESSION remove callback added");
    }

    val = rb_attr_get(self, id_i_servername_cb);
    if (!NIL_P(val)) {
        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
        OSSL_Debug("SSL TLSEXT servername callback added");
    }

#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER)
    /*
     * It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
     * SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see
     * https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6).
     */
    if (RTEST(rb_attr_get(self, id_i_keylog_cb))) {
        SSL_CTX_set_keylog_callback(ctx, ossl_sslctx_keylog_cb);
        OSSL_Debug("SSL keylog callback added");
    }
#endif

    return Qtrue;
}
也称为:freeze
ssl_version = :TLSv1 点击切换源代码
ssl_version = "SSLv23"

设置上下文的 SSL/TLS 协议版本。这将强制连接仅使用指定的协议版本。此方法已弃用,仅为了向后兼容性而提供。请使用 min_version=max_version= 代替。

历史记录

顾名思义,此方法以前调用 SSL_CTX_set_ssl_version() 函数,该函数设置用于从上下文中创建的连接的 SSL 方法。从 Ruby/OpenSSL 2.1 开始,此访问器方法被实现为调用 min_version=max_version= 代替。

# File openssl/lib/openssl/ssl.rb, line 209
def ssl_version=(meth)
  meth = meth.to_s if meth.is_a?(Symbol)
  if /(?<type>_client|_server)\z/ =~ meth
    meth = $`
    if $VERBOSE
      warn "#{caller(1, 1)[0]}: method type #{type.inspect} is ignored"
    end
  end
  version = METHODS_MAP[meth.intern] or
    raise ArgumentError, "unknown SSL method `%s'" % meth
  set_minmax_proto_version(version, version)
  @min_proto_version = @max_proto_version = version
end
tmp_dh = pkey 点击切换源代码

设置用于临时 DH 密钥交换的 DH 参数。这仅与服务器相关。

pkeyOpenSSL::PKey::DH 的实例。请注意,密钥对象中包含的密钥组件(如果有)将被忽略。服务器将始终为每次握手生成新的密钥对。

在版本 3.0 中添加。另请参阅手册页 SSL_set0_tmp_dh_pkey(3)。

示例

ctx = OpenSSL::SSL::SSLContext.new
ctx.tmp_dh = OpenSSL::DH.generate(2048)
svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx)
Thread.new { svr.accept }
static VALUE
ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg)
{
    SSL_CTX *ctx;
    EVP_PKEY *pkey;

    rb_check_frozen(self);
    GetSSLCTX(self, ctx);
    pkey = GetPKeyPtr(arg);

    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH)
        rb_raise(eSSLError, "invalid pkey type %s (expected DH)",
                 OBJ_nid2sn(EVP_PKEY_base_id(pkey)));
#ifdef HAVE_SSL_SET0_TMP_DH_PKEY
    if (!SSL_CTX_set0_tmp_dh_pkey(ctx, pkey))
        ossl_raise(eSSLError, "SSL_CTX_set0_tmp_dh_pkey");
    EVP_PKEY_up_ref(pkey);
#else
    if (!SSL_CTX_set_tmp_dh(ctx, EVP_PKEY_get0_DH(pkey)))
        ossl_raise(eSSLError, "SSL_CTX_set_tmp_dh");
#endif

    return arg;
}

私有实例方法

set_minmax_proto_version(min, max) → nil 点击切换源代码

设置支持的最小和最大协议版本。请参阅 min_version=max_version=

static VALUE
ossl_sslctx_set_minmax_proto_version(VALUE self, VALUE min_v, VALUE max_v)
{
    SSL_CTX *ctx;
    int min, max;

    GetSSLCTX(self, ctx);
    min = parse_proto_version(min_v);
    max = parse_proto_version(max_v);

#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
    if (!SSL_CTX_set_min_proto_version(ctx, min))
        ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version");
    if (!SSL_CTX_set_max_proto_version(ctx, max))
        ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version");
#else
    {
        unsigned long sum = 0, opts = 0;
        int i;
        static const struct {
            int ver;
            unsigned long opts;
        } options_map[] = {
            { SSL2_VERSION, SSL_OP_NO_SSLv2 },
            { SSL3_VERSION, SSL_OP_NO_SSLv3 },
            { TLS1_VERSION, SSL_OP_NO_TLSv1 },
            { TLS1_1_VERSION, SSL_OP_NO_TLSv1_1 },
            { TLS1_2_VERSION, SSL_OP_NO_TLSv1_2 },
# if defined(TLS1_3_VERSION)
            { TLS1_3_VERSION, SSL_OP_NO_TLSv1_3 },
# endif
        };

        for (i = 0; i < numberof(options_map); i++) {
            sum |= options_map[i].opts;
            if ((min && min > options_map[i].ver) ||
                (max && max < options_map[i].ver)) {
                opts |= options_map[i].opts;
            }
        }
        SSL_CTX_clear_options(ctx, sum);
        SSL_CTX_set_options(ctx, opts);
    }
#endif

    return Qnil;
}