diff --git a/TODO b/TODO index f796c61..95ce64d 100644 --- a/TODO +++ b/TODO @@ -22,8 +22,6 @@ TODO: - registry - test ! - - Clean pe_parse.py - Documentation * verif samples diff --git a/docs/source/crypto.rst b/docs/source/crypto.rst new file mode 100644 index 0000000..abcea1f --- /dev/null +++ b/docs/source/crypto.rst @@ -0,0 +1,89 @@ +``windows.crypto`` -- CryptoAPI +******************************* + +.. module:: windows.crypto + +The :mod:`windows.crypto` module offers some wrappers arround the CryptoAPI_. + +The main goal of this module (for now) is providing simple encryption/decryption methods. + +Encryption +"""""""""" + +.. note:: + + See sample :ref:`sample_crypto_encryption` + +encrypt +''''''' + +.. autofunction:: encrypt + +decrypt +''''''' + +.. autofunction:: decrypt + +import_pfx +'''''''''' + +.. autofunction:: import_pfx + + +Certificate +""""""""""" + +.. note:: + + See sample :ref:`sample_crypto_certificate` + +CertificateContext +'''''''''''''''''' + +.. autoclass:: CertificateContext + +EHCERTSTORE +''''''''''' + +.. autoclass:: EHCERTSTORE + + +CryptContext +'''''''''''' + +.. autoclass:: CryptContext + + +Generating componants +""""""""""""""""""""" + +.. module:: windows.crypto.generation + +This module is used to generate selfsigned-certificates / keypair and pfx file. + +.. note:: + + See ``genkeys()`` in the sample :ref:`sample_crypto_encryption` + + +generate_selfsigned_certificate +''''''''''''''''''''''''''''''' + +.. autofunction:: generate_selfsigned_certificate + + +generate_key +'''''''''''' + +.. autofunction:: generate_key + + +generate_pfx +'''''''''''' + +.. autofunction:: generate_pfx + + + + +.. _CryptoAPI: https://msdn.microsoft.com/en-us/library/windows/desktop/aa380252(v=vs.85).aspx \ No newline at end of file diff --git a/docs/source/sample.rst b/docs/source/sample.rst index 928c9bf..bdec430 100644 --- a/docs/source/sample.rst +++ b/docs/source/sample.rst @@ -525,7 +525,7 @@ This sample is a working POC able to generate key-pair, encrypt and decrypt file Ouput:: - (cmd λ) python ..\samples\encryption_demo.py genkey YOLOCERTIF mykey --pfxpassword MYPASSWORD + (cmd λ) python samples\encryption_demo.py genkey YOLOCERTIF mykey --pfxpassword MYPASSWORD (cmd λ) ls @@ -533,7 +533,7 @@ Ouput:: (cmd λ) echo|set /p="my secret message" > message.txt - (cmd λ) python ..\samples\encryption_demo.py crypt message.txt message.crypt mykey.cer + (cmd λ) python samples\encryption_demo.py crypt message.txt message.crypt mykey.cer Encryption done. Result: bytearray(b'0\x82\x01\x19\x06\t*\x86H\x86\xf7\r\x01\x07\x03\xa0\x82\x01\n0\x82\x01\x06\x02\x01\x001\x81\xc30\x81 \xc0\x02\x01\x000)0\x151\x130\x11\x06\x03U\x04\x03\x13\nYOLOCERTIF\x02\x10\x1b\xa4>\x17\xf7\xed\xec\xabO\xf8\x11 @@ -543,7 +543,7 @@ Ouput:: \xc6\x12x\x1am\xc8\x01t\xac\xa6\xf3#\x02\xd4J \x8eZ\xbb\x10W\xe1 0;\x06\t*\x86H\x86\xf7\r\x01\x07\x010\x14\x06\x08* \x86H\x86\xf7\r\x03\x07\x04\x08\x14F\x04\xad\xed9\xed<\x80\x18\x80]6\xccTV\xbc\xb8*\x84QY!~\xb3\n\x1aV\xd4\rf\xd1n:') - (cmd λ) python ..\samples\encryption_demo.py decrypt message.crypt mykey.pfx BADPASS + (cmd λ) python samples\encryption_demo.py decrypt message.crypt mykey.pfx BADPASS Traceback (most recent call last): File "..\samples\encryption_demo.py", line 103, in res.func(**res.__dict__) @@ -559,5 +559,38 @@ Ouput:: raise Kernel32Error(func_name) windows.winproxy.Kernel32Error: PFXImportCertStore: [Error 86] The specified network password is not correct. - (cmd λ) python ..\samples\encryption_demo.py decrypt message.crypt mykey.pfx MYPASSWORD - Result = \ No newline at end of file + (cmd λ) python samples\encryption_demo.py decrypt message.crypt mykey.pfx MYPASSWORD + Result = + + +.. _sample_crypto_certificate: + +Certificate demo +'''''''''''''''' + +.. literalinclude:: ..\..\samples\certificate.py + + +Ouput:: + + + (cmd λ) python .\samples\certificate.py + Analysing certificate: + * name: + * issuer: + * raw_serial: <[51, 0, 0, 1, 6, 110, 195, 37, 196, 49, 201, 24, 14, 0, 0, 0, 0, 1, 6]> + * serial: <33 00 00 01 06 6e c3 25 c4 31 c9 18 0e 00 00 00 00 01 06> + * encoded start: + + This certificate has 1 certificate chain + Chain 0: + : + * issuer: + : + * issuer: + : + * issuer: + + Looking for in trusted certificates + matches = [] + Found it ! \ No newline at end of file diff --git a/samples/certificate.py b/samples/certificate.py index 8611954..3b251d9 100644 --- a/samples/certificate.py +++ b/samples/certificate.py @@ -63,8 +63,4 @@ print("matches = {0}".format(matchs)) if matchs: print("Found it !") else: - print("Not found :(") - - - -# TODO: add store enumeration \ No newline at end of file + print("Not found :(") \ No newline at end of file diff --git a/samples/encryption_demo.py b/samples/encryption_demo.py index beb654c..1dafa41 100644 --- a/samples/encryption_demo.py +++ b/samples/encryption_demo.py @@ -26,12 +26,13 @@ def decrypt(src, pfxfile, password, **kwargs): pfx = crypto.import_pfx(pfxfile.read(), password) # Decrypt the content of the file decrypted = crypto.decrypt(pfx, src.read()) - print(u"Result = <{0}>".format(decrypted.decode("utf8"))) + print(u"Result = <{0}>".format(decrypted)) + return decrypted PFW_TMP_KEY_CONTAINER = "PythonForWindowsTMPContainer" def genkeys(common_name, pfxpassword, outname, **kwargs): - """Generate a SHA256/RSA key pair. A self-signed certificate with 'common_name' is store as 'outname'.cer. + """Generate a SHA256/RSA key pair. A self-signed certificate with 'common_name' is stored as 'outname'.cer. The private key is stored in 'outname'.pfx protected with 'pfxpassword'""" cert_store = crypto.EHCERTSTORE.new_in_memory() # Create a TMP context that will hold our newly generated key-pair @@ -39,6 +40,9 @@ def genkeys(common_name, pfxpassword, outname, **kwargs): key = HCRYPTKEY() # Generate a key-pair that is exportable winproxy.CryptGenKey(ctx, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, key) + # It does NOT destroy the key-pair from the container, + # It only release the key handle + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379918(v=vs.85).aspx winproxy.CryptDestroyKey(key) # Descrption of the key-container that will be used to generate the certificate @@ -68,7 +72,7 @@ def genkeys(common_name, pfxpassword, outname, **kwargs): # Dump the certif (public key) and pfx (public + private keys) with open(outname + ".cer", "wb") as f: # The encoded certif only contains the public key - f.write(certif.encoded()) + f.write(certif.encoded) with open(outname + ".pfx", "wb") as f: f.write(pfx) print(certif) diff --git a/windows/crypto/certificate.py b/windows/crypto/certificate.py index 5f52ea9..c02e282 100644 --- a/windows/crypto/certificate.py +++ b/windows/crypto/certificate.py @@ -112,9 +112,13 @@ class CryptObject(object): class EHCERTSTORE(HCERTSTORE): + """A certificate store""" @property def certs(self): - "Based on CertEnumCertificatesInStore" + """The certificates in the store + + :type: [:class:`CertificateContext`] -- A list of Certificate + """ res = [] last = None while True: @@ -131,10 +135,12 @@ class EHCERTSTORE(HCERTSTORE): raise RuntimeError("Out of infinit loop") def add_certificate(self, certificate): + """Add a certificate to the store""" winproxy.CertAddCertificateContextToStore(self, certificate, CERT_STORE_ADD_NEW, None) @classmethod def from_file(cls, filename): + """Create a new :class:`EHCERTSTORE` from ``filename``""" res = winproxy.CertOpenStore(CERT_STORE_PROV_FILENAME_A, DEFAULT_ENCODING, None, CERT_STORE_OPEN_EXISTING_FLAG, filename) return ctypes.cast(res, cls) @@ -142,11 +148,15 @@ class EHCERTSTORE(HCERTSTORE): # See https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx @classmethod def from_system_store(cls, store_name): + """Create a new :class:`EHCERTSTORE` from system store``store_name`` + (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx) + """ res = winproxy.CertOpenStore(CERT_STORE_PROV_SYSTEM_A, DEFAULT_ENCODING, None, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READONLY_FLAG, store_name) return ctypes.cast(res, cls) @classmethod def new_in_memory(cls): + """Create a new temporary :class:`EHCERTSTORE` in memory""" res = winproxy.CertOpenStore(CERT_STORE_PROV_MEMORY, DEFAULT_ENCODING, None, 0, None) return ctypes.cast(res, cls) @@ -155,6 +165,14 @@ class EHCERTSTORE(HCERTSTORE): # 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' def import_pfx(pfx, password=None, flags=CRYPT_USER_KEYSET | PKCS12_NO_PERSIST_KEY): + """Import the file ``pfx`` with the ``password``. + + ``default flags = PKCS12_NO_PERSIST_KEY | CRYPT_USER_KEYSET``. + + ``PKCS12_NO_PERSIST_KEY`` tells ``CryptoAPI`` to NOT save the keys in a on-disk container. + + :return: :class:`EHCERTSTORE` + """ if isinstance(pfx, basestring): pfx = ECRYPT_DATA_BLOB.from_string(pfx) cert_store = winproxy.PFXImportCertStore(pfx, password, flags) @@ -164,7 +182,7 @@ def import_pfx(pfx, password=None, flags=CRYPT_USER_KEYSET | PKCS12_NO_PERSIST_K # Why PCCERT_CONTEXT (pointer type) and not _CERT_CONTEXT ? class CertificateContext(PCCERT_CONTEXT): """Represent a Certificate. - + note: It is a pointer ctypes structure (``PCCERT_CONTEXT``) """ _type_ = PCCERT_CONTEXT._type_ # Not herited from PCCERT_CONTEXT @@ -185,7 +203,7 @@ class CertificateContext(PCCERT_CONTEXT): def serial(self): """The string representation of the certificate's serial. - :type: :class:``str`` + :type: :class:`str` """ serial_number = self[0].pCertInfo[0].SerialNumber serial_bytes = self.raw_serial @@ -218,7 +236,7 @@ class CertificateContext(PCCERT_CONTEXT): def store(self): """The certificate store that contains the certificate - :type: :class:``EHCERTSTORE`` + :type: :class:`EHCERTSTORE` """ return EHCERTSTORE(self[0].hCertStore) @@ -301,7 +319,7 @@ class CertificateContext(PCCERT_CONTEXT): @classmethod def from_file(cls, filename): - """Create a :class:``CertificateContext`` for the file ``filename`` + """Create a :class:`CertificateContext` for the file ``filename`` :return: :class:`CertificateContext` """ @@ -313,7 +331,7 @@ class CertificateContext(PCCERT_CONTEXT): @classmethod def from_buffer(cls, data): - """Create a :class:``CertificateContext`` from the buffer ``data`` + """Create a :class:`CertificateContext` from the buffer ``data`` :return: :class:`CertificateContext` """ diff --git a/windows/crypto/generation.py b/windows/crypto/generation.py index 1e2dbf7..a351423 100644 --- a/windows/crypto/generation.py +++ b/windows/crypto/generation.py @@ -6,6 +6,12 @@ from windows.crypto import DEFAULT_ENCODING, EHCERTSTORE def generate_selfsigned_certificate(name="CN=DEFAULT", prov=None, key_info=None, flags=0, signature_algo=None): + """Generate a selfsigned certificate. + + See https://msdn.microsoft.com/en-us/library/windows/desktop/aa376039(v=vs.85).aspx + + :return: :class:`windows.crypto.CertificateContext` + """ size = ULONG(len(name) + 0x100) buffer = (ctypes.c_ubyte * size.value)() winproxy.CertStrToNameA(X509_ASN_ENCODING, name, CERT_OID_NAME_STR, None, buffer, size, None) @@ -15,6 +21,10 @@ def generate_selfsigned_certificate(name="CN=DEFAULT", prov=None, key_info=None, def generate_key(prov, keytype=AT_KEYEXCHANGE, flags=CRYPT_EXPORTABLE): + """Generate a keypair if type ``keytype``. + + :return: :class:`HCRYPTKEY` + """ key = HCRYPTKEY() winproxy.CryptGenKey(prov, keytype, flags , key) return key @@ -33,6 +43,10 @@ def generate_key(prov, keytype=AT_KEYEXCHANGE, flags=CRYPT_EXPORTABLE): # return key def generate_pfx(hstore, password=None): + """Generate a pfx protected by ``password`` contaning the certificates in ``hstore`` + + :return: :class:`bytearray` -- The raw PFX + """ blob = ECRYPT_DATA_BLOB(0, None) winproxy.PFXExportCertStoreEx(hstore, blob, password, None, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY) blob.pbData = (ctypes.c_ubyte * blob.cbData)()