From: Michael Brown Date: Sun, 15 Feb 2026 22:50:12 +0000 (+0000) Subject: [ci] Add support for building UEFI Secure Boot signed binaries X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=21b5bd8406227074c4a13e847cd8523d036731a9;p=thirdparty%2Fipxe.git [ci] Add support for building UEFI Secure Boot signed binaries Add a job that takes the bin-x86_64-efi-sb and bin-arm64-efi-sb build artifacts and signs them for UEFI Secure Boot. The hardware token containing the trusted signing key is attached to a dedicated self-hosted GitHub Actions runner. Only tagged release versions (and commits on the "sbsign" testing branch) will be signed on this dedicated runner. All other commits will be signed on a standard GitHub hosted runner using an ephemeral test certificate that is not trusted for UEFI Secure Boot. No other work is done as part of the signing job. The iPXE source code is not even checked out, minimising any opportunity to grant untrusted code access to the hardware token. The hardware token password is held as a deployment environment secret, with the environment being restricted to allow access only for tagged release versions (and commits on the "sbsign" testing branch) to provide an additional layer of security. The signing certificates and intermediate certificates are obtained from the iPXE Secure Boot CA repository, with the certificate selected via deployment environment variables. To minimise hidden state held on the self-hosted runner, the pcscd service is run via a service container, with the hardware token passed in via "--devices /dev/bus/usb". Select the deployment environment name (and hence runner tag) via a repository variable SBSIGN_ENVIRONMENT, so that forks do not attempt to start jobs on a non-existent self-hosted runner. Signed-off-by: Michael Brown --- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f69f2d5b0..c00318ef4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,6 +158,8 @@ jobs: image: ghcr.io/ipxe/ipxe-builder-${{ matrix.arch }} env: bindir: bin-${{ matrix.arch }}-efi-sb + outputs: + sbsignenv: ${{ steps.sbsignenv.outputs.sbsignenv }} steps: - name: Check out code @@ -172,12 +174,109 @@ jobs: - name: Upload uses: actions/upload-artifact@v6 with: - name: ${{ env.bindir }} + name: unsigned-${{ env.bindir }} if-no-files-found: error path: | src/${{ env.bindir }}/ipxe.efi src/${{ env.bindir }}/snponly.efi + - name: Select environment + id: sbsignenv + if: >- + github.ref == 'refs/heads/sbsign' || + startsWith ( github.ref, 'refs/tags/v' ) + run: | + echo "sbsignenv=${{ vars.SBSIGN_ENVIRONMENT }}" >> $GITHUB_OUTPUT + + sbsign: + name: SB Sign / ${{ matrix.arch }} + runs-on: ${{ needs.uefi-sb.outputs.sbsignenv || 'ubuntu-latest' }} + needs: + - uefi-sb + strategy: + fail-fast: false + matrix: + arch: + - arm64 + - x86_64 + container: + image: ghcr.io/ipxe/ipxe-signer + volumes: + - run-pcscd:/run/pcscd + services: + pcscd: + image: ghcr.io/ipxe/ipxe-signer-pcscd + volumes: + - run-pcscd:/run/pcscd + options: >- + ${{ needs.uefi-sb.outputs.sbsignenv && '--device /dev/bus/usb' }} + --label OPTIONS_VALUE_CANNOT_BE_EMPTY=1 + env: + binaries: >- + ipxe.efi + snponly.efi + bindir: bin-${{ matrix.arch }}-efi-sb + cacert: ${{ vars.SBSIGN_CA_CERT || 'testsign.crt' }} + pkcs11: ${{ secrets.SBSIGN_PASSWORD && 'true' }} + signcerts: ${{ vars.SBSIGN_CERTS || 'testsign.crt' }} + signkey: ${{ vars.SBSIGN_KEY || 'testsign.key' }} + signpass: ${{ secrets.SBSIGN_PASSWORD || 'testpw' }} + environment: ${{ needs.uefi-sb.outputs.sbsignenv }} + steps: + + - name: Check out code + uses: actions/checkout@v6 + with: + repository: ipxe/secure-boot-ca + + - name: Download + uses: actions/download-artifact@v7 + with: + name: unsigned-${{ env.bindir }} + path: unsigned + + - name: Test certificate + run: | + openssl req \ + -newkey rsa:2048 -passout 'pass:testpw' -keyout testsign.key \ + -subj '/CN=Test Signing/' -x509 -out testsign.crt + + - name: Certificate chain + run: | + for cert in ${{ env.signcerts }} ; do + openssl x509 -in ${cert} -noout -text + cat ${cert} >> chain.crts + done + + - name: Sign + run: | + for binary in ${{ env.binaries }} ; do + osslsigncode sign \ + ${{ env.pkcs11 && '-pkcs11module' }} \ + ${{ env.pkcs11 && '/usr/lib64/opensc-pkcs11.so' }} \ + -certs chain.crts \ + -key ${{ env.signkey }} \ + -pass ${{ env.signpass }} \ + -ts http://timestamp.digicert.com \ + -in unsigned/${binary} \ + -out signed/${binary} + done + + - name: Verify + run: | + for binary in ${{ env.binaries }} ; do + osslsigncode verify -CAfile ${{ env.cacert }} signed/${binary} + done + + - name: Upload + uses: actions/upload-artifact@v6 + with: + name: ${{ env.bindir }} + if-no-files-found: error + path: | + signed/ipxe.efi + signed/snponly.efi + linux: name: Linux / ${{ matrix.arch }} runs-on: ubuntu-latest @@ -292,7 +391,7 @@ jobs: - bios - sbi - uefi - - uefi-sb + - sbsign - linux - combine if: >-