My opinions of code signing, notarising and the hardened runtime

 I had a long rant/chat on Slack about what I've figured out about code signing and notarisation after many late nights. Someone pointed out I should make a blog post about my opinions for posterity, even if it's not 100% accurate (I'm sure it's not), it will be helpful for some tidbits, hopefully.


...

The way the bundle is structured, it has a standard structure, Contents containing the actual executable in MacOS, resources in Resources and helper service applications in XPCServices. The two services are the simduino plugin, which is basically a wrapper around simavr and the build engine. The main app is codesigned in the standard way, code signing is a slight misnomer.


Carl  12:28 PM

“Code signing” is essentially the same process as signing a document or file using PGP or GNUPG, the cryptographic program has a certificate and private key pair, which are equivalent to a public/private key pair in RSA signing, it takes the document, makes a cryptographically secure hash of it, then encrypts that hash using the private key to make a small, digital “signature” that is then attached to the executable file as one of the pieces of metadata in the so called “load commands” in the mach-o executable file format. I don’t know much about these but they include things like @rpath entries, dylib dependencies and suchlike, the logical name for each type begins with LC_, so the code signature is in LC_CODE_SIGNATURE.

12:29

This can be tested by anyone who has the public key/certificate but cannot be reverse engineered or altered, so you can be sure that the contents of the file have not been tampered with since it was signed by performing the calculation yourself when you try to run the executable. This is done automatically by Gatekeeper whenever you run a program, if the signatures don’t match, Gatekeeper will not run the program as it may have been tampered with.


Carl  12:37 PM

My guesswork is when you generate a certificate in developer.apple.com using them as Certificate Authority for deploying with the “DeveloperID” route (these certificates are totally separate from your usual Mac or iOS app store certificates and different again from push APNS notification certificates or any other type)… Apple keeps the “certificate”/public key part of it and lets you download both certificate/private key. So when you open a program and you see “Verification…” and the gatekeeper logo on a popup, that’s what it’s doing…

12:38

That was basic code signing from 10 years ago or more… then the next layers…

12:41

Resources in a bundle need to be signed too, after all things like nib/xib files, if they were tampered with could cause malware issues. So the Resources folder is smushed together and signed as a whole, but because it’s not an executable file, there’s nowhere to put that code signature, no “load command” headers, no metadata. So there’s a folder created in the bundle called _CodeSignature that contains a file CodeResources with the signature of the Resources folder.

12:44

And each dylib that’s bundled in the MacOS folder or elsewhere, all frameworks, etc. will have their own signature. These signatures will have been made when that binary was builts, if it’s a framework copied in then the framework is built and signed, then copied into the bundle, of course to prevent tamper, these all need to be signed too… so the final step of code signing is making a signature overall combining all parts and put that in the metadata of the main executable file in Contents/MacOS/SwiftForArduino . Gatekeeper checks the whole bundle when starting the program so that the resources and libraries the executable depends on can be certain not to have been tampered with.


Carl  12:55 PM

This all worked fine before notarisation/runtime hardening, Swift for Arduino since version 1.0 has been developer ID signed in this way, ensuring that the contents of the file had not been tampered with when you first open it. Notarisation is another layer on top of that. I think their primary motivation was that people can get a developer ID then go wayward and start writing bad apps/malware and more likely the developer may accidentally include some malware without meaning to, so developer ID just made sure that at one time Apple had issued a certificate and that certificate hadn’t been revoked but it was not enough. So they made notarisation, where you have to send a zipped copy of the app bundle to apple for an automated check on their server. The app must have been signed properly using developer ID otherwise it will immediately fail. Then they presumably run some malware scans and then mark pass/fail. If it’s marked as “pass” that’s stored in some online database. Whenever an app is opened that is in quarantine (downloaded from the internet, saved from an email attachment, copied from a networked drive, etc.), the signature is verified and Apple’s online database contacted to check that this app is “bona fide” and approved by Apple.


Carl  1:03 PM

But of course, Apple being Apple, they keep moving the goalposts. They are now using this as a way to ensure various other features they deem critical for security are also enacted. One of these is the hardened runtime. This is a difficult beast to tame and is the one I only just got working. It has multiple components. One part is familiar to iOS and Android developers, an app has “entitlements” that are added to the code signature, for things such as the contacts, camera, photos, location, calendar, apple events, microphone secure mechanisms have been added at a low level in iOS so that if your executable file does not have signed entitlement, any attempt to access these things in any way (even sneaky unix filesystem accesses in some library somewhere) will fail.


Carl  1:12 PM

Of course, none of this gives user agency, for that, the hardened runtime asks on first access if each resource in the list can be accessed.

1:13

The hardened runtime is opt-in. Programs must be code signed with the -o runtime flag, then when they run, they will run within the hardened runtime.


Carl  1:19 PM

The “good” news for Swift for Arduino was this was actually quite easy to do. I used codesign in a command line script with the appropriate options to sign/re-sign each of the executable files within the Swift for Arduino bundle (all of the executables in gcc, like cc1 , all the shared libraries, everything executable). This then enables hard runtime on everything and it can be notarised.

1:20

Another side is executable protection. As I understand it, this means that even when loaded into memory, code signatures are maintained, so that executable pages cannot be run unless they still match their signature, this should prevent in-memory modification exploits so some extent.

1:22

Finally, the same protections are then required for all shared libraries used by code running in the hardened runtime and disable things like DYLD variable overrides. Plus entitlements to loosen all these restrictions if required (for example apps that modify their own code, JIT, etc.)

1:26

Here’s a sample command similar to what I have used to sign each executable: /usr/bin/codesign --force --sign "Developer ID Application: XXX" --entitlements "…/S4A Build Engines/notarizing/tools.entitlements" --timestamp -o runtime <executable to sign/re-sign> …for those not familiar with code signing by hand/command line, the name in quotes after --sign is the Common Name of the certificate on your login keychain that you want to use for signing the code (must contain a private key, and can use another keychain if required).

1:27

Of course all of the above is done automatically for you when you build only in Xcode with no embedded binaries by just turning on the “hardened runtime” setting. But where’s the fun in that?? :slightly_smiling_face:

Comments

Popular posts from this blog

Halloween LED lights on a plastic trick or treat cauldron

All about bootloaders

code signing, entitlements, bundles, sandboxes, hardened runtime, notarisation, app store security