microsoft / msix-packaging

MSIX SDK
MIT License
988 stars 166 forks source link

Feature request: Signing MSIX packages #340

Open alant2020 opened 4 years ago

alant2020 commented 4 years ago

Signing MSIX packages with the "makemsix pack" command seems to be not supported. However, for the installation of msix files under Win 10 the package has to be signed.

This missing feature would be highly appreciated because I am using makemsix in a crosscompiling environment (Windows executables are built under macOS). Everything up to creating the misx file is possible except for the final step: getting a signed msix which can be installed under Win 10.

Using "signtool.exe" under Windows is a hassle as there seems no be no way to sign msix by means of openssl tools under macOS / Unix.

JohnMcPMS commented 4 years ago

There is a work in progress branch that unfortunately has not had enough time to become integrated to master: https://github.com/microsoft/msix-packaging/tree/johnmcpms/signing

If you would like to try it out and provide feedback here that would be most helpful.

It only supports signing using PFX files right now, and I cannot remember if it properly inserts the full cert chain into the signature.

floppinger commented 4 years ago

Hi John, is there a plan when the branch: https://github.com/microsoft/msix-packaging/tree/johnmcpms/signing will be integrated to master? We do a lot of dirty hacks to sign MSIX in a linux infrastructure. kind regard

JohnMcPMS commented 4 years ago

I will ask about getting some time to get it to a usable state. If you have tried it and can give feedback on whether it is working for your use case, that would help. If it will take a lot more work then it will be a harder sell to management, but it would also be a specific target.

Below is the makemsix command definition. So something like:

makemsix sign -p <package> -c <pfx> -cf pfx
    Command result{ "sign", "Signs an existing package in-place",
        {
            Option{ "-p", "Package file path.", true, 1, "package" },
            Option{ "-c", "Certificate file path.", true, 1, "cert" },
            Option{ "-cf", "Certificate format.", false, 1, "format" },
            // TODO: Potentially support other types of certificate files, along with separate private keys.
            // TODO: Support passing in the certificate chain separately.
            // TODO: Windows signing allows choosing whether to validate the block hashes, could add here.
            // TODO: Potentially allow non-inplace by making an output file param.
            // TODO: Full package content hash is optional
            // TODO: Flag to control CI catalog generation
            Option{ TOOL_HELP_COMMAND_STRING, "Displays this help text." },
        }
    };
floppinger commented 4 years ago

Hi John, thank you for your prompt reply. compiling master with makewin.cmd x86 --pack works. compiling johnmcpms/signingwith makewin.cmd x86 --pack is not working. compiling johnmcpms/signingwith makewin.cmd x86 (without --pack) works, but has no sign option. Error: Unrecognized command: sign

comile.log

JohnMcPMS commented 4 years ago

I've merged master into the signing branch, and was able to build makewin.cmd x86 --pack locally.

JohnMcPMS commented 4 years ago

Sorry, to be clear, I also pushed the merge back to https://github.com/microsoft/msix-packaging/tree/johnmcpms/signing.

floppinger commented 4 years ago

Hello John, I was able to build it locally.The merge with stable solved the problem. Very good! Unfortunately I get the following error when signing:

C:\temp>call "C:\temp\msix-packaging-johnmcpms-signing.vs\bin\makemsix.exe" sign -p C:\temp\Client.msix -c C:\Temp\MSIX.pfx -cf pfx Microsoft (R) makemsix version 0.0.0 Copyright (C) 2017 Microsoft. All rights reserved. WARNING: The signing feature is not complete, see the help for this command for more information.

Error: 0x80070032 LOG:

Removing files from the middle of the archive is not supported Call failed in C:\temp\msix-packaging-johnmcpms-signing\src\msix\pack\ZipObjectWriter.cpp on line 176

JohnMcPMS commented 4 years ago

Debugging that might take a bit longer to get to than just merging. I suspect that it is a result of the merge, because I did some pretty major refactoring of the zip handling. I promise it really was working when I wrote it 😉

nephatrine commented 4 years ago

I tried to test this on Linux. I'm building the signing branch with ./makelinux.sh --pack --validation-parser and do see the sign option. When I attempt to sign a .msix with it though, I get the following error:

Microsoft (R) makemsix version 1.7.129
Copyright (C) 2017 Microsoft.  All rights reserved.
WARNING: The signing feature is not complete, see the help for this command for more information.

Error: 0x80fa11ed
LOG:

Unable to open PFX file
OpenSSL Error Data:
  at /home/nephatrine/Projects/msix-packaging/lib/openssl/crypto/pkcs12/p12_kiss.c[116]
    error:23076071:PKCS12 routines:PKCS12_parse:mac verify failure

Call failed in /home/nephatrine/Projects/msix-packaging/src/msix/PAL/Crypto/OpenSSL/SignatureCreator.cpp on line 53

And this leaves the msix in a bad/invalid state afterwards as well unfortunately where it cannot even be read as an unsigned package.

I've tested the pfx itself on Windows using signtool and it seems to have no issues, though the cert is self-signed. I assume the actual issue is probably because there doesn't seem to be any way to specify the password/credentials for the certificate using makemsix? Is there going to be a syntax added for providing that added?

floppinger commented 4 years ago

Hi John, will there be a way to pass a password to the makemsix in the way signtool does ist? Or is a password not required? Kind regards

JohnMcPMS commented 4 years ago

Yes, adding support for a password would definitely be part of getting it to a shippable state.

I'm quite surprised about that behavior @nephatrine , as I thought that I tried to ensure that everything was going to succeed before modifying the file.

nephatrine commented 4 years ago

Yeah I wasn't expecting that either. There are some msix files that I do get the other error @floppinger reported about removing files in the middle of the archive and that doesn't do anything to corrupt the file, but the unable to open pfx error does.

The following is a screenshot of the hexdiff and the depicted region is the only part of the resulting file that was changed by the failed signing operation. It's towards the tail end of the file and I'm not sure what the structure of these files is, but if I had to guess maybe the compressed contents of [Content_Types].xml? In any case, it's the area immediately preceding what appears to be a file listing or table of contents at the end of the file. It's literally just that small block that is changed by the operation and whatever change it performs corrupts the package.

https://imgur.com/L94AdvD

floppinger commented 4 years ago

Hi John, any news about the signing feature? Do you need some testing support? Kind regards

JohnMcPMS commented 4 years ago

I'm sorry, but I haven't had any time to work on it.

mikehearn commented 3 years ago

Hi guys,

In the absence of completed code here, could you maybe put together some notes on the exact signing process? I'm trying to implement this with independent code, but it is proving remarkably difficult. If MSIX signatures were better documented perhaps other people could provide signing tools.

The sticking point is the hash calculation. The MSIX format has an interesting design in which the zip file effectively contains its own hash. Because you can't hash something that contains its own hash, the writing process has to be done in the following stages:

  1. Write out the first part of the zip, adding all the files. When you write out the manifest, blockmap and content types file, hash their raw (uncompressed contents) and record it.
  2. Hash everything written so far, make a note of that.
  3. Create a fake central directory header, hash that.
  4. Glue together these hashes in a little custom format prefixed by type codes.
  5. Use this small serialized data structure as the digest field in an Authenticate P7X signature file.
  6. Write out the resulting file as another zip file entry.
  7. Write out a new central directory structure containing the signature file as well.

The high level approach is clear, but the devil is in the details. The problem is that without being able to see the Windows code it's extremely unclear how this is validated. Presumably Windows is editing the central directory or reformatting it to reproduce the original "fake" central directory table, so it can hash that and compare it to the signed digest. But ... how? I've tried writing out the zip, hashing the CD and then using a normal ZIP library to add the P7X file, but it doesn't work, the digests don't match what Windows expects. Presumably the ZIP library is editing the CD in a way that breaks the reconstruction, but because these are hashes of large complex binary structures I am at a bit of a loss.

The source code in this repository leaves a few questions open I'm hoping you can answer:

  1. It always generates Zip64 and this is deliberate. Do MSIX files have to be Zip64? Windows can read non-zip64 msix files, but presumably this choice is made for a reason (or is it just implementation complexity?).
  2. Do the "extra local headers" matter at all? I'm assuming the axpc hash is just the entire file up to the start of the central directory but is that right?
  3. How exactly does Windows reconstruct/edit the CD to get the CD hash? Does it simply delete that table entry and then adjust the final end-of-directory section to reduce the number of included files or is there more to it?
  4. Is there any way to get detailed debug logging from Windows about the validation process?
ebourg commented 1 year ago
  1. It always generates Zip64 and this is deliberate. Do MSIX files have to be Zip64?

It doesn't look so, the APPX_PACKAGE_SETTINGS structure in the Windows API has a forceZip32 flag, so MSIX files are ZIP64 archives by default, but that's not mandatory.

https://learn.microsoft.com/en-us/windows/win32/api/appxpackaging/ns-appxpackaging-appx_package_settings