diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 2dfdf09..957c5c5 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -46,7 +46,7 @@ jobs: - name: Install some build requirements id: dnf run: | - dnf install -y efivar-devel nss-devel openssl-devel + dnf install -y efivar-devel nss-devel openssl-devel nss-tools - name: Run tests on ${{ matrix.distro }} for ${{ matrix.arch }} id: test run: | diff --git a/Makefile b/Makefile index a2e41f8..112ce1e 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,11 @@ deps all : $(SUBDIRS) : $(MAKE) -C $@ all -.PHONY: $(SUBDIRS) +test : all + @echo "Running functional tests..." + @cd $(TOPDIR) && tests/run-tests.sh + +.PHONY: $(SUBDIRS) test GITTAG = $(VERSION) diff --git a/src/efikeygen.c b/src/efikeygen.c index e9070e3..1c97d83 100644 --- a/src/efikeygen.c +++ b/src/efikeygen.c @@ -1313,7 +1313,8 @@ int main(int argc, char *argv[]) memcpy(&ias.issuer, &cert->issuer, sizeof(ias.issuer)); memcpy(&ias.serialNumber, &cert->serialNumber, sizeof(ias.serialNumber)); - add_trust(cms, &ias, is_ca ? ",,CTu" : ",,u"); + if (add_trust(cms, &ias, is_ca ? ",,CTu" : ",,u") < 0) + nsserr(1, "could not set trust for certificate"); SECITEM_FreeItem(&sigder, PR_FALSE); SECITEM_FreeItem(&signature, PR_FALSE); diff --git a/src/file_kmod.c b/src/file_kmod.c index 6880cda..cf68336 100644 --- a/src/file_kmod.c +++ b/src/file_kmod.c @@ -10,6 +10,7 @@ #include #include "pesign.h" +#include "signed_data.h" #include @@ -35,67 +36,32 @@ kmod_generate_digest(cms_context *cms, unsigned char *addr, size_t len) return 0; } -struct write_sig_info { - int outfd; - int rc; - size_t sig_len; -}; - -static void -kmod_signature_out(void *arg, const char *buf, unsigned long len) -{ - struct write_sig_info *info = (struct write_sig_info *) arg; - int rc; - - rc = write_file(info->outfd, buf, len); - if (rc < 0) { - info->rc = rc; - return; - } - - info->sig_len += len; -} - ssize_t kmod_write_signature(cms_context *cms, int outfd) { - SEC_PKCS7ContentInfo *cinfo; - SECItem *digest = cms->digests[cms->selected_digest].pe_digest; - SECStatus rv; - struct write_sig_info info = { - .outfd = outfd, - }; + SECItem sd_der; ssize_t rc = -1; - cinfo = SEC_PKCS7CreateSignedData(cms->cert, - certUsageObjectSigner, NULL, - digest_get_digest_oid(cms), - digest, NULL, NULL); - if (!cinfo) { + memset(&sd_der, '\0', sizeof(sd_der)); + + /* Use generate_spc_signed_data() instead of SEC_PKCS7CreateSignedData() + * to avoid strict certificate chain validation that fails for self-signed + * certificates even when trust flags are properly set. */ + rc = generate_spc_signed_data(cms, &sd_der); + if (rc < 0) { cms->log(cms, LOG_ERR, "failed to create signed data: %s (%s)", PORT_ErrorToString(PORT_GetError()), PORT_ErrorToName(PORT_GetError())); return -1; } - rv = SEC_PKCS7Encode(cinfo, kmod_signature_out, &info, NULL, NULL, - NULL); - if (rv != SECSuccess) { - cms->log(cms, LOG_ERR, "failed to encode signed data: %d", rv); - goto out; - } - - if (info.rc != 0) { - cms->log(cms, LOG_ERR, "Signed data encode error %d", info.rc); - rc = info.rc; - goto out; + rc = write_file(outfd, sd_der.data, sd_der.len); + if (rc < 0) { + cms->log(cms, LOG_ERR, "failed to write signature: %m"); + return -1; } - rc = info.sig_len; - -out: - SEC_PKCS7DestroyContentInfo(cinfo); - return rc; + return sd_der.len; } static const char magic_number[] = "~Module signature appended~\n"; diff --git a/tests/run-tests.sh b/tests/run-tests.sh new file mode 100755 index 0000000..2c41575 --- /dev/null +++ b/tests/run-tests.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Main test runner for pesign functional tests + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +FAILED_TESTS=() +PASSED_TESTS=() + +echo "======================================" +echo "Running pesign functional tests" +echo "======================================" +echo + +run_test() { + local test_script="$1" + local test_name=$(basename "$test_script" .sh) + + echo -e "${YELLOW}Running: ${test_name}${NC}" + + if bash "$test_script"; then + echo -e "${GREEN}✓ PASSED: ${test_name}${NC}" + PASSED_TESTS+=("$test_name") + else + echo -e "${RED}✗ FAILED: ${test_name}${NC}" + FAILED_TESTS+=("$test_name") + fi + echo +} + +# Run all test scripts +for test in "$SCRIPT_DIR"/test-*.sh; do + if [ -f "$test" ]; then + run_test "$test" + fi +done + +# Summary +echo "======================================" +echo "Test Summary" +echo "======================================" +echo -e "${GREEN}Passed: ${#PASSED_TESTS[@]}${NC}" +echo -e "${RED}Failed: ${#FAILED_TESTS[@]}${NC}" + +if [ ${#FAILED_TESTS[@]} -gt 0 ]; then + echo + echo "Failed tests:" + for test in "${FAILED_TESTS[@]}"; do + echo -e " ${RED}✗${NC} $test" + done + exit 1 +fi + +echo +echo -e "${GREEN}All tests passed!${NC}" +exit 0 diff --git a/tests/test-ca-signed.sh b/tests/test-ca-signed.sh new file mode 100755 index 0000000..307d25d --- /dev/null +++ b/tests/test-ca-signed.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# Test CA and signing certificate setup +# Based on AlmaLinux documentation + +set -e + +TEST_NAME="ca-signed" +WORK_DIR=$(mktemp -d) +trap "rm -rf $WORK_DIR" EXIT + +echo " Setting up CA and signing certificates..." + +# Create certificate database +mkdir -m 0700 -p "$WORK_DIR/ca" +echo "" | certutil -d "$WORK_DIR/ca" -N --empty-password + +# Generate CA certificate +./src/efikeygen -d "$WORK_DIR/ca" \ + --ca --self-sign \ + --not-valid-after=$(date +%s --date='+10 years') \ + --nickname='Test Secure Boot CA' \ + --common-name='CN=Test Secure Boot CA,O=Test Organization,E=test@example.com' + +# Verify CA was created +if ! certutil -d "$WORK_DIR/ca" -L -n 'Test Secure Boot CA' > /dev/null 2>&1; then + echo " ERROR: CA certificate not created" + exit 1 +fi + +# Generate signing certificate signed by CA (marked as --kernel) +./src/efikeygen -d "$WORK_DIR/ca" \ + --kernel \ + --not-valid-after=$(date +%s --date='+10 years') \ + --signer='Test Secure Boot CA' \ + --nickname='Test Secure Boot Signing' \ + --common-name='CN=Test Secure Boot Signing,O=Test Organization,E=test@example.com' + +# Verify signing certificate was created +if ! certutil -d "$WORK_DIR/ca" -L -n 'Test Secure Boot Signing' > /dev/null 2>&1; then + echo " ERROR: Signing certificate not created" + exit 1 +fi + +# Export certificates +certutil -d "$WORK_DIR/ca" -L -n "Test Secure Boot CA" -r > "$WORK_DIR/test-secureboot-ca.cer" +certutil -d "$WORK_DIR/ca" -L -n "Test Secure Boot Signing" -r > "$WORK_DIR/test-secureboot.cer" + +# Verify certificate files exist and are not empty +if [ ! -s "$WORK_DIR/test-secureboot-ca.cer" ]; then + echo " ERROR: CA certificate export failed" + exit 1 +fi + +if [ ! -s "$WORK_DIR/test-secureboot.cer" ]; then + echo " ERROR: Signing certificate export failed" + exit 1 +fi + +echo " ✓ CA and signing certificates created successfully" + +# Verify CA trust flags (CA cert should have CA trust) +CA_TRUST=$(certutil -d "$WORK_DIR/ca" -L | grep "Test Secure Boot CA" | awk '{print $NF}') +if echo "$CA_TRUST" | grep -q "C\|T"; then + echo " ✓ CA trust flags: $CA_TRUST" +else + echo " ✗ CA trust flags missing CA trust: $CA_TRUST" + exit 1 +fi + +# Verify signing cert trust flags +SIGN_TRUST=$(certutil -d "$WORK_DIR/ca" -L | grep "Test Secure Boot Signing" | awk '{print $NF}') +if [ -z "$SIGN_TRUST" ]; then + echo " ✗ No trust flags found for signing cert" + exit 1 +else + echo " ✓ Signing cert trust flags: $SIGN_TRUST" +fi + +# Get test data directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEST_DATA="$SCRIPT_DIR/data" + +# Use real kernel and module files +KERNEL="$TEST_DATA/vmlinuz-6.19.10-200.fc43.x86_64" +MODULE="$TEST_DATA/vfat.ko" + +# Test signing a kernel (should work - cert was created with --kernel) +echo " Testing kernel signing with CA-signed certificate..." +if ./src/pesign --certdir "$WORK_DIR/ca" \ + --certificate 'Test Secure Boot Signing' \ + --in "$KERNEL" \ + --sign \ + --out "$WORK_DIR/kernel-signed" 2>"$WORK_DIR/kernel-error.log"; then + echo " ✓ CA-signed cert signed kernel (exit 0)" + ./src/pesign --show-signature --in "$WORK_DIR/kernel-signed" 2>/dev/null | head -5 +else + echo " ✗ CA-signed cert failed to sign kernel (exit $?)" + [ -s "$WORK_DIR/kernel-error.log" ] && cat "$WORK_DIR/kernel-error.log" + exit 1 +fi + +# Test signing a module (kernel certs can sign modules) +echo " Testing module signing with CA-signed certificate..." +if ./src/pesign --certdir "$WORK_DIR/ca" \ + --certificate 'Test Secure Boot Signing' \ + --in "$MODULE" \ + --sign \ + --out "$WORK_DIR/module-signed.ko" 2>"$WORK_DIR/module-error.log"; then + echo " ✓ CA-signed cert signed module (exit 0)" + ./src/pesign --show-signature --in "$WORK_DIR/module-signed.ko" 2>/dev/null | head -5 +else + echo " ✗ CA-signed cert failed to sign module (exit $?)" + [ -s "$WORK_DIR/module-error.log" ] && cat "$WORK_DIR/module-error.log" + exit 1 +fi + +exit 0 diff --git a/tests/test-self-signed-kernel.sh b/tests/test-self-signed-kernel.sh new file mode 100755 index 0000000..f2b43d3 --- /dev/null +++ b/tests/test-self-signed-kernel.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# Test self-signed kernel signing certificate setup +# Based on Red Hat documentation + +set -e + +TEST_NAME="self-signed-kernel" +WORK_DIR=$(mktemp -d) +trap "rm -rf $WORK_DIR" EXIT + +echo " Setting up self-signed kernel signing certificate..." + +# Create certificate database +mkdir -m 0700 -p "$WORK_DIR/certdb" +echo "" | certutil -d "$WORK_DIR/certdb" -N --empty-password + +# Generate self-signed kernel certificate +./src/efikeygen --dbdir "$WORK_DIR/certdb" \ + --self-sign \ + --kernel \ + --common-name 'CN=Test Kernel Signing Key' \ + --nickname 'Test Kernel Key' + +# Verify certificate was created +if ! certutil -d "$WORK_DIR/certdb" -L -n 'Test Kernel Key' > /dev/null 2>&1; then + echo " ERROR: Certificate not created" + exit 1 +fi + +# Export certificate +certutil -d "$WORK_DIR/certdb" \ + -n 'Test Kernel Key' \ + -Lr \ + > "$WORK_DIR/kernel_cert.cer" + +# Verify certificate file exists and is not empty +if [ ! -s "$WORK_DIR/kernel_cert.cer" ]; then + echo " ERROR: Certificate export failed" + exit 1 +fi + +echo " ✓ Self-signed kernel certificate created successfully" + +# Verify trust flags are set (should be u,u,u for user cert) +TRUST=$(certutil -d "$WORK_DIR/certdb" -L | grep "Test Kernel Key" | awk '{print $NF}') +if [ -z "$TRUST" ]; then + echo " ✗ No trust flags found" + exit 1 +else + echo " ✓ Trust flags: $TRUST" +fi + +# Get test data directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEST_DATA="$SCRIPT_DIR/data" + +# Use real kernel and module files +KERNEL="$TEST_DATA/vmlinuz-6.19.10-200.fc43.x86_64" +MODULE="$TEST_DATA/vfat.ko" + +# Test signing a kernel (should work) +echo " Testing kernel signing with kernel certificate..." +if ./src/pesign --certdir "$WORK_DIR/certdb" \ + --certificate 'Test Kernel Key' \ + --in "$KERNEL" \ + --sign \ + --out "$WORK_DIR/kernel-signed" 2>"$WORK_DIR/kernel-error.log"; then + echo " ✓ Kernel cert signed kernel (exit 0)" + ./src/pesign --show-signature --in "$WORK_DIR/kernel-signed" 2>/dev/null | head -5 +else + echo " ✗ Kernel cert failed to sign kernel (exit $?)" + [ -s "$WORK_DIR/kernel-error.log" ] && cat "$WORK_DIR/kernel-error.log" + exit 1 +fi + +# Test signing a module (kernel certs can sign modules) +echo " Testing module signing with kernel certificate..." +if ./src/pesign --certdir "$WORK_DIR/certdb" \ + --certificate 'Test Kernel Key' \ + --in "$MODULE" \ + --sign \ + --out "$WORK_DIR/module-signed.ko" 2>"$WORK_DIR/module-error.log"; then + echo " ✓ Kernel cert signed module (exit 0)" + ./src/pesign --show-signature --in "$WORK_DIR/module-signed.ko" 2>/dev/null | head -5 +else + echo " ✗ Kernel cert failed to sign module (exit $?)" + [ -s "$WORK_DIR/module-error.log" ] && cat "$WORK_DIR/module-error.log" + exit 1 +fi + +exit 0 diff --git a/tests/test-self-signed-module.sh b/tests/test-self-signed-module.sh new file mode 100755 index 0000000..e12bf4d --- /dev/null +++ b/tests/test-self-signed-module.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Test self-signed module-only signing certificate setup +# Based on Red Hat documentation + +set -e + +TEST_NAME="self-signed-module" +WORK_DIR=$(mktemp -d) +trap "rm -rf $WORK_DIR" EXIT + +echo " Setting up self-signed module-only certificate..." + +# Create certificate database +mkdir -m 0700 -p "$WORK_DIR/certdb" +echo "" | certutil -d "$WORK_DIR/certdb" -N --empty-password + +# Generate self-signed module certificate +./src/efikeygen --dbdir "$WORK_DIR/certdb" \ + --self-sign \ + --module \ + --common-name 'CN=Test Module Signing Key' \ + --nickname 'Test Module Key' + +# Verify certificate was created +if ! certutil -d "$WORK_DIR/certdb" -L -n 'Test Module Key' > /dev/null 2>&1; then + echo " ERROR: Certificate not created" + exit 1 +fi + +# Export certificate +certutil -d "$WORK_DIR/certdb" \ + -n 'Test Module Key' \ + -Lr \ + > "$WORK_DIR/module_cert.cer" + +# Verify certificate file exists and is not empty +if [ ! -s "$WORK_DIR/module_cert.cer" ]; then + echo " ERROR: Certificate export failed" + exit 1 +fi + +echo " ✓ Self-signed module certificate created successfully" + +# Verify trust flags are set (should be u,u,u for user cert) +TRUST=$(certutil -d "$WORK_DIR/certdb" -L | grep "Test Module Key" | awk '{print $NF}') +if [ -z "$TRUST" ]; then + echo " ✗ No trust flags found" + exit 1 +else + echo " ✓ Trust flags: $TRUST" +fi + +# Get test data directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEST_DATA="$SCRIPT_DIR/data" + +# Use real kernel and module files +KERNEL="$TEST_DATA/vmlinuz-6.19.10-200.fc43.x86_64" +MODULE="$TEST_DATA/vfat.ko" + +# Test signing a kernel (should this work with module cert?) +echo " Testing kernel signing with module certificate..." +if ./src/pesign --certdir "$WORK_DIR/certdb" \ + --certificate 'Test Module Key' \ + --in "$KERNEL" \ + --sign \ + --out "$WORK_DIR/kernel-signed" 2>"$WORK_DIR/kernel-error.log"; then + echo " ⚠ Module cert signed kernel (exit 0)" + ./src/pesign --show-signature --in "$WORK_DIR/kernel-signed" 2>/dev/null | head -5 +else + echo " ✓ Module cert rejected for kernel signing (exit $?)" + [ -s "$WORK_DIR/kernel-error.log" ] && cat "$WORK_DIR/kernel-error.log" +fi + +# Test signing a module (should work) +echo " Testing module signing with module certificate..." +if ./src/pesign --certdir "$WORK_DIR/certdb" \ + --certificate 'Test Module Key' \ + --in "$MODULE" \ + --sign \ + --out "$WORK_DIR/module-signed.ko" 2>"$WORK_DIR/module-error.log"; then + echo " ✓ Module cert signed module (exit 0)" + ./src/pesign --show-signature --in "$WORK_DIR/module-signed.ko" 2>/dev/null | head -5 +else + echo " ✗ Module cert failed to sign module (exit $?)" + [ -s "$WORK_DIR/module-error.log" ] && cat "$WORK_DIR/module-error.log" + exit 1 +fi + +exit 0