From 7a24d2c79bc90262a13792caca364c2cebee2476 Mon Sep 17 00:00:00 2001 From: iadgovuser59 <133057011+iadgovuser59@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:15:57 -0400 Subject: [PATCH 1/2] Adding UEFI unit tests --- HIRS_Utils/build.gradle | 22 ++- .../tpm/eventlog/uefi/UefiProcessingTest.java | 171 ++++++++++++++++++ 2 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 HIRS_Utils/src/test/java/hirs/tpm/eventlog/uefi/UefiProcessingTest.java diff --git a/HIRS_Utils/build.gradle b/HIRS_Utils/build.gradle index ea0abd57..da399e5f 100644 --- a/HIRS_Utils/build.gradle +++ b/HIRS_Utils/build.gradle @@ -35,16 +35,24 @@ dependencies { implementation libs.guava implementation libs.commons.codec implementation libs.commons.lang3 + implementation libs.commons.io implementation libs.minimal.json implementation 'org.apache.logging.log4j:log4j-core:2.19.0' implementation 'org.apache.logging.log4j:log4j-api:2.19.0' implementation 'org.glassfish.jaxb:jaxb-runtime:4.0.1' + + implementation 'org.junit.jupiter:junit-jupiter-api:5.9.3' + implementation 'org.junit.jupiter:junit-jupiter-engine:5.9.3' + testImplementation 'junit:junit:4.13.1' + compileOnly libs.lombok annotationProcessor libs.lombok - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + //testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' + //testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testImplementation 'org.junit.platform:junit-platform-launcher:1.9.3' + testImplementation 'org.hamcrest:hamcrest:2.2' } test { @@ -55,11 +63,11 @@ jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes( - 'Class-Path': configurations.runtimeClasspath.files.collect { it.getName() }.join(' ') - ) + 'Class-Path': configurations.runtimeClasspath.files.collect { it.getName() }.join(' ') + ) } - //jar name format: [archiveBaseName]-[archiveAppendix]-[archiveVersion]-[archiveClassifier].[archiveExtension] - archiveVersion = jarVersion + //jar name format: [archiveBaseName]-[archiveAppendix]-[archiveVersion]-[archiveClassifier].[archiveExtension] + archiveVersion = jarVersion } //task generateXjcLibrary(type:Exec) { @@ -67,4 +75,4 @@ jar { // // commandLine './genXjcLibrary.sh' //} -//compileJava.dependsOn generateXjcLibrary +//compileJava.dependsOn generateXjcLibrary \ No newline at end of file diff --git a/HIRS_Utils/src/test/java/hirs/tpm/eventlog/uefi/UefiProcessingTest.java b/HIRS_Utils/src/test/java/hirs/tpm/eventlog/uefi/UefiProcessingTest.java new file mode 100644 index 00000000..183af293 --- /dev/null +++ b/HIRS_Utils/src/test/java/hirs/tpm/eventlog/uefi/UefiProcessingTest.java @@ -0,0 +1,171 @@ +package hirs.tpm.eventlog.uefi; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import com.eclipsesource.json.JsonObject; +import hirs.utils.JsonUtils; +import hirs.utils.tpm.eventlog.uefi.*; +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import hirs.utils.HexUtils; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Class for testing TCG Event Log processing of UEFI defined Data. + */ +public class UefiProcessingTest { + // Variable files collected using an Event Parsing tool + private static final String JSON_FILE = "/tcgeventlog/uefi/vendor-table.json"; + private static final String UEFI_VARIABLE_BOOT = "/tcgeventlog/uefi/EV_EFI_VARIABLE_BOOT.txt"; + private static final String UEFI_VARIABLE_BOOT_SECURE_BOOT + = "/tcgeventlog/uefi/EV_EFI_VAR_SECURE_BOOT.txt"; + private static final String UEFI_VARIABLE_BOOT_DRIVER_CONFIG_KEK + = "/tcgeventlog/uefi/EV_EFI_VARIABLE_DRIVER_CONFIG_KEK.txt"; + private static final String UEFI_GPT_EVENT = "/tcgeventlog/uefi/EV_EFI_GPT_EVENT.txt"; + private static final String UEFI_FW_BLOB = "/tcgeventlog/uefi/EFI_PLATFORM_FIRMWARE_BLOB.txt"; + private static final String UEFI_DEVICE_PATH = "/tcgeventlog/uefi/EFI_DEVICE_PATH.txt"; + + private static final Logger LOGGER + = LogManager.getLogger(UefiProcessingTest.class); + + /** + * Initializes a SessionFactory. + * The factory is used for an in-memory database that is used for testing. + */ + @BeforeAll + public static final void setup() { + LOGGER.debug("retrieving session factory"); + } + + /** + * Closes the SessionFactory from setup. + */ + @AfterAll + public static final void tearDown() { + LOGGER.debug("closing session factory"); + } + + /** + * Tests the processing of UEFI Variables. + * + * @throws IOException when processing the test fails. + * @throws NoSuchAlgorithmException if non TCG Algorithm is encountered. + * @throws CertificateException if parsing issue for X509 cert is encountered. + * @throws URISyntaxException File location exception + */ + @Test + public final void testUefiVariables() throws IOException, + CertificateException, NoSuchAlgorithmException, URISyntaxException { + LOGGER.debug("Testing the parsing of UEFI Variables"); + Path jsonPath = Paths.get(this.getClass() + .getResource(JSON_FILE).toURI()); + String uefiTxt = IOUtils.toString(this.getClass().getResourceAsStream(UEFI_VARIABLE_BOOT), + "UTF-8"); + byte[] uefiVariableBytes = HexUtils.hexStringToByteArray(uefiTxt); + UefiVariable uefiVariable = new UefiVariable(uefiVariableBytes); + UefiGuid guid = uefiVariable.getUefiVarGuid(); + String varName = uefiVariable.getEfiVarName(); + JsonObject jsonObject = JsonUtils.getSpecificJsonObject(jsonPath, "VendorTable"); + String guidStr = jsonObject.getString( + guid.toStringNoLookup().toLowerCase(), "Unknown GUID reference"); + Assertions.assertEquals("EFI_Global_Variable", guidStr); + Assertions.assertEquals("BootOrder", varName); + + uefiTxt = IOUtils.toString(this.getClass() + .getResourceAsStream(UEFI_VARIABLE_BOOT_SECURE_BOOT), + "UTF-8"); + uefiVariableBytes = HexUtils.hexStringToByteArray(uefiTxt); + uefiVariable = new UefiVariable(uefiVariableBytes); + guid = uefiVariable.getUefiVarGuid(); + varName = uefiVariable.getEfiVarName(); + guidStr = jsonObject.getString( + guid.toStringNoLookup().toLowerCase(), "Unknown GUID reference"); + Assertions.assertEquals("EFI_Global_Variable", guidStr); + Assertions.assertEquals("SecureBoot", varName); + + uefiTxt = IOUtils.toString(this.getClass().getResourceAsStream( + UEFI_VARIABLE_BOOT_DRIVER_CONFIG_KEK), "UTF-8"); + uefiVariableBytes = HexUtils.hexStringToByteArray(uefiTxt); + uefiVariable = new UefiVariable(uefiVariableBytes); + varName = uefiVariable.getEfiVarName(); + Assertions.assertEquals("KEK", varName); + } + + /** + * Tests the processing of a UEFI defined GPT Partition event. + * + * @throws IOException when processing the test fails. + * @throws NoSuchAlgorithmException if non TCG Algorithm is encountered. + * @throws CertificateException if parsing issue for X509 cert is encountered. + * @throws URISyntaxException File location exception + */ + @Test + public final void testUefiPartiton() throws IOException, + CertificateException, NoSuchAlgorithmException, URISyntaxException { + LOGGER.debug("Testing the parsing of GPT Data"); + Path jsonPath = Paths.get(this.getClass() + .getResource(JSON_FILE).toURI()); + String uefiTxt = IOUtils.toString(this.getClass().getResourceAsStream(UEFI_GPT_EVENT), + "UTF-8"); + byte[] uefiPartitionBytes = HexUtils.hexStringToByteArray(uefiTxt); + UefiPartition gptPart = new UefiPartition(uefiPartitionBytes); + String gptPartName = gptPart.getPartitionName(); + UefiGuid gptTypeuid = gptPart.getPartitionTypeGUID(); + UefiGuid gptUniqueGuid = gptPart.getUniquePartitionGUID(); + JsonObject jsonObject = JsonUtils.getSpecificJsonObject(jsonPath, "VendorTable"); + String guidStr = jsonObject.getString( + gptTypeuid.toStringNoLookup().toLowerCase(), "Unknown GUID reference"); + Assertions.assertEquals("EFI System Partition", guidStr); + Assertions.assertEquals("8ca7623c-041e-4fab-8c12-f49a86b85d73 : Unknown GUID reference", + gptUniqueGuid.toString()); + Assertions.assertEquals("EFI system partition", gptPartName); + } + + /** + * Tests the processing of a UEFI defined GPT Partition event. + * + * @throws IOException when processing the test fails. + * @throws NoSuchAlgorithmException if non TCG Algorithm is encountered. + * @throws CertificateException if parsing issue for X509 cert is encountered. + */ + @Test + public final void testUefiFirmwareBlob() throws IOException, + CertificateException, NoSuchAlgorithmException { + LOGGER.debug("Testing the parsing of Uefi Firmware Blob"); + String uefiTxt = IOUtils.toString(this.getClass() + .getResourceAsStream(UEFI_FW_BLOB), "UTF-8"); + byte[] uefiFwBlobBytes = HexUtils.hexStringToByteArray(uefiTxt); + UefiFirmware uefiFWBlob = new UefiFirmware(uefiFwBlobBytes); + int fwAddress = uefiFWBlob.getPhysicalBlobAddress(); + int fwLength = uefiFWBlob.getBlobLength(); + Assertions.assertEquals(1797287936, fwAddress); + Assertions.assertEquals(851968, fwLength); + } + + /** + * Tests the processing of a UEFI defined Device Path. + * + * @throws IOException when processing the test fails. + * @throws URISyntaxException File location exception + */ + @Test + public final void testUefiDevicePath() throws IOException, URISyntaxException { + LOGGER.debug("Testing the parsing of Uefi Device Path"); + String uefiTxt = IOUtils.toString(this.getClass().getResourceAsStream(UEFI_DEVICE_PATH), + "UTF-8"); + byte[] uefiFwBlobBytes = HexUtils.hexStringToByteArray(uefiTxt); + UefiDevicePath uefiDevPath = new UefiDevicePath(uefiFwBlobBytes); + String devPathType = uefiDevPath.getType(); + Assertions.assertEquals("Media Device Path", devPathType); + } +} \ No newline at end of file From dbc11f15c45304f7b68fa05120b4c866bdc3b4fd Mon Sep 17 00:00:00 2001 From: iadgovuser59 <133057011+iadgovuser59@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:17:24 -0400 Subject: [PATCH 2/2] Adding resources for unit tests --- .../src/test/resources/tcgeventlog/TpmLog.bin | Bin 0 -> 7549 bytes .../tcgeventlog/TpmLogExpectedPcrs.txt | 24 ++ .../test/resources/tcgeventlog/TpmLogSHA1.bin | Bin 0 -> 18675 bytes .../tcgeventlog/TpmLogSHA1ExpectedPcrs.txt | 24 ++ .../events/EvBootServicesApplication.txt | 1 + .../tcgeventlog/events/EvEfiGptPartition.txt | 1 + .../tcgeventlog/events/EvEfiSpecId.txt | 1 + .../tcgeventlog/events/EvHandoffTables.txt | 1 + .../tcgeventlog/events/EvPostCode.txt | 1 + .../tcgeventlog/uefi/EFI_DEVICE_PATH.txt | 1 + .../uefi/EFI_PLATFORM_FIRMWARE_BLOB.txt | 1 + .../tcgeventlog/uefi/EV_EFI_GPT_EVENT.txt | 1 + .../tcgeventlog/uefi/EV_EFI_VARIABLE_BOOT.txt | 1 + .../EV_EFI_VARIABLE_DRIVER_CONFIG_KEK.txt | 1 + .../uefi/EV_EFI_VAR_SECURE_BOOT.txt | 1 + .../tcgeventlog/uefi/vendor-table.json | 233 ++++++++++++++++++ 16 files changed, 292 insertions(+) create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/TpmLog.bin create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/TpmLogExpectedPcrs.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/TpmLogSHA1.bin create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/TpmLogSHA1ExpectedPcrs.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/events/EvBootServicesApplication.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/events/EvEfiGptPartition.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/events/EvEfiSpecId.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/events/EvHandoffTables.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/events/EvPostCode.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/uefi/EFI_DEVICE_PATH.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/uefi/EFI_PLATFORM_FIRMWARE_BLOB.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/uefi/EV_EFI_GPT_EVENT.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/uefi/EV_EFI_VARIABLE_BOOT.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/uefi/EV_EFI_VARIABLE_DRIVER_CONFIG_KEK.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/uefi/EV_EFI_VAR_SECURE_BOOT.txt create mode 100644 HIRS_Utils/src/test/resources/tcgeventlog/uefi/vendor-table.json diff --git a/HIRS_Utils/src/test/resources/tcgeventlog/TpmLog.bin b/HIRS_Utils/src/test/resources/tcgeventlog/TpmLog.bin new file mode 100644 index 0000000000000000000000000000000000000000..0b8f1f398d51035bc91afbe8400d4888a28d5669 GIT binary patch literal 7549 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 Z3Uxc}%4JljeszIlpGQT``pbIpsYM;7r7Mol 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