diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 33eb64a..90599f5 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -68,6 +68,7 @@ def test_certificate(rawcert): cert = windows.crypto.CertificateContext.from_buffer(rawcert) assert cert.serial == '1b 8e 94 cb 0b 3e eb b6 41 39 f3 c9 09 b1 6b 46' assert cert.name == 'PythonForWindowsTest' + cert.chains # TODO: craft a certificate with a chain for test purpose def test_pfx(rawcert, rawpfx): @@ -101,4 +102,12 @@ def test_encrypt_decrypt(rawcert, rawpfx): assert message_to_encrypt == decrypt assert decrypt == decrypt2 +def test_crypt_obj(): + path = r"C:\windows\system32\kernel32.dll" + x = windows.crypto.CryptObject(path) + x.crypt_msg.certs + x.crypt_msg.signers + x.signers_and_certs + # TODO: Need some better ideas + diff --git a/windows/crypto/certificate.py b/windows/crypto/certificate.py index 4cf3333..680d888 100644 --- a/windows/crypto/certificate.py +++ b/windows/crypto/certificate.py @@ -3,11 +3,11 @@ import ctypes import windows from windows import winproxy -# from windows.generated_def import * import windows.generated_def as gdef from windows.crypto import DEFAULT_ENCODING -# from windows.crypto.helper import ECRYPT_DATA_BLOB + +import windows.crypto.cryptmsg CRYPT_OBJECT_FORMAT_TYPE = [ @@ -44,17 +44,16 @@ class CryptObject(object): def __init__(self, filename, content_type=gdef.CERT_QUERY_CONTENT_FLAG_ALL): # No other API than filename for now.. self.filename = filename - if filename is None: - return # TMP ! - dwEncoding = DWORD() - dwContentType = DWORD() - dwFormatType = DWORD() - hStore = PVOID() - hMsg = PVOID() + dwEncoding = gdef.DWORD() + dwContentType = gdef.DWORD() + dwFormatType = gdef.DWORD() + hStore = EHCERTSTORE() + hMsg = windows.crypto.cryptmsg.CryptMessage() winproxy.CryptQueryObject(gdef.CERT_QUERY_OBJECT_FILE, - LPWSTR(filename), + gdef.LPWSTR(filename), + # filename, content_type, gdef.CERT_QUERY_FORMAT_FLAG_BINARY, 0, @@ -65,84 +64,24 @@ class CryptObject(object): hMsg, None) - self.hstore = hStore - self.hmsg = hMsg + self.cert_store = hStore + self.crypt_msg = hMsg self.encoding = dwEncoding self.content_type = CRYPT_OBJECT_FORMAT_TYPE_DICT.get(dwContentType.value, dwContentType) - def msg_get_param(self, param_type, index=0): - signer_info = DWORD() - # https://msdn.microsoft.com/en-us/library/windows/desktop/aa380227(v=vs.85).aspx - winproxy.CryptMsgGetParam(self.hmsg, param_type, index, None, signer_info) - buffer = ctypes.c_buffer(signer_info.value) - winproxy.CryptMsgGetParam(self.hmsg, param_type, index, buffer, signer_info) - - if param_type in self.MSG_PARAM_KNOW_TYPES: - buffer = self.MSG_PARAM_KNOW_TYPES[param_type].from_buffer(buffer) - return buffer + def _signers_and_certs_generator(self): + for signer in self.crypt_msg.signers: + cert = self.cert_store.find(signer.Issuer, signer.SerialNumber) + yield signer, cert @property - def nb_signer(self): - """The number of signers for the CryptObject - - :type: :class:`int` - """ - return self.msg_get_param(CMSG_SIGNER_COUNT_PARAM).value - - def get_signer_data(self, index=0): - """Returns the signer informations for signer nb ``index`` - - :return: :class:`CMSG_SIGNER_INFO` - """ - return self.msg_get_param(CMSG_SIGNER_INFO_PARAM, index) - - def get_signer_certificate(self, index=0): - """Return the certificate used for signer nb ``index`` - - :return: :class:`CertificateContext` - """ - data = self.get_signer_data(index) - cert_info = CERT_INFO() - cert_info.Issuer = data.Issuer - cert_info.SerialNumber = data.SerialNumber - rawcertcontext = winproxy.CertFindCertificateInStore(self.hstore, self.encoding, 0, CERT_FIND_SUBJECT_CERT, byref(cert_info), None) - #return rawcertcontext - return CertificateContext(rawcertcontext[0]) - - def get_raw_cert(self, index=0): - return self.msg_get_param(CMSG_CERT_PARAM, index) - - def get_cert(self, index=0): - """Return embded certificate number ``index``. - - note: not all embded certificate are directly used to sign the :class:`CryptObject`. - - :return: :class:`CertificateContext` - """ - return CertificateContext.from_buffer(self.get_raw_cert(index)) - - cert = property(get_cert) - - @property - def nb_cert(self): - """The number of certificate embded in the :class:`CryptObject` - - :type: :class:`int` - """ - return self.msg_get_param(CMSG_CERT_COUNT_PARAM).value - - @property - def signers(self): - return [self.get_signer_data(i) for i in range(self.nb_signer)] - - @property - def certs(self): - return [self.get_cert(i) for i in range(self.nb_cert)] + def signers_and_certs(self): + return list(self._signers_and_certs_generator()) def __repr__(self): return '<{0} "{1}" content_type={2}>'.format(type(self).__name__, self.filename, self.content_type) - +# TODO: rename to CertificateStore ? class EHCERTSTORE(gdef.HCERTSTORE): """A certificate store""" @property @@ -192,7 +131,21 @@ class EHCERTSTORE(gdef.HCERTSTORE): res = winproxy.CertOpenStore(CERT_STORE_PROV_MEMORY, DEFAULT_ENCODING, None, 0, None) return ctypes.cast(res, cls) - # Add API arround 'CertFindCertificateInStore' ? + + # TODO: a more complete search API ? + def find(self, issuer, serialnumber): + """Return the certificate that match `issuer` and `serialnumber` + + :return: :class:`CertificateContext` + """ + # data = self.get_signer_data(index) + cert_info = gdef.CERT_INFO() + cert_info.Issuer = issuer + cert_info.SerialNumber = serialnumber + rawcertcontext = winproxy.CertFindCertificateInStore(self, DEFAULT_ENCODING, 0, gdef.CERT_FIND_SUBJECT_CERT, ctypes.byref(cert_info), None) + # return rawcertcontext + return CertificateContext(rawcertcontext[0]) + # PKCS12_NO_PERSIST_KEY -> do not save it in a key container on disk # Without it, a key container is created at 'C:\Users\USERNAME\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-3241049326-165485355-1070449050-1001' @@ -275,19 +228,19 @@ class CertificateContext(gdef.PCCERT_CONTEXT): def get_raw_certificate_chains(self): # Rename to all_chains ? chain_context = EPCCERT_CHAIN_CONTEXT() - enhkey_usage = CERT_ENHKEY_USAGE() + enhkey_usage = gdef.CERT_ENHKEY_USAGE() enhkey_usage.cUsageIdentifier = 0 enhkey_usage.rgpszUsageIdentifier = None - cert_usage = CERT_USAGE_MATCH() - cert_usage.dwType = USAGE_MATCH_TYPE_AND + cert_usage = gdef.CERT_USAGE_MATCH() + cert_usage.dwType = gdef.USAGE_MATCH_TYPE_AND cert_usage.Usage = enhkey_usage - chain_para = CERT_CHAIN_PARA() - chain_para.cbSize = sizeof(chain_para) + chain_para = gdef.CERT_CHAIN_PARA() + chain_para.cbSize = ctypes.sizeof(chain_para) chain_para.RequestedUsage = cert_usage - winproxy.CertGetCertificateChain(None, self, None, self[0].hCertStore, byref(chain_para), 0, None, byref(chain_context)) + winproxy.CertGetCertificateChain(None, self, None, self[0].hCertStore, ctypes.byref(chain_para), 0, None, ctypes.byref(chain_context)) #return CertficateChain(chain_context) return chain_context @@ -349,6 +302,12 @@ class CertificateContext(gdef.PCCERT_CONTEXT): :type: :class:`bytearray`""" return bytearray(self[0].pbCertEncoded[:self[0].cbCertEncoded]) + @property + def version(self): + "TODO: doc" + return self[0].pbCertInfo.dwVersion + + @classmethod def from_file(cls, filename): """Create a :class:`CertificateContext` from the file ``filename`` @@ -392,7 +351,6 @@ class CertificateContext(gdef.PCCERT_CONTEXT): # Those classes are more of a POC than anything else - class EPCCERT_CHAIN_CONTEXT(gdef.PCCERT_CHAIN_CONTEXT): _type_ = gdef.PCCERT_CHAIN_CONTEXT._type_ @@ -400,7 +358,7 @@ class EPCCERT_CHAIN_CONTEXT(gdef.PCCERT_CHAIN_CONTEXT): def chains(self): res = [] for i in range(self[0].cChain): - simple_chain = ctypes.cast(self[0].rgpChain[i], gdef.EPCCERT_SIMPLE_CHAIN) + simple_chain = ctypes.cast(self[0].rgpChain[i], EPCCERT_SIMPLE_CHAIN) res.append(simple_chain) return res diff --git a/windows/crypto/cryptmsg.py b/windows/crypto/cryptmsg.py new file mode 100644 index 0000000..5efeb69 --- /dev/null +++ b/windows/crypto/cryptmsg.py @@ -0,0 +1,80 @@ +import ctypes + +from windows import winproxy +import windows.generated_def as gdef +import windows.crypto + +class CryptMessage(gdef.HCRYPTMSG): + MSG_PARAM_KNOW_TYPES = {gdef.CMSG_SIGNER_INFO_PARAM: gdef.CMSG_SIGNER_INFO, + gdef.CMSG_SIGNER_COUNT_PARAM: gdef.DWORD, + gdef.CMSG_CERT_COUNT_PARAM: gdef.DWORD} + + + def get_param(self, param_type, index=0): + data_size = gdef.DWORD() + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa380227(v=vs.85).aspx + winproxy.CryptMsgGetParam(self, param_type, index, None, data_size) + buffer = ctypes.c_buffer(data_size.value) + winproxy.CryptMsgGetParam(self, param_type, index, buffer, data_size) + + if param_type in self.MSG_PARAM_KNOW_TYPES: + buffer = self.MSG_PARAM_KNOW_TYPES[param_type].from_buffer(buffer) + if isinstance(buffer, gdef.DWORD): # DWORD -> return the Python int + return buffer.value + return buffer + + + # Certificate accessors + + @property + def nb_cert(self): + """The number of certificate embded in the :class:`CryptObject` + + :type: :class:`int` + """ + return self.get_param(gdef.CMSG_CERT_COUNT_PARAM) + + def get_raw_cert(self, index=0): + return self.get_param(gdef.CMSG_CERT_PARAM, index) + + def get_cert(self, index=0): + """Return embded certificate number ``index``. + + note: not all embded certificate are directly used to sign the :class:`CryptObject`. + + :return: :class:`CertificateContext` + """ + return windows.crypto.CertificateContext.from_buffer(self.get_raw_cert(index)) + + @property + def certs(self): + "TODO: DOC" + return [self.get_cert(i) for i in range(self.nb_cert)] + + # Signers accessors + + @property + def nb_signer(self): + """The number of signers for the CryptObject + + :type: :class:`int` + """ + return self.get_param(gdef.CMSG_SIGNER_COUNT_PARAM) + + + def get_signer_data(self, index=0): + """Returns the signer informations for signer nb ``index`` + + :return: :class:`CMSG_SIGNER_INFO` + """ + return self.get_param(gdef.CMSG_SIGNER_INFO_PARAM, index) + + @property + def signers(self): + return [self.get_signer_data(i) for i in range(self.nb_signer)] + + @property + def signers_and_certs(self): + return [(self.get_signer_data(i), self.get_signer_certificate(i)) for i in range(self.nb_signer)] + + # def __repr__ \ No newline at end of file