我正在使用Ruby中的Crypto API来查找Windows证书,使用指纹。为此,我正在使用需要CRYPT_HASH_BLOB结构的CertFindCertificateInStore函数。
我正在使用FFI::MemoryPointer
来处理指针。尝试了各种方法来创建此结构,但似乎都没有工作。
有人可以看看并为我提供建议吗?
以下是供参考的完整代码:
require "ffi"
module LibC
extend FFI::Library
ffi_lib FFI::Library::LIBC
# memory allocators
attach_function :malloc, [:size_t], :pointer
attach_function :calloc, [:size_t], :pointer
attach_function :free, [:pointer], :void
end
module Crypto
extend LibC
extend FFI::Library
ffi_lib "Crypt32"
HCERTSTORE = FFI::TypeDefs[:pointer]
HCRYPTPROV_LEGACY = FFI::TypeDefs[:pointer]
PCCERT_CONTEXT = FFI::TypeDefs[:pointer]
DWORD = FFI::TypeDefs[:uint32]
BLOB = FFI::TypeDefs[:ulong]
LPCTSTR = FFI::TypeDefs[:pointer]
BOOL = FFI::TypeDefs[:bool]
LPVOID = FFI::TypeDefs[:pointer]
class CRYPT_HASH_BLOB < FFI::Struct
layout :cbData, DWORD, # Count, in bytes, of data
:pbData, :pointer # Pointer to data buffer
def initialize(str)
super(nil)
if str
# Method 1: Simply using thumbprint string
buffer1 = LibC.malloc str.size
buffer1.write_string str
self[:pbData] = buffer1
self[:cbData] = str.size
# Converting thumbpring string into a byte array
# arr = [str].pack('H*').unpack('C*')
# Method 2: Using Byte Array with LibC
# buffer2 = LibC.malloc(arr.first.size * arr.size) # Create the pointer to the array
# buffer2.write_array_of_uint32 arr # Fill the memory location with data
# self[:pbData] = buffer2
# self[:cbData] = arr.size
# # Method 3: Using Byte Array with FFI::MemoryPointer
# buffer3 = FFI::MemoryPointer.new :uint32, arr.size # Create the pointer to the array
# buffer3.put_array_of_uint32 0, arr # Fill the memory location with data
# self[:pbData] = buffer3
# self[:cbData] = arr.size
end
end
end
attach_function :CertOpenStore, [DWORD, DWORD, HCRYPTPROV_LEGACY, DWORD, LPCTSTR], HCERTSTORE
attach_function :CertCloseStore, [HCERTSTORE, DWORD], BOOL
attach_function :CertFindCertificateInStore, [HCERTSTORE, DWORD, DWORD, DWORD, LPVOID, PCCERT_CONTEXT], PCCERT_CONTEXT
attach_function :CertFreeCertificateContext, [PCCERT_CONTEXT], BOOL
=begin
PCCERT_CONTEXT CertFindCertificateInStore(
HCERTSTORE hCertStore,
DWORD dwCertEncodingType,
DWORD dwFindFlags,
DWORD dwFindType,
const void *pvFindPara,
PCCERT_CONTEXT pPrevCertContext
);
=end
end
class CertificateHandler
include Crypto
CERT_STORE_PROV_SYSTEM = 10
CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x00020000
X509_ASN_ENCODING = 0x00000001
PKCS_7_ASN_ENCODING = 0x00010000
ENCODING_TYPE = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
CERT_CLOSE_STORE_FORCE_FLAG = 1
CERT_COMPARE_SHA1_HASH = 1
CERT_COMPARE_SHIFT = 16
CERT_FIND_SHA1_HASH = CERT_COMPARE_SHA1_HASH << CERT_COMPARE_SHIFT
def self.finalize(certstore_handler)
proc { certstore_handler.to_s }
end
def add_finalizer(certstore_handler)
ObjectSpace.define_finalizer(self, self.class.finalize(certstore_handler))
end
def remove_finalizer
ObjectSpace.undefine_finalizer(self)
end
def utf8_to_wide(ustring)
# ensure it is actually UTF-8
# Ruby likes to mark binary data as ASCII-8BIT
ustring = (ustring + "").force_encoding("UTF-8") if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
# ensure we have the double-null termination Windows Wide likes
ustring += " 00 00" if ustring.length == 0 || ustring[-1].chr != " 00"
# encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
ustring = ustring.encode("UTF-16LE") if ustring.respond_to?(:encode)
ustring
end
def cert_find_by_thumbprint(thumbprint = nil)
store_name = "Root"
thumbprint ||= "1D F4 AB B6 13 F2 12 27 1C 04 F8 52 9D DE 38 E4 B7 24 2E 6C" # Assume this is a valid thumbprint in Root
thumbprint.gsub!(/[^A-Za-z0-9]/, "") # Discard WhiteSpaces
certstore_handler = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, nil,
CERT_SYSTEM_STORE_LOCAL_MACHINE, utf8_to_wide(store_name))
add_finalizer(certstore_handler)
pcert_context = CertFindCertificateInStore(certstore_handler, ENCODING_TYPE, 0, CERT_FIND_SHA1_HASH, CRYPT_HASH_BLOB.new(thumbprint), nil)
puts "Certificate Found = #{!pcert_context.null?}" # It should be true
CertFreeCertificateContext(pcert_context)
closed = CertCloseStore(@certstore_handler, CERT_CLOSE_STORE_FORCE_FLAG)
remove_finalizer
end
end
CertificateHandler.new.cert_find_by_thumbprint
我根本不熟悉Ruby,但我可以做一些假设。如果我错了,请纠正我。
正在将源指纹(SHA1 值)转换为八位字节之间没有空格的字符串:
thumbprint ||= "1D F4 AB B6 13 F2 12 27 1C 04 F8 52 9D DE 38 E4 B7 24 2E 6C" # Assume this is a valid thumbprint in Root
thumbprint.gsub!(/[^A-Za-z0-9]/, "") # Discard WhiteSpaces
这将产生如下结果:1DF4ABB613F212271C04F8529DDE38E4B7242E6C
然后将此字符串传递到此行中的构造函数CRYPT_HASH_BLOB
:
pcert_context = CertFindCertificateInStore(certstore_handler, ENCODING_TYPE, 0, CERT_FIND_SHA1_HASH, CRYPT_HASH_BLOB.new(thumbprint), nil)
在CRYPT_HASH_BLOB.initialize(str)
中,您将 utf8 字符串解压缩为字节数组。第一种方法不正确,因为您将输入字符串视为原始字符数组。相反,它是一个十六进制编码的字节数组,因此必须涉及arr = [str].pack('H*').unpack('C*')
行。至少,谷歌建议这条线将一系列八位字节转换为相应的字节数组。因此,方法 3 很好,除了您必须分配 *byte* 数组(不是整数数组)并写入字符而不是整数。正确的构造函数代码应如下所示:
def initialize(str)
super(nil)
if str
# Converting thumbpring string into a byte array
arr = [str].pack('H*').unpack('C*')
# Method 3: Using Byte Array with FFI::MemoryPointer
buffer3 = FFI::MemoryPointer.new(:char, arr.size) # Create the pointer to the array
buffer3.write_array_of_char(arr) # Fill the memory location with data
self[:pbData] = buffer3
self[:cbData] = arr.size
end
end
我相信,这将完成这项工作。