In part three I covered how chains of trust could be used by a signing service. But what happens if there are multiple signing services? Is it possible to create an executable which would be trusted by both root keys?
In certain situations this can be done. For example suppose we have two arbitrary root keys:
$ makecert -n "CN=ARoot" -r -sv ARoot.pvk ARoot.cer
$ makecert -n "CN=BRoot" -r -sv BRoot.pvk BRoot.cer
We can issue a sub certificate of A:
$ makecert -n "CN=ASub" -iv ARoot.pvk -ic ARoot.cer -sv ASub.pvk ASub.cer
And we can also sign the ARoot certificate with BRoot:
$ makecert -n "CN=ARoot" -iv BRoot.pvk -ic BRoot.cer -sv ARoot.pvk ARootCross.cer
We now make a PFX containing ACross.cer and ASub.cer and sign our binary using it:
$ cert2spc BRoot.cer ARootCross.cer ASub.cer ASub.spc
$ pvk2pfx -pvk ASub.pvk -spc ASub.spc -pfx ASub.pfx -f
$ copy HelloWorld.efi.unsigned HelloWorldCrossSigned.efi
$ signtool sign /f Sub.pfx /fd sha256 HelloWorldCrossSigned.efi
The way this is supposed to work is a little tricky. We sign the binary with ASub. The final binary will contain ASub and ACross. Suppose the target machine has ARoot enrolled as a KEK. ASub was issued by ACross, so it will verify as trusted, as normal:
ARoot -> ASub -> HelloWorld
Now suppose that the machine only has BRoot enrolled as a KEK. ARoot certificate is not available, but the ARootCross certificate has the same name and the same public key, so it will be used instead. ARootCross was issued by BRoot, so the chain is complete:
BRoot -> ARootCross -> ASub -> HelloWorld
In detail, when the system attempts to verify ASub, it will look at the issuer name (ARoot) and then attempt to find a certificate with this name. When we made ACrossRoot we specified “CN=ARoot” so the cross-certificate can be used instead of ARoot.cer.
This method is used by Microsoft for boot time kernel mode driver signing. The signing certificates are issued by 3rd party certificate authorities whose root certificates are not available at boot time, so these drivers include a cross-certificate issued by Microsoft signing the CA’s root certificate. Once the system boots up and the full certificate store is available the same drivers validate against the CA’s own certificates.
That’s the theory anyway. This does NOT currently work in OVMF/Tianocore (v2.31). The binary will verify if BRoot is enrolled, but will not verify if only ARoot is enrolled. This is apparently due to a limitation in OpenSSL.
Even if cross-certificates did work, they are not very useful in practice. To see why, remember that the intention was to create software that can run on a machine that only has ARoot or a machine that only has BRoot. In doing so, we created ARootCross. This certificate allows anything signed by ARoot to run under BRoot, and can be included in executables by A as well as B. Thus A can now sign for BRoot at will – that is, when the cross-certificate is present, any machine with BRoot.cer enrolled effectively behaves as if it also had ARoot enrolled. So we achieved the goal, but in the process we allowed the possibility for every binary signed by A to run under BRoot, which makes the goal pointless, as we could have achieved the same thing by simply including ARoot.cer and BRoot.cer on all of B’s machines.
What we really wanted was a method by which only ARoot can sign software for A’s machines, and only BRoot can sign software for B’s machines, but A and B can cooperate to make a single binary which runs on both. This can only be achieved by multiple signatures on a single binary. PKCS#7 allows this, but unfortunately Microsoft Authenticode is only “based on” PKCS#7 – one of the differences is that binaries may only contain a single signature.
That’s it for part 4. Unfortunately we didn’t really learn anything useful in this part, even though it looked like we might at the outset.