mirror of
https://github.com/e-m-b-a/emba.git
synced 2026-10-11 22:09:54 +00:00
230 lines
9.5 KiB
Bash
Executable File
230 lines
9.5 KiB
Bash
Executable File
#!/bin/bash -p
|
|
|
|
# EMBA - EMBEDDED LINUX ANALYZER
|
|
#
|
|
# Copyright 2020-2026 Siemens Energy AG
|
|
#
|
|
# EMBA comes with ABSOLUTELY NO WARRANTY. This is free software, and you are
|
|
# welcome to redistribute it under the terms of the GNU General Public License.
|
|
# See LICENSE file for usage of this software.
|
|
#
|
|
# EMBA is licensed under GPLv3
|
|
# SPDX-License-Identifier: GPL-3.0-only
|
|
#
|
|
# Author(s): Michael Messner
|
|
|
|
# Description: This module generates a complete json SBOM from the identified software inventory
|
|
|
|
# shellcheck disable=SC2034
|
|
|
|
F15_cyclonedx_sbom() {
|
|
module_log_init "${FUNCNAME[0]}"
|
|
module_title "CycloneDX SBOM generator"
|
|
pre_module_reporter "${FUNCNAME[0]}"
|
|
|
|
local lNEG_LOG=0
|
|
|
|
if [[ -d "${SBOM_LOG_PATH}" ]]; then
|
|
if [[ -f "${CSV_DIR}"/f15_cyclonedx_sbom.json ]]; then
|
|
rm "${CSV_DIR}"/f15_cyclonedx_sbom.json
|
|
fi
|
|
|
|
local lSBOM_LOG_FILE="${SBOM_LOG_PATH%\/}/EMBA_cyclonedx_sbom"
|
|
local lSBOM_JSON=""
|
|
local lSBOM_SCHEMA="http://cyclonedx.org/schema/bom-1.5.schema.json"
|
|
local lSBOM_FORMAT="CycloneDX"
|
|
local lSBOM_SPEC_VERS="1.5"
|
|
local lSBOM_VER=1
|
|
local lSBOM_SERIAL_NR="urn:uuid:"
|
|
lSBOM_SERIAL_NR+="$(uuidgen)"
|
|
local lSBOM_TIMESTAMP=""
|
|
lSBOM_TIMESTAMP=$(date -Iseconds)
|
|
local lFW_TYPE=""
|
|
local lFW_PATH=""
|
|
# local lEMBA_COMMAND=""
|
|
local lCOMP_FILES_ARR=()
|
|
local lCOMP_FILE_ID=""
|
|
local lCOMP_FILE=""
|
|
local lDEP_FILE=""
|
|
local lDEP_FILE_ID=""
|
|
local lDEP_FILES_ARR=()
|
|
|
|
if [[ -f "${BASIC_DATA_LOG_DIR}"/fw_name.log ]] && [[ -f "${BASIC_DATA_LOG_DIR}"/emba_command.log ]]; then
|
|
lFW_PATH=$(sort -u "${BASIC_DATA_LOG_DIR}"/fw_name.log | head -n 1)
|
|
if [[ $(sort -u "${BASIC_DATA_LOG_DIR}"/fw_name.log | wc -l) -gt 1 ]]; then
|
|
print_output "[*] Warning: Multiple firmware paths detected in fw_name.log. Using the first entry: ${lFW_PATH}"
|
|
fi
|
|
# lEMBA_COMMAND=$(sort -u "${BASIC_DATA_LOG_DIR}"/emba_command.log)
|
|
else
|
|
lFW_PATH="${FIRMWARE_PATH_BAK}"
|
|
fi
|
|
|
|
if [[ -v CONTAINER_ID ]]; then
|
|
lFW_TYPE="container"
|
|
elif [[ "${lFW_PATH}" == *".exe" ]]; then
|
|
lFW_TYPE="application"
|
|
elif [[ -f "${lFW_PATH}" ]]; then
|
|
lFW_TYPE="file"
|
|
else
|
|
lFW_TYPE="data"
|
|
fi
|
|
|
|
# EMBA details for the SBOM
|
|
local lSBOM_TOOL="EMBA binary analysis environment"
|
|
local lSBOM_TOOL_VERS=""
|
|
lSBOM_TOOL_VERS="$(cat "${CONFIG_DIR}"/VERSION.txt)"
|
|
local lEMBA_URLS_ARR=("https://github.com/e-m-b-a/emba")
|
|
|
|
local lTOOL_COMP_ARR=()
|
|
lTOOL_COMP_ARR+=(type="application")
|
|
lTOOL_COMP_ARR+=(author="EMBA community")
|
|
lTOOL_COMP_ARR+=(name="${lSBOM_TOOL}")
|
|
if [[ -f "${INVOCATION_PATH}"/.git/refs/heads/master ]]; then
|
|
lSBOM_TOOL_VERS+="-$(cat "${INVOCATION_PATH}"/.git/refs/heads/master)"
|
|
fi
|
|
lTOOL_COMP_ARR+=(version="${lSBOM_TOOL_VERS}")
|
|
lTOOL_COMP_ARR+=(description="EMBA firmware analyzer - ${lEMBA_URLS_ARR[*]}")
|
|
|
|
# the following removes the duplicate untracked files that are handled from an other SBOM entry
|
|
if [[ -s "${SBOM_LOG_PATH}"/duplicates_to_delete.txt ]]; then
|
|
local lDUP_DEL=""
|
|
print_output "[*] Deleting duplicates" "no_log"
|
|
while read -r lDUP_DEL; do
|
|
rm -f "${lDUP_DEL}" || true
|
|
done <"${SBOM_LOG_PATH}"/duplicates_to_delete.txt
|
|
fi
|
|
|
|
# Firmeware details for the SBOM
|
|
local lFW_COMPONENT_DATA_ARR=()
|
|
lFW_COMPONENT_DATA_ARR+=(name="${lFW_PATH}")
|
|
lFW_COMPONENT_DATA_ARR+=(type="${lFW_TYPE}")
|
|
lFW_COMPONENT_DATA_ARR+=(bom-ref="$(uuidgen)")
|
|
|
|
# generate hashes for the firmware itself:
|
|
if [[ -f "${FIRMWARE_PATH_BAK}" ]]; then
|
|
build_sbom_json_hashes_arr "${FIRMWARE_PATH_BAK}" "NA" "NA"
|
|
fi
|
|
|
|
[[ -v HASHES_ARR ]] && lFW_COMPONENT_DATA_ARR+=("hashes=$(jo -a "${HASHES_ARR[@]}")")
|
|
|
|
# build the component array for final sbom build:
|
|
mapfile -t lCOMP_FILES_ARR < <(find "${SBOM_LOG_PATH}" -maxdepth 1 -type f -name "*.json" -not -name "unhandled_file_*" | sort -u)
|
|
if [[ "${SBOM_UNTRACKED_FILES}" -gt 0 ]]; then
|
|
mapfile -t lCOMP_FILES_ARR_UNHANDLED < <(find "${SBOM_LOG_PATH}" -maxdepth 1 -type f -name "unhandled_file_*.json" | sort -u)
|
|
lCOMP_FILES_ARR+=("${lCOMP_FILES_ARR_UNHANDLED[@]}")
|
|
fi
|
|
|
|
# as we can have so many components that everything goes b00m we need to build the
|
|
# components json manually:
|
|
echo -n "[" >"${SBOM_LOG_PATH}/sbom_components_tmp.json"
|
|
for lCOMP_FILE_ID in "${!lCOMP_FILES_ARR[@]}"; do
|
|
lCOMP_FILE="${lCOMP_FILES_ARR["${lCOMP_FILE_ID}"]}"
|
|
|
|
if [[ "${SBOM_UNTRACKED_FILES:-0}" -ne 1 ]] && [[ "${lCOMP_FILE}" == *"unhandled_file_"* ]]; then
|
|
# if we do not include unhandled_file entries we can skipe them here
|
|
continue
|
|
fi
|
|
|
|
if [[ -s "${lCOMP_FILE}" ]]; then
|
|
if (json_pp <"${lCOMP_FILE}" &>/dev/null); then
|
|
# before adding the new component we need to check that this is not our first entry (after the initial [) and for a ','
|
|
# if it is not found we need to add it now
|
|
if [[ "$(tail -c1 "${SBOM_LOG_PATH}/sbom_components_tmp.json")" != '[' ]] && [[ "$(tail -n1 "${SBOM_LOG_PATH}/sbom_components_tmp.json")" != ',' ]]; then
|
|
echo -n "," >>"${SBOM_LOG_PATH}/sbom_components_tmp.json"
|
|
fi
|
|
cat "${lCOMP_FILE}" >>"${SBOM_LOG_PATH}/sbom_components_tmp.json"
|
|
else
|
|
print_error "[-] WARNING: SBOM component ${lCOMP_FILE} failed to validate with json_pp"
|
|
continue
|
|
fi
|
|
else
|
|
print_error "[-] WARNING: SBOM component ${lCOMP_FILE} failed to decode"
|
|
continue
|
|
fi
|
|
done
|
|
echo -n "]" >>"${SBOM_LOG_PATH}/sbom_components_tmp.json"
|
|
tr -d '\n' <"${SBOM_LOG_PATH}/sbom_components_tmp.json" >"${lSBOM_LOG_FILE}_components.json"
|
|
|
|
if [[ -d "${SBOM_LOG_PATH}/SBOM_deps/" ]]; then
|
|
# build the dependency array for final sbom build:
|
|
mapfile -t lDEP_FILES_ARR < <(find "${SBOM_LOG_PATH}/SBOM_deps/" -type f -name "SBOM_dependency_*.json" | sort -u)
|
|
fi
|
|
|
|
if [[ "${#lDEP_FILES_ARR[@]}" -gt 0 ]]; then
|
|
# as we could have so many components that everything goes b00m we need to build the
|
|
# components json now manually:
|
|
echo -n "[" >"${SBOM_LOG_PATH}/sbom_dependencies_tmp.json"
|
|
for lDEP_FILE_ID in "${!lDEP_FILES_ARR[@]}"; do
|
|
lDEP_FILE="${lDEP_FILES_ARR["${lDEP_FILE_ID}"]}"
|
|
cat "${lDEP_FILE}" >>"${SBOM_LOG_PATH}/sbom_dependencies_tmp.json"
|
|
if [[ $((lDEP_FILE_ID + 1)) -lt "${#lDEP_FILES_ARR[@]}" ]]; then
|
|
echo -n "," >>"${SBOM_LOG_PATH}/sbom_dependencies_tmp.json"
|
|
fi
|
|
done
|
|
echo -n "]" >>"${SBOM_LOG_PATH}/sbom_dependencies_tmp.json"
|
|
tr -d '\n' <"${SBOM_LOG_PATH}/sbom_dependencies_tmp.json" >"${lSBOM_LOG_FILE}_dependencies.json"
|
|
else
|
|
echo -n "[" >"${lSBOM_LOG_FILE}_dependencies.json"
|
|
echo -n "]" >>"${lSBOM_LOG_FILE}_dependencies.json"
|
|
fi
|
|
|
|
# final sbom build:
|
|
jo -p -n -- \
|
|
\$schema="${lSBOM_SCHEMA}" \
|
|
bomFormat="${lSBOM_FORMAT}" \
|
|
-s specVersion="${lSBOM_SPEC_VERS}" \
|
|
serialNumber="${lSBOM_SERIAL_NR}" \
|
|
version="${lSBOM_VER}" \
|
|
metadata="$(jo \
|
|
timestamp="${lSBOM_TIMESTAMP}" \
|
|
tools="$(jo \
|
|
components="$(jo -a "$(jo -n "${lTOOL_COMP_ARR[@]}")")")" \
|
|
component="$(jo -n \
|
|
"${lFW_COMPONENT_DATA_ARR[@]}")" \
|
|
supplier="$(jo -n \
|
|
name="${FW_VENDOR:-EMBA binary analyzer}" url="$(jo -a "${lEMBA_URLS_ARR[@]}")")" \
|
|
lifecycles="$(jo -a \
|
|
"$(jo phase="${SBOM_LIFECYCLE_PHASE}")")")" \
|
|
components=:"${lSBOM_LOG_FILE}_components.json" \
|
|
dependencies=:"${lSBOM_LOG_FILE}_dependencies.json" \
|
|
vulnerabilities="[]" \
|
|
>"${lSBOM_LOG_FILE}.json" || print_error "[-] SBOM builder error!"
|
|
|
|
# Replace placeholder '%SPACE%' with actual spaces in the generated JSON file to ensure proper formatting.
|
|
sed -i 's/%SPACE%/\ /g' "${lSBOM_LOG_FILE}.json"
|
|
|
|
if [[ -s "${lSBOM_LOG_FILE}.json" ]]; then
|
|
local lNEG_LOG=1
|
|
print_output "[*] Converting SBOM to further SBOM formats ..." "no_log"
|
|
cyclonedx convert --output-format xml --input-file "${lSBOM_LOG_FILE}.json" --output-file "${lSBOM_LOG_FILE}.xml" || print_error "[-] Error while generating xml SBOM for SBOM"
|
|
cyclonedx convert --output-format protobuf --input-file "${lSBOM_LOG_FILE}.json" --output-file "${lSBOM_LOG_FILE}.proto" || print_error "[-] Error while generating protobuf SBOM for SBOM"
|
|
cyclonedx convert --output-format spdxjson --input-file "${lSBOM_LOG_FILE}.json" --output-file "${lSBOM_LOG_FILE}.spdx" || print_error "[-] Error while generating spdxjson SBOM for SBOM"
|
|
|
|
print_output "[+] Cyclonedx SBOM in json and CSV format created:"
|
|
print_output "$(indent "$(orange "-> Download SBOM as JSON${NC}")")" "" "${lSBOM_LOG_FILE}.json"
|
|
if [[ -f "${lSBOM_LOG_FILE}.xml" ]]; then
|
|
print_output "$(indent "$(orange "-> Download SBOM as XML${NC}")")" "" "${lSBOM_LOG_FILE}.xml"
|
|
fi
|
|
if [[ -f "${lSBOM_LOG_FILE}.spdx" ]]; then
|
|
print_output "$(indent "$(orange "-> Download SBOM as SPDX JSON${NC}")")" "" "${lSBOM_LOG_FILE}.spdx"
|
|
fi
|
|
if [[ -f "${lSBOM_LOG_FILE}.proto" ]]; then
|
|
print_output "$(indent "$(orange "-> Download SBOM as PROTOBUF${NC}")")" "" "${lSBOM_LOG_FILE}.proto"
|
|
fi
|
|
if [[ -f "${S08_CSV_LOG}" ]]; then
|
|
print_output "$(indent "$(orange "-> Download SBOM as EMBA CSV${NC}")")" "" "${S08_CSV_LOG}"
|
|
fi
|
|
print_ln
|
|
print_output "[+] Cyclonedx SBOM in json format:" "" "${lSBOM_LOG_FILE}.json"
|
|
print_ln
|
|
tee -a "${LOG_FILE}" <"${lSBOM_LOG_FILE}.json"
|
|
print_ln
|
|
else
|
|
print_output "[-] No SBOM created!"
|
|
local lNEG_LOG=0
|
|
fi
|
|
fi
|
|
|
|
module_end_log "${FUNCNAME[0]}" "${lNEG_LOG}"
|
|
}
|