diff --git a/cyclonedx-lib/build.xml b/cyclonedx-lib/build.xml index f2bc74648..96ff9ec5c 100644 --- a/cyclonedx-lib/build.xml +++ b/cyclonedx-lib/build.xml @@ -468,8 +468,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cyclonedx-lib/src/temurin/sbom/TemurinGenSBOM.java b/cyclonedx-lib/src/temurin/sbom/TemurinGenSBOM.java index fce3789c1..5f3dd82aa 100644 --- a/cyclonedx-lib/src/temurin/sbom/TemurinGenSBOM.java +++ b/cyclonedx-lib/src/temurin/sbom/TemurinGenSBOM.java @@ -20,6 +20,7 @@ import org.cyclonedx.model.Bom; import org.cyclonedx.model.Component; import org.cyclonedx.model.ExternalReference; +import org.cyclonedx.model.formulation.Formula; import org.cyclonedx.model.Hash; import org.cyclonedx.model.Metadata; import org.cyclonedx.model.OrganizationalContact; @@ -31,6 +32,7 @@ import java.io.FileWriter; import java.util.Collections; import java.util.List; +import java.util.LinkedList; /** * Command line tool to construct a CycloneDX SBOM. @@ -50,6 +52,7 @@ public static void main(final String[] args) { String cmd = null; String comment = null; String compName = null; + String formulaName = null; String description = null; String fileName = null; String hash = null; @@ -77,6 +80,8 @@ public static void main(final String[] args) { hash = args[++i]; } else if (args[i].equals("--compName")) { compName = args[++i]; + } else if (args[i].equals("--formulaName")) { + formulaName = args[++i]; } else if (args[i].equals("--description")) { description = args[++i]; } else if (args[i].equals("--type")) { @@ -103,6 +108,12 @@ public static void main(final String[] args) { cmd = "addComponentExternalReference"; } else if (args[i].equals("--addMetadataTools")) { cmd = "addMetadataTools"; + } else if (args[i].equals("--addFormulation")) { // Formulation Component. We can set "name" for Formulation. + cmd = "addFormulation"; + } else if (args[i].equals("--addFormulationComp")) { // Formulation Component. We can set "name" for Formulation. + cmd = "addFormulationComp"; + } else if (args[i].equals("--addFormulationCompProp")) { // Formulation --> Component --> Property --> name-value + cmd = "addFormulationCompProp"; } else if (args[i].equals("--verbose")) { verbose = true; } @@ -128,6 +139,20 @@ public static void main(final String[] args) { writeJSONfile(bom, fileName); break; + case "addFormulation": // Adds Formulation --> name + bom = addFormulation(fileName, formulaName); + writeJSONfile(bom, fileName); + break; + + case "addFormulationComp": // Adds Formulation --> Component--> name + bom = addFormulationComp(fileName, formulaName, name, type); + writeJSONfile(bom, fileName); + break; + case "addFormulationCompProp": // Adds Formulation --> Component -> name-value: + bom = addFormulationCompProp(fileName, formulaName, compName, name, value); + writeJSONfile(bom, fileName); + break; + case "addMetadataTools": bom = addMetadataTools(fileName, tool, version); writeJSONfile(bom, fileName); @@ -302,9 +327,91 @@ static Bom addComponentExternalReference(final String fileName, final String has return bom; } + static Bom addFormulation(final String fileName, final String name) { + Bom bom = readJSONfile(fileName); + List formulation = bom.getFormulation(); + if (formulation == null) { + formulation = new LinkedList(); + Formula formula = new Formula(); + System.err.println("SXAECW: " + name); + formula.setBomRef(name); + formulation.add(formula); + bom.setFormulation(formulation); + } + return bom; + } + + static Bom addFormulationComp(final String fileName, final String formulaName, final String name, final String type) { + Bom bom = readJSONfile(fileName); + if (formulaName == null) { + System.out.println("addFormulationComp: formulaName is null"); + return bom; + } else if (name == null) { + System.out.println("addFormulationComp: name is null"); + return bom; + } + List formulation = bom.getFormulation(); + // Look for the formula, and add the new component to it + boolean found = false; + for (Formula item : formulation) { + if (item.getBomRef().equals(formulaName)) { + found = true; + Component comp = new Component(); + Component.Type compType = Component.Type.FRAMEWORK; + comp.setType(Component.Type.FRAMEWORK); + comp.setName(name); + List components = item.getComponents(); + if (components == null) { + components = new LinkedList(); + } + components.add(comp); + item.setComponents(components); + } + } + if (!found) { + System.out.println("addFormulationComp could not add component as it couldn't find an entry for formula " + formulaName); + } + return bom; + } + + static Bom addFormulationCompProp(final String fileName, final String formulaName, final String componentName, final String name, final String value) { + Bom bom = readJSONfile(fileName); + boolean foundFormula = false; + boolean foundComponent = false; + List formulation = bom.getFormulation(); + // Look for the formula, and add the new component to it + for (Formula item : formulation) { + if (item.getBomRef().equals(formulaName)) { + foundFormula = true; + // Search for the component in the formula and add new component to it + List components = item.getComponents(); + if (components == null) { + System.out.println("addFormulationCompProp: Components is null - has addFormulationComp been called?"); + } else { + for (Component comp : components) { + if (comp.getName().equals(componentName)) { + foundComponent = true; + Property prop1 = new Property(); + prop1.setName(name); + prop1.setValue(value); + comp.addProperty(prop1); + item.setComponents(components); + } + } + } + } + } + if (!foundFormula) { + System.out.println("addFormulationCompProp could not add add property as it couldn't find an entry for formula " + formulaName); + } else if (!foundComponent) { + System.out.println("addFormulationCompProp could not add add property as it couldn't find an entry for component " + componentName); + } + return bom; + } + static String generateBomJson(final Bom bom) { - // Use schema v14: https://cyclonedx.org/schema/bom-1.4.schema.json - BomJsonGenerator bomGen = BomGeneratorFactory.createJson(CycloneDxSchema.Version.VERSION_14, bom); + // Use schema v15: https://cyclonedx.org/schema/bom-1.5.schema.json + BomJsonGenerator bomGen = BomGeneratorFactory.createJson(CycloneDxSchema.Version.VERSION_15, bom); String json = bomGen.toJsonString(); return json; } diff --git a/sbin/build.sh b/sbin/build.sh index faae6d926..0221a63e8 100755 --- a/sbin/build.sh +++ b/sbin/build.sh @@ -877,6 +877,10 @@ generateSBoM() { addSBOMMetadataProperty "${javaHome}" "${classpath}" "${sbomJson}" "OS version" "${BUILD_CONFIG[OS_FULL_VERSION]^}" addSBOMMetadataProperty "${javaHome}" "${classpath}" "${sbomJson}" "OS architecture" "${BUILD_CONFIG[OS_ARCHITECTURE]^}" + # Set default SBOM formulation + addSBOMFormulation "${javaHome}" "${classpath}" "${sbomJson}" "CycloneDX" + addSBOMFormulationComp "${javaHome}" "${classpath}" "${sbomJson}" "CycloneDX" "CycloneDX jar SHAs" + # Below add build tools into metadata tools if [ "${BUILD_CONFIG[OS_KERNEL_NAME]}" == "linux" ]; then addGLIBCforLinux @@ -894,6 +898,8 @@ generateSBoM() { if [ -f "${freemarker_version}" ]; then addSBOMMetadataTools "${javaHome}" "${classpath}" "${sbomJson}" "FreeMarker" "$(cat ${freemarker_version})" fi + # Add CycloneDX versions + addCycloneDXVersions # Add Build Docker image SHA1 local buildimagesha=$(cat ${BUILD_CONFIG[WORKSPACE_DIR]}/${BUILD_CONFIG[TARGET_DIR]}/metadata/docker.txt) @@ -1046,6 +1052,24 @@ addFreeTypeVersionInfo() { addSBOMMetadataTools "${javaHome}" "${classpath}" "${sbomJson}" "FreeType" "${version}" } +# Determine and store CycloneDX SHAs that have been used to provide the SBOMs +addCycloneDXVersions() { + if [ ! -d "${CYCLONEDB_DIR}/build/jar" ]; then + echo "ERROR: CycloneDX jar directory not found at ${CYCLONEDB_DIR}/build/jar - cannot store checksums in SBOM" + else + # Should we do something special if the sha256sum fails? + for JAR in "${CYCLONEDB_DIR}/build/jar"/*.jar; do + JarName=$(basename "$JAR") + if [ "$(uname)" = "Darwin" ]; then + JarSha=$(shasum -a 256 "${CYCLONEDB_DIR}/build/jar/cyclonedx-core-java.jar" | cut -d' ' -f1) + else + JarSha=$(sha256sum "${CYCLONEDB_DIR}/build/jar/cyclonedx-core-java.jar" | cut -d' ' -f1) + fi + addSBOMFormulationComponentProperty "${javaHome}" "${classpath}" "${sbomJson}" "CycloneDX" "CycloneDX jar SHAs" "${JarName}" "${JarSha}" + done + fi +} + # Below add versions to sbom | Facilitate reproducible builds addGLIBCforLinux() { diff --git a/sbin/common/sbom.sh b/sbin/common/sbom.sh index f7fe0caf9..24fa0b060 100755 --- a/sbin/common/sbom.sh +++ b/sbin/common/sbom.sh @@ -44,6 +44,39 @@ addSBOMMetadataProperty() { fi "${javaHome}"/bin/java -cp "${classpath}" temurin.sbom.TemurinGenSBOM --addMetadataProp --jsonFile "${jsonFile}" --name "${name}" --value "${value}" } + +# Set basic SBoM formulation +addSBOMFormulation() { + local javaHome="${1}" + local classpath="${2}" + local jsonFile="${3}" + local formulaName="${4}" + "${javaHome}"/bin/java -cp "${classpath}" temurin.sbom.TemurinGenSBOM --addFormulation --formulaName "${formulaName}" --jsonFile "${jsonFile}" +} + +addSBOMFormulationComp() { + local javaHome="${1}" + local classpath="${2}" + local jsonFile="${3}" + local formulaName="${4}" + local name="${5}" + "${javaHome}"/bin/java -cp "${classpath}" temurin.sbom.TemurinGenSBOM --addFormulationComp --jsonFile "${jsonFile}" --formulaName "${formulaName}" --name "${name}" +} + +# Ref: https://cyclonedx.org/docs/1.4/json/#formulation +# Add the given Property name & value to the SBOM Formulation +addSBOMFormulationComponentProperty() { + local javaHome="${1}" + local classpath="${2}" + local jsonFile="${3}" + local formulaName="${4}" + local compName="${5}" + local name="${6}" + local value="${7}" + "${javaHome}"/bin/java -cp "${classpath}" temurin.sbom.TemurinGenSBOM --addFormulationCompProp --jsonFile "${jsonFile}" --formulaName "${formulaName}" --compName "${compName}" --name "${name}" --value "${value}" +} + + # Ref: https://cyclonedx.org/docs/1.4/json/#metadata # If the given property file exists and size over 2bytes, then add the given Property name with the given file contents value to the SBOM Metadata addSBOMMetadataPropertyFromFile() {