indygreg / PyOxidizer

A modern Python application packaging and distribution tool
Mozilla Public License 2.0
5.4k stars 234 forks source link

[apple-codesign] Signing golang binaries using rcodesign corrupts them #634

Closed deansheather closed 2 years ago

deansheather commented 2 years ago

Signing binaries built by the Go toolchain seems to produce invalid signatures and corrupt the binaries.

Launching binaries signed by rcodesign will result in the process being immediately killed by the OS, codesign -dvv reports that it is not signed, but rcodesign verify reports that it's signed properly. Signing with native codesign however yields a valid binary that can be executed and has the signature details correctly baked in.

$ echo 'package main\nfunc main() { println("hello world") }' > hello.go
$ go build -o hello hello.go
$ ./hello
hello world

$ rcodesign sign --p12-file ./cert.p12 --p12-password blah --code-signature-flags runtime -vvv hello
$ ./hello
zsh: killed ./hello
$ codesign -dvv hello
hello: code object is not signed at all
$ rcodesign verify hello
no problems detected!
...

This seems to be only a problem with Go binaries. I'm assuming it's something to do with how the go toolchain structures the data segments in the output binary. C programs work correctly:

$ echo '#include <stdio.h>\nint main(void) { puts("hello world"); }' > helloc.c
$ gcc -o helloc helloc.c
$ rcodesign sign --p12-file ./cert.p12 --p12-password blah --code-signature-flags runtime -vvv helloc
$ ./helloc
hello world
$ codesign -dvv helloc
... (valid output)
indygreg commented 2 years ago

It appears that this implementation is inserting additional digests over the Mach-O content before the code signature. Why, I'm not sure. Probably has something to do with segment layouts.

This is the same class of bug as #616, which was previously preventing signing Go binaries. Apparently the fixes for segment handling were not sufficient.

Thank you for the steps to reproduce! That makes debugging this a breeze!

indygreg commented 2 years ago

Currently, our code for signing the binary computes digests (e.g. SHA-256) over segments, ending [prematurely] at a segment boundary before starting a new digest. Turns out that digests can span segments and the digests are at the file level and naive of segment layout.

I don't think this mattered much for most binaries since segments were aligned to 4kb (or 16kb) boundaries with padding inside the segments to ensure that alignment. But Go doesn't do this. As a result, this exposed the bug in our digesting code.

Ripping out the complex code for splitting digests at segment boundaries appears to make the discrepancy go away and for Apple's tooling to recognize our signature.

I have a patch half coded locally. Will push it... sometime. But a fix should be in the next release!

indygreg commented 1 year ago

The new release is out: https://github.com/indygreg/apple-platform-rs/releases/tag/apple-codesign%2F0.19.0.

(I moved the code signing project to a separate repo.)