require_relative '../data_conversion' module Ccrypto module Java class HKDFEngine include DataConversion include TR::CondUtils class HKDFEngineError < StandardError; end class HKDFSupportedDigest include InMemoryRecord def initialize #define_search_key(:algo) end end def self.supported_hkdf_configs if ENV[Java::ENV_PROBE_DIGEST_KEY] == "true" @_supportedHkdf = HKDFSupportedDigest.new else @_supportedHkdf = HKDFSupportedDigest.load_from_storage("supported_hkdf") end if @_supportedHkdf.empty? @_supportedHkdf = HKDFSupportedDigest.new Ccrypto::Java::DigestEngine.supported.each do |dig| bcDig = Ccrypto::Java::DigestEngine.to_bc_digest_inst(dig) if not bcDig.nil? logger.debug "Digest #{dig.inspect} has BC instance #{bcDig}" conf = Ccrypto::HKDFConfig.new conf.digest = dig conf.provider_config = { bc_digest: bcDig } @_supportedHkdf.register(conf, { tag_under: :calgo, tag_value: dig.algo }) end end @_supportedHkdf.save_to_storage("supported_hkdf") end @_supportedHkdf end def self.find_hkdf_config_by_digest(algo) supported_hkdf_configs.find({ calgo: algo }) end private def self.logger Ccrypto::Java.logger(:cj_hkdf_eng_c) end public def initialize(*args, &block) @config = args.first raise KDFEngineException, "HKDF config is expected. Given #{@config}" if not @config.is_a?(Ccrypto::HKDFConfig) raise KDFEngineException, "Output bit length (outBitLength) value is not given or not a positive value (#{@config.outBitLength})" if is_empty?(@config.outBitLength) or @config.outBitLength <= 0 end def derive(input, output = :binary) begin logger.debug "HKDF config : #{@config.inspect}" case @config.digest when Symbol, String hkdfConf = self.class.find_hkdf_config_by_digest(@config.digest) raise HKDFEngineError, "Unsupported digest '#{@config.digest}'" if is_empty?(hkdfConf) digest = hkdfConf.first.digest when Ccrypto::DigestConfig digest = @config.digest else raise HKDFEngineError, "Unsupported digest '#{@config.digest}'" end logger.debug "Digest for HKDF : #{digest.inspect}" begin dig = Ccrypto::Java::DigestEngine.instance(digest) rescue Exception => ex raise KDFEngineException, "Failed to initialize digest engine. Error was : #{ex}" end #bcDigest = Ccrypto::Java::DigestEngine.to_bc_digest_inst(digest.provider_config[:algo_name]) #raise KDFEngineException, "Digest '#{digest.algo}' not supported. Please report to library owner for further verification" if bcDigest.nil? bcDigest = eval(@config.provider_config[:bc_digest]) # https://soatok.blog/2021/11/17/understanding-hkdf/ # info field should be the randomness entrophy compare to salt # HKDf can have fix or null salt but better have additional info for each purposes @config.info = "" if @config.info.nil? logger.debug "Salt length : #{@config.salt.nil? ? "0" : @config.salt.length}" logger.debug "Info length : #{@config.info.nil? ? "0" : @config.info.length}" logger.debug "Digest : #{bcDigest}" logger.warn "Salt is empty!" if is_empty?(@config.salt) hkdf = org.bouncycastle.crypto.generators.HKDFBytesGenerator.new(bcDigest) hkdfParam = org.bouncycastle.crypto.params.HKDFParameters.new(to_java_bytes(input), to_java_bytes(@config.salt) ,to_java_bytes(@config.info)) hkdf.init(hkdfParam) out = ::Java::byte[@config.outBitLength/8].new hkdf.generateBytes(out, 0, out.length) case output when :b64 to_b64(out) when :hex to_hex(out) else out end rescue Exception => ex raise KDFEngineException, ex end end private def logger Ccrypto::Java.logger(:cj_hkdf_eng) end end end end