summaryrefslogtreecommitdiff
path: root/ext/openssl/lib
diff options
context:
space:
mode:
Diffstat (limited to 'ext/openssl/lib')
-rw-r--r--ext/openssl/lib/net/ftptls.rb43
-rw-r--r--ext/openssl/lib/net/https.rb188
-rw-r--r--ext/openssl/lib/net/protocols.rb56
-rw-r--r--ext/openssl/lib/net/telnets.rb250
-rw-r--r--ext/openssl/lib/openssl.rb41
-rw-r--r--ext/openssl/lib/openssl/bn.rb47
-rw-r--r--ext/openssl/lib/openssl/buffering.rb433
-rw-r--r--ext/openssl/lib/openssl/cipher.rb103
-rw-r--r--ext/openssl/lib/openssl/digest.rb99
-rw-r--r--ext/openssl/lib/openssl/hmac.rb78
-rw-r--r--ext/openssl/lib/openssl/marshal.rb30
-rw-r--r--ext/openssl/lib/openssl/pkcs5.rb22
-rw-r--r--ext/openssl/lib/openssl/pkey.rb493
-rw-r--r--ext/openssl/lib/openssl/ssl.rb484
-rw-r--r--ext/openssl/lib/openssl/version.rb6
-rw-r--r--ext/openssl/lib/openssl/x509.rb388
16 files changed, 2003 insertions, 758 deletions
diff --git a/ext/openssl/lib/net/ftptls.rb b/ext/openssl/lib/net/ftptls.rb
deleted file mode 100644
index f433457923..0000000000
--- a/ext/openssl/lib/net/ftptls.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-=begin
-= $RCSfile$ -- SSL/TLS enhancement for Net::HTTP.
-
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2003 Blaz Grilc <farmer@gmx.co.uk>
- All rights reserved.
-
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Requirements
-
-= Version
- $Id$
-
-= Notes
- Tested on FreeBSD 5-CURRENT and 4-STABLE
- - ruby 1.6.8 (2003-01-17) [i386-freebsd5]
- - OpenSSL 0.9.7a Feb 19 2003
- - ruby-openssl-0.2.0.p0
- tested on ftp server: glftpd 1.30
-=end
-
-require 'socket'
-require 'openssl'
-require 'net/ftp'
-
-module Net
- class FTPTLS < FTP
- def login(user = "anonymous", passwd = nil, acct = nil)
- ctx = OpenSSL::SSL::SSLContext.new('SSLv23')
- ctx.key = nil
- ctx.cert = nil
- voidcmd("AUTH TLS")
- @sock = OpenSSL::SSL::SSLSocket.new(@sock, ctx)
- @sock.connect
- super(user, passwd, acct)
- voidcmd("PBSZ 0")
- end
- end
-end
diff --git a/ext/openssl/lib/net/https.rb b/ext/openssl/lib/net/https.rb
deleted file mode 100644
index fb7f53c555..0000000000
--- a/ext/openssl/lib/net/https.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-=begin
-= $RCSfile$ -- SSL/TLS enhancement for Net::HTTP.
-
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2001 GOTOU Yuuzou <gotoyuzo@notwork.org>
- All rights reserved.
-
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Requirements
- This program requires Net 1.2.0 or higher version.
- You can get it from RAA or Ruby's CVS repository.
-
-= Version
- $Id$
-
- 2001/11/06: Contiributed to Ruby/OpenSSL project.
-
-== class Net::HTTP
-
-== Example
-
-Simple HTTP client is here:
-
- require 'net/http'
- host, port, path = "localhost", 80, "/"
- if %r!http://(.*?)(?::(\d+))?(/.*)! =~ ARGV[0]
- host = $1
- port = $2.to_i if $2
- path = $3
- end
- h = Net::HTTP.new(host, port)
- h.get2(path){ |resp| print resp.body }
-
-It can be replaced by follow one:
-
- require 'net/https'
- host, port, path = "localhost", 80, "/"
- if %r!(https?)://(.*?)(?::(\d+))?(/.*)! =~ ARGV[0]
- scheme = $1
- host = $2
- port = $3 ? $3.to_i : ((scheme == "http") ? 80 : 443)
- path = $4
- end
- h = Net::HTTP.new(host, port)
- h.use_ssl = true if scheme == "https" # enable SSL/TLS
- h.get2(path){ |resp| print resp.body }
-
-=== Instance Methods
-
-: use_ssl
- returns ture if use SSL/TLS with HTTP.
-
-: use_ssl=((|true_or_false|))
- sets use_ssl.
-
-: peer_cert
- return the X.509 certificates the server presented.
-
-: key=((|key|))
- Sets an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
- (This method is appeared in Michal Rokos's OpenSSL extention.)
-
-: key_file=((|path|))
- Sets a private key file to use in PEM format.
-
-: cert=((|cert|))
- Sets an OpenSSL::X509::Certificate object as client certificate.
- (This method is appeared in Michal Rokos's OpenSSL extention.)
-
-: cert_file=((|path|))
- Sets pathname of a X.509 certification file in PEM format.
-
-: ca_file=((|path|))
- Sets path of a CA certification file in PEM format.
- The file can contrain several CA certificats.
-
-: ca_path=((|path|))
- Sets path of a CA certification directory containing certifications
- in PEM format.
-
-: verify_mode=((|mode|))
- Sets the flags for server the certification verification at
- begining of SSL/TLS session.
- OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable.
-
-: verify_callback=((|proc|))
- Sets the verify callback for the server certification verification.
-
-: verify_depth=((|num|))
- Sets the maximum depth for the certificate chain verification.
-
-: cert_store=((|store|))
- Sets the X509::Store to verify peer certificate.
-
-=end
-
-require 'net/protocols'
-require 'net/http'
-
-module Net
- class HTTP
- class Conn < HTTPRequest
- REQUEST_HAS_BODY=false
- RESPONSE_HAS_BODY=false
- METHOD="connect"
-
- def initialize
- super nil, nil
- end
-
- def exec( sock, addr, port, ver )
- @socket = sock
- request(addr, port, ver)
- end
-
- def request( addr, port, ver )
- @socket.writeline sprintf('CONNECT %s:%s HTTP/%s', addr, port, ver)
- @socket.writeline ''
- end
- end
-
- module ProxyMod
- def edit_path( path )
- if use_ssl
- 'https://' + addr_port + path
- else
- 'http://' + addr_port + path
- end
- end
- end
-
- def self.socket_type
- SSLIO
- end
-
- attr_reader :use_ssl
- attr_writer :key, :cert
- attr_writer :ca_file, :ca_path
- attr_writer :verify_mode, :verify_callback, :verify_depth
- attr_writer :cert_store, :timeout
- attr_reader :peer_cert
-
- alias :default_initialize :initialize
-
- def initialize(*args)
- default_initialize(*args)
- @key = @cert = @ca_file = @ca_path = @verify_mode =
- @verify_callback = @verify_depth = @timeout = @cert_store = nil
- @already_connected = false
- end
-
- def use_ssl=(flag)
- if @already_connected && !@use_ssl
- raise ProtocolError, "connection is alrady set up"
- end
- @use_ssl = flag
- end
-
- def on_connect
- if use_ssl
- if proxy?
- Conn.new.exec(@socket, @address, @port, "1.0")
- resp = HTTPResponse.read_new(@socket)
- if resp.code != '200'
- raise resp.message
- end
- end
- @socket.key = @key if @key
- @socket.cert = @cert if @cert
- @socket.ca_file = @ca_file
- @socket.ca_path = @ca_path
- @socket.verify_mode = @verify_mode
- @socket.verify_callback = @verify_callback
- @socket.verify_depth = @verify_depth
- @socket.timeout = @timeout
- @socket.cert_store = @cert_store
- @socket.ssl_connect
- @peer_cert = @socket.peer_cert
- end
- @already_connected = true
- end
-
- end
-end
diff --git a/ext/openssl/lib/net/protocols.rb b/ext/openssl/lib/net/protocols.rb
deleted file mode 100644
index 073d4f3027..0000000000
--- a/ext/openssl/lib/net/protocols.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-=begin
-= $RCSfile$ -- SSL/TLS enhancement for Net.
-
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
- All rights reserved.
-
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Requirements
- This program requires Net 1.2.0 or higher version.
- You can get it from RAA or Ruby's CVS repository.
-
-= Version
- $Id$
-
- 2001/11/06: Contiributed to Ruby/OpenSSL project.
-=end
-
-require 'net/protocol'
-require 'forwardable'
-require 'openssl'
-
-module Net
- class SSLIO < InternetMessageIO
- extend Forwardable
-
- def_delegators(:@ssl_context,
- :key=, :cert=, :key_file=, :cert_file=,
- :ca_file=, :ca_path=,
- :verify_mode=, :verify_callback=, :verify_depth=,
- :timeout=, :cert_store=)
-
- def initialize(addr, port, otime = nil, rtime = nil, dout = nil)
- super
- @ssl_context = OpenSSL::SSL::SSLContext.new()
- end
-
- def ssl_connect()
- unless @ssl_context.verify_mode
- warn "warning: peer certificate won't be verified in this SSL session."
- @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
- end
- @socket = OpenSSL::SSL::SSLSocket.new(@socket, @ssl_context)
- @socket.sync_close = true
- @socket.connect
- end
-
- def peer_cert
- @socket.peer_cert
- end
- end
-end
diff --git a/ext/openssl/lib/net/telnets.rb b/ext/openssl/lib/net/telnets.rb
deleted file mode 100644
index c7ecbd717a..0000000000
--- a/ext/openssl/lib/net/telnets.rb
+++ /dev/null
@@ -1,250 +0,0 @@
-=begin
-= $RCSfile$ -- SSL/TLS enhancement for Net::Telnet.
-
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
- All rights reserved.
-
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Version
- $Id$
-
- 2001/11/06: Contiributed to Ruby/OpenSSL project.
-
-== class Net::Telnet
-
-This class will initiate SSL/TLS session automaticaly if the server
-sent OPT_STARTTLS. Some options are added for SSL/TLS.
-
- host = Net::Telnet::new({
- "Host" => "localhost",
- "Port" => "telnets",
- ## follows are new options.
- 'CertFile' => "user.crt",
- 'KeyFile' => "user.key",
- 'CAFile' => "/some/where/certs/casert.pem",
- 'CAPath' => "/some/where/caserts",
- 'VerifyMode' => SSL::VERIFY_PEER,
- 'VerifyCallback' => verify_proc
- })
-
-Or, the new options ('Cert', 'Key' and 'CACert') are available from
-Michal Rokos's OpenSSL module.
-
- cert_data = File.open("user.crt"){|io| io.read }
- pkey_data = File.open("user.key"){|io| io.read }
- cacert_data = File.open("your_ca.pem"){|io| io.read }
- host = Net::Telnet::new({
- "Host" => "localhost",
- "Port" => "telnets",
- 'Cert' => OpenSSL::X509::Certificate.new(cert_data)
- 'Key' => OpenSSL::PKey::RSA.new(pkey_data)
- 'CACert' => OpenSSL::X509::Certificate.new(cacert_data)
- 'CAFile' => "/some/where/certs/casert.pem",
- 'CAPath' => "/some/where/caserts",
- 'VerifyMode' => SSL::VERIFY_PEER,
- 'VerifyCallback' => verify_proc
- })
-
-This class is expected to be a superset of usual Net::Telnet.
-=end
-
-require "net/telnet"
-require "openssl"
-
-module Net
- class Telnet
- attr_reader :ssl
-
- OPT_STARTTLS = 46.chr # "\056" # "\x2e" # Start TLS
- TLS_FOLLOWS = 1.chr # "\001" # "\x01" # FOLLOWS (for STARTTLS)
-
- alias preprocess_orig preprocess
-
- def ssl?; @ssl; end
-
- def preprocess(string)
- # combine CR+NULL into CR
- string = string.gsub(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"]
-
- # combine EOL into "\n"
- string = string.gsub(/#{EOL}/no, "\n") unless @options["Binmode"]
-
- string.gsub(/#{IAC}(
- [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
- [#{DO}#{DONT}#{WILL}#{WONT}][#{OPT_BINARY}-#{OPT_EXOPL}]|
- #{SB}[#{OPT_BINARY}-#{OPT_EXOPL}]
- (#{IAC}#{IAC}|[^#{IAC}])+#{IAC}#{SE}
- )/xno) do
- if IAC == $1 # handle escaped IAC characters
- IAC
- elsif AYT == $1 # respond to "IAC AYT" (are you there)
- self.write("nobody here but us pigeons" + EOL)
- ''
- elsif DO[0] == $1[0] # respond to "IAC DO x"
- if OPT_BINARY[0] == $1[1]
- @telnet_option["BINARY"] = true
- self.write(IAC + WILL + OPT_BINARY)
- elsif OPT_STARTTLS[0] == $1[1]
- self.write(IAC + WILL + OPT_STARTTLS)
- self.write(IAC + SB + OPT_STARTTLS + TLS_FOLLOWS + IAC + SE)
- else
- self.write(IAC + WONT + $1[1..1])
- end
- ''
- elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x"
- self.write(IAC + WONT + $1[1..1])
- ''
- elsif WILL[0] == $1[0] # respond to "IAC WILL x"
- if OPT_BINARY[0] == $1[1]
- self.write(IAC + DO + OPT_BINARY)
- elsif OPT_ECHO[0] == $1[1]
- self.write(IAC + DO + OPT_ECHO)
- elsif OPT_SGA[0] == $1[1]
- @telnet_option["SGA"] = true
- self.write(IAC + DO + OPT_SGA)
- else
- self.write(IAC + DONT + $1[1..1])
- end
- ''
- elsif WONT[0] == $1[0] # respond to "IAC WON'T x"
- if OPT_ECHO[0] == $1[1]
- self.write(IAC + DONT + OPT_ECHO)
- elsif OPT_SGA[0] == $1[1]
- @telnet_option["SGA"] = false
- self.write(IAC + DONT + OPT_SGA)
- else
- self.write(IAC + DONT + $1[1..1])
- end
- ''
- elsif SB[0] == $1[0] # respond to "IAC SB xxx IAC SE"
- if OPT_STARTTLS[0] == $1[1] && TLS_FOLLOWS[0] == $2[0]
- @sock = OpenSSL::SSL::SSLSocket.new(@sock)
- @sock.cert_file = @options['CertFile']
- @sock.cert = @options['Cert'] unless @sock.cert
- @sock.key_file = @options['KeyFile']
- @sock.key = @options['Key'] unless @sock.key
- @sock.ca_cert = @options['CACert']
- @sock.ca_file = @options['CAFile']
- @sock.ca_path = @options['CAPath']
- @sock.timeout = @options['Timeout']
- @sock.verify_mode = @options['VerifyMode']
- @sock.verify_callback = @options['VerifyCallback']
- @sock.verify_depth = @options['VerifyDepth']
- @sock.connect
- @ssl = true
- end
- ''
- else
- ''
- end
- end
- end # preprocess
-
- alias waitfor_org waitfor
-
- def waitfor(options)
- time_out = @options["Timeout"]
- waittime = @options["Waittime"]
-
- if options.kind_of?(Hash)
- prompt = if options.has_key?("Match")
- options["Match"]
- elsif options.has_key?("Prompt")
- options["Prompt"]
- elsif options.has_key?("String")
- Regexp.new( Regexp.quote(options["String"]) )
- end
- time_out = options["Timeout"] if options.has_key?("Timeout")
- waittime = options["Waittime"] if options.has_key?("Waittime")
- else
- prompt = options
- end
-
- if time_out == false
- time_out = nil
- end
-
- line = ''
- buf = ''
- @rest = '' unless @rest
-
- until(prompt === line and not IO::select([@sock], nil, nil, waittime))
- unless IO::select([@sock], nil, nil, time_out)
- raise TimeoutError, "timed-out; wait for the next data"
- end
- begin
- c = @rest + @sock.sysread(1024 * 1024)
- @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
- if @options["Telnetmode"]
- pos = 0
- catch(:next){
- while true
- case c[pos]
- when IAC[0]
- case c[pos+1]
- when DO[0], DONT[0], WILL[0], WONT[0]
- throw :next unless c[pos+2]
- pos += 3
- when SB[0]
- ret = detect_sub_negotiation(c, pos)
- throw :next unless ret
- pos = ret
- when nil
- throw :next
- else
- pos += 2
- end
- when nil
- throw :next
- else
- pos += 1
- end
- end
- }
-
- buf = preprocess(c[0...pos])
- @rest = c[pos..-1]
- end
- @log.print(buf) if @options.has_key?("Output_log")
- line.concat(buf)
- yield buf if block_given?
- rescue EOFError # End of file reached
- if line == ''
- line = nil
- yield nil if block_given?
- end
- break
- end
- end
- line
- end
-
- private
-
- def detect_sub_negotiation(data, pos)
- return nil if data.length < pos+6 # IAC SB x param IAC SE
- pos += 3
- while true
- case data[pos]
- when IAC[0]
- if data[pos+1] == SE[0]
- pos += 2
- return pos
- else
- pos += 2
- end
- when nil
- return nil
- else
- pos += 1
- end
- end
- end
-
- end
-end
diff --git a/ext/openssl/lib/openssl.rb b/ext/openssl/lib/openssl.rb
index 24a9eed136..98fa8d39f2 100644
--- a/ext/openssl/lib/openssl.rb
+++ b/ext/openssl/lib/openssl.rb
@@ -1,24 +1,41 @@
+# frozen_string_literal: true
=begin
-= $RCSfile$ -- Loader for all OpenSSL C-space and Ruby-space definitions
-
= Info
'OpenSSL for Ruby 2' project
Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
All rights reserved.
= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Version
- $Id$
+ This program is licensed under the same licence as Ruby.
+ (See the file 'COPYING'.)
=end
require 'openssl.so'
-require 'openssl/bn'
-require 'openssl/cipher'
-require 'openssl/digest'
-require 'openssl/ssl'
-require 'openssl/x509'
+require_relative 'openssl/bn'
+require_relative 'openssl/cipher'
+require_relative 'openssl/digest'
+require_relative 'openssl/hmac'
+require_relative 'openssl/pkcs5'
+require_relative 'openssl/pkey'
+require_relative 'openssl/ssl'
+require_relative 'openssl/version'
+require_relative 'openssl/x509'
+module OpenSSL
+ # :call-seq:
+ # OpenSSL.secure_compare(string, string) -> true or false
+ #
+ # Constant time memory comparison. Inputs are hashed using SHA-256 to mask
+ # the length of the secret. Returns +true+ if the strings are identical,
+ # +false+ otherwise.
+ #
+ # This method is expensive due to the SHA-256 hashing. In most cases, where
+ # the input lengths are known to be equal or are not sensitive,
+ # OpenSSL.fixed_length_secure_compare should be used instead.
+ def self.secure_compare(a, b)
+ hashed_a = OpenSSL::Digest.digest('SHA256', a)
+ hashed_b = OpenSSL::Digest.digest('SHA256', b)
+ OpenSSL.fixed_length_secure_compare(hashed_a, hashed_b) && a == b
+ end
+end
diff --git a/ext/openssl/lib/openssl/bn.rb b/ext/openssl/lib/openssl/bn.rb
index e7cbf2cfaf..e4889a140c 100644
--- a/ext/openssl/lib/openssl/bn.rb
+++ b/ext/openssl/lib/openssl/bn.rb
@@ -1,35 +1,40 @@
-=begin
-= $RCSfile$ -- Ruby-space definitions that completes C-space funcs for BN
-
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
- All rights reserved.
-
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Version
- $Id$
-=end
-
-##
-# Should we care what if somebody require this file directly?
-#require 'openssl'
+# frozen_string_literal: true
+#--
+#
+# = Ruby-space definitions that completes C-space funcs for BN
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'COPYING'.)
+#++
module OpenSSL
class BN
include Comparable
+
+ def pretty_print(q)
+ q.object_group(self) {
+ q.text ' '
+ q.text to_i.to_s
+ }
+ end
end # BN
end # OpenSSL
##
+#--
# Add double dispatch to Integer
-#
+#++
class Integer
+ # Casts an Integer as an OpenSSL::BN
+ #
+ # See `man bn` for more info.
def to_bn
OpenSSL::BN::new(self)
end
end # Integer
-
diff --git a/ext/openssl/lib/openssl/buffering.rb b/ext/openssl/lib/openssl/buffering.rb
index fdbd71bc0c..1464a4292d 100644
--- a/ext/openssl/lib/openssl/buffering.rb
+++ b/ext/openssl/lib/openssl/buffering.rb
@@ -1,25 +1,65 @@
-=begin
-= $RCSfile$ -- Buffering mix-in module.
+# coding: binary
+# frozen_string_literal: true
+#--
+#= Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
+# All rights reserved.
+#
+#= Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'COPYING'.)
+#++
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
- All rights reserved.
+##
+# OpenSSL IO buffering mix-in module.
+#
+# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO.
+#
+# You typically won't use this module directly, you can see it implemented in
+# OpenSSL::SSL::SSLSocket.
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
+module OpenSSL::Buffering
+ include Enumerable
-= Version
- $Id$
-=end
+ # A buffer which will retain binary encoding.
+ class Buffer < String
+ unless String.method_defined?(:append_as_bytes)
+ alias_method :_append, :<<
+ def append_as_bytes(string)
+ if string.encoding == Encoding::BINARY
+ _append(string)
+ else
+ _append(string.b)
+ end
+
+ self
+ end
+ end
+
+ undef_method :concat
+ undef_method :<<
+ end
+
+ ##
+ # The "sync mode" of the SSLSocket.
+ #
+ # See IO#sync for full details.
-module Buffering
- include Enumerable
attr_accessor :sync
+
+ ##
+ # Default size to read from or write to the SSLSocket for buffer operations.
+
BLOCK_SIZE = 1024*16
- def initialize(*args)
+ ##
+ # Creates an instance of OpenSSL's buffering IO module.
+
+ def initialize(*)
+ super
+ @eof = false
+ @rbuffer = Buffer.new
@sync = @io.sync
end
@@ -28,46 +68,171 @@ module Buffering
#
private
+ ##
+ # Fills the buffer from the underlying SSLSocket
+
def fill_rbuff
- @rbuffer = "" unless defined? @rbuffer
begin
- if self.respond_to?(:to_io)
- IO.select([self.to_io], nil, nil)
- end
- @rbuffer << self.sysread(BLOCK_SIZE)
+ @rbuffer.append_as_bytes(self.sysread(BLOCK_SIZE))
+ rescue Errno::EAGAIN
+ retry
rescue EOFError
@eof = true
end
end
+ ##
+ # Consumes _size_ bytes from the buffer
+
def consume_rbuff(size=nil)
- if @rbuffer.size == 0
- @eof = nil
+ if @rbuffer.empty?
nil
else
size = @rbuffer.size unless size
- ret = @rbuffer[0, size]
- @rbuffer[0, size] = ""
- ret
+ @rbuffer.slice!(0, size)
end
end
public
- def read(size=nil)
- fill_rbuff unless defined? @rbuffer
- @eof ||= nil
+ # call-seq:
+ # ssl.getbyte => 81
+ #
+ # Get the next 8bit byte from `ssl`. Returns `nil` on EOF
+ def getbyte
+ read(1)&.ord
+ end
+
+ # Get the next 8bit byte. Raises EOFError on EOF
+ def readbyte
+ raise EOFError if eof?
+ getbyte
+ end
+
+ ##
+ # Reads _size_ bytes from the stream. If _buf_ is provided it must
+ # reference a string which will receive the data.
+ #
+ # See IO#read for full details.
+
+ def read(size=nil, buf=nil)
+ if size == 0
+ if buf
+ buf.clear
+ return buf
+ else
+ return ""
+ end
+ end
until @eof
break if size && size <= @rbuffer.size
fill_rbuff
end
- consume_rbuff(size)
+ ret = consume_rbuff(size) || ""
+ if buf
+ buf.replace(ret)
+ ret = buf
+ end
+ (size && ret.empty?) ? nil : ret
end
- def gets(eol=$/)
- fill_rbuff unless defined? @rbuffer
+ ##
+ # Reads at most _maxlen_ bytes from the stream. If _buf_ is provided it
+ # must reference a string which will receive the data.
+ #
+ # See IO#readpartial for full details.
+
+ def readpartial(maxlen, buf=nil)
+ if maxlen == 0
+ if buf
+ buf.clear
+ return buf
+ else
+ return ""
+ end
+ end
+ if @rbuffer.empty?
+ begin
+ return sysread(maxlen, buf)
+ rescue Errno::EAGAIN
+ retry
+ end
+ end
+ ret = consume_rbuff(maxlen)
+ if buf
+ buf.replace(ret)
+ ret = buf
+ end
+ ret
+ end
+
+ ##
+ # Reads at most _maxlen_ bytes in the non-blocking manner.
+ #
+ # When no data can be read without blocking it raises
+ # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
+ #
+ # IO::WaitReadable means SSL needs to read internally so read_nonblock
+ # should be called again when the underlying IO is readable.
+ #
+ # IO::WaitWritable means SSL needs to write internally so read_nonblock
+ # should be called again after the underlying IO is writable.
+ #
+ # OpenSSL::Buffering#read_nonblock needs two rescue clause as follows:
+ #
+ # # emulates blocking read (readpartial).
+ # begin
+ # result = ssl.read_nonblock(maxlen)
+ # rescue IO::WaitReadable
+ # IO.select([io])
+ # retry
+ # rescue IO::WaitWritable
+ # IO.select(nil, [io])
+ # retry
+ # end
+ #
+ # Note that one reason that read_nonblock writes to the underlying IO is
+ # when the peer requests a new TLS/SSL handshake. See openssl the FAQ for
+ # more details. http://www.openssl.org/support/faq.html
+ #
+ # By specifying a keyword argument _exception_ to +false+, you can indicate
+ # that read_nonblock should not raise an IO::Wait*able exception, but
+ # return the symbol +:wait_writable+ or +:wait_readable+ instead. At EOF,
+ # it will return +nil+ instead of raising EOFError.
+
+ def read_nonblock(maxlen, buf=nil, exception: true)
+ if maxlen == 0
+ if buf
+ buf.clear
+ return buf
+ else
+ return ""
+ end
+ end
+ if @rbuffer.empty?
+ return sysread_nonblock(maxlen, buf, exception: exception)
+ end
+ ret = consume_rbuff(maxlen)
+ if buf
+ buf.replace(ret)
+ ret = buf
+ end
+ ret
+ end
+
+ ##
+ # Reads the next "line" from the stream. Lines are separated by _eol_. If
+ # _limit_ is provided the result will not be longer than the given number of
+ # bytes.
+ #
+ # _eol_ may be a String or Regexp.
+ #
+ # Unlike IO#gets the line read will not be assigned to +$_+.
+ #
+ # Unlike IO#gets the separator must be provided if a limit is provided.
+
+ def gets(eol=$/, limit=nil, chomp: false)
idx = @rbuffer.index(eol)
- @eof ||= nil
until @eof
break if idx
fill_rbuff
@@ -78,16 +243,34 @@ module Buffering
else
size = idx ? idx+eol.size : nil
end
- consume_rbuff(size)
+ if size && limit && limit >= 0
+ size = [size, limit].min
+ end
+ line = consume_rbuff(size)
+ if chomp && line
+ line.chomp!(eol)
+ end
+ line
end
+ ##
+ # Executes the block for every line in the stream where lines are separated
+ # by _eol_.
+ #
+ # See also #gets
+
def each(eol=$/)
- while line = self.gets(eol?)
+ while line = self.gets(eol)
yield line
end
end
alias each_line each
+ ##
+ # Reads lines from the stream which are separated by _eol_.
+ #
+ # See also #gets
+
def readlines(eol=$/)
ary = []
while line = self.gets(eol)
@@ -96,34 +279,61 @@ module Buffering
ary
end
+ ##
+ # Reads a line from the stream which is separated by _eol_.
+ #
+ # Raises EOFError if at end of file.
+
def readline(eol=$/)
- raise EOFErorr if eof?
+ raise EOFError if eof?
gets(eol)
end
+ ##
+ # Reads one character from the stream. Returns nil if called at end of
+ # file.
+
def getc
- c = read(1)
- c ? c.to_i : nil
+ read(1)
end
- def each_byte
+ ##
+ # Calls the given block once for each byte in the stream.
+
+ def each_byte # :yields: byte
while c = getc
- yield(c)
+ yield(c.ord)
end
end
+ ##
+ # Reads a one-character string from the stream. Raises an EOFError at end
+ # of file.
+
def readchar
- raise EOFErorr if eof?
+ raise EOFError if eof?
getc
end
+ ##
+ # Pushes character _c_ back onto the stream such that a subsequent buffered
+ # character read will return it.
+ #
+ # Unlike IO#getc multiple bytes may be pushed back onto the stream.
+ #
+ # Has no effect on unbuffered reads (such as #sysread).
+
def ungetc(c)
@rbuffer[0,0] = c.chr
end
+ ##
+ # Returns true if the stream is at file which means there is no more data to
+ # be read.
+
def eof?
- @eof ||= nil
- @eof && @rbuffer.size == 0
+ fill_rbuff if !@eof && @rbuffer.empty?
+ @eof && @rbuffer.empty?
end
alias eof eof?
@@ -132,65 +342,162 @@ module Buffering
#
private
+ ##
+ # Writes _s_ to the buffer. When the buffer is full or #sync is true the
+ # buffer is flushed to the underlying socket.
+
def do_write(s)
- @wbuffer = "" unless defined? @wbuffer
- @wbuffer << s
+ @wbuffer = Buffer.new unless defined? @wbuffer
+ @wbuffer.append_as_bytes(s)
+
@sync ||= false
- if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/)
- remain = idx ? idx + $/.size : @wbuffer.length
- nwritten = 0
- while remain > 0
- nwrote = syswrite(@wbuffer[nwritten,remain])
- remain -= nwrote
- nwritten += nwrote
+ buffer_size = @wbuffer.bytesize
+ if @sync or buffer_size > BLOCK_SIZE
+ nwrote = 0
+ begin
+ while nwrote < buffer_size do
+ begin
+ chunk = if nwrote > 0
+ @wbuffer.byteslice(nwrote, @wbuffer.bytesize)
+ else
+ @wbuffer
+ end
+
+ nwrote += syswrite(chunk)
+ rescue Errno::EAGAIN
+ retry
+ end
+ end
+ ensure
+ if nwrote < @wbuffer.bytesize
+ @wbuffer[0, nwrote] = ""
+ else
+ @wbuffer.clear
+ end
end
- @wbuffer = ""
end
end
public
- def write(s)
- do_write(s)
- s.length
+ ##
+ # Writes _s_ to the stream. If the argument is not a String it will be
+ # converted using +.to_s+ method. Returns the number of bytes written.
+
+ def write(*s)
+ s.inject(0) do |written, str|
+ do_write(str)
+ written + str.bytesize
+ end
+ end
+
+ ##
+ # Writes _s_ in the non-blocking manner.
+ #
+ # If there is buffered data, it is flushed first. This may block.
+ #
+ # write_nonblock returns number of bytes written to the SSL connection.
+ #
+ # When no data can be written without blocking it raises
+ # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
+ #
+ # IO::WaitReadable means SSL needs to read internally so write_nonblock
+ # should be called again after the underlying IO is readable.
+ #
+ # IO::WaitWritable means SSL needs to write internally so write_nonblock
+ # should be called again after underlying IO is writable.
+ #
+ # So OpenSSL::Buffering#write_nonblock needs two rescue clause as follows.
+ #
+ # # emulates blocking write.
+ # begin
+ # result = ssl.write_nonblock(str)
+ # rescue IO::WaitReadable
+ # IO.select([io])
+ # retry
+ # rescue IO::WaitWritable
+ # IO.select(nil, [io])
+ # retry
+ # end
+ #
+ # Note that one reason that write_nonblock reads from the underlying IO
+ # is when the peer requests a new TLS/SSL handshake. See the openssl FAQ
+ # for more details. http://www.openssl.org/support/faq.html
+ #
+ # By specifying a keyword argument _exception_ to +false+, you can indicate
+ # that write_nonblock should not raise an IO::Wait*able exception, but
+ # return the symbol +:wait_writable+ or +:wait_readable+ instead.
+
+ def write_nonblock(s, exception: true)
+ flush
+ syswrite_nonblock(s, exception: exception)
end
- def << (s)
+ ##
+ # Writes _s_ to the stream. _s_ will be converted to a String using
+ # +.to_s+ method.
+
+ def <<(s)
do_write(s)
self
end
+ ##
+ # Writes _args_ to the stream along with a record separator.
+ #
+ # See IO#puts for full details.
+
def puts(*args)
- s = ""
+ s = Buffer.new
+ if args.empty?
+ s.append_as_bytes("\n")
+ end
args.each{|arg|
- s << arg.to_s
- unless /#{$/}\Z/o =~ s
- s << $/
- end
+ s.append_as_bytes(arg.to_s)
+ s.sub!(/(?<!\n)\z/, "\n")
}
do_write(s)
nil
end
+ ##
+ # Writes _args_ to the stream.
+ #
+ # See IO#print for full details.
+
def print(*args)
- s = ""
- args.each{ |arg| s << arg.to_s }
+ s = Buffer.new
+ args.each{ |arg| s.append_as_bytes(arg.to_s) }
do_write(s)
nil
end
+ ##
+ # Formats and writes to the stream converting parameters under control of
+ # the format string.
+ #
+ # See Kernel#sprintf for format string details.
+
def printf(s, *args)
do_write(s % args)
nil
end
+ ##
+ # Flushes buffered data to the SSLSocket.
+
def flush
osync = @sync
@sync = true
do_write ""
+ return self
+ ensure
@sync = osync
end
+ ##
+ # Closes the SSLSocket and flushes any unwritten data.
+
def close
flush rescue nil
sysclose
diff --git a/ext/openssl/lib/openssl/cipher.rb b/ext/openssl/lib/openssl/cipher.rb
index 11153104ee..ab75ac8e1a 100644
--- a/ext/openssl/lib/openssl/cipher.rb
+++ b/ext/openssl/lib/openssl/cipher.rb
@@ -1,52 +1,67 @@
-=begin
-= $RCSfile$ -- Ruby-space predefined Cipher subclasses
-
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
- All rights reserved.
-
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Version
- $Id$
-=end
-
-##
-# Should we care what if somebody require this file directly?
-#require 'openssl'
+# frozen_string_literal: true
+#--
+# = Ruby-space predefined Cipher subclasses
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'COPYING'.)
+#++
module OpenSSL
- module Cipher
- %w(AES Cast5 BF DES Idea RC2 RC4 RC5).each{|cipher|
- eval(<<-EOD)
- class #{cipher} < Cipher
- def initialize(*args)
- args = args.join('-')
- if args.size == 0
- super(\"#{cipher}\")
- else
- super(\"#{cipher}-#\{args\}\")
- end
- end
- end
- EOD
+ class Cipher
+ %w(AES CAST5 BF DES IDEA RC2 RC4 RC5).each{|name|
+ klass = Class.new(Cipher){
+ define_method(:initialize){|*args|
+ cipher_name = args.inject(name){|n, arg| "#{n}-#{arg}" }
+ super(cipher_name.downcase)
+ }
+ }
+ const_set(name, klass)
}
- class Cipher
- def random_key
- str = OpenSSL::Random.random_bytes(self.key_len)
- self.key = str
- return str
- end
+ %w(128 192 256).each{|keylen|
+ klass = Class.new(Cipher){
+ define_method(:initialize){|mode = "CBC"|
+ super("aes-#{keylen}-#{mode}".downcase)
+ }
+ }
+ const_set("AES#{keylen}", klass)
+ }
+
+ # call-seq:
+ # cipher.random_key -> key
+ #
+ # Generate a random key with OpenSSL::Random.random_bytes and sets it to
+ # the cipher, and returns it.
+ #
+ # You must call #encrypt or #decrypt before calling this method.
+ def random_key
+ str = OpenSSL::Random.random_bytes(self.key_len)
+ self.key = str
+ end
- def random_iv
- str = OpenSSL::Random.random_bytes(self.iv_len)
- self.iv = str
- return str
- end
+ # call-seq:
+ # cipher.random_iv -> iv
+ #
+ # Generate a random IV with OpenSSL::Random.random_bytes and sets it to the
+ # cipher, and returns it.
+ #
+ # You must call #encrypt or #decrypt before calling this method.
+ def random_iv
+ str = OpenSSL::Random.random_bytes(self.iv_len)
+ self.iv = str
end
+
+ # Deprecated.
+ #
+ # This class is only provided for backwards compatibility.
+ # Use OpenSSL::Cipher.
+ class Cipher < Cipher; end
+ deprecate_constant :Cipher
end # Cipher
end # OpenSSL
diff --git a/ext/openssl/lib/openssl/digest.rb b/ext/openssl/lib/openssl/digest.rb
index 58622f543e..4e6dea8d03 100644
--- a/ext/openssl/lib/openssl/digest.rb
+++ b/ext/openssl/lib/openssl/digest.rb
@@ -1,44 +1,73 @@
-=begin
-= $RCSfile$ -- Ruby-space predefined Digest subclasses
+# frozen_string_literal: true
+#--
+# = Ruby-space predefined Digest subclasses
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'COPYING'.)
+#++
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
- All rights reserved.
+module OpenSSL
+ class Digest
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
+ # Return the hash value computed with _name_ Digest. _name_ is either the
+ # long name or short name of a supported digest algorithm.
+ #
+ # === Example
+ #
+ # OpenSSL::Digest.digest("SHA256", "abc")
-= Version
- $Id$
-=end
+ def self.digest(name, data)
+ super(data, name)
+ end
-##
-# Should we care what if somebody require this file directly?
-#require 'openssl'
+ %w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512).each do |name|
+ klass = Class.new(self)
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def initialize(data = nil)
+ super("#{name}", data)
+ end
+ RUBY
-module OpenSSL
- module Digest
-
- %w(DSS DSS1 MD2 MD4 MD5 MDC2 RIPEMD160 SHA SHA1).each{|digest|
- eval(<<-EOD)
- class #{digest} < Digest
- def initialize(data=nil)
- super(\"#{digest}\", data)
- end
-
- def #{digest}::digest(data)
- Digest::digest(\"#{digest}\", data)
- end
-
- def #{digest}::hexdigest(data)
- Digest::hexdigest(\"#{digest}\", data)
- end
+ klass.singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def digest(data)
+ new.digest(data)
end
- EOD
- }
+ def hexdigest(data)
+ new.hexdigest(data)
+ end
+ RUBY
+ const_set(name.tr('-', '_'), klass)
+ end
+
+ # Deprecated.
+ #
+ # This class is only provided for backwards compatibility.
+ # Use OpenSSL::Digest instead.
+ class Digest < Digest; end # :nodoc:
+ deprecate_constant :Digest
end # Digest
-end # OpenSSL
+ # Returns a Digest subclass by _name_
+ #
+ # require 'openssl'
+ #
+ # OpenSSL::Digest("MD5")
+ # # => OpenSSL::Digest::MD5
+ #
+ # OpenSSL::Digest("Foo")
+ # # => NameError: wrong constant name Foo
+
+ def Digest(name)
+ OpenSSL::Digest.const_get(name)
+ end
+
+ module_function :Digest
+
+end # OpenSSL
diff --git a/ext/openssl/lib/openssl/hmac.rb b/ext/openssl/lib/openssl/hmac.rb
new file mode 100644
index 0000000000..c8c844d8d7
--- /dev/null
+++ b/ext/openssl/lib/openssl/hmac.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module OpenSSL
+ class HMAC
+ # Securely compare with another HMAC instance in constant time.
+ def ==(other)
+ return false unless HMAC === other
+ return false unless self.digest.bytesize == other.digest.bytesize
+
+ OpenSSL.fixed_length_secure_compare(self.digest, other.digest)
+ end
+
+ # :call-seq:
+ # hmac.base64digest -> string
+ #
+ # Returns the authentication code an a Base64-encoded string.
+ def base64digest
+ [digest].pack("m0")
+ end
+
+ class << self
+ # :call-seq:
+ # HMAC.digest(digest, key, data) -> aString
+ #
+ # Returns the authentication code as a binary string. The _digest_ parameter
+ # specifies the digest algorithm to use. This may be a String representing
+ # the algorithm name or an instance of OpenSSL::Digest.
+ #
+ # === Example
+ # key = 'key'
+ # data = 'The quick brown fox jumps over the lazy dog'
+ #
+ # hmac = OpenSSL::HMAC.digest('SHA1', key, data)
+ # #=> "\xDE|\x9B\x85\xB8\xB7\x8A\xA6\xBC\x8Az6\xF7\n\x90p\x1C\x9D\xB4\xD9"
+ def digest(digest, key, data)
+ hmac = new(key, digest)
+ hmac << data
+ hmac.digest
+ end
+
+ # :call-seq:
+ # HMAC.hexdigest(digest, key, data) -> aString
+ #
+ # Returns the authentication code as a hex-encoded string. The _digest_
+ # parameter specifies the digest algorithm to use. This may be a String
+ # representing the algorithm name or an instance of OpenSSL::Digest.
+ #
+ # === Example
+ # key = 'key'
+ # data = 'The quick brown fox jumps over the lazy dog'
+ #
+ # hmac = OpenSSL::HMAC.hexdigest('SHA1', key, data)
+ # #=> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"
+ def hexdigest(digest, key, data)
+ hmac = new(key, digest)
+ hmac << data
+ hmac.hexdigest
+ end
+
+ # :call-seq:
+ # HMAC.base64digest(digest, key, data) -> aString
+ #
+ # Returns the authentication code as a Base64-encoded string. The _digest_
+ # parameter specifies the digest algorithm to use. This may be a String
+ # representing the algorithm name or an instance of OpenSSL::Digest.
+ #
+ # === Example
+ # key = 'key'
+ # data = 'The quick brown fox jumps over the lazy dog'
+ #
+ # hmac = OpenSSL::HMAC.base64digest('SHA1', key, data)
+ # #=> "3nybhbi3iqa8ino29wqQcBydtNk="
+ def base64digest(digest, key, data)
+ [digest(digest, key, data)].pack("m0")
+ end
+ end
+ end
+end
diff --git a/ext/openssl/lib/openssl/marshal.rb b/ext/openssl/lib/openssl/marshal.rb
new file mode 100644
index 0000000000..eb8eda4748
--- /dev/null
+++ b/ext/openssl/lib/openssl/marshal.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+#--
+# = Ruby-space definitions to add DER (de)serialization to classes
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'COPYING'.)
+#++
+module OpenSSL
+ module Marshal
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def _load(string)
+ new(string)
+ end
+ end
+
+ def _dump(_level)
+ to_der
+ end
+ end
+end
diff --git a/ext/openssl/lib/openssl/pkcs5.rb b/ext/openssl/lib/openssl/pkcs5.rb
new file mode 100644
index 0000000000..8dedc4beef
--- /dev/null
+++ b/ext/openssl/lib/openssl/pkcs5.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+#--
+# Ruby/OpenSSL Project
+# Copyright (C) 2017 Ruby/OpenSSL Project Authors
+#++
+
+module OpenSSL
+ module PKCS5
+ module_function
+
+ # OpenSSL::PKCS5.pbkdf2_hmac has been renamed to OpenSSL::KDF.pbkdf2_hmac.
+ # This method is provided for backwards compatibility.
+ def pbkdf2_hmac(pass, salt, iter, keylen, digest)
+ OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
+ length: keylen, hash: digest)
+ end
+
+ def pbkdf2_hmac_sha1(pass, salt, iter, keylen)
+ pbkdf2_hmac(pass, salt, iter, keylen, "sha1")
+ end
+ end
+end
diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb
new file mode 100644
index 0000000000..39871e15dd
--- /dev/null
+++ b/ext/openssl/lib/openssl/pkey.rb
@@ -0,0 +1,493 @@
+# frozen_string_literal: true
+#--
+# Ruby/OpenSSL Project
+# Copyright (C) 2017 Ruby/OpenSSL Project Authors
+#++
+
+require_relative 'marshal'
+
+module OpenSSL::PKey
+ # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError.
+ DHError = PKeyError
+
+ class DH
+ include OpenSSL::Marshal
+
+ # :call-seq:
+ # dh.public_key -> dhnew
+ #
+ # Returns a new DH instance that carries just the \DH parameters.
+ #
+ # Contrary to the method name, the returned DH object contains only
+ # parameters and not the public key.
+ #
+ # This method is provided for backwards compatibility. In most cases, there
+ # is no need to call this method.
+ #
+ # For the purpose of re-generating the key pair while keeping the
+ # parameters, check OpenSSL::PKey.generate_key.
+ #
+ # Example:
+ # # OpenSSL::PKey::DH.generate by default generates a random key pair
+ # dh1 = OpenSSL::PKey::DH.generate(2048)
+ # p dh1.priv_key #=> #<OpenSSL::BN 1288347...>
+ # dhcopy = dh1.public_key
+ # p dhcopy.priv_key #=> nil
+ def public_key
+ DH.new(to_der)
+ end
+
+ # :call-seq:
+ # dh.params -> hash
+ #
+ # Stores all parameters of key to a Hash.
+ #
+ # The hash has keys 'p', 'q', 'g', 'pub_key', and 'priv_key'.
+ def params
+ %w{p q g pub_key priv_key}.map { |name|
+ [name, send(name)]
+ }.to_h
+ end
+
+ # :call-seq:
+ # dh.compute_key(pub_bn) -> string
+ #
+ # Returns a String containing a shared secret computed from the other
+ # party's public value.
+ #
+ # This method is provided for backwards compatibility, and calls #derive
+ # internally.
+ #
+ # === Parameters
+ # * _pub_bn_ is a OpenSSL::BN, *not* the DH instance returned by
+ # DH#public_key as that contains the DH parameters only.
+ def compute_key(pub_bn)
+ # FIXME: This is constructing an X.509 SubjectPublicKeyInfo and is very
+ # inefficient
+ obj = OpenSSL::ASN1.Sequence([
+ OpenSSL::ASN1.Sequence([
+ OpenSSL::ASN1.ObjectId("dhKeyAgreement"),
+ OpenSSL::ASN1.Sequence([
+ OpenSSL::ASN1.Integer(p),
+ OpenSSL::ASN1.Integer(g),
+ ]),
+ ]),
+ OpenSSL::ASN1.BitString(OpenSSL::ASN1.Integer(pub_bn).to_der),
+ ])
+ derive(OpenSSL::PKey.read(obj.to_der))
+ end
+
+ # :call-seq:
+ # dh.generate_key! -> self
+ #
+ # Generates a private and public key unless a private key already exists.
+ # If this DH instance was generated from public \DH parameters (e.g. by
+ # encoding the result of DH#public_key), then this method needs to be
+ # called first in order to generate the per-session keys before performing
+ # the actual key exchange.
+ #
+ # <b>Deprecated in version 3.0</b>. This method is incompatible with
+ # OpenSSL 3.0.0 or later.
+ #
+ # See also OpenSSL::PKey.generate_key.
+ #
+ # Example:
+ # # DEPRECATED USAGE: This will not work on OpenSSL 3.0 or later
+ # dh0 = OpenSSL::PKey::DH.new(2048)
+ # dh = dh0.public_key # #public_key only copies the DH parameters (contrary to the name)
+ # dh.generate_key!
+ # puts dh.private? # => true
+ # puts dh0.pub_key == dh.pub_key #=> false
+ #
+ # # With OpenSSL::PKey.generate_key
+ # dh0 = OpenSSL::PKey::DH.new(2048)
+ # dh = OpenSSL::PKey.generate_key(dh0)
+ # puts dh0.pub_key == dh.pub_key #=> false
+ def generate_key!
+ if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000
+ raise PKeyError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \
+ "use OpenSSL::PKey.generate_key instead"
+ end
+
+ unless priv_key
+ tmp = OpenSSL::PKey.generate_key(self)
+ set_key(tmp.pub_key, tmp.priv_key)
+ end
+ self
+ end
+
+ class << self
+ # :call-seq:
+ # DH.generate(size, generator = 2) -> dh
+ #
+ # Creates a new DH instance from scratch by generating random parameters
+ # and a key pair.
+ #
+ # See also OpenSSL::PKey.generate_parameters and
+ # OpenSSL::PKey.generate_key.
+ #
+ # +size+::
+ # The desired key size in bits.
+ # +generator+::
+ # The generator.
+ def generate(size, generator = 2, &blk)
+ dhparams = OpenSSL::PKey.generate_parameters("DH", {
+ "dh_paramgen_prime_len" => size,
+ "dh_paramgen_generator" => generator,
+ }, &blk)
+ OpenSSL::PKey.generate_key(dhparams)
+ end
+
+ # Handle DH.new(size, generator) form here; new(str) and new() forms
+ # are handled by #initialize
+ def new(*args, &blk) # :nodoc:
+ if args[0].is_a?(Integer)
+ generate(*args, &blk)
+ else
+ super
+ end
+ end
+ end
+ end
+
+ # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError.
+ DSAError = PKeyError
+
+ class DSA
+ include OpenSSL::Marshal
+
+ # :call-seq:
+ # dsa.public_key -> dsanew
+ #
+ # Returns a new DSA instance that carries just the \DSA parameters and the
+ # public key.
+ #
+ # This method is provided for backwards compatibility. In most cases, there
+ # is no need to call this method.
+ #
+ # For the purpose of serializing the public key, to PEM or DER encoding of
+ # X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
+ # PKey#public_to_der.
+ def public_key
+ OpenSSL::PKey.read(public_to_der)
+ end
+
+ # :call-seq:
+ # dsa.params -> hash
+ #
+ # Stores all parameters of key to a Hash.
+ #
+ # The hash has keys 'p', 'q', 'g', 'pub_key', and 'priv_key'.
+ def params
+ %w{p q g pub_key priv_key}.map { |name|
+ [name, send(name)]
+ }.to_h
+ end
+
+ class << self
+ # :call-seq:
+ # DSA.generate(size) -> dsa
+ #
+ # Creates a new DSA instance by generating a private/public key pair
+ # from scratch.
+ #
+ # See also OpenSSL::PKey.generate_parameters and
+ # OpenSSL::PKey.generate_key.
+ #
+ # +size+::
+ # The desired key size in bits.
+ def generate(size, &blk)
+ # FIPS 186-4 specifies four (L,N) pairs: (1024,160), (2048,224),
+ # (2048,256), and (3072,256).
+ #
+ # q size is derived here with compatibility with
+ # DSA_generator_parameters_ex() which previous versions of ruby/openssl
+ # used to call.
+ qsize = size >= 2048 ? 256 : 160
+ dsaparams = OpenSSL::PKey.generate_parameters("DSA", {
+ "dsa_paramgen_bits" => size,
+ "dsa_paramgen_q_bits" => qsize,
+ }, &blk)
+ OpenSSL::PKey.generate_key(dsaparams)
+ end
+
+ # Handle DSA.new(size) form here; new(str) and new() forms
+ # are handled by #initialize
+ def new(*args, &blk) # :nodoc:
+ if args[0].is_a?(Integer)
+ generate(*args, &blk)
+ else
+ super
+ end
+ end
+ end
+
+ # :call-seq:
+ # dsa.syssign(string) -> string
+ #
+ # Computes and returns the \DSA signature of +string+, where +string+ is
+ # expected to be an already-computed message digest of the original input
+ # data. The signature is issued using the private key of this DSA instance.
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+ #
+ # +string+::
+ # A message digest of the original input data to be signed.
+ #
+ # Example:
+ # dsa = OpenSSL::PKey::DSA.new(2048)
+ # doc = "Sign me"
+ # digest = OpenSSL::Digest.digest('SHA1', doc)
+ #
+ # # With legacy #syssign and #sysverify:
+ # sig = dsa.syssign(digest)
+ # p dsa.sysverify(digest, sig) #=> true
+ #
+ # # With #sign_raw and #verify_raw:
+ # sig = dsa.sign_raw(nil, digest)
+ # p dsa.verify_raw(nil, sig, digest) #=> true
+ def syssign(string)
+ q or raise PKeyError, "incomplete DSA"
+ private? or raise PKeyError, "Private DSA key needed!"
+ sign_raw(nil, string)
+ end
+
+ # :call-seq:
+ # dsa.sysverify(digest, sig) -> true | false
+ #
+ # Verifies whether the signature is valid given the message digest input.
+ # It does so by validating +sig+ using the public key of this DSA instance.
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+ #
+ # +digest+::
+ # A message digest of the original input data to be signed.
+ # +sig+::
+ # A \DSA signature value.
+ def sysverify(digest, sig)
+ verify_raw(nil, sig, digest)
+ end
+ end
+
+ if defined?(EC)
+ # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError.
+ ECError = PKeyError
+
+ class EC
+ include OpenSSL::Marshal
+
+ # :call-seq:
+ # key.dsa_sign_asn1(data) -> String
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+ def dsa_sign_asn1(data)
+ sign_raw(nil, data)
+ end
+
+ # :call-seq:
+ # key.dsa_verify_asn1(data, sig) -> true | false
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
+ def dsa_verify_asn1(data, sig)
+ verify_raw(nil, sig, data)
+ end
+
+ # :call-seq:
+ # ec.dh_compute_key(pubkey) -> string
+ #
+ # Derives a shared secret by ECDH. _pubkey_ must be an instance of
+ # OpenSSL::PKey::EC::Point and must belong to the same group.
+ #
+ # This method is provided for backwards compatibility, and calls #derive
+ # internally.
+ def dh_compute_key(pubkey)
+ obj = OpenSSL::ASN1.Sequence([
+ OpenSSL::ASN1.Sequence([
+ OpenSSL::ASN1.ObjectId("id-ecPublicKey"),
+ group.to_der,
+ ]),
+ OpenSSL::ASN1.BitString(pubkey.to_octet_string(:uncompressed)),
+ ])
+ derive(OpenSSL::PKey.read(obj.to_der))
+ end
+ end
+
+ class EC::Point
+ # :call-seq:
+ # point.to_bn([conversion_form]) -> OpenSSL::BN
+ #
+ # Returns the octet string representation of the EC point as an instance of
+ # OpenSSL::BN.
+ #
+ # If _conversion_form_ is not given, the _point_conversion_form_ attribute
+ # set to the group is used.
+ #
+ # See #to_octet_string for more information.
+ def to_bn(conversion_form = group.point_conversion_form)
+ OpenSSL::BN.new(to_octet_string(conversion_form), 2)
+ end
+ end
+ end
+
+ # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError.
+ RSAError = PKeyError
+
+ class RSA
+ include OpenSSL::Marshal
+
+ # :call-seq:
+ # rsa.public_key -> rsanew
+ #
+ # Returns a new RSA instance that carries just the public key components.
+ #
+ # This method is provided for backwards compatibility. In most cases, there
+ # is no need to call this method.
+ #
+ # For the purpose of serializing the public key, to PEM or DER encoding of
+ # X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
+ # PKey#public_to_der.
+ def public_key
+ OpenSSL::PKey.read(public_to_der)
+ end
+
+ # :call-seq:
+ # rsa.params -> hash
+ #
+ # Stores all parameters of key to a Hash.
+ #
+ # The hash has keys 'n', 'e', 'd', 'p', 'q', 'dmp1', 'dmq1', and 'iqmp'.
+ def params
+ %w{n e d p q dmp1 dmq1 iqmp}.map { |name|
+ [name, send(name)]
+ }.to_h
+ end
+
+ class << self
+ # :call-seq:
+ # RSA.generate(size, exponent = 65537) -> RSA
+ #
+ # Generates an \RSA keypair.
+ #
+ # See also OpenSSL::PKey.generate_key.
+ #
+ # +size+::
+ # The desired key size in bits.
+ # +exponent+::
+ # An odd Integer, normally 3, 17, or 65537.
+ def generate(size, exp = 0x10001, &blk)
+ OpenSSL::PKey.generate_key("RSA", {
+ "rsa_keygen_bits" => size,
+ "rsa_keygen_pubexp" => exp,
+ }, &blk)
+ end
+
+ # Handle RSA.new(size, exponent) form here; new(str) and new() forms
+ # are handled by #initialize
+ def new(*args, &blk) # :nodoc:
+ if args[0].is_a?(Integer)
+ generate(*args, &blk)
+ else
+ super
+ end
+ end
+ end
+
+ # :call-seq:
+ # rsa.private_encrypt(string) -> String
+ # rsa.private_encrypt(string, padding) -> String
+ #
+ # Encrypt +string+ with the private key. +padding+ defaults to
+ # PKCS1_PADDING, which is known to be insecure but is kept for backwards
+ # compatibility. The encrypted string output can be decrypted using
+ # #public_decrypt.
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and
+ # PKey::PKey#verify_recover instead.
+ def private_encrypt(string, padding = PKCS1_PADDING)
+ n or raise PKeyError, "incomplete RSA"
+ private? or raise PKeyError, "private key needed."
+ sign_raw(nil, string, {
+ "rsa_padding_mode" => translate_padding_mode(padding),
+ })
+ end
+
+ # :call-seq:
+ # rsa.public_decrypt(string) -> String
+ # rsa.public_decrypt(string, padding) -> String
+ #
+ # Decrypt +string+, which has been encrypted with the private key, with the
+ # public key. +padding+ defaults to PKCS1_PADDING which is known to be
+ # insecure but is kept for backwards compatibility.
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and
+ # PKey::PKey#verify_recover instead.
+ def public_decrypt(string, padding = PKCS1_PADDING)
+ n or raise PKeyError, "incomplete RSA"
+ verify_recover(nil, string, {
+ "rsa_padding_mode" => translate_padding_mode(padding),
+ })
+ end
+
+ # :call-seq:
+ # rsa.public_encrypt(string) -> String
+ # rsa.public_encrypt(string, padding) -> String
+ #
+ # Encrypt +string+ with the public key. +padding+ defaults to
+ # PKCS1_PADDING, which is known to be insecure but is kept for backwards
+ # compatibility. The encrypted string output can be decrypted using
+ # #private_decrypt.
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead.
+ def public_encrypt(data, padding = PKCS1_PADDING)
+ n or raise PKeyError, "incomplete RSA"
+ encrypt(data, {
+ "rsa_padding_mode" => translate_padding_mode(padding),
+ })
+ end
+
+ # :call-seq:
+ # rsa.private_decrypt(string) -> String
+ # rsa.private_decrypt(string, padding) -> String
+ #
+ # Decrypt +string+, which has been encrypted with the public key, with the
+ # private key. +padding+ defaults to PKCS1_PADDING, which is known to be
+ # insecure but is kept for backwards compatibility.
+ #
+ # <b>Deprecated in version 3.0</b>.
+ # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead.
+ def private_decrypt(data, padding = PKCS1_PADDING)
+ n or raise PKeyError, "incomplete RSA"
+ private? or raise PKeyError, "private key needed."
+ decrypt(data, {
+ "rsa_padding_mode" => translate_padding_mode(padding),
+ })
+ end
+
+ PKCS1_PADDING = 1
+ SSLV23_PADDING = 2
+ NO_PADDING = 3
+ PKCS1_OAEP_PADDING = 4
+
+ private def translate_padding_mode(num)
+ case num
+ when PKCS1_PADDING
+ "pkcs1"
+ when SSLV23_PADDING
+ "sslv23"
+ when NO_PADDING
+ "none"
+ when PKCS1_OAEP_PADDING
+ "oaep"
+ else
+ raise PKeyError, "unsupported padding mode"
+ end
+ end
+ end
+end
diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb
index 811a935509..3268c126b9 100644
--- a/ext/openssl/lib/openssl/ssl.rb
+++ b/ext/openssl/lib/openssl/ssl.rb
@@ -1,24 +1,184 @@
+# frozen_string_literal: true
=begin
-= $RCSfile$ -- Ruby-space definitions that completes C-space funcs for SSL
-
= Info
'OpenSSL for Ruby 2' project
Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
All rights reserved.
= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Version
- $Id$
+ This program is licensed under the same licence as Ruby.
+ (See the file 'COPYING'.)
=end
-require 'openssl/buffering'
+require "openssl/buffering"
+
+if defined?(OpenSSL::SSL)
+
+require "io/nonblock"
+require "ipaddr"
+require "socket"
module OpenSSL
module SSL
+ class SSLContext
+ DEFAULT_PARAMS = { # :nodoc:
+ :verify_mode => OpenSSL::SSL::VERIFY_PEER,
+ :verify_hostname => true,
+ :options => -> {
+ opts = OpenSSL::SSL::OP_ALL
+ opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
+ opts |= OpenSSL::SSL::OP_NO_COMPRESSION
+ opts
+ }.call
+ }
+
+ if !OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL")
+ DEFAULT_PARAMS.merge!(
+ min_version: OpenSSL::SSL::TLS1_VERSION,
+ ciphers: %w{
+ ECDHE-ECDSA-AES128-GCM-SHA256
+ ECDHE-RSA-AES128-GCM-SHA256
+ ECDHE-ECDSA-AES256-GCM-SHA384
+ ECDHE-RSA-AES256-GCM-SHA384
+ DHE-RSA-AES128-GCM-SHA256
+ DHE-DSS-AES128-GCM-SHA256
+ DHE-RSA-AES256-GCM-SHA384
+ DHE-DSS-AES256-GCM-SHA384
+ ECDHE-ECDSA-AES128-SHA256
+ ECDHE-RSA-AES128-SHA256
+ ECDHE-ECDSA-AES128-SHA
+ ECDHE-RSA-AES128-SHA
+ ECDHE-ECDSA-AES256-SHA384
+ ECDHE-RSA-AES256-SHA384
+ ECDHE-ECDSA-AES256-SHA
+ ECDHE-RSA-AES256-SHA
+ DHE-RSA-AES128-SHA256
+ DHE-RSA-AES256-SHA256
+ DHE-RSA-AES128-SHA
+ DHE-RSA-AES256-SHA
+ DHE-DSS-AES128-SHA256
+ DHE-DSS-AES256-SHA256
+ DHE-DSS-AES128-SHA
+ DHE-DSS-AES256-SHA
+ AES128-GCM-SHA256
+ AES256-GCM-SHA384
+ AES128-SHA256
+ AES256-SHA256
+ AES128-SHA
+ AES256-SHA
+ }.join(":").freeze,
+ )
+ end
+ DEFAULT_PARAMS.freeze
+
+ DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc:
+ DEFAULT_CERT_STORE.set_default_paths
+
+ # A callback invoked at connect time to distinguish between multiple
+ # server names.
+ #
+ # The callback is invoked with an SSLSocket and a server name. The
+ # callback must return an SSLContext for the server name or nil.
+ attr_accessor :servername_cb
+
+ # call-seq:
+ # SSLContext.new -> ctx
+ # SSLContext.new(:TLSv1) -> ctx
+ # SSLContext.new("SSLv23") -> ctx
+ #
+ # Creates a new SSL context.
+ #
+ # If an argument is given, #ssl_version= is called with the value. Note
+ # that this form is deprecated. New applications should use #min_version=
+ # and #max_version= as necessary.
+ def initialize(version = nil)
+ self.ssl_version = version if version
+ self.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ self.verify_hostname = false
+ end
+
+ ##
+ # call-seq:
+ # ctx.set_params(params = {}) -> params
+ #
+ # Sets saner defaults optimized for the use with HTTP-like protocols.
+ #
+ # If a Hash _params_ is given, the parameters are overridden with it.
+ # The keys in _params_ must be assignment methods on SSLContext.
+ #
+ # If the verify_mode is not VERIFY_NONE and ca_file, ca_path and
+ # cert_store are not set then the system default certificate store is
+ # used.
+ 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
+ if not defined?(Ractor) or Ractor.current == Ractor.main
+ self.cert_store = DEFAULT_CERT_STORE
+ else
+ self.cert_store = Ractor.current[:__openssl_default_store__] ||=
+ OpenSSL::X509::Store.new.tap { |store|
+ store.set_default_paths
+ }
+ end
+ end
+ end
+ return params
+ end
+
+ # call-seq:
+ # ctx.ssl_version = :TLSv1
+ # ctx.ssl_version = "SSLv23"
+ #
+ # Sets the SSL/TLS protocol version for the context. This forces
+ # connections to use only the specified protocol version. This is
+ # deprecated and only provided for backwards compatibility. Use
+ # #min_version= and #max_version= instead.
+ #
+ # === History
+ # As the name hints, this used to call the SSL_CTX_set_ssl_version()
+ # function which sets the SSL method used for connections created from
+ # the context. As of Ruby/OpenSSL 2.1, this accessor method is
+ # implemented to call #min_version= and #max_version= instead.
+ 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
+ self.min_version = self.max_version = version
+ end
+
+ METHODS_MAP = {
+ SSLv23: 0,
+ SSLv2: OpenSSL::SSL::SSL2_VERSION,
+ SSLv3: OpenSSL::SSL::SSL3_VERSION,
+ TLSv1: OpenSSL::SSL::TLS1_VERSION,
+ TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION,
+ TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION,
+ }.freeze
+ private_constant :METHODS_MAP
+
+ # The list of available SSL/TLS methods. This constant is only provided
+ # for backwards compatibility.
+ METHODS = METHODS_MAP.flat_map { |name,|
+ [name, :"#{name}_client", :"#{name}_server"]
+ }.freeze
+ deprecate_constant :METHODS
+ end
+
module SocketForwarder
+ # The file descriptor for the socket.
+ def fileno
+ to_io.fileno
+ end
+
def addr
to_io.addr
end
@@ -27,12 +187,20 @@ module OpenSSL
to_io.peeraddr
end
- def getsockopt(level, optname, optval)
+ def local_address
+ to_io.local_address
+ end
+
+ def remote_address
+ to_io.remote_address
+ end
+
+ def setsockopt(level, optname, optval)
to_io.setsockopt(level, optname, optval)
end
- def setsockopt(level, optname)
- to_io.setsockopt(level, optname)
+ def getsockopt(level, optname)
+ to_io.getsockopt(level, optname)
end
def fcntl(*args)
@@ -42,47 +210,333 @@ module OpenSSL
def closed?
to_io.closed?
end
+
+ def do_not_reverse_lookup=(flag)
+ to_io.do_not_reverse_lookup = flag
+ end
+
+ def close_on_exec=(value)
+ to_io.close_on_exec = value
+ end
+
+ def close_on_exec?
+ to_io.close_on_exec?
+ end
+
+ def wait(*args)
+ to_io.wait(*args)
+ end
+
+ def wait_readable(*args)
+ to_io.wait_readable(*args)
+ end
+
+ def wait_writable(*args)
+ to_io.wait_writable(*args)
+ end
+
+ if IO.method_defined?(:timeout)
+ def timeout
+ to_io.timeout
+ end
+
+ def timeout=(value)
+ to_io.timeout=(value)
+ end
+ end
+ end
+
+ def verify_certificate_identity(cert, hostname)
+ should_verify_common_name = true
+ cert.extensions.each{|ext|
+ next if ext.oid != "subjectAltName"
+ ostr = OpenSSL::ASN1.decode(ext.to_der).value.last
+ sequence = OpenSSL::ASN1.decode(ostr.value)
+ sequence.value.each{|san|
+ case san.tag
+ when 2 # dNSName in GeneralName (RFC5280)
+ should_verify_common_name = false
+ return true if verify_hostname(hostname, san.value)
+ when 7 # iPAddress in GeneralName (RFC5280)
+ should_verify_common_name = false
+ if san.value.size == 4 || san.value.size == 16
+ begin
+ return true if san.value == IPAddr.new(hostname).hton
+ rescue IPAddr::InvalidAddressError
+ end
+ end
+ end
+ }
+ }
+ if should_verify_common_name
+ cert.subject.to_a.each{|oid, value|
+ if oid == "CN"
+ return true if verify_hostname(hostname, value)
+ end
+ }
+ end
+ return false
+ end
+ module_function :verify_certificate_identity
+
+ def verify_hostname(hostname, san) # :nodoc:
+ # RFC 5280, IA5String is limited to the set of ASCII characters
+ return false unless san.ascii_only?
+ return false unless hostname.ascii_only?
+
+ # See RFC 6125, section 6.4.1
+ # Matching is case-insensitive.
+ san_parts = san.downcase.split(".")
+
+ # TODO: this behavior should probably be more strict
+ return san == hostname if san_parts.size < 2
+
+ # Matching is case-insensitive.
+ host_parts = hostname.downcase.split(".")
+
+ # RFC 6125, section 6.4.3, subitem 2.
+ # If the wildcard character is the only character of the left-most
+ # label in the presented identifier, the client SHOULD NOT compare
+ # against anything but the left-most label of the reference
+ # identifier (e.g., *.example.com would match foo.example.com but
+ # not bar.foo.example.com or example.com).
+ return false unless san_parts.size == host_parts.size
+
+ # RFC 6125, section 6.4.3, subitem 1.
+ # The client SHOULD NOT attempt to match a presented identifier in
+ # which the wildcard character comprises a label other than the
+ # left-most label (e.g., do not match bar.*.example.net).
+ return false unless verify_wildcard(host_parts.shift, san_parts.shift)
+
+ san_parts.join(".") == host_parts.join(".")
+ end
+ module_function :verify_hostname
+
+ def verify_wildcard(domain_component, san_component) # :nodoc:
+ parts = san_component.split("*", -1)
+
+ return false if parts.size > 2
+ return san_component == domain_component if parts.size == 1
+
+ # RFC 6125, section 6.4.3, subitem 3.
+ # The client SHOULD NOT attempt to match a presented identifier
+ # where the wildcard character is embedded within an A-label or
+ # U-label of an internationalized domain name.
+ return false if domain_component.start_with?("xn--") && san_component != "*"
+
+ parts[0].length + parts[1].length < domain_component.length &&
+ domain_component.start_with?(parts[0]) &&
+ domain_component.end_with?(parts[1])
end
+ module_function :verify_wildcard
class SSLSocket
include Buffering
include SocketForwarder
+
+ attr_reader :hostname
+
+ # The underlying IO object.
+ attr_reader :io
+ alias :to_io :io
+
+ # The SSLContext object used in this connection.
+ attr_reader :context
+
+ # Whether to close the underlying socket as well, when the SSL/TLS
+ # connection is shut down. This defaults to +false+.
+ attr_accessor :sync_close
+
+ # call-seq:
+ # ssl.sysclose => nil
+ #
+ # 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.
+ def sysclose
+ return if closed?
+ stop
+ io.close if sync_close
+ end
+
+ # call-seq:
+ # ssl.post_connection_check(hostname) -> true
+ #
+ # 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.
+ 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
+
+ # call-seq:
+ # ssl.session -> aSession
+ #
+ # Returns the SSLSession object currently used, or nil if the session is
+ # not established.
+ def session
+ SSL::Session.new(self)
+ rescue SSL::Session::SessionError
+ nil
+ end
+
+ # Close the stream for reading.
+ # This method is ignored by OpenSSL as there is no reasonable way to
+ # implement it, but exists for compatibility with IO.
+ def close_read
+ # Unsupported and ignored.
+ # Just don't read any more.
+ 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.
+ def close_write
+ stop
+ end
+
+ private
+
+ def using_anon_cipher?
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.ciphers = "aNULL"
+ ctx.ciphers.include?(cipher)
+ end
+
+ def client_cert_cb
+ @context.client_cert_cb
+ end
+
+ def session_new_cb
+ @context.session_new_cb
+ end
+
+ def session_get_cb
+ @context.session_get_cb
+ end
+
+ class << self
+
+ # call-seq:
+ # open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil)
+ #
+ # 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
+ 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
+ end
end
+ ##
+ # SSLServer represents a TCP/IP server socket with Secure Sockets Layer.
class SSLServer
include SocketForwarder
+ # When true then #accept works exactly the same as TCPServer#accept
attr_accessor :start_immediately
+ # Creates a new instance of SSLServer.
+ # * _srv_ is an instance of TCPServer.
+ # * _ctx_ is an instance of OpenSSL::SSL::SSLContext.
def initialize(svr, ctx)
@svr = svr
@ctx = ctx
+ unless ctx.session_id_context
+ # see #6137 - session id may not exceed 32 bytes
+ prng = ::Random.new($0.hash)
+ session_id = prng.bytes(16).unpack1('H*')
+ @ctx.session_id_context = session_id
+ end
@start_immediately = true
end
+ # Returns the TCPServer passed to the SSLServer when initialized.
def to_io
@svr
end
- def listen(basklog=5)
+ # See TCPServer#listen for details.
+ def listen(backlog=Socket::SOMAXCONN)
@svr.listen(backlog)
end
+ # See BasicSocket#shutdown for details.
+ def shutdown(how=Socket::SHUT_RDWR)
+ @svr.shutdown(how)
+ end
+
+ # Works similar to TCPServer#accept.
def accept
- sock = @svr.accept
+ # Socket#accept returns [socket, addrinfo].
+ # TCPServer#accept returns a socket.
+ # The following comma strips addrinfo.
+ sock, = @svr.accept
begin
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
ssl.sync_close = true
ssl.accept if @start_immediately
ssl
- rescue SSLError => ex
- sock.close
+ rescue Exception => ex
+ if ssl
+ ssl.close
+ else
+ sock.close
+ end
raise ex
end
end
+ # See IO#close for details.
def close
@svr.close
end
end
end
end
+
+end
diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb
new file mode 100644
index 0000000000..395a720a31
--- /dev/null
+++ b/ext/openssl/lib/openssl/version.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+module OpenSSL
+ # The version string of Ruby/OpenSSL.
+ VERSION = "4.0.2"
+end
diff --git a/ext/openssl/lib/openssl/x509.rb b/ext/openssl/lib/openssl/x509.rb
index 40b1a6dd15..66765ffeab 100644
--- a/ext/openssl/lib/openssl/x509.rb
+++ b/ext/openssl/lib/openssl/x509.rb
@@ -1,22 +1,18 @@
-=begin
-= $RCSfile$ -- Ruby-space definitions that completes C-space funcs for X509 and subclasses
+# frozen_string_literal: true
+#--
+# = Ruby-space definitions that completes C-space funcs for X509 and subclasses
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'COPYING'.)
+#++
-= Info
- 'OpenSSL for Ruby 2' project
- Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
- All rights reserved.
-
-= Licence
- This program is licenced under the same licence as Ruby.
- (See the file 'LICENCE'.)
-
-= Version
- $Id$
-=end
-
-##
-# Should we care what if somebody require this file directly?
-#require 'openssl'
+require_relative 'marshal'
module OpenSSL
module X509
@@ -30,7 +26,7 @@ module OpenSSL
end
def create_ext_from_array(ary)
- raise ExtensionError, "unexpected array form" if ary.size > 3
+ raise ExtensionError, "unexpected array form" if ary.size > 3
create_ext(ary[0], ary[1], ary[2])
end
@@ -40,20 +36,27 @@ module OpenSSL
value.strip!
create_ext(oid, value)
end
-
+
def create_ext_from_hash(hash)
create_ext(hash["oid"], hash["value"], hash["critical"])
end
- end # ExtensionFactory
-
+ end
+
class Extension
+ include OpenSSL::Marshal
+
+ def ==(other)
+ return false unless Extension === other
+ to_der == other.to_der
+ end
+
def to_s # "oid = critical, value"
str = self.oid
str << " = "
str << "critical, " if self.critical?
str << self.value.gsub(/\n/, ", ")
end
-
+
def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false}
{"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical?}
end
@@ -61,14 +64,337 @@ module OpenSSL
def to_a
[ self.oid, self.value, self.critical? ]
end
- end # Extension
-
+
+ module Helpers
+ def find_extension(oid)
+ extensions.find { |e| e.oid == oid }
+ end
+ end
+
+ module SubjectKeyIdentifier
+ include Helpers
+
+ # Get the subject's key identifier from the subjectKeyIdentifier
+ # exteension, as described in RFC5280 Section 4.2.1.2.
+ #
+ # Returns the binary String key identifier or nil or raises
+ # ASN1::ASN1Error.
+ def subject_key_identifier
+ ext = find_extension("subjectKeyIdentifier")
+ return nil if ext.nil?
+
+ ski_asn1 = ASN1.decode(ext.value_der)
+ if ext.critical? || ski_asn1.tag_class != :UNIVERSAL || ski_asn1.tag != ASN1::OCTET_STRING
+ raise ASN1::ASN1Error, "invalid extension"
+ end
+
+ ski_asn1.value
+ end
+ end
+
+ module AuthorityKeyIdentifier
+ include Helpers
+
+ # Get the issuing certificate's key identifier from the
+ # authorityKeyIdentifier extension, as described in RFC5280
+ # Section 4.2.1.1
+ #
+ # Returns the binary String keyIdentifier or nil or raises
+ # ASN1::ASN1Error.
+ def authority_key_identifier
+ ext = find_extension("authorityKeyIdentifier")
+ return nil if ext.nil?
+
+ aki_asn1 = ASN1.decode(ext.value_der)
+ if ext.critical? || aki_asn1.tag_class != :UNIVERSAL || aki_asn1.tag != ASN1::SEQUENCE
+ raise ASN1::ASN1Error, "invalid extension"
+ end
+
+ key_id = aki_asn1.value.find do |v|
+ v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0
+ end
+
+ key_id.nil? ? nil : key_id.value
+ end
+ end
+
+ module CRLDistributionPoints
+ include Helpers
+
+ # Get the distributionPoint fullName URI from the certificate's CRL
+ # distribution points extension, as described in RFC 5280 Section
+ # 4.2.1.13.
+ #
+ # Returns an array of strings or nil or raises ASN1::ASN1Error.
+ def crl_uris
+ ext = find_extension("crlDistributionPoints")
+ return nil if ext.nil?
+
+ cdp_asn1 = ASN1.decode(ext.value_der)
+ if cdp_asn1.tag_class != :UNIVERSAL || cdp_asn1.tag != ASN1::SEQUENCE
+ raise ASN1::ASN1Error, "invalid extension"
+ end
+
+ crl_uris = cdp_asn1.flat_map do |crl_distribution_point|
+ distribution_point = crl_distribution_point.value.find do |v|
+ v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0
+ end
+ full_name = distribution_point&.value&.find do |v|
+ v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0
+ end
+ full_name&.value&.select do |v|
+ v.tag_class == :CONTEXT_SPECIFIC && v.tag == 6 # uniformResourceIdentifier
+ end
+ end
+
+ crl_uris.empty? ? nil : crl_uris.map(&:value)
+ end
+ end
+
+ module AuthorityInfoAccess
+ include Helpers
+
+ # Get the information and services for the issuer from the certificate's
+ # authority information access extension exteension, as described in RFC5280
+ # Section 4.2.2.1.
+ #
+ # Returns an array of strings or nil or raises ASN1::ASN1Error.
+ def ca_issuer_uris
+ aia_asn1 = parse_aia_asn1
+ return nil if aia_asn1.nil?
+
+ ca_issuer = aia_asn1.value.select do |authority_info_access|
+ authority_info_access.value.first.value == "caIssuers"
+ end
+
+ ca_issuer&.map(&:value)&.map(&:last)&.map(&:value)
+ end
+
+ # Get the URIs for OCSP from the certificate's authority information access
+ # extension exteension, as described in RFC5280 Section 4.2.2.1.
+ #
+ # Returns an array of strings or nil or raises ASN1::ASN1Error.
+ def ocsp_uris
+ aia_asn1 = parse_aia_asn1
+ return nil if aia_asn1.nil?
+
+ ocsp = aia_asn1.value.select do |authority_info_access|
+ authority_info_access.value.first.value == "OCSP"
+ end
+
+ ocsp&.map(&:value)&.map(&:last)&.map(&:value)
+ end
+
+ private
+
+ def parse_aia_asn1
+ ext = find_extension("authorityInfoAccess")
+ return nil if ext.nil?
+
+ aia_asn1 = ASN1.decode(ext.value_der)
+ if ext.critical? || aia_asn1.tag_class != :UNIVERSAL || aia_asn1.tag != ASN1::SEQUENCE
+ raise ASN1::ASN1Error, "invalid extension"
+ end
+
+ aia_asn1
+ end
+ end
+ end
+
class Name
- def self.parse(str, type=ASN1::UTF8STRING)
- ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=") }
- self.new(ary, type)
+ include OpenSSL::Marshal
+
+ module RFC2253DN
+ Special = ',=+<>#;'
+ HexChar = /[0-9a-fA-F]/
+ HexPair = /#{HexChar}#{HexChar}/
+ HexString = /#{HexPair}+/
+ Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/
+ StringChar = /[^\\"#{Special}]/
+ QuoteChar = /[^\\"]/
+ AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/
+ AttributeValue = /
+ (?!["#])((?:#{StringChar}|#{Pair})*)|
+ \#(#{HexString})|
+ "((?:#{QuoteChar}|#{Pair})*)"
+ /x
+ TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/
+
+ module_function
+
+ def expand_pair(str)
+ return nil unless str
+ return str.gsub(Pair){
+ pair = $&
+ case pair.size
+ when 2 then pair[1,1]
+ when 3 then Integer("0x#{pair[1,2]}").chr
+ else raise OpenSSL::X509::NameError, "invalid pair: #{str}"
+ end
+ }
+ end
+
+ def expand_hexstring(str)
+ return nil unless str
+ der = str.gsub(HexPair){$&.to_i(16).chr }
+ a1 = OpenSSL::ASN1.decode(der)
+ return a1.value, a1.tag
+ end
+
+ def expand_value(str1, str2, str3)
+ value = expand_pair(str1)
+ value, tag = expand_hexstring(str2) unless value
+ value = expand_pair(str3) unless value
+ return value, tag
+ end
+
+ def scan(dn)
+ str = dn
+ ary = []
+ while true
+ if md = TypeAndValue.match(str)
+ remain = md.post_match
+ type = md[1]
+ value, tag = expand_value(md[2], md[3], md[4]) rescue nil
+ if value
+ type_and_value = [type, value]
+ type_and_value.push(tag) if tag
+ ary.unshift(type_and_value)
+ if remain.length > 2 && remain[0] == ?,
+ str = remain[1..-1]
+ next
+ elsif remain.length > 2 && remain[0] == ?+
+ raise OpenSSL::X509::NameError,
+ "multi-valued RDN is not supported: #{dn}"
+ elsif remain.empty?
+ break
+ end
+ end
+ end
+ msg_dn = dn[0, dn.length - str.length] + " =>" + str
+ raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}"
+ end
+ return ary
+ end
+ end
+
+ class << self
+ # Parses the UTF-8 string representation of a distinguished name,
+ # according to RFC 2253.
+ #
+ # See also #to_utf8 for the opposite operation.
+ def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE)
+ ary = OpenSSL::X509::Name::RFC2253DN.scan(str)
+ self.new(ary, template)
+ end
+
+ # Parses the string representation of a distinguished name. Two
+ # different forms are supported:
+ #
+ # - \OpenSSL format (<tt>X509_NAME_oneline()</tt>) used by
+ # <tt>#to_s</tt>. For example: <tt>/DC=com/DC=example/CN=nobody</tt>
+ # - \OpenSSL format (<tt>X509_NAME_print()</tt>)
+ # used by <tt>#to_s(OpenSSL::X509::Name::COMPAT)</tt>. For example:
+ # <tt>DC=com, DC=example, CN=nobody</tt>
+ #
+ # Neither of them is standardized and has quirks and inconsistencies
+ # in handling of escaped characters or multi-valued RDNs.
+ #
+ # Use of this method is discouraged in new applications. See
+ # Name.parse_rfc2253 and #to_utf8 for the alternative.
+ def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE)
+ if str.start_with?("/")
+ # /A=B/C=D format
+ ary = str[1..-1].split("/").map { |i| i.split("=", 2) }
+ else
+ # Comma-separated
+ ary = str.split(",").map { |i| i.strip.split("=", 2) }
+ end
+ self.new(ary, template)
+ end
+
+ alias parse parse_openssl
end
- end # Name
- end # X509
-end # OpenSSL
+ def pretty_print(q)
+ q.object_group(self) {
+ q.text ' '
+ q.text to_s(OpenSSL::X509::Name::RFC2253)
+ }
+ end
+ end
+
+ class Attribute
+ include OpenSSL::Marshal
+
+ def ==(other)
+ return false unless Attribute === other
+ to_der == other.to_der
+ end
+ end
+
+ class StoreContext
+ def cleanup
+ warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE
+ end
+ end
+
+ class Certificate
+ include OpenSSL::Marshal
+ include Extension::SubjectKeyIdentifier
+ include Extension::AuthorityKeyIdentifier
+ include Extension::CRLDistributionPoints
+ include Extension::AuthorityInfoAccess
+
+ def inspect
+ "#<#{self.class}: " \
+ "subject=#{subject.inspect}, " \
+ "issuer=#{issuer.inspect}, " \
+ "serial=#{serial.inspect}, " \
+ "not_before=#{not_before.inspect rescue "(error)"}, " \
+ "not_after=#{not_after.inspect rescue "(error)"}>"
+ end
+
+ def pretty_print(q)
+ q.object_group(self) {
+ q.breakable
+ q.text 'subject='; q.pp self.subject; q.text ','; q.breakable
+ q.text 'issuer='; q.pp self.issuer; q.text ','; q.breakable
+ q.text 'serial='; q.pp self.serial; q.text ','; q.breakable
+ q.text 'not_before='; q.pp self.not_before; q.text ','; q.breakable
+ q.text 'not_after='; q.pp self.not_after
+ }
+ end
+
+ def self.load_file(path)
+ load(File.binread(path))
+ end
+ end
+
+ class CRL
+ include OpenSSL::Marshal
+ include Extension::AuthorityKeyIdentifier
+
+ def ==(other)
+ return false unless CRL === other
+ to_der == other.to_der
+ end
+ end
+
+ class Revoked
+ def ==(other)
+ return false unless Revoked === other
+ to_der == other.to_der
+ end
+ end
+
+ class Request
+ include OpenSSL::Marshal
+
+ def ==(other)
+ return false unless Request === other
+ to_der == other.to_der
+ end
+ end
+ end
+end