Repository: pyinstaller/pyinstaller

Issue:
PyInstaller corrupts python35.dll?
I'm on Windows 7, using 32 bit Python 3.5.3 installed using the official installer. The problem I'm describing happens both with PyInstaller version 3.2.1, as well as the current development version `https://github.com/pyinstaller/pyinstaller/archive/develop.zip`, which I assume is revision 82fb738b8. Clearing the caches by deleting `%APPDATA%\pyinstaller` does not solve the problem.

My Python distribution contains the file `python35.dll`. This file lies at `C:/Users/Michael/AppData/Local/Programs/Python/Python35-32/python35.dll`. When I inspect the file's properties, I see that it is signed by the PSF:

![image](https://cloud.githubusercontent.com/assets/1076393/24300400/a5a9afd8-10ac-11e7-93b0-8f37ea25f0cc.png)

When I package my app with PyInstaller, a copy of `python35.dll` is included. However! This file is not the same as the one shown above. In particular, it is missing the digital signature:

![image](https://cloud.githubusercontent.com/assets/1076393/24300490/eb34a530-10ac-11e7-85a5-2f89dbd895bb.png)

When I attempt to sign the file myself, I get an error from `signtool`:

```
SignTool Error: SignedCode::Sign returned error: 0x800700C1
        %1 is not a valid Win32 application.
SignTool Error: An error occurred while attempting to sign: [...]\python35.dll
```

There is no other copy of `python35.dll` on my system. So it's not like the "corrupted" version already exists; It gets created by PyInstaller.

My frozen app runs just fine when I replace the "corrupted" copy of `python35.dll` by the original one.

Is PyInstaller changing `python35.dll`? If yes, is it doing so on purpose? Why?

PyInstaller is extremely useful - thank you so much for developing it.
Michael


Discussion:
I'm not aware of PyInstaller changing the .dll. I just checked the code and the dll is handled like any other binary added to `binaries` (except that it search as a dependency of `sys.executable`.

Maybe some other version of the dll is hanging around in your PATH and this gets picked up?! Please search the `outAnylisis-*.toc` file (and others) for the dll`s name and verify the path.
Thank you very much for your reply.

As I said, there is no other version of the DLL hanging around in my PATH. My file `out00-Analysis.toc` contains the following entry:

```
('python35.dll',
   'c:\\users\\michael\\dev\\fman\\venv\\scripts\\python35.dll',
   'BINARY')
```

This file has the PSF signature as shown in the screenshot above. However, the file `c:\Users\Michael\AppData\Roaming\pyinstaller\bincache00_py35_32bit\python35.dll` which also ends up in my PyInstaller's build output does not have the signature. 
I'd be happy to debug into this myself. Could you give me a few pointers? Where in the code are .dll dependencies gathered and copied to the `bincache...` directory?
I've identified the code in PyInstaller that modifies `python35.dll`. The function that does it is `UpdateManifestResourcesFromXML` in `pyinstaller.utils.win32.winmanifest`. Before the function is called, `python35.dll` in PyInstaller's `bincache` directory is unmodified. Afterwards, the Digital Signature is gone. The stack trace is:

```
  c:\users\michael\dev\fman\venv\scripts\pyinstaller-script.py(11)<module>()
-> load_entry_point('PyInstaller==3.2.1', 'console_scripts', 'pyinstaller')()
  pyinstaller\__main__.py(90)run()
-> run_build(pyi_config, spec_file, **vars(args))
  pyinstaller\__main__.py(46)run_build()
-> PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs)
  pyinstaller\building\build_main.py(788)main()
-> build(specfile, kw.get('distpath'), kw.get('workpath'), kw.get('clean_build'))
  pyinstaller\building\build_main.py(734)build()
-> exec(text, spec_namespace)
  <string>(33)<module>()
  pyinstaller\building\api.py(650)__init__()
-> self.__postinit__()
  pyinstaller\building\datastruct.py(161)__postinit__()
-> self.assemble()
  pyinstaller\building\api.py(682)assemble()
-> dist_nm=inm)
  pyinstaller\building\utils.py(309)checkCache()
-> [language])
  pyinstaller\utils\win32\winmanifest.py(939)update_resources()
-> languages)
> pyinstaller\utils\win32\winmanifest.py(1017)UpdateManifestResourcesFromXML()->None
```

Could it be that [lines 305 - 312 in `utils.py`](https://github.com/pyinstaller/pyinstaller/blob/1ee8330/PyInstaller/building/utils.py#L305-L312) should have one more level of indent, so they're only called when the if condition `CONF.get('win_private_assemblies', False)` in line 294 is true?
No. Those lines are needed to alter the dependency information for the DLL, changing any version numbers to those of the files that are actually bundled with the built application. But this is only relevant for Python 2.7.

Rather, the call to `applyRedirects()` should return True or False to indicate whether any redirects where applied, and the `manifest.update_resources` should be skipped if there were no redirects *and* assemblies are not changed to private, in order to avoid modifying the DLL and rendering its signature invalid.

By the way, you didn't describe a problem other than "the digital signature is not valid when I right click and go to properties". Are you having any problems actually running the built application?
If this is only relevant for Python 2.7, is it a bug if it's also executed when I'm using Python 3.5?

I don't have problems running the built application - it runs fine. I came across the problem because `python35.dll` doesn't have a signature, yet at the same time can't be signed any more with `signtool`. I'd like to sign all binary files included in my application so Windows doesn't warn that the app is untrusted.


> is it a bug if it's also executed when I'm using Python 3.5?

The bug isn't that it's executed for the Python 3.5 DLL specifically, but that it's executed for any DLL that does not need to be modified in this way. Any DLL that references another DLL via SxS manifests, where that second DLL is redirected to a different version of the same DLL via SxS version redirect policies, needs to be modified in this way.

Verify that you are not trying to use a 64-bit SignTool on a 32-bit python DLL, and then try to use [delcert](https://forum.xda-developers.com/showthread.php?t=416175) to remove the existing-yet-invalid signature.
PyInstaller already has support for codesigning OS X binaries using a provided key - I wonder if it should do the same for Windows ones too.
Support for codesigning would be a useful feature.

I checked the `signtool` I'm using - it's 32 bit.

My current workaround is to simply replace PyInstaller's modified copy of `python35.dll` by the original one (which contains the PSF signature). It seems to work. But could it be that it breaks one a user's machine?
> could it be that it breaks one a user's machine?

In this case, no. python35.dll does not need to be modified at all.
Ok so could we then patch PyInstaller so it doesn't modify files that don't need to be modified? Eg. by making `applyRedirects()` return whether any redirects were applied like you suggested?
same to me.
I`m trying to sign *.dll , *.pyd files but some  sucessed some failed and the exe will work normally.
Same for me too...
And I need to have all my binaries to be signed...
Any news on this issue ?

Resolving patch:
diff --git a/PyInstaller/building/utils.py b/PyInstaller/building/utils.py
--- a/PyInstaller/building/utils.py
+++ b/PyInstaller/building/utils.py
@@ -122,12 +122,15 @@ def applyRedirects(manifest, redirects):
     :return:
     :rtype:
     """
+    redirecting = False
     for binding in redirects:
         for dep in manifest.dependentAssemblies:
             if match_binding_redirect(dep, binding):
                 logger.info("Redirecting %s version %s -> %s",
                             binding.name, dep.version, binding.newVersion)
                 dep.version = binding.newVersion
+                redirecting = True
+    return redirecting
 
 def checkCache(fnm, strip=False, upx=False, dist_nm=None):
     """
@@ -287,7 +290,8 @@ def checkCache(fnm, strip=False, upx=False, dist_nm=None):
                             logger.error("From file %s", cachedfile, exc_info=1)
                         else:
                             # optionally change manifest to private assembly
-                            if CONF.get('win_private_assemblies', False):
+                            private = CONF.get('win_private_assemblies', False)
+                            if private:
                                 if manifest.publicKeyToken:
                                     logger.info("Changing %s into a private assembly",
                                                 os.path.basename(fnm))
@@ -298,14 +302,15 @@ def checkCache(fnm, strip=False, upx=False, dist_nm=None):
                                     # Exclude common-controls which is not bundled
                                     if dep.name != "Microsoft.Windows.Common-Controls":
                                         dep.publicKeyToken = None
-                            applyRedirects(manifest, redirects)
-                            try:
-                                manifest.update_resources(os.path.abspath(cachedfile),
-                                                          [name],
-                                                          [language])
-                            except Exception as e:
-                                logger.error(os.path.abspath(cachedfile))
-                                raise
+                            redirecting = applyRedirects(manifest, redirects)
+                            if redirecting or private:
+                                try:
+                                    manifest.update_resources(os.path.abspath(cachedfile),
+                                                              [name],
+                                                              [language])
+                                except Exception as e:
+                                    logger.error(os.path.abspath(cachedfile))
+                                    raise
 
     if cmd:
         try:


Test patch: