lib/ccrypto/ruby/engines/cipher_engine.rb in ccrypto-ruby-0.1.0 vs lib/ccrypto/ruby/engines/cipher_engine.rb in ccrypto-ruby-0.1.1

- old
+ new

@@ -9,78 +9,199 @@ include TeLogger::TeLogHelper teLogger_tag :r_cipher_eng + NotAbleToFigureOutOnOpenSSLv2 = [ + "aes-128-cbc-hmac-sha1", + "aes-256-cbc-hmac-sha1", + "aes-128-cbc-hmac-sha256", + "aes-256-cbc-hmac-sha256", + "aes-128-ccm", + "aes-192-ccm", + "aes-256-ccm", + "aria-128-ccm", + "aria-192-ccm", + "aria-256-ccm", + "rc4-hmac-md5" + ] + + #CherryPick = [ + # "aes-128-cbc", "aes-192-cbc", "aes-256-cbc", + # "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", + # "aria-128-cbc","aria-192-cbc", "aria-256-cbc", + # "aria-128-gcm","aria-192-gcm", "aria-256-gcm", + # "camellia-128-cbc","camellia-192-cbc", "camellia-256-cbc", + # "camellia-128-ctr","camellia-192-ctr", "camellia-256-ctr", + # "chacha20-poly1305" + #] + def self.supported_ciphers if @sCipher.nil? - @sCipher = OpenSSL::Cipher.ciphers + @sCipherStr = [] + @sCipher = [] + + OpenSSL::Cipher.ciphers.each do |c| + + teLogger.debug "found cipher : #{c}" + next if c =~ /^id-\w*/ + teLogger.debug "loading cipher : #{c}" + + if OpenSSL::VERSION < "3.0.0" and NotAbleToFigureOutOnOpenSSLv2.include?(c) + teLogger.debug "Running on OpenSSL < v3. Skipping cipher #{c}" + next + end + + begin + co = OpenSSL::Cipher.new(c) + #teLogger.debug "Algo #{c} : authenticated? #{co.authenticated?}" + #p co.methods.sort + cc = c.split("-") + @sCipherStr << c + + algo = cc.first + exclusion = ["chacha20","sm4"] + # to find string like aes128, aes256 from openssl + if not exclusion.include?(algo) and (algo =~ /[0-9]/) != nil + # match the one before the numbers + algo = $` + end + + native = { algo_str: c } + keysize = co.key_len*8 + opts = { keysize: keysize, ivLength: co.iv_len, authMode: co.authenticated? } + + if c.downcase =~ /\w*(gcm|ecb|cbc|cfb|cfb1|cfb8|ofb|ctr|ccm|xts|wrap|poly1305)/ + teLogger.debug "Mode extraction : #{$&} / #{$'}" + # after the match + if not_empty?($') + opts[:mode] = "#{$&}#{$'}" + else + # the match + opts[:mode] = $&.downcase + end + else + # no mode defined. Seems defaulted to cbc according to document + if co.authenticated? + opts[:mode] = "gcm" + else + opts[:mode] = "cbc" + end + end + + teLogger.debug "Mode : #{opts[:mode]}" + if opts[:mode] == :xts.to_s + opts[:min_input_length] = 16 + elsif opts[:mode] == "cbc-hmac-sha1" + opts[:min_input_length] = 16 + opts[:mandatory_block_size] = 16 + elsif opts[:mode] == "cbc-hmac-sha256" + opts[:min_input_length] = 32 + opts[:mandatory_block_size] = 32 + elsif opts[:mode] == "wrap" + case algo + when "aes" + case keysize + when 128 + opts[:min_input_length] = 16 + opts[:mandatory_block_size] = 8 + when 192 + opts[:min_input_length] = 24 + opts[:mandatory_block_size] = 8 + when 256 + opts[:min_input_length] = 32 + opts[:mandatory_block_size] = 8 + end + when "des3", "des" + opts[:min_input_length] = 8 + opts[:mandatory_block_size] = 8 + end + end + + conf = Ccrypto::CipherConfig.new(algo, opts) + conf.native_config = native + + Ccrypto::SupportedCipherList.instance.register(conf) + + @sCipher << conf + rescue OpenSSL::Cipher::CipherError => ex + teLogger.debug "Algo '#{c}' hit error : #{ex.message}" + end + end end @sCipher end + def self.get_cipher(algo, keysize = nil, mode = nil) + supported_ciphers if Ccrypto::SupportedCipherList.instance.algo_count == 0 + + if is_empty?(algo) + [] + elsif is_empty?(keysize) and is_empty?(mode) + teLogger.debug "get_cipher algo #{algo} only" + Ccrypto::SupportedCipherList.instance.find_algo(algo) + elsif is_empty?(mode) and not_empty?(keysize) + teLogger.debug "get_cipher algo #{algo} keysize #{keysize}" + Ccrypto::SupportedCipherList.instance.find_algo_keysize(algo, keysize) + elsif not_empty?(mode) and is_empty?(keysize) + teLogger.debug "get_cipher algo #{algo} mode #{mode}" + Ccrypto::SupportedCipherList.instance.find_algo_mode(algo, mode) + elsif not_empty?(keysize) and not_empty?(mode) + teLogger.debug "get_cipher #{algo}/#{keysize}/#{mode}" + Ccrypto::SupportedCipherList.instance.find_algo_keysize_mode(algo, keysize, mode) + end + end + class << self + alias_method :get_cipher_config, :get_cipher + end + + def self.supported_cipher_list + supported_ciphers if Ccrypto::SupportedCipherList.instance.algo_count == 0 + Ccrypto::SupportedCipherList.instance + end + def self.is_supported_cipher?(c) case c when String - supported_ciphers.include?(c) - when Hash - spec = to_openssl_spec(c) - begin - OpenSSL::Cipher.new(spec) - true - rescue Exception => ex + supported_ciphers if @sCipherStr.nil? + + if not @sCipherStr.nil? + @sCipherStr.include?(C) + else false end + when Ccrypto::CipherConfig + @sCipher.include?(c) else raise Ccrypto::CipherEngineException, "Unsupported input #{c} to check supported cipher" end end def self.to_openssl_spec(spec) - res = [] - teLogger.debug "to_openssl_spec #{spec}" - case spec.algo - when :blowfish - res << "bf" + case spec + when Ccrypto::CipherConfig + spec.native_config[:algo_str] + #@sCipher[spec] else - res << spec.algo + raise Ccrypto::Error, "Unknown spec #{spec.inspect}" end - res << spec.keysize if not_empty?(spec.keysize) and spec.keysize.to_i > 0 and not spec.is_algo?(:chacha20) and not spec.is_algo?(:seed) and not spec.is_algo?(:sm4) and not spec.is_algo?(:blowfish) + end # self.to_openssl_spec(spec) - res << spec.mode - - teLogger.debug "to_openssl_spec #{res}" - - res.join("-") - - end - def initialize(*args, &block) + @spec = args.first - #teLogger = TteLogger.new - teLogger.debug "Cipher spec : #{@spec}" + raise Ccrypto::CipherEngineException, "Not supported cipher spec #{@spec.class}" if not @spec.is_a?(Ccrypto::CipherConfig) - begin - case @spec - #when String - # @cipher = OpenSSL::Cipher.new(@spec) - when Ccrypto::CipherEngineConfig - @cipher = OpenSSL::Cipher.new(@spec.provider_config) - when Ccrypto::DirectCipherConfig - @cipher = OpenSSL::Cipher.new(self.class.to_openssl_spec(@spec)) - else - raise Ccrypto::CipherEngineException, "Not supported cipher init type #{@spec.class}" - end - rescue OpenSSL::Cipher::CipherError, RuntimeError => ex - raise Ccrypto::CipherEngineException, ex - end + teLogger.debug "Cipher spec : #{@spec} (Native algo : #{@spec.native_config[:algo_str]})" + @cipher = OpenSSL::Cipher.new(@spec.native_config[:algo_str]) + case @spec.cipherOps when :encrypt, :enc teLogger.debug "Operation encrypt" @cipher.encrypt when :decrypt, :dec @@ -92,15 +213,15 @@ if @spec.has_iv? teLogger.debug "IV from spec" @cipher.iv = @spec.iv - teLogger.debug "IV : #{to_hex(@spec.iv)}" + teLogger.debug "IV : #{to_hex(@spec.iv)} / #{@spec.iv.length}" else teLogger.debug "Generate random IV" @spec.iv = @cipher.random_iv - teLogger.debug "IV : #{to_hex(@spec.iv)}" + teLogger.debug "IV : #{to_hex(@spec.iv)} / #{@spec.iv.length}" end if @spec.has_key? teLogger.debug "Key from spec" @@ -116,54 +237,92 @@ teLogger.debug "Generate random Key" @spec.key = @cipher.random_key end - if @spec.is_mode?(:gcm) + if @spec.is_mode?(:ccm) + #if not_empty?(@spec.auth_data) + if @spec.is_encrypt_cipher_mode? + teLogger.debug "Setting ccm plaintext data length (ccm mode) [#{@spec.plaintext_length}]" + @cipher.ccm_data_len = @spec.plaintext_length + teLogger.debug "Setting auth data (ccm mode)" + @cipher.auth_data = @spec.auth_data.nil? ? "" : @spec.auth_data + teLogger.debug "Setting auth tag len (ccm mode)" + @cipher.auth_tag_len = 12 + elsif @spec.is_decrypt_cipher_mode? + teLogger.debug "Setting ccm cipher data length (ccm mode) #{@spec.ciphertext_length}" + @cipher.ccm_data_len = @spec.ciphertext_length + teLogger.debug "Setting auth data (ccm mode)" + @cipher.auth_data = @spec.auth_data.nil? ? "" : @spec.auth_data + teLogger.debug "Setting auth tag (ccm mode)" + @cipher.auth_tag = @spec.auth_tag + end + #end - if not_empty?(@spec.auth_data) - teLogger.debug "Setting auth data" + + elsif @spec.is_auth_mode_cipher? + + if not_empty?(@spec.auth_data) and not ((@spec.is_mode?("cbc-hmac-sha1") or @spec.is_mode?("cbc-hmac-sha256"))) + teLogger.debug "Setting auth data (generic mode)" @cipher.auth_data = @spec.auth_data - end - if not_empty?(@spec.auth_tag) - raise CipherEngineException, "Tag length of 16 bytes is expected" if @spec.auth_tag.bytesize != 16 - teLogger.debug "Setting auth tag" - @cipher.auth_tag = @spec.auth_tag + if @spec.is_decrypt_cipher_mode? + teLogger.debug "Setting auth tag (generic mode)" + raise CipherEngineException, "Tag length of 16 bytes is expected" if @spec.auth_tag.bytesize != 16 and @spec.is_mode?(:gcm) + @cipher.auth_tag = @spec.auth_tag + end end end - end + end # initialize + def update(val) - @cipher.update(val) + if not_empty?(val) + res = @cipher.update(val) + teLogger.debug "(update) Written #{val.length} bytes" + res + end end def final(val = nil) res = [] begin if not_empty?(val) - res << @cipher.update(val) + teLogger.debug "(final) Written #{val.length} bytes" + res << @cipher.update(val) end res << @cipher.final + teLogger.debug "(final) cipher finalized" rescue Exception => ex + teLogger.error self + teLogger.error ex.backtrace.join("\n") raise CipherEngineException, ex end - if @spec.is_mode?(:gcm) and @spec.is_encrypt_cipher_mode? - @spec.auth_tag = @cipher.auth_tag + #if @spec.is_mode?(:gcm) + if @spec.is_auth_mode_cipher? and @spec.is_encrypt_cipher_mode? + @spec.auth_tag = @cipher.auth_tag end res.join end def reset @cipher.reset + end + + def method_missing(mtd, *args, &block) + if not @cipher.nil? + @cipher.send(mtd, *args, &block) + else + super + end end end end end