Skip to content

Commit

Permalink
Add UEFI capsule authentication
Browse files Browse the repository at this point in the history
These changes allow for the EDK2 UEFI to be build with a public key that
can verify capsule updates as well as for each subsequent capsule update
to be built with a corresponding signing key so that update is accepted
by the firmware. Enabling/disabling capsule authentication and
configuring the keys used during builds are exposed via NixOS module
options.
  • Loading branch information
jmbaur committed May 18, 2023
1 parent c9ce9c5 commit 1476cf4
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
result*
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ The Capsule update status is one of the following integers:
- 2 - Capsule install successfully but boot new firmware failed
- 3 - Capsule install failed

### UEFI Capsule Authentication

To ensure only authenticated capsule updates are applied to the device, you can
build the UEFI firmware and each subsequent capsule update using your own signing keys.
An overview of the key generation can be found at [EDK2 Capsule Signing](https://github.com/tianocore/tianocore.github.io/wiki/Capsule-Based-System-Firmware-Update-Generate-Keys).

To include your own signing keys in the EDK2 build and capsule update, make
sure the option `hardware.nvidia-jetpack.firmware.uefi.capsuleAuthentication.enable`
is turned on and each signing key option is set.

## Additional Links

Much of this is inspired by the great work done by [OpenEmbedded for Tegra](https://github.com/OE4T).
Expand Down
12 changes: 12 additions & 0 deletions capsule-authentication.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
diff --git a/Platform/NVIDIA/Jetson/Jetson.dsc b/Platform/NVIDIA/Jetson/Jetson.dsc
index 39e2c1d..3d6911d 100644
--- a/Platform/NVIDIA/Jetson/Jetson.dsc
+++ b/Platform/NVIDIA/Jetson/Jetson.dsc
@@ -33,3 +33,7 @@

[PcdsFixedAtBuild]
gNVIDIATokenSpaceGuid.PcdPlatformFamilyName|L"Jetson"
+!if $(CUSTOM_CAPSULE_CERT)
+ !include PublicCapsuleKey.cer.gEfiSecurityPkgTokenSpaceGuid.PcdPkcs7CertBuffer.inc
+ !include PublicCapsuleKey.cer.gFmpDevicePkgTokenSpaceGuid.PcdFmpDevicePkcs7CertBufferXdr.inc
+!endif
35 changes: 29 additions & 6 deletions device-pkgs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ let
postPatch = postPatch + cfg.flashScriptOverrides.postPatch;
});

uefi-firmware = uefi-firmware.override {
uefi-firmware = uefi-firmware.override ({
bootLogo = cfg.firmware.uefi.logo;
debugMode = cfg.firmware.uefi.debugMode;
errorLevelInfo = cfg.firmware.uefi.errorLevelInfo;
edk2NvidiaPatches = cfg.firmware.uefi.edk2NvidiaPatches;
};
} // lib.optionalAttrs cfg.firmware.uefi.capsuleAuthentication.enable {
inherit (cfg.firmware.uefi.capsuleAuthentication)
publicCertificateDerFile
requiredSystemFeatures;
});

inherit socType;

Expand Down Expand Up @@ -156,10 +160,29 @@ let

# See l4t_generate_soc_bup.sh
# python ${edk2-jetson}/BaseTools/BinWrappers/PosixLike/GenerateCapsule -v --encode --monotonic-count 1
uefiCapsuleUpdate = runCommand "uefi-${hostName}-${l4tVersion}.Cap" { nativeBuildInputs = [ python3 openssl ]; } ''
bash ${bspSrc}/generate_capsule/l4t_generate_soc_capsule.sh -i ${bup}/bl_only_payload -o $out ${socType}
'';
# NOTE: providing null public certs here will use the test certs in the EDK2 repo
mkUefiCapsuleUpdate = lib.makeOverridable ({
trustedPublicCertPemFile ? null,
otherPublicCertPemFile ? null,
signerPrivateCertPemFile ? null,
requiredSystemFeatures ? [ ],
}: runCommand "uefi-${hostName}-${l4tVersion}.Cap" { nativeBuildInputs = [ python3 openssl ]; inherit requiredSystemFeatures; } ''
bash ${bspSrc}/generate_capsule/l4t_generate_soc_capsule.sh \
${lib.optionalString (trustedPublicCertPemFile != null) "--trusted-public-cert ${trustedPublicCertPemFile}"} \
${lib.optionalString (otherPublicCertPemFile != null) "--other-public-cert ${otherPublicCertPemFile}"} \
${lib.optionalString (signerPrivateCertPemFile != null) "--signer-private-cert ${signerPrivateCertPemFile}"} \
-i ${bup}/bl_only_payload \
-o $out \
${socType}
'');
in {
inherit (tosImage) nvLuksSrv hwKeyAgent;
inherit flashScript initrdFlashScript tosImage signedFirmware bup uefiCapsuleUpdate;
inherit flashScript initrdFlashScript tosImage signedFirmware bup;
uefiCapsuleUpdate = mkUefiCapsuleUpdate (lib.optionalAttrs cfg.firmware.uefi.capsuleAuthentication.enable {
inherit (cfg.firmware.uefi.capsuleAuthentication)
trustedPublicCertPemFile
otherPublicCertPemFile
signerPrivateCertPemFile
requiredSystemFeatures;
});
}
52 changes: 50 additions & 2 deletions modules/flash-script.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
# You could do the overrides yourself if you'd prefer.
let
inherit (lib)
mkDefault
mkIf
mkEnableOption
mkOption
types;

Expand Down Expand Up @@ -50,6 +49,55 @@ in
type = types.listOf types.path;
default = [];
};

capsuleAuthentication = {
enable = mkEnableOption "capsule update authentication";

publicCertificateDerFile = mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The path to the public certificate (in DER format) that will be
used for validating capsule updates. Capsule files must be signed
with a private key in the same certificate chain. This file will
be included in the EDK2 build.
'';
};

trustedPublicCertPemFile = mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The path to the public certificate (in PEM format) that will be
used when signing capsule payloads.
'';
};

otherPublicCertPemFile = mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The path to another public certificate (in PEM format) that will
be used when signing capsule payloads. This can be the same as
`trustedPublicCertPem`, but it can also be an intermediate
certificate further down in the chain of your PKI.
'';
};

signerPrivateCertPemFile = mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The path to the private certificate (in PEM format) that will be
used for signing capsule payloads.
'';
};

requiredSystemFeatures = lib.mkOption {
type = types.listOf types.str;
default = [ ];
description = lib.mdDoc ''
Additional `requiredSystemFeatures` to add to derivations which
make use of capsule authentication private keys.
'';
};
};
};

optee = {
Expand Down
52 changes: 31 additions & 21 deletions uefi-firmware.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{ lib, stdenv, buildPackages, fetchFromGitHub, runCommand, edk2, acpica-tools,
dtc, python3, bc, imagemagick, applyPatches, nukeReferences,
dtc, python3, bc, imagemagick, unixtools, applyPatches, nukeReferences,
l4tVersion,

# Optional path to a boot logo that will be converted and cropped into the format required
Expand All @@ -10,6 +10,12 @@

debugMode ? false,
errorLevelInfo ? debugMode, # Enables a bunch more info messages

# The root certificate (in DER format) for authenticating capsule updates. By
# default, EDK2 authenticates using a test keypair commited upstream.
publicCertificateDerFile ? null,

requiredSystemFeatures ? [],
}:

let
Expand Down Expand Up @@ -48,26 +54,22 @@ let
sha256 = "sha256-EPtI63jYhEIo4uVTH3lUt9NC/lK5vPVacUAc5qgmz9M=";
};

_edk2-nvidia = fetchFromGitHub {
owner = "NVIDIA";
repo = "edk2-nvidia";
rev = "ad78b07af65d41bb96839f0bbe67bb445e04272f"; # Latest on r35.3.1-updates as of 2023-05-01
sha256 = "sha256-PdrisHYkmBXGvfkNboVvKJnBqORiM8sUsGySj7n5Y5c=";
edk2-nvidia = applyPatches {
src = fetchFromGitHub {
owner = "NVIDIA";
repo = "edk2-nvidia";
rev = "ad78b07af65d41bb96839f0bbe67bb445e04272f"; # Latest on r35.3.1-updates as of 2023-05-01
sha256 = "sha256-PdrisHYkmBXGvfkNboVvKJnBqORiM8sUsGySj7n5Y5c=";
};
patches = edk2NvidiaPatches ++ [ ./capsule-authentication.patch ];
postPatch = lib.optionalString errorLevelInfo ''
sed -i 's#PcdDebugPrintErrorLevel|.*#PcdDebugPrintErrorLevel|0x8000004F#' Platform/NVIDIA/NVIDIA.common.dsc.inc
'' + lib.optionalString (bootLogo != null) ''
cp ${bootLogoVariants}/logo1080.bmp Silicon/NVIDIA/Assets/nvidiagray1080.bmp
cp ${bootLogoVariants}/logo720.bmp Silicon/NVIDIA/Assets/nvidiagray720.bmp
cp ${bootLogoVariants}/logo480.bmp Silicon/NVIDIA/Assets/nvidiagray480.bmp
'';
};
edk2-nvidia =
if (errorLevelInfo || bootLogo != null)
then applyPatches {
src = _edk2-nvidia;
patches = edk2NvidiaPatches;
postPatch = lib.optionalString errorLevelInfo ''
sed -i 's#PcdDebugPrintErrorLevel|.*#PcdDebugPrintErrorLevel|0x8000004F#' Platform/NVIDIA/NVIDIA.common.dsc.inc
'' + lib.optionalString (bootLogo != null) ''
cp ${bootLogoVariants}/logo1080.bmp Silicon/NVIDIA/Assets/nvidiagray1080.bmp
cp ${bootLogoVariants}/logo720.bmp Silicon/NVIDIA/Assets/nvidiagray720.bmp
cp ${bootLogoVariants}/logo480.bmp Silicon/NVIDIA/Assets/nvidiagray480.bmp
'';
}
else _edk2-nvidia;

edk2-nvidia-non-osi = fetchFromGitHub {
owner = "NVIDIA";
Expand Down Expand Up @@ -105,7 +107,7 @@ let
src = edk2-src;

depsBuildBuild = [ buildPackages.stdenv.cc ];
nativeBuildInputs = [ bc pythonEnv acpica-tools dtc ];
nativeBuildInputs = [ bc pythonEnv acpica-tools dtc unixtools.whereis ];
strictDeps = true;

NIX_CFLAGS_COMPILE = [ "-Wno-error=format-security" ];
Expand All @@ -131,6 +133,13 @@ let
runHook preConfigure
export WORKSPACE="$PWD"
source ./edksetup.sh BaseTools
${lib.optionalString (publicCertificateDerFile != null) ''
echo Using ${publicCertificateDerFile} as public certificate for capsule verification
python3 BaseTools/Scripts/BinToPcd.py -p gEfiSecurityPkgTokenSpaceGuid.PcdPkcs7CertBuffer -i ${publicCertificateDerFile} -o PublicCapsuleKey.cer.gEfiSecurityPkgTokenSpaceGuid.PcdPkcs7CertBuffer.inc
python3 BaseTools/Scripts/BinToPcd.py -x -p gFmpDevicePkgTokenSpaceGuid.PcdFmpDevicePkcs7CertBufferXdr -i ${publicCertificateDerFile} -o PublicCapsuleKey.cer.gFmpDevicePkgTokenSpaceGuid.PcdFmpDevicePkcs7CertBufferXdr.inc
''}
runHook postConfigure
'';

Expand All @@ -142,6 +151,7 @@ let
build -a ${targetArch} -b ${buildTarget} -t ${buildType} -p Platform/NVIDIA/Jetson/Jetson.dsc -n $NIX_BUILD_CORES \
-D BUILDID_STRING=${l4tVersion} \
-D BUILD_DATE_TIME="$(date --utc --iso-8601=seconds --date=@$SOURCE_DATE_EPOCH)" \
${lib.optionalString (publicCertificateDerFile != null) "-D CUSTOM_CAPSULE_CERT"} \
$buildFlags
runHook postBuild
Expand Down

0 comments on commit 1476cf4

Please sign in to comment.