diff options
Diffstat (limited to 'ext/openssl/lib')
| -rw-r--r-- | ext/openssl/lib/openssl.rb | 42 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/bn.rb | 13 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/buffering.rb | 170 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/cipher.rb | 48 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/config.rb | 472 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/digest.rb | 69 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/hmac.rb | 78 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/marshal.rb | 30 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/pkcs5.rb | 22 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/pkey.rb | 519 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/ssl.rb | 407 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/version.rb | 6 | ||||
| -rw-r--r-- | ext/openssl/lib/openssl/x509.rb | 236 |
13 files changed, 1308 insertions, 804 deletions
diff --git a/ext/openssl/lib/openssl.rb b/ext/openssl/lib/openssl.rb index c2a17aae4f..98fa8d39f2 100644 --- a/ext/openssl/lib/openssl.rb +++ b/ext/openssl/lib/openssl.rb @@ -1,6 +1,5 @@ +# 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> @@ -8,18 +7,35 @@ = Licence This program is licensed under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ + (See the file 'COPYING'.) =end require 'openssl.so' -require 'openssl/bn' -require 'openssl/pkey' -require 'openssl/cipher' -require 'openssl/config' -require 'openssl/digest' -require 'openssl/x509' -require 'openssl/ssl' +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 1adf89f704..e4889a140c 100644 --- a/ext/openssl/lib/openssl/bn.rb +++ b/ext/openssl/lib/openssl/bn.rb @@ -1,7 +1,6 @@ +# frozen_string_literal: true #-- # -# $RCSfile$ -# # = Ruby-space definitions that completes C-space funcs for BN # # = Info @@ -11,11 +10,7 @@ # # = Licence # This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# +# (See the file 'COPYING'.) #++ module OpenSSL @@ -32,8 +27,9 @@ module OpenSSL end # OpenSSL ## +#-- # Add double dispatch to Integer -# +#++ class Integer # Casts an Integer as an OpenSSL::BN # @@ -42,4 +38,3 @@ class Integer OpenSSL::BN::new(self) end end # Integer - diff --git a/ext/openssl/lib/openssl/buffering.rb b/ext/openssl/lib/openssl/buffering.rb index 63444cc45e..1464a4292d 100644 --- a/ext/openssl/lib/openssl/buffering.rb +++ b/ext/openssl/lib/openssl/buffering.rb @@ -1,7 +1,6 @@ # coding: binary +# frozen_string_literal: true #-- -#= $RCSfile$ -- Buffering mix-in module. -# #= Info # 'OpenSSL for Ruby 2' project # Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org> @@ -9,10 +8,7 @@ # #= Licence # This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -#= Version -# $Id$ +# (See the file 'COPYING'.) #++ ## @@ -26,6 +22,25 @@ module OpenSSL::Buffering include Enumerable + # 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. # @@ -44,7 +59,7 @@ module OpenSSL::Buffering def initialize(*) super @eof = false - @rbuffer = "" + @rbuffer = Buffer.new @sync = @io.sync end @@ -58,7 +73,7 @@ module OpenSSL::Buffering def fill_rbuff begin - @rbuffer << self.sysread(BLOCK_SIZE) + @rbuffer.append_as_bytes(self.sysread(BLOCK_SIZE)) rescue Errno::EAGAIN retry rescue EOFError @@ -67,23 +82,35 @@ module OpenSSL::Buffering end ## - # Consumes +size+ bytes from the buffer + # Consumes _size_ bytes from the buffer def consume_rbuff(size=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 + # 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 + # 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. @@ -110,7 +137,7 @@ module OpenSSL::Buffering end ## - # Reads at most +maxlen+ bytes from the stream. If +buf+ is provided it + # 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. @@ -136,12 +163,11 @@ module OpenSSL::Buffering buf.replace(ret) ret = buf end - raise EOFError if ret.empty? ret end ## - # Reads at most +maxlen+ bytes in the non-blocking manner. + # 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. @@ -168,6 +194,11 @@ module OpenSSL::Buffering # 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 @@ -186,22 +217,21 @@ module OpenSSL::Buffering buf.replace(ret) ret = buf end - raise EOFError if ret.empty? 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 + # 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. + # _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) + def gets(eol=$/, limit=nil, chomp: false) idx = @rbuffer.index(eol) until @eof break if idx @@ -216,12 +246,16 @@ module OpenSSL::Buffering if size && limit && limit >= 0 size = [size, limit].min end - consume_rbuff(size) + 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+. + # by _eol_. # # See also #gets @@ -233,7 +267,7 @@ module OpenSSL::Buffering alias each_line each ## - # Reads lines from the stream which are separated by +eol+. + # Reads lines from the stream which are separated by _eol_. # # See also #gets @@ -246,7 +280,7 @@ module OpenSSL::Buffering end ## - # Reads a line from the stream which is separated by +eol+. + # Reads a line from the stream which is separated by _eol_. # # Raises EOFError if at end of file. @@ -282,7 +316,7 @@ module OpenSSL::Buffering end ## - # Pushes character +c+ back onto the stream such that a subsequent buffered + # 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. @@ -309,44 +343,56 @@ module OpenSSL::Buffering private ## - # Writes +s+ to the buffer. When the buffer is full or #sync is true the + # 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.force_encoding(Encoding::BINARY) + @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 - str = @wbuffer[nwritten,remain] - begin - nwrote = syswrite(str) - rescue Errno::EAGAIN - retry + 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 - remain -= nwrote - nwritten += nwrote end - @wbuffer[0,nwritten] = "" end end public ## - # Writes +s+ to the stream. If the argument is not a string it will be - # converted using String#to_s. Returns the number of bytes written. + # 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) - do_write(s) - s.bytesize + def write(*s) + s.inject(0) do |written, str| + do_write(str) + written + str.bytesize + end end ## - # Writes +str+ in the non-blocking manner. + # Writes _s_ in the non-blocking manner. # # If there is buffered data, it is flushed first. This may block. # @@ -377,6 +423,10 @@ module OpenSSL::Buffering # 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 @@ -384,42 +434,40 @@ module OpenSSL::Buffering end ## - # Writes +s+ to the stream. +s+ will be converted to a String using - # String#to_s. + # Writes _s_ to the stream. _s_ will be converted to a String using + # +.to_s+ method. - def << (s) + def <<(s) do_write(s) self end ## - # Writes +args+ to the stream along with a record separator. + # 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 << "\n" + s.append_as_bytes("\n") end args.each{|arg| - s << arg.to_s - if $/ && /\n\z/ !~ s - s << "\n" - end + s.append_as_bytes(arg.to_s) + s.sub!(/(?<!\n)\z/, "\n") } do_write(s) nil end ## - # Writes +args+ to the stream. + # 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 diff --git a/ext/openssl/lib/openssl/cipher.rb b/ext/openssl/lib/openssl/cipher.rb index aacb02ad17..ab75ac8e1a 100644 --- a/ext/openssl/lib/openssl/cipher.rb +++ b/ext/openssl/lib/openssl/cipher.rb @@ -1,7 +1,5 @@ +# frozen_string_literal: true #-- -# -# $RCSfile$ -# # = Ruby-space predefined Cipher subclasses # # = Info @@ -11,11 +9,7 @@ # # = Licence # This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# +# (See the file 'COPYING'.) #++ module OpenSSL @@ -24,7 +18,7 @@ module OpenSSL klass = Class.new(Cipher){ define_method(:initialize){|*args| cipher_name = args.inject(name){|n, arg| "#{n}-#{arg}" } - super(cipher_name) + super(cipher_name.downcase) } } const_set(name, klass) @@ -32,34 +26,42 @@ module OpenSSL %w(128 192 256).each{|keylen| klass = Class.new(Cipher){ - define_method(:initialize){|mode| - mode ||= "CBC" - cipher_name = "AES-#{keylen}-#{mode}" - super(cipher_name) + define_method(:initialize){|mode = "CBC"| + super("aes-#{keylen}-#{mode}".downcase) } } const_set("AES#{keylen}", klass) } - # Generate, set, and return a random key. - # You must call cipher.encrypt or cipher.decrypt before calling this method. + # 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 - return str end - # Generate, set, and return a random iv. - # You must call cipher.encrypt or cipher.decrypt before calling this method. + # 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 - return str end - # This class is only provided for backwards compatibility. Use OpenSSL::Cipher in the future. - class Cipher < Cipher - # add warning - 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/config.rb b/ext/openssl/lib/openssl/config.rb deleted file mode 100644 index e1f5dbeab8..0000000000 --- a/ext/openssl/lib/openssl/config.rb +++ /dev/null @@ -1,472 +0,0 @@ -=begin -= Ruby-space definitions that completes C-space funcs for Config - -= Info - Copyright (C) 2010 Hiroshi Nakamura <nahi@ruby-lang.org> - -= Licence - This program is licensed under the same licence as Ruby. - (See the file 'LICENCE'.) - -=end - -require 'stringio' - -module OpenSSL - ## - # = OpenSSL::Config - # - # Configuration for the openssl library. - # - # Many system's installation of openssl library will depend on your system - # configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for - # the location of the file for your host. - # - # See also http://www.openssl.org/docs/apps/config.html - class Config - include Enumerable - - class << self - - ## - # Parses a given +string+ as a blob that contains configuration for openssl. - # - # If the source of the IO is a file, then consider using #parse_config. - def parse(string) - c = new() - parse_config(StringIO.new(string)).each do |section, hash| - c[section] = hash - end - c - end - - ## - # load is an alias to ::new - alias load new - - ## - # Parses the configuration data read from +io+, see also #parse. - # - # Raises a ConfigError on invalid configuration data. - def parse_config(io) - begin - parse_config_lines(io) - rescue ConfigError => e - e.message.replace("error in line #{io.lineno}: " + e.message) - raise - end - end - - def get_key_string(data, section, key) # :nodoc: - if v = data[section] && data[section][key] - return v - elsif section == 'ENV' - if v = ENV[key] - return v - end - end - if v = data['default'] && data['default'][key] - return v - end - end - - private - - def parse_config_lines(io) - section = 'default' - data = {section => {}} - while definition = get_definition(io) - definition = clear_comments(definition) - next if definition.empty? - if definition[0] == ?[ - if /\[([^\]]*)\]/ =~ definition - section = $1.strip - data[section] ||= {} - else - raise ConfigError, "missing close square bracket" - end - else - if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition - if $2 - section = $1 - key = $2 - else - key = $1 - end - value = unescape_value(data, section, $3) - (data[section] ||= {})[key] = value.strip - else - raise ConfigError, "missing equal sign" - end - end - end - data - end - - # escape with backslash - QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/ - # escape with backslash and doubled dq - QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/ - # escaped char map - ESCAPE_MAP = { - "r" => "\r", - "n" => "\n", - "b" => "\b", - "t" => "\t", - } - - def unescape_value(data, section, value) - scanned = [] - while m = value.match(/['"\\$]/) - scanned << m.pre_match - c = m[0] - value = m.post_match - case c - when "'" - if m = value.match(QUOTE_REGEXP_SQ) - scanned << m[1].gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when '"' - if m = value.match(QUOTE_REGEXP_DQ) - scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when "\\" - c = value.slice!(0, 1) - scanned << (ESCAPE_MAP[c] || c) - when "$" - ref, value = extract_reference(value) - refsec = section - if ref.index('::') - refsec, ref = ref.split('::', 2) - end - if v = get_key_string(data, refsec, ref) - scanned << v - else - raise ConfigError, "variable has no value" - end - else - raise 'must not reaced' - end - end - scanned << value - scanned.join - end - - def extract_reference(value) - rest = '' - if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/) - value = m[1] || m[2] - rest = m.post_match - elsif [?(, ?{].include?(value[0]) - raise ConfigError, "no close brace" - end - if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/) - return m[0], m.post_match + rest - else - raise - end - end - - def clear_comments(line) - # FCOMMENT - if m = line.match(/\A([\t\n\f ]*);.*\z/) - return m[1] - end - # COMMENT - scanned = [] - while m = line.match(/[#'"\\]/) - scanned << m.pre_match - c = m[0] - line = m.post_match - case c - when '#' - line = nil - break - when "'", '"' - regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ - scanned << c - if m = line.match(regexp) - scanned << m[0] - line = m.post_match - else - scanned << line - line = nil - break - end - when "\\" - scanned << c - scanned << line.slice!(0, 1) - else - raise 'must not reaced' - end - end - scanned << line - scanned.join - end - - def get_definition(io) - if line = get_line(io) - while /[^\\]\\\z/ =~ line - if extra = get_line(io) - line += extra - else - break - end - end - return line.strip - end - end - - def get_line(io) - if line = io.gets - line.gsub(/[\r\n]*/, '') - end - end - end - - ## - # Creates an instance of OpenSSL's configuration class. - # - # This can be used in contexts like OpenSSL::X509::ExtensionFactory.config= - # - # If the optional +filename+ parameter is provided, then it is read in and - # parsed via #parse_config. - # - # This can raise IO exceptions based on the access, or availability of the - # file. A ConfigError exception may be raised depending on the validity of - # the data being configured. - # - def initialize(filename = nil) - @data = {} - if filename - File.open(filename.to_s) do |file| - Config.parse_config(file).each do |section, hash| - self[section] = hash - end - end - end - end - - ## - # Gets the value of +key+ from the given +section+ - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> #<OpenSSL::Config sections=["default"]> - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can get a specific value from the config if you know the +section+ - # and +key+ like so: - # - # config.get_value('default','foo') - # #=> "bar" - # - def get_value(section, key) - if section.nil? - raise TypeError.new('nil not allowed') - end - section = 'default' if section.empty? - get_key_string(section, key) - end - - ## - # - # *Deprecated* - # - # Use #get_value instead - def value(arg1, arg2 = nil) # :nodoc: - warn('Config#value is deprecated; use Config#get_value') - if arg2.nil? - section, key = 'default', arg1 - else - section, key = arg1, arg2 - end - section ||= 'default' - section = 'default' if section.empty? - get_key_string(section, key) - end - - ## - # Set the target +key+ with a given +value+ under a specific +section+. - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> #<OpenSSL::Config sections=["default"]> - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can set the value of +foo+ under the +default+ section to a new - # value: - # - # config.add_value('default', 'foo', 'buzz') - # #=> "buzz" - # puts config.to_s - # #=> [ default ] - # # foo=buzz - # - def add_value(section, key, value) - check_modify - (@data[section] ||= {})[key] = value - end - - ## - # Get a specific +section+ from the current configuration - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> #<OpenSSL::Config sections=["default"]> - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can get a hash of the specific section like so: - # - # config['default'] - # #=> {"foo"=>"bar"} - # - def [](section) - @data[section] || {} - end - - ## - # Deprecated - # - # Use #[] instead - def section(name) # :nodoc: - warn('Config#section is deprecated; use Config#[]') - @data[name] || {} - end - - ## - # Sets a specific +section+ name with a Hash +pairs+ - # - # Given the following configuration being created: - # - # config = OpenSSL::Config.new - # #=> #<OpenSSL::Config sections=[]> - # config['default'] = {"foo"=>"bar","baz"=>"buz"} - # #=> {"foo"=>"bar", "baz"=>"buz"} - # puts config.to_s - # #=> [ default ] - # # foo=bar - # # baz=buz - # - # It's important to note that this will essentially merge any of the keys - # in +pairs+ with the existing +section+. For example: - # - # config['default'] - # #=> {"foo"=>"bar", "baz"=>"buz"} - # config['default'] = {"foo" => "changed"} - # #=> {"foo"=>"changed"} - # config['default'] - # #=> {"foo"=>"changed", "baz"=>"buz"} - # - def []=(section, pairs) - check_modify - @data[section] ||= {} - pairs.each do |key, value| - self.add_value(section, key, value) - end - end - - ## - # Get the names of all sections in the current configuration - def sections - @data.keys - end - - ## - # Get the parsable form of the current configuration - # - # Given the following configuration being created: - # - # config = OpenSSL::Config.new - # #=> #<OpenSSL::Config sections=[]> - # config['default'] = {"foo"=>"bar","baz"=>"buz"} - # #=> {"foo"=>"bar", "baz"=>"buz"} - # puts config.to_s - # #=> [ default ] - # # foo=bar - # # baz=buz - # - # You can parse get the serialized configuration using #to_s and then parse - # it later: - # - # serialized_config = config.to_s - # # much later... - # new_config = OpenSSL::Config.parse(serialized_config) - # #=> #<OpenSSL::Config sections=["default"]> - # puts new_config - # #=> [ default ] - # foo=bar - # baz=buz - # - def to_s - ary = [] - @data.keys.sort.each do |section| - ary << "[ #{section} ]\n" - @data[section].keys.each do |key| - ary << "#{key}=#{@data[section][key]}\n" - end - ary << "\n" - end - ary.join - end - - ## - # For a block. - # - # Receive the section and its pairs for the current configuration. - # - # config.each do |section, key, value| - # # ... - # end - # - def each - @data.each do |section, hash| - hash.each do |key, value| - yield [section, key, value] - end - end - end - - ## - # String representation of this configuration object, including the class - # name and its sections. - def inspect - "#<#{self.class.name} sections=#{sections.inspect}>" - end - - protected - - def data # :nodoc: - @data - end - - private - - def initialize_copy(other) - @data = other.data.dup - end - - def check_modify - raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen? - end - - def get_key_string(section, key) - Config.get_key_string(@data, section, key) - end - end -end diff --git a/ext/openssl/lib/openssl/digest.rb b/ext/openssl/lib/openssl/digest.rb index a33ff27669..4e6dea8d03 100644 --- a/ext/openssl/lib/openssl/digest.rb +++ b/ext/openssl/lib/openssl/digest.rb @@ -1,7 +1,5 @@ +# frozen_string_literal: true #-- -# -# $RCSfile$ -# # = Ruby-space predefined Digest subclasses # # = Info @@ -11,71 +9,59 @@ # # = Licence # This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# +# (See the file 'COPYING'.) #++ module OpenSSL class Digest - alg = %w(DSS DSS1 MD2 MD4 MD5 MDC2 RIPEMD160 SHA SHA1) - if OPENSSL_VERSION_NUMBER > 0x00908000 - alg += %w(SHA224 SHA256 SHA384 SHA512) - end - - # Return the +data+ hash computed with +name+ Digest. +name+ is either the + # Return the hash value computed with _name_ Digest. _name_ is either the # long name or short name of a supported digest algorithm. # - # === Examples + # === Example # # OpenSSL::Digest.digest("SHA256", "abc") - # - # which is equivalent to: - # - # OpenSSL::Digest::SHA256.digest("abc") def self.digest(name, data) super(data, name) end - alg.each{|name| - klass = Class.new(self) { - define_method(:initialize, ->(data = nil) {super(name, data)}) - } - singleton = (class << klass; self; end) - singleton.class_eval{ - define_method(:digest){|data| new.digest(data) } - define_method(:hexdigest){|data| new.hexdigest(data) } - } - const_set(name, klass) - } + %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 + + klass.singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def digest(data) + new.digest(data) + end + def hexdigest(data) + new.hexdigest(data) + end + RUBY + const_set(name.tr('-', '_'), klass) + end # Deprecated. # # This class is only provided for backwards compatibility. - class Digest < Digest # :nodoc: - # Deprecated. - # - # See OpenSSL::Digest.new - def initialize(*args) - warn('Digest::Digest is deprecated; use Digest') - super(*args) - end - end + # Use OpenSSL::Digest instead. + class Digest < Digest; end # :nodoc: + deprecate_constant :Digest end # Digest - # Returns a Digest subclass by +name+. + # Returns a Digest subclass by _name_ # # require 'openssl' # # OpenSSL::Digest("MD5") # # => OpenSSL::Digest::MD5 # - # Digest("Foo") + # OpenSSL::Digest("Foo") # # => NameError: wrong constant name Foo def Digest(name) @@ -85,4 +71,3 @@ module OpenSSL 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 index 007934f81e..39871e15dd 100644 --- a/ext/openssl/lib/openssl/pkey.rb +++ b/ext/openssl/lib/openssl/pkey.rb @@ -1,36 +1,493 @@ -module OpenSSL - module PKey - if defined?(OpenSSL::PKey::DH) - - class DH - DEFAULT_512 = new <<-_end_of_pem_ ------BEGIN DH PARAMETERS----- -MEYCQQD0zXHljRg/mJ9PYLACLv58Cd8VxBxxY7oEuCeURMiTqEhMym16rhhKgZG2 -zk2O9uUIBIxSj+NKMURHGaFKyIvLAgEC ------END DH PARAMETERS----- - _end_of_pem_ - - DEFAULT_1024 = new <<-_end_of_pem_ ------BEGIN DH PARAMETERS----- -MIGHAoGBAJ0lOVy0VIr/JebWn0zDwY2h+rqITFOpdNr6ugsgvkDXuucdcChhYExJ -AV/ZD2AWPbrTqV76mGRgJg4EddgT1zG0jq3rnFdMj2XzkBYx3BVvfR0Arnby0RHR -T4h7KZ/2zmjvV+eF8kBUHBJAojUlzxKj4QeO2x20FP9X5xmNUXeDAgEC ------END DH PARAMETERS----- - _end_of_pem_ - end - - DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| - warn "using default DH parameters." if $VERBOSE - case keylen - when 512 then OpenSSL::PKey::DH::DEFAULT_512 - when 1024 then OpenSSL::PKey::DH::DEFAULT_1024 - else - nil +# 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 - else - DEFAULT_TMP_DH_CALLBACK = nil + 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 631943cc3f..3268c126b9 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -1,6 +1,5 @@ +# 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> @@ -8,135 +7,178 @@ = Licence This program is licensed under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ + (See the file 'COPYING'.) =end require "openssl/buffering" + +if defined?(OpenSSL::SSL) + require "io/nonblock" +require "ipaddr" +require "socket" module OpenSSL module SSL class SSLContext - DEFAULT_PARAMS = { - :ssl_version => "SSLv23", + DEFAULT_PARAMS = { # :nodoc: :verify_mode => OpenSSL::SSL::VERIFY_PEER, - :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 - ECDHE-ECDSA-RC4-SHA - ECDHE-RSA-RC4-SHA - RC4-SHA - }.join(":"), + :verify_hostname => true, :options => -> { opts = OpenSSL::SSL::OP_ALL - opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS) - opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) - opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) - opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) + opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS + opts |= OpenSSL::SSL::OP_NO_COMPRESSION opts }.call } - DEFAULT_CERT_STORE = OpenSSL::X509::Store.new - DEFAULT_CERT_STORE.set_default_paths - if defined?(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL) - DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + 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 - INIT_VARS = ["cert", "key", "client_ca", "ca_file", "ca_path", - "timeout", "verify_mode", "verify_depth", "renegotiation_cb", - "verify_callback", "cert_store", "extra_chain_cert", - "client_cert_cb", "session_id_context", "tmp_dh_callback", - "session_get_cb", "session_new_cb", "session_remove_cb", - "tmp_ecdh_callback", "servername_cb", "npn_protocols", - "alpn_protocols", "alpn_select_cb", - "npn_select_cb"].map { |x| "@#{x}" } + DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: + DEFAULT_CERT_STORE.set_default_paths - # A callback invoked when DH parameters are required. + # A callback invoked at connect time to distinguish between multiple + # server names. # - # The callback is invoked with the Session for the key exchange, an - # flag indicating the use of an export cipher and the keylength - # required. - # - # The callback must return an OpenSSL::PKey::DH instance of the correct - # key length. - - attr_accessor :tmp_dh_callback - - if ExtConfig::HAVE_TLSEXT_HOST_NAME - # 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 - end + # 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_client") => ctx + # SSLContext.new -> ctx + # SSLContext.new(:TLSv1) -> ctx + # SSLContext.new("SSLv23") -> ctx # - # You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS + # 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) - INIT_VARS.each { |v| instance_variable_set v, nil } - self.options = self.options | OpenSSL::SSL::OP_ALL - return unless version - self.ssl_version = version + self.ssl_version = version if version + self.verify_mode = OpenSSL::SSL::VERIFY_NONE + self.verify_hostname = false end ## - # Sets the parameters for this SSL context to the values in +params+. - # The keys in +params+ must be assignment methods on SSLContext. + # 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 - self.cert_store = DEFAULT_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 @@ -145,6 +187,14 @@ module OpenSSL to_io.peeraddr end + 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 @@ -164,6 +214,36 @@ module OpenSSL 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) @@ -179,11 +259,11 @@ module OpenSSL return true if verify_hostname(hostname, san.value) when 7 # iPAddress in GeneralName (RFC5280) should_verify_common_name = false - # follows GENERAL_NAME_print() in x509v3/v3_alt.c - if san.value.size == 4 - return true if san.value.unpack('C*').join('.') == hostname - elsif san.value.size == 16 - return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname + 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 } @@ -254,55 +334,36 @@ module OpenSSL include Buffering include SocketForwarder - if ExtConfig::OPENSSL_NO_SOCK - def initialize(io, ctx = nil); raise NotImplmentedError; end - else - if ExtConfig::HAVE_TLSEXT_HOST_NAME - attr_accessor :hostname - end + attr_reader :hostname - attr_reader :io, :context - attr_accessor :sync_close - alias :to_io :io + # The underlying IO object. + attr_reader :io + alias :to_io :io - # call-seq: - # SSLSocket.new(io) => aSSLSocket - # SSLSocket.new(io, ctx) => aSSLSocket - # - # Creates a new SSL socket from +io+ which must be a real ruby 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 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. - - def initialize(io, context = OpenSSL::SSL::SSLContext.new) - @io = io - @context = context - @sync_close = false - @hostname = nil - @io.nonblock = true if @io.respond_to?(:nonblock=) - context.setup - super() - end - end + # 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 # - # Shuts down the SSL connection and prepares it for another connection. + # 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 - ## - # Perform hostname verification after an SSL connection is established + # 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. @@ -310,7 +371,8 @@ module OpenSSL 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." + msg += " Anonymous cipher suite #{cipher[0]} was negotiated. " \ + "Anonymous suites must be disabled to use peer verification." end raise SSLError, msg end @@ -321,12 +383,43 @@ module OpenSSL 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? @@ -339,14 +432,6 @@ module OpenSSL @context.client_cert_cb end - def tmp_dh_callback - @context.tmp_dh_callback || OpenSSL::PKey::DEFAULT_TMP_DH_CALLBACK - end - - def tmp_ecdh_callback - @context.tmp_ecdh_callback - end - def session_new_cb @context.session_new_cb end @@ -354,6 +439,38 @@ module OpenSSL 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 ## @@ -364,15 +481,15 @@ module OpenSSL attr_accessor :start_immediately # Creates a new instance of SSLServer. - # * +srv+ is an instance of TCPServer. - # * +ctx+ is an instance of OpenSSL::SSL::SSLContext. + # * _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).unpack('H*')[0] + session_id = prng.bytes(16).unpack1('H*') @ctx.session_id_context = session_id end @start_immediately = true @@ -384,7 +501,7 @@ module OpenSSL end # See TCPServer#listen for details. - def listen(backlog=5) + def listen(backlog=Socket::SOMAXCONN) @svr.listen(backlog) end @@ -421,3 +538,5 @@ module OpenSSL 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 497ffe0a83..66765ffeab 100644 --- a/ext/openssl/lib/openssl/x509.rb +++ b/ext/openssl/lib/openssl/x509.rb @@ -1,7 +1,5 @@ +# frozen_string_literal: true #-- -# -# $RCSfile$ -# # = Ruby-space definitions that completes C-space funcs for X509 and subclasses # # = Info @@ -11,13 +9,11 @@ # # = Licence # This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# +# (See the file 'COPYING'.) #++ +require_relative 'marshal' + module OpenSSL module X509 class ExtensionFactory @@ -47,6 +43,13 @@ module OpenSSL 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 << " = " @@ -61,9 +64,146 @@ module OpenSSL def to_a [ self.oid, self.value, self.critical? ] end + + 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 + include OpenSSL::Marshal + module RFC2253DN Special = ',=+<>#;' HexChar = /[0-9a-fA-F]/ @@ -139,13 +279,37 @@ module OpenSSL 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) - ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) } + 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 @@ -160,6 +324,15 @@ module OpenSSL 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 @@ -167,6 +340,21 @@ module OpenSSL 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 @@ -177,6 +365,36 @@ module OpenSSL 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 |
