Skip to content

Commit ba6b22a

Browse files
committed
testing crypto stuff
1 parent 930c992 commit ba6b22a

3 files changed

Lines changed: 135 additions & 88 deletions

File tree

tests/test_crypto.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def test_certificate(rawcert):
6868
cert = windows.crypto.CertificateContext.from_buffer(rawcert)
6969
assert cert.serial == '1b 8e 94 cb 0b 3e eb b6 41 39 f3 c9 09 b1 6b 46'
7070
assert cert.name == 'PythonForWindowsTest'
71+
cert.chains # TODO: craft a certificate with a chain for test purpose
7172

7273

7374
def test_pfx(rawcert, rawpfx):
@@ -101,4 +102,12 @@ def test_encrypt_decrypt(rawcert, rawpfx):
101102
assert message_to_encrypt == decrypt
102103
assert decrypt == decrypt2
103104

105+
def test_crypt_obj():
106+
path = r"C:\windows\system32\kernel32.dll"
107+
x = windows.crypto.CryptObject(path)
108+
x.crypt_msg.certs
109+
x.crypt_msg.signers
110+
x.signers_and_certs
111+
# TODO: Need some better ideas
112+
104113

windows/crypto/certificate.py

Lines changed: 46 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
import windows
55
from windows import winproxy
6-
# from windows.generated_def import *
76
import windows.generated_def as gdef
87

98
from windows.crypto import DEFAULT_ENCODING
10-
# from windows.crypto.helper import ECRYPT_DATA_BLOB
9+
10+
import windows.crypto.cryptmsg
1111

1212

1313
CRYPT_OBJECT_FORMAT_TYPE = [
@@ -44,17 +44,16 @@ class CryptObject(object):
4444
def __init__(self, filename, content_type=gdef.CERT_QUERY_CONTENT_FLAG_ALL):
4545
# No other API than filename for now..
4646
self.filename = filename
47-
if filename is None:
48-
return # TMP !
4947

50-
dwEncoding = DWORD()
51-
dwContentType = DWORD()
52-
dwFormatType = DWORD()
53-
hStore = PVOID()
54-
hMsg = PVOID()
48+
dwEncoding = gdef.DWORD()
49+
dwContentType = gdef.DWORD()
50+
dwFormatType = gdef.DWORD()
51+
hStore = EHCERTSTORE()
52+
hMsg = windows.crypto.cryptmsg.CryptMessage()
5553

5654
winproxy.CryptQueryObject(gdef.CERT_QUERY_OBJECT_FILE,
57-
LPWSTR(filename),
55+
gdef.LPWSTR(filename),
56+
# filename,
5857
content_type,
5958
gdef.CERT_QUERY_FORMAT_FLAG_BINARY,
6059
0,
@@ -65,84 +64,24 @@ def __init__(self, filename, content_type=gdef.CERT_QUERY_CONTENT_FLAG_ALL):
6564
hMsg,
6665
None)
6766

68-
self.hstore = hStore
69-
self.hmsg = hMsg
67+
self.cert_store = hStore
68+
self.crypt_msg = hMsg
7069
self.encoding = dwEncoding
7170
self.content_type = CRYPT_OBJECT_FORMAT_TYPE_DICT.get(dwContentType.value, dwContentType)
7271

73-
def msg_get_param(self, param_type, index=0):
74-
signer_info = DWORD()
75-
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa380227(v=vs.85).aspx
76-
winproxy.CryptMsgGetParam(self.hmsg, param_type, index, None, signer_info)
77-
buffer = ctypes.c_buffer(signer_info.value)
78-
winproxy.CryptMsgGetParam(self.hmsg, param_type, index, buffer, signer_info)
79-
80-
if param_type in self.MSG_PARAM_KNOW_TYPES:
81-
buffer = self.MSG_PARAM_KNOW_TYPES[param_type].from_buffer(buffer)
82-
return buffer
72+
def _signers_and_certs_generator(self):
73+
for signer in self.crypt_msg.signers:
74+
cert = self.cert_store.find(signer.Issuer, signer.SerialNumber)
75+
yield signer, cert
8376

8477
@property
85-
def nb_signer(self):
86-
"""The number of signers for the CryptObject
87-
88-
:type: :class:`int`
89-
"""
90-
return self.msg_get_param(CMSG_SIGNER_COUNT_PARAM).value
91-
92-
def get_signer_data(self, index=0):
93-
"""Returns the signer informations for signer nb ``index``
94-
95-
:return: :class:`CMSG_SIGNER_INFO`
96-
"""
97-
return self.msg_get_param(CMSG_SIGNER_INFO_PARAM, index)
98-
99-
def get_signer_certificate(self, index=0):
100-
"""Return the certificate used for signer nb ``index``
101-
102-
:return: :class:`CertificateContext`
103-
"""
104-
data = self.get_signer_data(index)
105-
cert_info = CERT_INFO()
106-
cert_info.Issuer = data.Issuer
107-
cert_info.SerialNumber = data.SerialNumber
108-
rawcertcontext = winproxy.CertFindCertificateInStore(self.hstore, self.encoding, 0, CERT_FIND_SUBJECT_CERT, byref(cert_info), None)
109-
#return rawcertcontext
110-
return CertificateContext(rawcertcontext[0])
111-
112-
def get_raw_cert(self, index=0):
113-
return self.msg_get_param(CMSG_CERT_PARAM, index)
114-
115-
def get_cert(self, index=0):
116-
"""Return embded certificate number ``index``.
117-
118-
note: not all embded certificate are directly used to sign the :class:`CryptObject`.
119-
120-
:return: :class:`CertificateContext`
121-
"""
122-
return CertificateContext.from_buffer(self.get_raw_cert(index))
123-
124-
cert = property(get_cert)
125-
126-
@property
127-
def nb_cert(self):
128-
"""The number of certificate embded in the :class:`CryptObject`
129-
130-
:type: :class:`int`
131-
"""
132-
return self.msg_get_param(CMSG_CERT_COUNT_PARAM).value
133-
134-
@property
135-
def signers(self):
136-
return [self.get_signer_data(i) for i in range(self.nb_signer)]
137-
138-
@property
139-
def certs(self):
140-
return [self.get_cert(i) for i in range(self.nb_cert)]
78+
def signers_and_certs(self):
79+
return list(self._signers_and_certs_generator())
14180

14281
def __repr__(self):
14382
return '<{0} "{1}" content_type={2}>'.format(type(self).__name__, self.filename, self.content_type)
14483

145-
84+
# TODO: rename to CertificateStore ?
14685
class EHCERTSTORE(gdef.HCERTSTORE):
14786
"""A certificate store"""
14887
@property
@@ -192,7 +131,21 @@ def new_in_memory(cls):
192131
res = winproxy.CertOpenStore(CERT_STORE_PROV_MEMORY, DEFAULT_ENCODING, None, 0, None)
193132
return ctypes.cast(res, cls)
194133

195-
# Add API arround 'CertFindCertificateInStore' ?
134+
135+
# TODO: a more complete search API ?
136+
def find(self, issuer, serialnumber):
137+
"""Return the certificate that match `issuer` and `serialnumber`
138+
139+
:return: :class:`CertificateContext`
140+
"""
141+
# data = self.get_signer_data(index)
142+
cert_info = gdef.CERT_INFO()
143+
cert_info.Issuer = issuer
144+
cert_info.SerialNumber = serialnumber
145+
rawcertcontext = winproxy.CertFindCertificateInStore(self, DEFAULT_ENCODING, 0, gdef.CERT_FIND_SUBJECT_CERT, ctypes.byref(cert_info), None)
146+
# return rawcertcontext
147+
return CertificateContext(rawcertcontext[0])
148+
196149

197150
# PKCS12_NO_PERSIST_KEY -> do not save it in a key container on disk
198151
# 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 @@ def store(self):
275228
def get_raw_certificate_chains(self): # Rename to all_chains ?
276229
chain_context = EPCCERT_CHAIN_CONTEXT()
277230

278-
enhkey_usage = CERT_ENHKEY_USAGE()
231+
enhkey_usage = gdef.CERT_ENHKEY_USAGE()
279232
enhkey_usage.cUsageIdentifier = 0
280233
enhkey_usage.rgpszUsageIdentifier = None
281234

282-
cert_usage = CERT_USAGE_MATCH()
283-
cert_usage.dwType = USAGE_MATCH_TYPE_AND
235+
cert_usage = gdef.CERT_USAGE_MATCH()
236+
cert_usage.dwType = gdef.USAGE_MATCH_TYPE_AND
284237
cert_usage.Usage = enhkey_usage
285238

286-
chain_para = CERT_CHAIN_PARA()
287-
chain_para.cbSize = sizeof(chain_para)
239+
chain_para = gdef.CERT_CHAIN_PARA()
240+
chain_para.cbSize = ctypes.sizeof(chain_para)
288241
chain_para.RequestedUsage = cert_usage
289242

290-
winproxy.CertGetCertificateChain(None, self, None, self[0].hCertStore, byref(chain_para), 0, None, byref(chain_context))
243+
winproxy.CertGetCertificateChain(None, self, None, self[0].hCertStore, ctypes.byref(chain_para), 0, None, ctypes.byref(chain_context))
291244
#return CertficateChain(chain_context)
292245
return chain_context
293246

@@ -349,6 +302,12 @@ def encoded(self):
349302
:type: :class:`bytearray`"""
350303
return bytearray(self[0].pbCertEncoded[:self[0].cbCertEncoded])
351304

305+
@property
306+
def version(self):
307+
"TODO: doc"
308+
return self[0].pbCertInfo.dwVersion
309+
310+
352311
@classmethod
353312
def from_file(cls, filename):
354313
"""Create a :class:`CertificateContext` from the file ``filename``
@@ -392,15 +351,14 @@ def __eq__(self, other):
392351

393352

394353
# Those classes are more of a POC than anything else
395-
396354
class EPCCERT_CHAIN_CONTEXT(gdef.PCCERT_CHAIN_CONTEXT):
397355
_type_ = gdef.PCCERT_CHAIN_CONTEXT._type_
398356

399357
@property
400358
def chains(self):
401359
res = []
402360
for i in range(self[0].cChain):
403-
simple_chain = ctypes.cast(self[0].rgpChain[i], gdef.EPCCERT_SIMPLE_CHAIN)
361+
simple_chain = ctypes.cast(self[0].rgpChain[i], EPCCERT_SIMPLE_CHAIN)
404362
res.append(simple_chain)
405363
return res
406364

windows/crypto/cryptmsg.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import ctypes
2+
3+
from windows import winproxy
4+
import windows.generated_def as gdef
5+
import windows.crypto
6+
7+
class CryptMessage(gdef.HCRYPTMSG):
8+
MSG_PARAM_KNOW_TYPES = {gdef.CMSG_SIGNER_INFO_PARAM: gdef.CMSG_SIGNER_INFO,
9+
gdef.CMSG_SIGNER_COUNT_PARAM: gdef.DWORD,
10+
gdef.CMSG_CERT_COUNT_PARAM: gdef.DWORD}
11+
12+
13+
def get_param(self, param_type, index=0):
14+
data_size = gdef.DWORD()
15+
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa380227(v=vs.85).aspx
16+
winproxy.CryptMsgGetParam(self, param_type, index, None, data_size)
17+
buffer = ctypes.c_buffer(data_size.value)
18+
winproxy.CryptMsgGetParam(self, param_type, index, buffer, data_size)
19+
20+
if param_type in self.MSG_PARAM_KNOW_TYPES:
21+
buffer = self.MSG_PARAM_KNOW_TYPES[param_type].from_buffer(buffer)
22+
if isinstance(buffer, gdef.DWORD): # DWORD -> return the Python int
23+
return buffer.value
24+
return buffer
25+
26+
27+
# Certificate accessors
28+
29+
@property
30+
def nb_cert(self):
31+
"""The number of certificate embded in the :class:`CryptObject`
32+
33+
:type: :class:`int`
34+
"""
35+
return self.get_param(gdef.CMSG_CERT_COUNT_PARAM)
36+
37+
def get_raw_cert(self, index=0):
38+
return self.get_param(gdef.CMSG_CERT_PARAM, index)
39+
40+
def get_cert(self, index=0):
41+
"""Return embded certificate number ``index``.
42+
43+
note: not all embded certificate are directly used to sign the :class:`CryptObject`.
44+
45+
:return: :class:`CertificateContext`
46+
"""
47+
return windows.crypto.CertificateContext.from_buffer(self.get_raw_cert(index))
48+
49+
@property
50+
def certs(self):
51+
"TODO: DOC"
52+
return [self.get_cert(i) for i in range(self.nb_cert)]
53+
54+
# Signers accessors
55+
56+
@property
57+
def nb_signer(self):
58+
"""The number of signers for the CryptObject
59+
60+
:type: :class:`int`
61+
"""
62+
return self.get_param(gdef.CMSG_SIGNER_COUNT_PARAM)
63+
64+
65+
def get_signer_data(self, index=0):
66+
"""Returns the signer informations for signer nb ``index``
67+
68+
:return: :class:`CMSG_SIGNER_INFO`
69+
"""
70+
return self.get_param(gdef.CMSG_SIGNER_INFO_PARAM, index)
71+
72+
@property
73+
def signers(self):
74+
return [self.get_signer_data(i) for i in range(self.nb_signer)]
75+
76+
@property
77+
def signers_and_certs(self):
78+
return [(self.get_signer_data(i), self.get_signer_certificate(i)) for i in range(self.nb_signer)]
79+
80+
# def __repr__

0 commit comments

Comments
 (0)