Proposal for a design for signed kernel/modules/etc
Hello everyone, The following is a design proposal for signed kernel and kernel module loading, both at boot- and runtime (with the possibility open for signed executables and libraries if someone wanted to go that route). I'm interested in feedback on the idea before I start actually writing code for it. == Goals == 1) Be able to check for a correct cryptographic signature for any kernel or modules loaded at boot time for some platforms (EFI at a minimum). 2) Be able to check for a correct cryptographic signature for any kernel module loaded during normal operations (whether or not to do this could be controlled by a sysctl, securelevel, or some similar mechanism) 3) Work with what's in base already and minimize new additions (ideally, just a small utility to sign executables) 4) Minimize administrative overhead and ideally, require no changes at all to maintain signed kernel/modules 5) Have a clear path for supporting signed executables/libraries (I'm not planning on pursuing this, but it's worth considering that someone might want to) 6) The design *MUST* support the case where a system builds locally and uses its own key(s) for signing kernels and modules (and anything else) and *MUST* allow the administrator complete control over which key(s) are valid for a given system (ie. no "master keys" controlled by central organizations) 7) The design must allow for the adoption of new ciphers (there is an inevitable shift to post-quantum ciphers coming in the near future) == Non-Goals == * Hardware/firmware-based attacks are considered out-of-scope (there is no viable method for defending against them at the OS level) * Boot platforms that don't provide their own signature-checking framework up to loader/kernel can't be properly secured, and are considered out-of-scope * Boot platforms that impose size restrictions prohibiting incorporation of RSA and ED25519 crypto code (ex. i386 BIOS) are considered out-of-scope * GRUB support is desirable, however it is not necessary to support GRUB out-of-the-box (meaning a design requiring reasonable modifications to GRUB is acceptable). * I am not aiming to support signed executables/libraries now, only to avoid shutting the door on them. == Existing Solution(s) == EFI has a decent design with regard to key management (platform key, which signs system keys, which sign the actual loader); however, its cipher suite is sorely lacking (many broken hashes and weak ciphers, RSA 2048 being the "strongest", no ECC). It also only works with the COFF format, and is only available at boot time. However, it does provide a chain of custody up to loader (to the extent that anyone trusts closed-source firmware blobs, SHA1, and 512-2048 bit RSA keys...) Many implementations also have master keys "baked in" that would allow anything signed by random third parties (Microsoft) to boot regardless of local configurations, or they don't provide any sort of control over (or even access to) the keys at all. EFI obviously isn't viable beyond boot time, and misses most of the goals even there. Its key management hierarchy is an overall good design, however. GRUB currently supports signature checking. It can be configured to require signatures for any of its own modules as well as any kernel or modules that it loads. These signatures are stored *outside* the executable/library, in a file with an added .sig extension. The format is that of an external signature for the entire ELF file as produced by the gnupg program. Linux (I believe) also supports signature checking for modules using the same convention. While functional, this design doesn't meet the goals I outlined: * It relies on the gnupg framework, which is not part of FreeBSD base, and adding it would be a chore (and would end up duplicating a lot of functionality provided by OpenSSL) * It stores the signature separate from the file, which leads to x2 the number of files, would require modifying existing scripts, and complicates administrative tasks. It also leads to failure modes like stale signatures. * There are potential legitimate modifications to non-code parts of an ELF file (such as the .comment section or other similar sections) that would require re-signing the entire file. * The previous two problems really start to look bad when you consider signed executables and libraries, possibly with third-party build/install scripts... * Finally, the gnupg signature format doesn't actually seem to be documented anywhere, or at least not anywhere that doesn't require a lot of digging... An alternate solution, which I believe is used in some places is to wrap the entire executable in a PGP envelope-like format. This solves the issue of an external signature file, but would require extensive modification to the ELF parsing code, let alone the binutils programs that read/modify ELF files. This solution also isn't backwards-compatible at all. Old loaders/kernels will choke on the signed libraries. == Pr
Re: Proposal for a design for signed kernel/modules/etc
Hey Eric, Thank you for writing this! ELF binary signing has been on my ever-growing list of things to research and develop. If you'd like help, please let me know. I have a few comments, which I've made inline. On Mon, Mar 27, 2017 at 01:54:44PM -0400, Eric McCorkle wrote: > Hello everyone, > > The following is a design proposal for signed kernel and kernel module > loading, both at boot- and runtime (with the possibility open for signed > executables and libraries if someone wanted to go that route). I'm > interested in feedback on the idea before I start actually writing code > for it. > > == Goals == > > 1) Be able to check for a correct cryptographic signature for any kernel > or modules loaded at boot time for some platforms (EFI at a minimum). > > 2) Be able to check for a correct cryptographic signature for any kernel > module loaded during normal operations (whether or not to do this could > be controlled by a sysctl, securelevel, or some similar mechanism) > > 3) Work with what's in base already and minimize new additions (ideally, > just a small utility to sign executables) > > 4) Minimize administrative overhead and ideally, require no changes at > all to maintain signed kernel/modules > > 5) Have a clear path for supporting signed executables/libraries (I'm > not planning on pursuing this, but it's worth considering that someone > might want to) > > 6) The design *MUST* support the case where a system builds locally and > uses its own key(s) for signing kernels and modules (and anything else) > and *MUST* allow the administrator complete control over which key(s) > are valid for a given system (ie. no "master keys" controlled by central > organizations) > > 7) The design must allow for the adoption of new ciphers (there is an > inevitable shift to post-quantum ciphers coming in the near future) As git has shown, having a modular/configurable crypto interface is the best route. Right now, git is stuck using SHA1 because they didn't support users being able to choose which hashing algorithm to use. > > == Non-Goals == > > * Hardware/firmware-based attacks are considered out-of-scope (there is > no viable method for defending against them at the OS level) > > * Boot platforms that don't provide their own signature-checking > framework up to loader/kernel can't be properly secured, and are > considered out-of-scope > > * Boot platforms that impose size restrictions prohibiting incorporation > of RSA and ED25519 crypto code (ex. i386 BIOS) are considered out-of-scope > > * GRUB support is desirable, however it is not necessary to support GRUB > out-of-the-box (meaning a design requiring reasonable modifications to > GRUB is acceptable). > > * I am not aiming to support signed executables/libraries now, only to > avoid shutting the door on them. Since FreeBSD uses ELF for the kernel and its modules, it should be rather straightforward to apply the same work towards userland binaries and shared libraries. > > == Existing Solution(s) == > > EFI has a decent design with regard to key management (platform key, > which signs system keys, which sign the actual loader); however, its > cipher suite is sorely lacking (many broken hashes and weak ciphers, RSA > 2048 being the "strongest", no ECC). It also only works with the COFF > format, and is only available at boot time. However, it does provide a > chain of custody up to loader (to the extent that anyone trusts > closed-source firmware blobs, SHA1, and 512-2048 bit RSA keys...) Many > implementations also have master keys "baked in" that would allow > anything signed by random third parties (Microsoft) to boot regardless > of local configurations, or they don't provide any sort of control over > (or even access to) the keys at all. > > EFI obviously isn't viable beyond boot time, and misses most of the > goals even there. Its key management hierarchy is an overall good > design, however. > > GRUB currently supports signature checking. It can be configured to > require signatures for any of its own modules as well as any kernel or > modules that it loads. These signatures are stored *outside* the > executable/library, in a file with an added .sig extension. The format > is that of an external signature for the entire ELF file as produced by > the gnupg program. > > Linux (I believe) also supports signature checking for modules using the > same convention. > > While functional, this design doesn't meet the goals I outlined: > > * It relies on the gnupg framework, which is not part of FreeBSD base, > and adding it would be a chore (and would end up duplicating a lot of > functionality provided by OpenSSL) > > * It stores the signature separate from the file, which leads to x2 the > number of files, would require modifying existing scripts, and > complicates administrative tasks. It also leads to failure modes like > stale signatures. > > * There are potential legitimate modifications to non-code parts of an > ELF file
Re: Proposal for a design for signed kernel/modules/etc
On 03/27/2017 14:37, Shawn Webb wrote: > Hey Eric, > > Thank you for writing this! ELF binary signing has been on my > ever-growing list of things to research and develop. If you'd like help, > please let me know. I'll probably spin up a branch on my github in the near future. > As git has shown, having a modular/configurable crypto interface is the > best route. Right now, git is stuck using SHA1 because they didn't > support users being able to choose which hashing algorithm to use. Oh yes! And all current pubkey crypto has an expiration date probably between a decade and a century from now. > You might want to take a look at Microsoft's Authenticode. Microsoft > made some mistakes early on that allowed attackers to easily trojan > signed binaries. Your proposal up to this point makes those same > mistakes. It's been a few years since I researched Authenticode, so I > don't have any links or documentation handy. > > The conclusion Microsoft came to is that the file as a whole must be > signed, including offset metadata. Essentially, you'd determine how > large the .sig section needs to be ahead of time, create it and fill it > with zeros, then sign the whole file, stuffing the signature in the > zeroed .sig section. Same concept as calculating checksums of ICMP > packets. > > This prevents attackers from modifying critical pieces of metadata, > pointing them to maliciuos payloads. It also prevents attackers from > appending malicious data to the end of a loadable segment (something > Authenticode suffered from early on). Yeah, now that I think about it, there's various forms of evil you could pull off. I don't doubt that someone *could* design a scheme that would defeat all these attacks, but... is it worth it? Probably not. Better to just sign the whole file and seal off all the attack vectors, on second thought. > Userland shouldn't be trusted to enforce digital signatures. What if > someone at link time specifies a non-default RTLD? To enforce digital > signatures of userland binaries/libraries, the ELF image activator > should be modified to verify the DT_NEEDED entries. True, I was assuming dlopen just mmaps everything... (I was getting hungry by that point). As with the whole-file sigs, it would probably be better just to have a single syscall that securely loads dynamic libraries (but that's out-of-scope for now). > > The only other major thing to discuss is supporting public key chaining. > Ideally, digital signature support should also support chaining multiple > keys (similar to X.509 PKI). If the accepted solution supported cert > chaining, then the solution would be more modular. I don't want to go > down the route of the SSL/TLS PKI mess, but supporting chaining is a > must in some enterprise environments. > > If we were to support chaining, we could even stuff the pubkey half of > the key material into another ELF section, so that if a key becomes > compromised, the old pubkey can be revoked from the trust store and a > new binary can be generated with new key material. The trusted root > doesn't need to be cycled as often. HardenedBSD, for example, > distributes the pubkey that corresponds to the signing privkey inside > the update tarball for binary updates[1]. This allows us to change key > material often if desired without the user even noticing. I can see a two-tiered scheme where you have a "vendor key", which is used to sign various "application keys", which are used to sign the kernel and modules. The signed executables can then supply their application keys (signed by the vendor key) to the loader/kernel, which first checks the signature on the key, then on the file. This way, you could generate and use a different key for each build, and sign it with the same vendor key. There's at least three use cases in play here: 1) FreeBSD would want to publish ready-made installations, probably signed by a key they control. This ensures that someone who just wants to install binary packages and go doesn't load any kernel/modules other than the ones coming from FreeBSD. 2) I run an enterprise network, and I want to build everything internally on some build-box, and make sure I only run things that got built there, or that came from FreeBSD. 3) I have a highly secure network that is set up like in (2), except I only allow things I build internally to run. 3) I have my laptop, and I want to build everything on the laptop and make sure I don't boot anything I didn't build. For all of these, I'd want to bake in at least the vendor key(s). I could do this pretty easily by converting the key(s) to a header, then using it in buildworld/kerrnel. For (1), FreeBSD would have a vendor key, which it uses to sign the kernel/modules it publishes, and that would be baked in to those applications. For (2), I'd have my own vendor key for my organization, and I'd use that in addition to FreeBSD's key. This would allow anything signed by either of the two organizations. For (3),