77""" Tools to apply Authenticode code signing signatures and timestamps to files using the native Microsoft APIs.
88Only runs on Windows platforms."""
99
10+ cpdef enum CertificateStoreLocation:
11+ CURRENT_USER = 1
12+ LOCAL_MACHINE = 2
13+
1014IF UNAME_SYSNAME == " Windows" :
1115
1216 import os
@@ -32,11 +36,12 @@ IF UNAME_SYSNAME == "Windows":
3236 cpdef void codesign_file(filename,
3337 certificate,
3438 windows.LPCWSTR password,
35- windows.LPCWSTR timestamp = NULL ,
39+ timestamp = None ,
3640 bint use_rfc3161 = False ,
3741 bint use_sha256 = False ) except * :
38- """ Codesign a file using the Microsoft signing API found in mssign32.dll.
39-
42+ """ Codesign a file with the Microsoft signing API found in mssign32.dll using a certificate found in a PFX file
43+ or PKCS#12 container.
44+
4045 :param filename: the filename of the file to codesign
4146 :param certificate: the filename of the certificate file to codesign with
4247 :param password: the password used to unlock the certificate
@@ -46,13 +51,107 @@ IF UNAME_SYSNAME == "Windows":
4651 :param use_sha256: whether or not to use SHA-256 as the timestamping hash function. If ``True``, use SHA-256.
4752 If ``False``, use SHA-1.
4853 """
54+ cdef wincrypt.CRYPT_DATA_BLOB cert_blob
55+ cdef windows.HANDLE cert_store = NULL
56+ cdef windows.LPCWSTR timestamp_wstr = NULL
57+
58+ try :
59+ # Sanity check arguments
60+ if not os.path.isfile(certificate):
61+ raise FileNotFoundError(f" No such file: '{certificate}'" )
62+
63+ # Open the certificate file and convert it into an in-memory cert store
64+ with open (certificate, " rb" ) as cert:
65+ cert_data = cert.read()
66+ cert_blob.cbData = < windows.DWORD> len (cert_data)
67+ cert_blob.pbData = < char * > cert_data
68+ cert_store = wincrypt.PFXImportCertStore(& cert_blob, password, 0 )
69+ if cert_store is NULL :
70+ cert_store = wincrypt.PFXImportCertStore(& cert_blob, _empty_wstring, 0 )
71+ if cert_store is NULL :
72+ cert_store = wincrypt.PFXImportCertStore(& cert_blob, NULL , 0 )
73+ if cert_store is NULL :
74+ raise CodesignError(f" Could not load certificate {certificate}; is the password correct?" )
75+
76+ # Handle optional values
77+ if timestamp is not None :
78+ timestamp_pystr = str (timestamp)
79+ timestamp_wstr = timestamp_pystr
80+
81+ # Call the common signing code
82+ _codesign_file_core(filename, cert_store, wincrypt.CERT_FIND_ANY, NULL , timestamp_wstr,
83+ use_rfc3161, use_sha256)
84+ finally :
85+ if cert_store is not NULL :
86+ wincrypt.CertCloseStore(cert_store, wincrypt.CERT_CLOSE_STORE_CHECK_FLAG)
87+
88+ cpdef void codesign_file_from_store(filename,
89+ CertificateStoreLocation store_location,
90+ windows.LPCWSTR store_name,
91+ windows.LPCWSTR store_cn,
92+ timestamp = None ,
93+ bint use_rfc3161 = False ,
94+ bint use_sha256 = False ) except * :
95+ """ Codesign a file with the Microsoft signing API found in mssign32.dll using a certificate found in a system
96+ certificate store.
97+
98+ :param filename: the filename of the file to codesign
99+ :param store_location: the location of the Windows certificate store to find the certificate to sign with in
100+ :param store_name: the name of the Windows certificate store to find the certificate to sign with in
101+ :param store_cn: a string specifying the subject common name (or a substring thereof) of the certificate to
102+ sign with
103+ :param timestamp: a URL of the timestamping service to timestamp the code signature with
104+ :param use_rfc3161: whether or not to use the RFC 3161 timestamping protocol. If ``True``, use RFC 3161.
105+ If ``False``, use Authenticode.
106+ :param use_sha256: whether or not to use SHA-256 as the timestamping hash function. If ``True``, use SHA-256.
107+ If ``False``, use SHA-1.
108+ """
109+ cdef windows.HANDLE cert_store = NULL
110+ cdef windows.DWORD cert_location
111+ cdef windows.LPCWSTR timestamp_wstr = NULL
112+
113+ try :
114+ # Sanity check arguments
115+ if store_name is None or len (store_name) == 0 :
116+ raise ValueError (" System certificate store name is empty" )
117+ if store_cn is None or len (store_cn) == 0 :
118+ raise ValueError (" System store certificate common name is empty" )
119+
120+ # Open the system store
121+ if store_location == CertificateStoreLocation.CURRENT_USER:
122+ cert_location = wincrypt.CERT_SYSTEM_STORE_CURRENT_USER
123+ elif store_location == CertificateStoreLocation.LOCAL_MACHINE:
124+ cert_location = wincrypt.CERT_SYSTEM_STORE_LOCAL_MACHINE
125+ else :
126+ raise ValueError (f" Unknown local store location '{store_location}'" )
127+ cert_store = wincrypt.CertOpenStore(wincrypt.CERT_STORE_PROV_SYSTEM_W, 0 , 0 , cert_location, store_name)
128+ if cert_store is NULL :
129+ raise CodesignError(" Could not open system store" )
130+
131+ # Handle optional values
132+ if timestamp is not None :
133+ timestamp_pystr = str (timestamp)
134+ timestamp_wstr = timestamp_pystr
135+
136+ # Call the common signing code
137+ _codesign_file_core(filename, cert_store, wincrypt.CERT_FIND_SUBJECT_STR_W, store_cn, timestamp_wstr,
138+ use_rfc3161, use_sha256)
139+ finally :
140+ if cert_store is not NULL :
141+ wincrypt.CertCloseStore(cert_store, wincrypt.CERT_CLOSE_STORE_CHECK_FLAG)
142+
143+ cdef void _codesign_file_core(filename,
144+ windows.HANDLE cert_store,
145+ windows.DWORD cert_find_type,
146+ windows.LPCWSTR cert_find_param,
147+ windows.LPCWSTR timestamp,
148+ bint use_rfc3161,
149+ bint use_sha256) except * :
49150 cdef windows.HANDLE mssign32_library = NULL
50151 cdef mssign32.SignerSignExType signer_sign_ex_fun
51152 cdef mssign32.SignerTimeStampType signer_time_stamp_fun
52153 cdef mssign32.SignerTimeStampEx2Type signer_time_stamp_ex2_fun
53154 cdef mssign32.SignerFreeSignerContextType signer_free_signer_context_fun
54- cdef wincrypt.CRYPT_DATA_BLOB cert_blob
55- cdef windows.HANDLE cert_store = NULL
56155 cdef const wincrypt.CERT_CONTEXT* cert_context = NULL
57156 cdef windows.DWORD key_spec, key_spec_len
58157 cdef mssign32.SIGNER_FILE_INFO signer_file_info
@@ -68,8 +167,6 @@ IF UNAME_SYSNAME == "Windows":
68167 # Sanity check arguments
69168 if not os.path.isfile(filename):
70169 raise FileNotFoundError(f" No such file: '{filename}'" )
71- if not os.path.isfile(certificate):
72- raise FileNotFoundError(f" No such file: '{certificate}'" )
73170 if use_sha256 and not use_rfc3161:
74171 raise ValueError (" SHA-256 timestamping requires the RFC 3161 timestamping protocol" )
75172
@@ -90,23 +187,10 @@ IF UNAME_SYSNAME == "Windows":
90187 if signer_free_signer_context_fun is NULL :
91188 raise CodesignError(" Cannot find function 'SignerFreeSignerContext'" )
92189
93- # Open the certificate file and convert it into an in-memory cert store
94- with open (certificate, " rb" ) as cert:
95- cert_data = cert.read()
96- cert_blob.cbData = < windows.DWORD> len (cert_data)
97- cert_blob.pbData = < char * > cert_data
98- cert_store = wincrypt.PFXImportCertStore(& cert_blob, password, 0 )
99- if cert_store is NULL :
100- cert_store = wincrypt.PFXImportCertStore(& cert_blob, _empty_wstring, 0 )
101- if cert_store is NULL :
102- cert_store = wincrypt.PFXImportCertStore(& cert_blob, NULL , 0 )
103- if cert_store is NULL :
104- raise CodesignError(f" Could not load certificate {certificate}; is the password correct?" )
105-
106- # Extract the cert from the new cert store
190+ # Extract the cert from the store
107191 cert_context = wincrypt.CertFindCertificateInStore(cert_store,
108192 wincrypt.X509_ASN_ENCODING | wincrypt.PKCS_7_ASN_ENCODING,
109- 0 , wincrypt.CERT_FIND_ANY, NULL , NULL )
193+ 0 , cert_find_type, cert_find_param , NULL )
110194 if cert_context is NULL :
111195 raise CodesignError(" Could not get certificate from store" )
112196 found_private_key = False
@@ -120,7 +204,7 @@ IF UNAME_SYSNAME == "Windows":
120204 else :
121205 cert_context = wincrypt.CertFindCertificateInStore(cert_store,
122206 wincrypt.X509_ASN_ENCODING | wincrypt.PKCS_7_ASN_ENCODING,
123- 0 , wincrypt.CERT_FIND_ANY, NULL , cert_context)
207+ 0 , cert_find_type, cert_find_param , cert_context)
124208 if cert_context is NULL :
125209 raise CodesignError(" Could not get certificate from store" )
126210
@@ -182,15 +266,14 @@ IF UNAME_SYSNAME == "Windows":
182266 finally :
183267 if cert_context is not NULL :
184268 wincrypt.CertFreeCertificateContext(cert_context)
185- if cert_store is not NULL :
186- wincrypt.CertCloseStore(cert_store, wincrypt.CERT_CLOSE_STORE_CHECK_FLAG)
187269 if mssign32_library is not NULL :
188270 windows.FreeLibrary(mssign32_library)
189271
190272ELSE :
191273
192274 def codesign_file (filename , certificate , password , timestamp = None , use_rfc3161 = False , use_sha256 = False ):
193- """ Codesign a file using the Microsoft signing API found in mssign32.dll.
275+ """ Codesign a file with the Microsoft signing API found in mssign32.dll using a certificate found in a PFX file
276+ or PKCS#12 container.
194277
195278 :param filename: the filename of the file to codesign
196279 :param certificate: the filename of the certificate file to codesign with
@@ -202,3 +285,21 @@ ELSE:
202285 If ``False``, use SHA-1.
203286 """
204287 raise OSError (" Codesigning not supported on non-Win32 platforms" )
288+
289+ def codesign_file_from_store (filename , store_location , store_name , store_cn , timestamp = None , use_rfc3161 = False ,
290+ use_sha256 = False ):
291+ """ Codesign a file with the Microsoft signing API found in mssign32.dll using a certificate found in a system
292+ certificate store.
293+
294+ :param filename: the filename of the file to codesign
295+ :param store_location: the location of the Windows certificate store to find the certificate to sign with in
296+ :param store_name: the name of the Windows certificate store to find the certificate to sign with in
297+ :param store_cn: a string specifying the subject common name (or a substring thereof) of the certificate to
298+ sign with
299+ :param timestamp: a URL of the timestamping service to timestamp the code signature with
300+ :param use_rfc3161: whether or not to use the RFC 3161 timestamping protocol. If ``True``, use RFC 3161.
301+ If ``False``, use Authenticode.
302+ :param use_sha256: whether or not to use SHA-256 as the timestamping hash function. If ``True``, use SHA-256.
303+ If ``False``, use SHA-1.
304+ """
305+ raise OSError (" Codesigning not supported on non-Win32 platforms" )
0 commit comments