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() {