class OpenSSL::SSL::SSLSocket
Constants
- IO_TimeoutError
-
Ruby 3.2
Attributes
The SSLContext object used in this connection.
Whether to close the underlying socket as well, when the SSL/TLS connection is shut down. This defaults to false.
Public Class Methods
static VALUE
ossl_ssl_initialize(int argc, VALUE *argv, VALUE self)
{
static ID kw_ids[1];
VALUE kw_args[1];
VALUE opts;
VALUE io, v_ctx;
SSL *ssl;
SSL_CTX *ctx;
argc = rb_scan_args(argc, argv, "11:", &io, &v_ctx, &opts);
if (!kw_ids[0]) {
kw_ids[0] = rb_intern_const("sync_close");
}
rb_get_kwargs(opts, kw_ids, 0, 1, kw_args);
ossl_want_uninitialized(self, &ossl_ssl_type);
if (argc == 1)
v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0);
if (kw_args[0] != Qundef) {
rb_ivar_set(self, id_i_sync_close, kw_args[0]);
}
GetSSLCTX(v_ctx, ctx);
rb_ivar_set(self, id_i_context, v_ctx);
rb_ivar_set(self, id_original_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;
if (!SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self))
ossl_raise(eSSLError, "SSL_set_ex_data");
SSL_set_info_callback(ssl, ssl_info_cb);
rb_call_super(0, NULL);
return self;
}
Creates a new SSL socket from io which must be a real IO object (not an IO-like object that responds to read/write).
If ctx is provided the SSL Sockets initial params will be taken from the context.
The optional sync_close keyword parameter sets the sync_close instance variable. Setting this to true will cause the underlying socket to be closed when the SSL/TLS connection is shut down.
The OpenSSL::Buffering module provides additional IO methods.
This method will freeze the SSLContext if one is provided; however, session management is still allowed in the frozen SSLContext.
# File ext/openssl/lib/openssl/ssl.rb, line 647 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
Creates a new instance of SSLSocket. _remote_host_ and _remote_port_ are used to open TCPSocket. If _local_host_ and _local_port_ are specified, then those parameters are used on the local end to establish the connection. If context is provided, the SSL Sockets initial params will be taken from the context.
Examples
sock = OpenSSL::SSL::SSLSocket.open('localhost', 443) sock.connect # Initiates a connection to localhost:443
with 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
Public Instance Methods
# File ext/openssl/lib/openssl/ssl.rb, line 466 def accept(timeout: self.timeout) blocking(timeout) { ssl_accept } end
Waits for a SSL/TLS client to initiate a handshake.
If timeout is specified, and if the handshake does not complete within timeout seconds, IO::TimeoutError is raised.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 491 def accept_nonblock(exception: true) ret = ssl_accept check_nonblock(ret) if exception ret end
Initiates the SSL/TLS handshake as a server in non-blocking manner.
# emulates blocking accept begin ssl.accept_nonblock rescue IO::WaitReadable IO.select([s2]) retry rescue IO::WaitWritable IO.select(nil, [s2]) retry end
By specifying a keyword argument exception to false, you can indicate that accept_nonblock should not raise an IO::WaitReadable or IO::WaitWritable exception, but return the symbol :wait_readable or :wait_writable instead.
Source
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);
}
Returns the ALPN protocol string that was finally selected by the server during the handshake.
Source
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);
}
The X509 certificate for this socket endpoint.
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;
}
Returns the cipher suite actually used in the current session, or nil if no session has been established.
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);
if (!ca)
return Qnil;
return ossl_x509name_sk2ary(ca);
}
Returns the list of client CAs. Please note that in contrast to SSLContext#client_ca= no array of X509::Certificate is returned but X509::Name instances of the CA’s subject distinguished name.
In server mode, returns the list set by SSLContext#client_ca=. In client mode, returns the list of client CAs sent from the server.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 582 def close_read # Unsupported and ignored. # Just don't read any more. end
Source
# File ext/openssl/lib/openssl/ssl.rb, line 601 def close_write stop end
Closes the stream for writing. The behavior of this method depends on the version of OpenSSL and the TLS protocol in use.
-
Sends a ‘close_notify’ alert to the peer.
-
Does not wait for the peer’s ‘close_notify’ alert in response.
In TLS 1.2 and earlier:
-
On receipt of a ‘close_notify’ alert, responds with a ‘close_notify’ alert of its own and close down the connection immediately, discarding any pending writes.
Therefore, on TLS 1.2, this method will cause the connection to be completely shut down. On TLS 1.3, the connection will remain open for reading only.
# File ext/openssl/lib/openssl/ssl.rb, line 428 def connect(timeout: self.timeout) blocking(timeout) { ssl_connect } end
Initiates an SSL/TLS handshake with a server.
If timeout is specified, and if the handshake does not complete within timeout seconds, IO::TimeoutError is raised.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 453 def connect_nonblock(exception: true) ret = ssl_connect check_nonblock(ret) if exception ret end
Initiates the SSL/TLS handshake as a client in non-blocking manner.
# emulates blocking connect begin ssl.connect_nonblock rescue IO::WaitReadable IO.select([s2]) retry rescue IO::WaitWritable IO.select(nil, [s2]) retry end
By specifying a keyword argument exception to false, you can indicate that connect_nonblock should not raise an IO::WaitReadable or IO::WaitWritable exception, but return the symbol :wait_readable or :wait_writable instead.
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)NUM2LEN(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;
}
Enables use of shared session key material in accordance with RFC 5705.
static VALUE
ossl_ssl_get_finished(VALUE self)
{
SSL *ssl;
char sizer[1];
size_t len;
GetSSL(self, ssl);
len = SSL_get_finished(ssl, sizer, 0);
if (len == 0)
return Qnil;
VALUE str = rb_str_new(NULL, len);
SSL_get_finished(ssl, RSTRING_PTR(str), len);
return str;
}
Returns the contents of the last Finished message sent to the peer.
Source
static VALUE
ossl_ssl_get_group(VALUE self)
{
SSL *ssl;
const char *name;
GetSSL(self, ssl);
if (!(name = SSL_get0_group_name(ssl)))
return Qnil;
return rb_str_new_cstr(name);
}
Returns the name of the group that was used for the key agreement of the current TLS session establishment.
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;
}
Sets the server hostname used for SNI. This needs to be set before SSLSocket#connect.
Source
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);
}
Returns the protocol string that was finally selected by the client during the handshake.
Source
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;
}
The X509 certificate for this socket’s peer.
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;
}
The X509 certificate chain for this socket’s peer.
static VALUE
ossl_ssl_get_peer_finished(VALUE self)
{
SSL *ssl;
char sizer[1];
size_t len;
GetSSL(self, ssl);
len = SSL_get_peer_finished(ssl, sizer, 0);
if (len == 0)
return Qnil;
VALUE str = rb_str_new(NULL, len);
SSL_get_peer_finished(ssl, RSTRING_PTR(str), len);
return str;
}
Returns the contents of the last Finished message expected to be sent by the peer.
Source
static VALUE
ossl_ssl_get_peer_sigalg(VALUE self)
{
SSL *ssl;
const char *name;
GetSSL(self, ssl);
if (!SSL_get0_peer_signature_name(ssl, &name))
return Qnil;
return rb_str_new_cstr(name);
}
Returns the signature algorithm name, the IANA name of the signature scheme used by the peer to sign the TLS handshake.
Source
static VALUE
ossl_ssl_pending(VALUE self)
{
SSL *ssl;
GetSSL(self, ssl);
return INT2NUM(SSL_pending(ssl));
}
Returns the number of bytes buffered by the OpenSSL library and immediately available for reading with sysread.
This does not include data read ahead and buffered by SSLSocket. It may therefore return 0 even when data is available for reading with methods that are aware of the SSLSocket buffer, such as read or gets.
See also the man page SSL_pending(3).
# File ext/openssl/lib/openssl/ssl.rb, line 552 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
Perform hostname verification following RFC 6125.
This method MUST be called after calling connect to ensure that the hostname of a remote peer has been verified.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 573 def session SSL::Session.new(self) rescue SSL::Session::SessionError nil end
Returns the SSLSession object currently used, or nil if the session is not established.
Source
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;
}
Sets the Session to be used when the connection is established.
static VALUE
ossl_ssl_session_reused(VALUE self)
{
SSL *ssl;
GetSSL(self, ssl);
return SSL_session_reused(ssl) ? Qtrue : Qfalse;
}
Returns true if a reused session was negotiated during the handshake.
Source
static VALUE
ossl_ssl_get_sigalg(VALUE self)
{
SSL *ssl;
const char *name;
GetSSL(self, ssl);
if (!SSL_get0_signature_name(ssl, &name))
return Qnil;
return rb_str_new_cstr(name);
}
Returns the signature algorithm name, the IANA name of the signature scheme used by the local to sign the TLS handshake.
Source
static VALUE
ossl_ssl_get_version(VALUE self)
{
SSL *ssl;
GetSSL(self, ssl);
return rb_str_new2(SSL_get_version(ssl));
}
Returns a String representing the SSL/TLS version that was negotiated for the connection, for example “TLSv1.2”.
Source
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;
}
A description of the current connection state. This is for diagnostic purposes only.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 376 def sysclose return if closed? stop io.close if sync_close end
Sends “close notify” to the peer and tries to shut down the SSL connection gracefully.
If sync_close is set to true, the underlying IO is also closed.
# File ext/openssl/lib/openssl/ssl.rb, line 503 def sysread(length, buffer = nil) buffer ||= string_new_capacity(length) blocking { ssl_read(length, buffer) or raise EOFError, "end of file reached" } end
Reads length bytes from the SSL connection. If a pre-allocated buffer is provided the data will be written into it.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 530 def syswrite(string) blocking { ssl_write(string) } end
Writes string to the SSL connection.
Source
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_wrap(key);
}
Returns the ephemeral key used in case of forward secrecy cipher.
Source
static VALUE
ossl_ssl_get_verify_result(VALUE self)
{
SSL *ssl;
GetSSL(self, ssl);
return LONG2NUM(SSL_get_verify_result(ssl));
}
Returns the result of the peer certificates verification. See verify(1) for error values and descriptions.
If no peer certificate was presented X509_V_OK is returned.
Private Instance Methods
# File ext/openssl/lib/openssl/ssl.rb, line 386 def blocking(timeout = self.timeout) if timeout remaining = timeout start = Process.clock_gettime(Process::CLOCK_MONOTONIC) end while true ret = yield if :wait_readable == ret io.wait_readable(remaining) or raise IO_TimeoutError, "Timed out while waiting to become readable!" elsif :wait_writable == ret io.wait_writable(remaining) or raise IO_TimeoutError, "Timed out while waiting to become writable!" else return ret end if timeout now = Process.clock_gettime(Process::CLOCK_MONOTONIC) remaining = [0, timeout - (now - start)].max end end end
Source
# File ext/openssl/lib/openssl/ssl.rb, line 411 def check_nonblock(ret) if :wait_readable == ret raise SSLErrorWaitReadable, "read would block" elsif :wait_writable == ret raise SSLErrorWaitWritable, "write would block" else ret end end
Source
# File ext/openssl/lib/openssl/ssl.rb, line 613 def client_cert_cb @context.client_cert_cb end
Source
# File ext/openssl/lib/openssl/ssl.rb, line 621 def session_get_cb @context.session_get_cb end
Source
# File ext/openssl/lib/openssl/ssl.rb, line 617 def session_new_cb @context.session_new_cb end
static VALUE
ossl_ssl_accept(VALUE self)
{
ossl_ssl_setup(self);
return ossl_start_ssl(self, SSL_accept, "SSL_accept");
}
static VALUE
ossl_ssl_connect(VALUE self)
{
ossl_ssl_setup(self);
return ossl_start_ssl(self, SSL_connect, "SSL_connect");
}
static VALUE
ossl_ssl_read(VALUE self, VALUE len, VALUE str)
{
SSL *ssl;
int ilen;
VALUE cb_state;
GetSSL(self, ssl);
if (!ssl_started(ssl))
rb_raise(eSSLError, "SSL session is not started yet");
ilen = NUM2INT(len);
StringValue(str);
if ((rb_len_t)rb_str_capacity(str) >= ilen)
rb_str_modify(str);
else
rb_str_modify_expand(str, ilen - RSTRING_LEN(str));
if (ilen == 0) {
rb_str_set_len(str, 0);
return str;
}
rb_str_locktmp(str);
int nread = SSL_read(ssl, RSTRING_PTR(str), ilen);
rb_str_unlocktmp(str);
int saved_errno = errno_mapped();
cb_state = rb_attr_get(self, ID_callback_state);
if (!NIL_P(cb_state)) {
rb_ivar_set(self, ID_callback_state, Qnil);
ossl_clear_error();
rb_jump_tag(NUM2INT(cb_state));
}
switch (SSL_get_error(ssl, nread)) {
case SSL_ERROR_NONE:
rb_str_set_len(str, nread);
return str;
case SSL_ERROR_ZERO_RETURN:
return Qnil;
case SSL_ERROR_WANT_WRITE:
return sym_wait_writable;
case SSL_ERROR_WANT_READ:
return sym_wait_readable;
case SSL_ERROR_SYSCALL:
if (saved_errno) {
ossl_clear_error();
rb_exc_raise(rb_syserr_new(saved_errno, "SSL_read"));
}
if (!ERR_peek_error()) {
/*
* The underlying BIO returned 0. This is actually a
* protocol error. But unfortunately, not all
* implementations cleanly shutdown the TLS connection
* but just shutdown/close the TCP connection. So report
* EOF for now...
*/
return Qnil;
}
/* fall through */
default:
ossl_raise(eSSLError, "SSL_read");
}
}
static VALUE
ossl_ssl_write(VALUE self, VALUE str)
{
SSL *ssl;
VALUE cb_state;
GetSSL(self, ssl);
if (!ssl_started(ssl))
rb_raise(eSSLError, "SSL session is not started yet");
str = rb_obj_as_string(str);
/* SSL_write(3ssl) manpage states num == 0 is undefined */
int num = RSTRING_LENINT(str);
if (num == 0)
return INT2FIX(0);
#ifdef __APPLE__
retry:;
#endif
int frozen = RB_OBJ_FROZEN(str);
if (!frozen) {
rb_str_locktmp(str);
}
int nwritten = SSL_write(ssl, RSTRING_PTR(str), num);
if (!frozen) {
rb_str_unlocktmp(str);
}
int saved_errno = errno_mapped();
cb_state = rb_attr_get(self, ID_callback_state);
if (!NIL_P(cb_state)) {
rb_ivar_set(self, ID_callback_state, Qnil);
ossl_clear_error();
rb_jump_tag(NUM2INT(cb_state));
}
switch (SSL_get_error(ssl, nwritten)) {
case SSL_ERROR_NONE:
return INT2NUM(nwritten);
case SSL_ERROR_WANT_WRITE:
return sym_wait_writable;
case SSL_ERROR_WANT_READ:
return sym_wait_readable;
case SSL_ERROR_SYSCALL:
if (saved_errno) {
ossl_clear_error();
#ifdef __APPLE__
/*
* It appears that send syscall can return EPROTOTYPE if the
* socket is being torn down. Retry to get a proper errno to
* make the error handling in line with the socket library.
* [Bug #14713] https://bugs.ruby-lang.org/issues/14713
*/
if (saved_errno == EPROTOTYPE)
goto retry;
#endif
rb_exc_raise(rb_syserr_new(saved_errno, "SSL_write"));
}
/* fallthrough */
default:
ossl_raise(eSSLError, "SSL_write");
}
}
Source
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;
}
Sends “close notify” to the peer and tries to shut down the SSL connection gracefully.
Source
static VALUE
ossl_ssl_string_new_capacity(VALUE self, VALUE len)
{
rb_len_t capa = NUM2LEN(len);
return rb_str_buf_new(capa >= 0 ? capa : 0);
}
TODO: This shouldn’t be necessary, but we do not want to use String.new(capacity: len) in sysread because it allocates Hash objects for the kwargs.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 516 def sysread_nonblock(length, buffer = nil, exception: true) buffer ||= string_new_capacity(length) ret = ssl_read(length, buffer) if exception raise EOFError, "end of file reached" if ret.nil? check_nonblock(ret) end ret end
A non-blocking version of sysread.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 539 def syswrite_nonblock(string, exception: true) ret = ssl_write(string) check_nonblock(ret) if exception ret end
Writes string to the SSL connection in a non-blocking manner.
Source
# File ext/openssl/lib/openssl/ssl.rb, line 607 def using_anon_cipher? ctx = OpenSSL::SSL::SSLContext.new ctx.ciphers = "aNULL" ctx.ciphers.include?(cipher) end