Article: Comprehensive Software Bill of Materials (SBOM) Generation with CycloneDX
CycloneDX is a lightweight software bill of materials (SBOM) standard that provides inventory of all software components. This article covers comprehensive implementation of CycloneDX SBOM generation for Java applications with advanced features like vulnerability integration, dependency analysis, and automated SBOM management.
Core CycloneDX Plugin Implementation
package com.titliel.cyclonedx;
import org.cyclonedx.BomGeneratorFactory;
import org.cyclonedx.CycloneDxSchema;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.ExternalReference;
import org.cyclonedx.model.Hash;
import org.cyclonedx.model.License;
import org.cyclonedx.model.LicenseChoice;
import org.cyclonedx.model.Property;
import org.cyclonedx.model.vulnerability.Vulnerability;
import org.cyclonedx.model.vulnerability.VulnerabilitySource;
import org.cyclonedx.model.vulnerability.VulnerabilityAnalysis;
import org.cyclonedx.model.vulnerability.VulnerabilityAffectedStatus;
import java.io.*;
import java.nio.file.*;
import java.security.MessageDigest;
import java.util.*;
import java.util.stream.Collectors;
/**
* Titliel CycloneDX SBOM Generator
* Advanced SBOM generation with vulnerability integration and dependency analysis
*/
public class TitlielCycloneDxPlugin {
private final SbomConfig config;
private final DependencyResolver dependencyResolver;
private final VulnerabilityIntegrator vulnerabilityIntegrator;
private final SbomEnricher sbomEnricher;
private final SbomValidator sbomValidator;
public TitlielCycloneDxPlugin() {
this.config = SbomConfig.getInstance();
this.dependencyResolver = new DependencyResolver();
this.vulnerabilityIntegrator = new VulnerabilityIntegrator();
this.sbomEnricher = new SbomEnricher();
this.sbomValidator = new SbomValidator();
}
/**
* Generate comprehensive SBOM for a project
*/
public SbomResult generateSbom(ProjectContext context) {
long startTime = System.currentTimeMillis();
SbomResult result = new SbomResult(context);
try {
// Validate project structure
validateProject(context);
// Resolve dependencies
List<ResolvedDependency> dependencies = dependencyResolver.resolveDependencies(context);
result.setDependencies(dependencies);
// Create CycloneDX BOM
Bom bom = createBom(context, dependencies);
// Integrate vulnerability data
if (config.isVulnerabilityIntegrationEnabled()) {
bom = vulnerabilityIntegrator.integrateVulnerabilities(bom, dependencies);
}
// Enrich SBOM with additional metadata
bom = sbomEnricher.enrichBom(bom, context, dependencies);
// Validate SBOM
ValidationResult validation = sbomValidator.validateBom(bom);
result.setValidationResult(validation);
// Generate SBOM in multiple formats
generateSbomFiles(bom, context, result);
// Generate additional reports
generateAdditionalReports(result, dependencies);
result.setSuccess(true);
} catch (Exception e) {
result.setError(e.getMessage());
result.setSuccess(false);
} finally {
result.setGenerationTime(System.currentTimeMillis() - startTime);
}
return result;
}
/**
* Create CycloneDX BOM from dependencies
*/
private Bom createBom(ProjectContext context, List<ResolvedDependency> dependencies) {
Bom bom = new Bom();
// Set BOM metadata
bom.setSerialNumber(generateSerialNumber());
bom.setVersion(1);
bom.setMetadata(createBomMetadata(context));
// Add components
List<Component> components = createComponents(dependencies, context);
bom.setComponents(components);
// Add external references
bom.setExternalReferences(createExternalReferences(context));
// Add properties
bom.setProperties(createBomProperties(context));
return bom;
}
/**
* Create BOM metadata
*/
private org.cyclonedx.model.Metadata createBomMetadata(ProjectContext context) {
org.cyclonedx.model.Metadata metadata = new org.cyclonedx.model.Metadata();
// Component for the main application
Component component = new Component();
component.setType(org.cyclonedx.model.Component.Type.APPLICATION);
component.setName(context.getProjectName());
component.setVersion(context.getProjectVersion());
component.setGroup(context.getGroupId());
component.setDescription(context.getDescription());
component.setPurl(generatePurl(context));
component.setBomRef(generateBomRef(context));
// Add hashes
component.setHashes(calculateComponentHashes(context));
// Add licenses
component.setLicenses(createLicenseChoices(context.getLicenses()));
// Add external references
component.setExternalReferences(createComponentExternalReferences(context));
// Add properties
component.setProperties(createComponentProperties(context));
metadata.setComponent(component);
// Add tools
metadata.setTools(createToolsMetadata());
// Add authors
metadata.setAuthors(createAuthors(context));
return metadata;
}
/**
* Create components from dependencies
*/
private List<Component> createComponents(List<ResolvedDependency> dependencies,
ProjectContext context) {
List<Component> components = new ArrayList<>();
for (ResolvedDependency dep : dependencies) {
Component component = createComponentFromDependency(dep);
components.add(component);
}
// Add the main application component
Component mainComponent = createMainComponent(context);
components.add(mainComponent);
return components;
}
/**
* Create component from dependency
*/
private Component createComponentFromDependency(ResolvedDependency dep) {
Component component = new Component();
component.setType(org.cyclonedx.model.Component.Type.LIBRARY);
component.setGroup(dep.getGroupId());
component.setName(dep.getArtifactId());
component.setVersion(dep.getVersion());
component.setDescription(dep.getDescription());
component.setPurl(generatePurl(dep));
component.setBomRef(generateBomRef(dep));
// Set scope
if (dep.getScope() != null) {
component.setScope(org.cyclonedx.model.Component.Scope.fromString(dep.getScope()));
}
// Add hashes
component.setHashes(calculateDependencyHashes(dep));
// Add licenses
component.setLicenses(createLicenseChoices(dep.getLicenses()));
// Add external references
component.setExternalReferences(createDependencyExternalReferences(dep));
// Add properties
component.setProperties(createDependencyProperties(dep));
// Add supplier information if available
if (dep.getSupplier() != null) {
component.setSupplier(createSupplier(dep.getSupplier()));
}
return component;
}
/**
* Create main application component
*/
private Component createMainComponent(ProjectContext context) {
Component component = new Component();
component.setType(org.cyclonedx.model.Component.Type.APPLICATION);
component.setName(context.getProjectName());
component.setVersion(context.getProjectVersion());
component.setGroup(context.getGroupId());
component.setDescription(context.getDescription());
component.setPurl(generatePurl(context));
component.setBomRef(generateBomRef(context));
// Add hashes
component.setHashes(calculateComponentHashes(context));
// Add licenses
component.setLicenses(createLicenseChoices(context.getLicenses()));
return component;
}
/**
* Generate SBOM files in multiple formats
*/
private void generateSbomFiles(Bom bom, ProjectContext context, SbomResult result) {
Path outputDir = context.getOutputDirectory();
try {
// Ensure output directory exists
Files.createDirectories(outputDir);
// Generate JSON SBOM
if (config.isJsonOutputEnabled()) {
Path jsonFile = generateJsonSbom(bom, outputDir, context);
result.addGeneratedFile(jsonFile, SbomFormat.JSON);
}
// Generate XML SBOM
if (config.isXmlOutputEnabled()) {
Path xmlFile = generateXmlSbom(bom, outputDir, context);
result.addGeneratedFile(xmlFile, SbomFormat.XML);
}
// Generate Protobuf SBOM
if (config.isProtobufOutputEnabled()) {
Path protobufFile = generateProtobufSbom(bom, outputDir, context);
result.addGeneratedFile(protobufFile, SbomFormat.PROTOBUF);
}
} catch (Exception e) {
throw new RuntimeException("Failed to generate SBOM files", e);
}
}
/**
* Generate JSON format SBOM
*/
private Path generateJsonSbom(Bom bom, Path outputDir, ProjectContext context)
throws Exception {
String fileName = String.format("%s-%s-sbom.json",
context.getProjectName(), context.getProjectVersion());
Path outputFile = outputDir.resolve(fileName);
BomGeneratorFactory.createJson(CycloneDxSchema.Version.VERSION_14, bom)
.generateToFile(outputFile.toFile());
return outputFile;
}
/**
* Generate XML format SBOM
*/
private Path generateXmlSbom(Bom bom, Path outputDir, ProjectContext context)
throws Exception {
String fileName = String.format("%s-%s-sbom.xml",
context.getProjectName(), context.getProjectVersion());
Path outputFile = outputDir.resolve(fileName);
BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_14, bom)
.generateToFile(outputFile.toFile());
return outputFile;
}
/**
* Generate Protobuf format SBOM
*/
private Path generateProtobufSbom(Bom bom, Path outputDir, ProjectContext context)
throws Exception {
String fileName = String.format("%s-%s-sbom.protobuf",
context.getProjectName(), context.getProjectVersion());
Path outputFile = outputDir.resolve(fileName);
// Note: Protobuf generation might require additional dependencies
BomGeneratorFactory.createProtobuf(CycloneDxSchema.Version.VERSION_14, bom)
.generateToFile(outputFile.toFile());
return outputFile;
}
/**
* Generate additional reports
*/
private void generateAdditionalReports(SbomResult result,
List<ResolvedDependency> dependencies) {
try {
// Generate dependency tree report
if (config.isDependencyTreeEnabled()) {
Path treeReport = generateDependencyTreeReport(dependencies, result.getContext());
result.addReport(treeReport, ReportType.DEPENDENCY_TREE);
}
// Generate license report
if (config.isLicenseReportEnabled()) {
Path licenseReport = generateLicenseReport(dependencies, result.getContext());
result.addReport(licenseReport, ReportType.LICENSE);
}
// Generate vulnerability report
if (config.isVulnerabilityReportEnabled()) {
Path vulnReport = generateVulnerabilityReport(result);
result.addReport(vulnReport, ReportType.VULNERABILITY);
}
// Generate compliance report
if (config.isComplianceReportEnabled()) {
Path complianceReport = generateComplianceReport(result);
result.addReport(complianceReport, ReportType.COMPLIANCE);
}
} catch (Exception e) {
result.addWarning("Failed to generate additional reports: " + e.getMessage());
}
}
/**
* Generate dependency tree report
*/
private Path generateDependencyTreeReport(List<ResolvedDependency> dependencies,
ProjectContext context) throws IOException {
Path reportFile = context.getOutputDirectory().resolve("dependency-tree.txt");
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(reportFile))) {
writer.println("Dependency Tree Report");
writer.println("======================");
writer.println();
Map<String, List<ResolvedDependency>> byScope = dependencies.stream()
.collect(Collectors.groupingBy(ResolvedDependency::getScope));
for (Map.Entry<String, List<ResolvedDependency>> entry : byScope.entrySet()) {
writer.println("Scope: " + entry.getKey());
writer.println("----------------------");
for (ResolvedDependency dep : entry.getValue()) {
writer.printf(" %s:%s:%s%n",
dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
// Print transitive dependencies
for (ResolvedDependency transitive : dep.getTransitiveDependencies()) {
writer.printf(" └── %s:%s:%s%n",
transitive.getGroupId(), transitive.getArtifactId(),
transitive.getVersion());
}
}
writer.println();
}
}
return reportFile;
}
/**
* Generate license report
*/
private Path generateLicenseReport(List<ResolvedDependency> dependencies,
ProjectContext context) throws IOException {
Path reportFile = context.getOutputDirectory().resolve("license-report.csv");
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(reportFile))) {
writer.println("Component,Version,License,Risk Level,Approval Status");
for (ResolvedDependency dep : dependencies) {
for (DependencyLicense license : dep.getLicenses()) {
writer.printf("%s:%s,%s,%s,%s,%s%n",
dep.getGroupId(), dep.getArtifactId(),
dep.getVersion(),
license.getName(),
license.getRiskLevel(),
license.getApprovalStatus());
}
}
}
return reportFile;
}
// Utility methods for creating CycloneDX elements
private String generateSerialNumber() {
return "urn:uuid:" + UUID.randomUUID().toString();
}
private String generatePurl(ProjectContext context) {
return String.format("pkg:maven/%s/%s@%s",
context.getGroupId(), context.getProjectName(), context.getProjectVersion());
}
private String generatePurl(ResolvedDependency dep) {
return String.format("pkg:maven/%s/%s@%s",
dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
}
private String generateBomRef(ProjectContext context) {
return context.getGroupId() + ":" + context.getProjectName() + ":" + context.getProjectVersion();
}
private String generateBomRef(ResolvedDependency dep) {
return dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getVersion();
}
private List<Hash> calculateComponentHashes(ProjectContext context) {
List<Hash> hashes = new ArrayList<>();
try {
// Calculate hash of the main JAR/WAR file
Path artifactPath = findArtifactPath(context);
if (artifactPath != null && Files.exists(artifactPath)) {
byte[] fileContent = Files.readAllBytes(artifactPath);
// SHA-1
hashes.add(createHash("SHA-1", calculateHash("SHA-1", fileContent)));
// SHA-256
hashes.add(createHash("SHA-256", calculateHash("SHA-256", fileContent)));
}
} catch (Exception e) {
// Log warning but continue
}
return hashes;
}
private List<Hash> calculateDependencyHashes(ResolvedDependency dep) {
List<Hash> hashes = new ArrayList<>();
try {
// In a real implementation, you would read the actual dependency file
// For now, we'll create placeholder hashes
String content = dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getVersion();
byte[] contentBytes = content.getBytes();
hashes.add(createHash("SHA-1", calculateHash("SHA-1", contentBytes)));
hashes.add(createHash("SHA-256", calculateHash("SHA-256", contentBytes)));
} catch (Exception e) {
// Log warning but continue
}
return hashes;
}
private Hash createHash(String algorithm, String value) {
Hash hash = new Hash();
hash.setAlgorithm(algorithm);
hash.setValue(value);
return hash;
}
private String calculateHash(String algorithm, byte[] content) throws Exception {
MessageDigest digest = MessageDigest.getInstance(algorithm);
byte[] hashBytes = digest.digest(content);
return bytesToHex(hashBytes);
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
private Path findArtifactPath(ProjectContext context) {
// Look for built artifact in standard Maven/Gradle locations
Path targetDir = context.getProjectDirectory().resolve("target");
if (Files.exists(targetDir)) {
try {
return Files.list(targetDir)
.filter(path -> path.toString().endsWith(".jar") ||
path.toString().endsWith(".war"))
.findFirst()
.orElse(null);
} catch (IOException e) {
return null;
}
}
return null;
}
private List<LicenseChoice> createLicenseChoices(List<DependencyLicense> licenses) {
return licenses.stream()
.map(this::createLicenseChoice)
.collect(Collectors.toList());
}
private LicenseChoice createLicenseChoice(DependencyLicense license) {
LicenseChoice choice = new LicenseChoice();
if (license.getId() != null && !license.getId().isEmpty()) {
License lic = new License();
lic.setId(license.getId());
lic.setName(license.getName());
lic.setUrl(license.getUrl());
choice.setLicense(lic);
} else {
// Use license expression if ID is not available
choice.setExpression(license.getName());
}
return choice;
}
private List<ExternalReference> createExternalReferences(ProjectContext context) {
List<ExternalReference> refs = new ArrayList<>();
// Add VCS reference
if (context.getScmUrl() != null) {
ExternalReference vcsRef = new ExternalReference();
vcsRef.setType(ExternalReference.Type.VCS);
vcsRef.setUrl(context.getScmUrl());
refs.add(vcsRef);
}
// Add issue tracker reference
if (context.getIssueTrackerUrl() != null) {
ExternalReference issueRef = new ExternalReference();
issueRef.setType(ExternalReference.Type.ISSUE_TRACKER);
issueRef.setUrl(context.getIssueTrackerUrl());
refs.add(issueRef);
}
// Add website reference
if (context.getWebsiteUrl() != null) {
ExternalReference websiteRef = new ExternalReference();
websiteRef.setType(ExternalReference.Type.WEBSITE);
websiteRef.setUrl(context.getWebsiteUrl());
refs.add(websiteRef);
}
return refs;
}
private List<ExternalReference> createComponentExternalReferences(ProjectContext context) {
// Similar to createExternalReferences but for component
return createExternalReferences(context);
}
private List<ExternalReference> createDependencyExternalReferences(ResolvedDependency dep) {
List<ExternalReference> refs = new ArrayList<>();
// Add package registry reference
ExternalReference registryRef = new ExternalReference();
registryRef.setType(ExternalReference.Type.DISTRIBUTION);
registryRef.setUrl(String.format("https://repo1.maven.org/maven2/%s/%s/%s/",
dep.getGroupId().replace('.', '/'), dep.getArtifactId(), dep.getVersion()));
refs.add(registryRef);
return refs;
}
private List<Property> createBomProperties(ProjectContext context) {
List<Property> properties = new ArrayList<>();
properties.add(createProperty("buildTool", context.getBuildTool().name()));
properties.add(createProperty("javaVersion", context.getJavaVersion()));
properties.add(createProperty("timestamp", new Date().toString()));
properties.add(createProperty("generator", "Titliel-CycloneDX-Plugin"));
return properties;
}
private List<Property> createComponentProperties(ProjectContext context) {
List<Property> properties = new ArrayList<>();
properties.add(createProperty("packaging", context.getPackaging()));
properties.add(createProperty("encoding", context.getEncoding()));
return properties;
}
private List<Property> createDependencyProperties(ResolvedDependency dep) {
List<Property> properties = new ArrayList<>();
properties.add(createProperty("scope", dep.getScope()));
properties.add(createProperty("optional", String.valueOf(dep.isOptional())));
properties.add(createProperty("resolved", String.valueOf(dep.isResolved())));
return properties;
}
private Property createProperty(String name, String value) {
Property property = new Property();
property.setName(name);
property.setValue(value);
return property;
}
private List<org.cyclonedx.model.Tool> createToolsMetadata() {
List<org.cyclonedx.model.Tool> tools = new ArrayList<>();
org.cyclonedx.model.Tool tool = new org.cyclonedx.model.Tool();
tool.setName("Titliel CycloneDX Plugin");
tool.setVersion("1.0.0");
tool.setVendor("Titliel Security");
List<org.cyclonedx.model.Hash> hashes = new ArrayList<>();
// Add tool hashes if available
tools.add(tool);
return tools;
}
private List<org.cyclonedx.model.OrganizationalContact> createAuthors(ProjectContext context) {
return context.getAuthors().stream()
.map(this::createAuthor)
.collect(Collectors.toList());
}
private org.cyclonedx.model.OrganizationalContact createAuthor(ProjectAuthor author) {
org.cyclonedx.model.OrganizationalContact contact =
new org.cyclonedx.model.OrganizationalContact();
contact.setName(author.getName());
contact.setEmail(author.getEmail());
return contact;
}
private org.cyclonedx.model.OrganizationalEntity createSupplier(DependencySupplier supplier) {
org.cyclonedx.model.OrganizationalEntity entity =
new org.cyclonedx.model.OrganizationalEntity();
entity.setName(supplier.getName());
entity.setUrl(supplier.getUrl());
return entity;
}
private void validateProject(ProjectContext context) {
List<String> errors = new ArrayList<>();
if (!Files.exists(context.getProjectDirectory())) {
errors.add("Project directory does not exist: " + context.getProjectDirectory());
}
if (!hasBuildFile(context.getProjectDirectory())) {
errors.add("No build file (pom.xml, build.gradle) found in project directory");
}
if (context.getProjectName() == null || context.getProjectName().isEmpty()) {
errors.add("Project name is required");
}
if (context.getProjectVersion() == null || context.getProjectVersion().isEmpty()) {
errors.add("Project version is required");
}
if (!errors.isEmpty()) {
throw new SbomGenerationException("Project validation failed: " +
String.join(", ", errors));
}
}
private boolean hasBuildFile(Path projectDir) {
return Files.exists(projectDir.resolve("pom.xml")) ||
Files.exists(projectDir.resolve("build.gradle")) ||
Files.exists(projectDir.resolve("build.gradle.kts"));
}
}
Data Models and Configuration
package com.titliel.cyclonedx;
import java.nio.file.Path;
import java.util.*;
/**
* Project context for SBOM generation
*/
public class ProjectContext {
private final Path projectDirectory;
private final String projectName;
private final String projectVersion;
private final String groupId;
private final BuildTool buildTool;
private final Path outputDirectory;
private final String description;
private final List<ProjectAuthor> authors;
private final List<DependencyLicense> licenses;
private final String scmUrl;
private final String issueTrackerUrl;
private final String websiteUrl;
private final String javaVersion;
private final String packaging;
private final String encoding;
private final Map<String, String> properties;
public ProjectContext(Path projectDirectory, String projectName, String projectVersion) {
this.projectDirectory = projectDirectory;
this.projectName = projectName;
this.projectVersion = projectVersion;
this.groupId = "com.example";
this.buildTool = detectBuildTool(projectDirectory);
this.outputDirectory = projectDirectory.resolve("sbom");
this.description = "";
this.authors = new ArrayList<>();
this.licenses = new ArrayList<>();
this.properties = new HashMap<>();
this.javaVersion = System.getProperty("java.version");
this.packaging = "jar";
this.encoding = "UTF-8";
}
// Builder pattern
public static class Builder {
private Path projectDirectory;
private String projectName;
private String projectVersion;
private String groupId = "com.example";
private BuildTool buildTool;
private Path outputDirectory;
private String description = "";
private List<ProjectAuthor> authors = new ArrayList<>();
private List<DependencyLicense> licenses = new ArrayList<>();
private String scmUrl;
private String issueTrackerUrl;
private String websiteUrl;
private String javaVersion = System.getProperty("java.version");
private String packaging = "jar";
private String encoding = "UTF-8";
private Map<String, String> properties = new HashMap<>();
public Builder setProjectDirectory(Path projectDirectory) {
this.projectDirectory = projectDirectory;
return this;
}
public Builder setProjectName(String projectName) {
this.projectName = projectName;
return this;
}
public Builder setProjectVersion(String projectVersion) {
this.projectVersion = projectVersion;
return this;
}
public Builder setGroupId(String groupId) {
this.groupId = groupId;
return this;
}
public Builder setBuildTool(BuildTool buildTool) {
this.buildTool = buildTool;
return this;
}
public Builder setOutputDirectory(Path outputDirectory) {
this.outputDirectory = outputDirectory;
return this;
}
public Builder setDescription(String description) {
this.description = description;
return this;
}
public Builder addAuthor(ProjectAuthor author) {
this.authors.add(author);
return this;
}
public Builder addLicense(DependencyLicense license) {
this.licenses.add(license);
return this;
}
public Builder setScmUrl(String scmUrl) {
this.scmUrl = scmUrl;
return this;
}
public Builder setIssueTrackerUrl(String issueTrackerUrl) {
this.issueTrackerUrl = issueTrackerUrl;
return this;
}
public Builder setWebsiteUrl(String websiteUrl) {
this.websiteUrl = websiteUrl;
return this;
}
public Builder setJavaVersion(String javaVersion) {
this.javaVersion = javaVersion;
return this;
}
public Builder setPackaging(String packaging) {
this.packaging = packaging;
return this;
}
public Builder setEncoding(String encoding) {
this.encoding = encoding;
return this;
}
public Builder addProperty(String key, String value) {
this.properties.put(key, value);
return this;
}
public ProjectContext build() {
ProjectContext context = new ProjectContext(projectDirectory, projectName, projectVersion);
context.groupId = groupId;
context.buildTool = buildTool != null ? buildTool : context.buildTool;
context.outputDirectory = outputDirectory != null ? outputDirectory : context.outputDirectory;
context.description = description;
context.authors.addAll(authors);
context.licenses.addAll(licenses);
context.scmUrl = scmUrl;
context.issueTrackerUrl = issueTrackerUrl;
context.websiteUrl = websiteUrl;
context.javaVersion = javaVersion;
context.packaging = packaging;
context.encoding = encoding;
context.properties.putAll(properties);
return context;
}
}
private static BuildTool detectBuildTool(Path projectDirectory) {
if (Files.exists(projectDirectory.resolve("pom.xml"))) {
return BuildTool.MAVEN;
} else if (Files.exists(projectDirectory.resolve("build.gradle")) ||
Files.exists(projectDirectory.resolve("build.gradle.kts"))) {
return BuildTool.GRADLE;
}
return BuildTool.UNKNOWN;
}
// Getters
public Path getProjectDirectory() { return projectDirectory; }
public String getProjectName() { return projectName; }
public String getProjectVersion() { return projectVersion; }
public String getGroupId() { return groupId; }
public BuildTool getBuildTool() { return buildTool; }
public Path getOutputDirectory() { return outputDirectory; }
public String getDescription() { return description; }
public List<ProjectAuthor> getAuthors() { return Collections.unmodifiableList(authors); }
public List<DependencyLicense> getLicenses() { return Collections.unmodifiableList(licenses); }
public String getScmUrl() { return scmUrl; }
public String getIssueTrackerUrl() { return issueTrackerUrl; }
public String getWebsiteUrl() { return websiteUrl; }
public String getJavaVersion() { return javaVersion; }
public String getPackaging() { return packaging; }
public String getEncoding() { return encoding; }
public Map<String, String> getProperties() { return Collections.unmodifiableMap(properties); }
}
/**
* SBOM generation result
*/
public class SbomResult {
private final ProjectContext context;
private final List<ResolvedDependency> dependencies;
private final List<Path> generatedFiles;
private final List<Path> reports;
private final List<String> warnings;
private final Map<SbomFormat, Path> sbomFiles;
private final Map<ReportType, Path> reportFiles;
private ValidationResult validationResult;
private boolean success;
private String error;
private long generationTime;
public SbomResult(ProjectContext context) {
this.context = context;
this.dependencies = new ArrayList<>();
this.generatedFiles = new ArrayList<>();
this.reports = new ArrayList<>();
this.warnings = new ArrayList<>();
this.sbomFiles = new HashMap<>();
this.reportFiles = new HashMap<>();
}
// Add methods
public void addGeneratedFile(Path file, SbomFormat format) {
this.generatedFiles.add(file);
this.sbomFiles.put(format, file);
}
public void addReport(Path report, ReportType type) {
this.reports.add(report);
this.reportFiles.put(type, report);
}
public void addWarning(String warning) {
this.warnings.add(warning);
}
// Getters and setters
public ProjectContext getContext() { return context; }
public List<ResolvedDependency> getDependencies() { return Collections.unmodifiableList(dependencies); }
public void setDependencies(List<ResolvedDependency> dependencies) {
this.dependencies.clear();
this.dependencies.addAll(dependencies);
}
public List<Path> getGeneratedFiles() { return Collections.unmodifiableList(generatedFiles); }
public List<Path> getReports() { return Collections.unmodifiableList(reports); }
public List<String> getWarnings() { return Collections.unmodifiableList(warnings); }
public Map<SbomFormat, Path> getSbomFiles() { return Collections.unmodifiableMap(sbomFiles); }
public Map<ReportType, Path> getReportFiles() { return Collections.unmodifiableMap(reportFiles); }
public ValidationResult getValidationResult() { return validationResult; }
public void setValidationResult(ValidationResult validationResult) { this.validationResult = validationResult; }
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getError() { return error; }
public void setError(String error) { this.error = error; }
public long getGenerationTime() { return generationTime; }
public void setGenerationTime(long generationTime) { this.generationTime = generationTime; }
}
/**
* Resolved dependency information
*/
public class ResolvedDependency {
private final String groupId;
private final String artifactId;
private final String version;
private final String scope;
private final boolean optional;
private final String description;
private final List<DependencyLicense> licenses;
private final List<ResolvedDependency> transitiveDependencies;
private final DependencySupplier supplier;
private final boolean resolved;
public ResolvedDependency(String groupId, String artifactId, String version, String scope) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.scope = scope;
this.optional = false;
this.description = "";
this.licenses = new ArrayList<>();
this.transitiveDependencies = new ArrayList<>();
this.resolved = true;
}
// Getters
public String getGroupId() { return groupId; }
public String getArtifactId() { return artifactId; }
public String getVersion() { return version; }
public String getScope() { return scope; }
public boolean isOptional() { return optional; }
public String getDescription() { return description; }
public List<DependencyLicense> getLicenses() { return Collections.unmodifiableList(licenses); }
public List<ResolvedDependency> getTransitiveDependencies() { return Collections.unmodifiableList(transitiveDependencies); }
public DependencySupplier getSupplier() { return supplier; }
public boolean isResolved() { return resolved; }
// Builder methods
public ResolvedDependency withDescription(String description) {
this.description = description;
return this;
}
public ResolvedDependency withOptional(boolean optional) {
this.optional = optional;
return this;
}
public ResolvedDependency withSupplier(DependencySupplier supplier) {
this.supplier = supplier;
return this;
}
public ResolvedDependency addLicense(DependencyLicense license) {
this.licenses.add(license);
return this;
}
public ResolvedDependency addTransitiveDependency(ResolvedDependency dependency) {
this.transitiveDependencies.add(dependency);
return this;
}
}
/**
* Dependency license information
*/
public class DependencyLicense {
private final String id;
private final String name;
private final String url;
private final LicenseRiskLevel riskLevel;
private final LicenseApprovalStatus approvalStatus;
public DependencyLicense(String id, String name) {
this.id = id;
this.name = name;
this.url = "";
this.riskLevel = LicenseRiskLevel.UNKNOWN;
this.approvalStatus = LicenseApprovalStatus.UNREVIEWED;
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getUrl() { return url; }
public LicenseRiskLevel getRiskLevel() { return riskLevel; }
public LicenseApprovalStatus getApprovalStatus() { return approvalStatus; }
// Builder methods
public DependencyLicense withUrl(String url) {
this.url = url;
return this;
}
public DependencyLicense withRiskLevel(LicenseRiskLevel riskLevel) {
this.riskLevel = riskLevel;
return this;
}
public DependencyLicense withApprovalStatus(LicenseApprovalStatus approvalStatus) {
this.approvalStatus = approvalStatus;
return this;
}
}
/**
* Project author information
*/
public class ProjectAuthor {
private final String name;
private final String email;
public ProjectAuthor(String name, String email) {
this.name = name;
this.email = email;
}
// Getters
public String getName() { return name; }
public String getEmail() { return email; }
}
/**
* Dependency supplier information
*/
public class DependencySupplier {
private final String name;
private final String url;
public DependencySupplier(String name, String url) {
this.name = name;
this.url = url;
}
// Getters
public String getName() { return name; }
public String getUrl() { return url; }
}
/**
* Enums and constants
*/
public enum BuildTool {
MAVEN, GRADLE, SBT, UNKNOWN
}
public enum SbomFormat {
JSON, XML, PROTOBUF
}
public enum ReportType {
DEPENDENCY_TREE, LICENSE, VULNERABILITY, COMPLIANCE, SUMMARY
}
public enum LicenseRiskLevel {
HIGH, MEDIUM, LOW, UNKNOWN
}
public enum LicenseApprovalStatus {
APPROVED, REJECTED, UNREVIEWED, CONDITIONAL
}
/**
* Validation result for SBOM
*/
public class ValidationResult {
private final boolean valid;
private final List<String> errors;
private final List<String> warnings;
private final String schemaVersion;
public ValidationResult(boolean valid, String schemaVersion) {
this.valid = valid;
this.schemaVersion = schemaVersion;
this.errors = new ArrayList<>();
this.warnings = new ArrayList<>();
}
// Add methods
public void addError(String error) {
this.errors.add(error);
}
public void addWarning(String warning) {
this.warnings.add(warning);
}
// Getters
public boolean isValid() { return valid; }
public List<String> getErrors() { return Collections.unmodifiableList(errors); }
public List<String> getWarnings() { return Collections.unmodifiableList(warnings); }
public String getSchemaVersion() { return schemaVersion; }
}
class SbomGenerationException extends RuntimeException {
public SbomGenerationException(String message) {
super(message);
}
public SbomGenerationException(String message, Throwable cause) {
super(message, cause);
}
}
Dependency Resolution Engine
package com.titliel.cyclonedx;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* Advanced dependency resolver for multiple build tools
*/
public class DependencyResolver {
private final LicenseResolver licenseResolver;
private final TransitiveDependencyResolver transitiveResolver;
public DependencyResolver() {
this.licenseResolver = new LicenseResolver();
this.transitiveResolver = new TransitiveDependencyResolver();
}
/**
* Resolve dependencies for a project
*/
public List<ResolvedDependency> resolveDependencies(ProjectContext context) {
switch (context.getBuildTool()) {
case MAVEN:
return resolveMavenDependencies(context);
case GRADLE:
return resolveGradleDependencies(context);
default:
throw new UnsupportedOperationException(
"Unsupported build tool: " + context.getBuildTool());
}
}
/**
* Resolve Maven dependencies
*/
private List<ResolvedDependency> resolveMavenDependencies(ProjectContext context) {
List<ResolvedDependency> dependencies = new ArrayList<>();
try {
Path pomFile = context.getProjectDirectory().resolve("pom.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(pomFile.toFile());
// Parse direct dependencies
parseMavenDependencies(document, dependencies, context);
// Resolve transitive dependencies
resolveTransitiveDependencies(dependencies, context);
// Resolve licenses
resolveLicenses(dependencies);
} catch (Exception e) {
throw new RuntimeException("Failed to resolve Maven dependencies", e);
}
return dependencies;
}
/**
* Parse Maven dependencies from pom.xml
*/
private void parseMavenDependencies(Document document, List<ResolvedDependency> dependencies,
ProjectContext context) {
NodeList dependencyNodes = document.getElementsByTagName("dependency");
for (int i = 0; i < dependencyNodes.getLength(); i++) {
Element dependencyElement = (Element) dependencyNodes.item(i);
ResolvedDependency dependency = parseMavenDependency(dependencyElement);
if (dependency != null) {
dependencies.add(dependency);
}
}
// Parse dependency management
parseDependencyManagement(document, dependencies);
}
/**
* Parse individual Maven dependency
*/
private ResolvedDependency parseMavenDependency(Element dependencyElement) {
String groupId = getElementText(dependencyElement, "groupId");
String artifactId = getElementText(dependencyElement, "artifactId");
String version = getElementText(dependencyElement, "version");
String scope = getElementText(dependencyElement, "scope", "compile");
String optional = getElementText(dependencyElement, "optional", "false");
if (groupId == null || artifactId == null || version == null) {
return null;
}
ResolvedDependency dependency = new ResolvedDependency(groupId, artifactId, version, scope)
.withOptional(Boolean.parseBoolean(optional));
// Parse licenses
NodeList licenseNodes = dependencyElement.getElementsByTagName("license");
for (int i = 0; i < licenseNodes.getLength(); i++) {
Element licenseElement = (Element) licenseNodes.item(i);
DependencyLicense license = parseMavenLicense(licenseElement);
if (license != null) {
dependency.addLicense(license);
}
}
return dependency;
}
/**
* Parse Maven license information
*/
private DependencyLicense parseMavenLicense(Element licenseElement) {
String name = getElementText(licenseElement, "name");
String url = getElementText(licenseElement, "url");
if (name == null) {
return null;
}
String licenseId = inferLicenseId(name);
LicenseRiskLevel riskLevel = assessLicenseRisk(licenseId);
return new DependencyLicense(licenseId, name)
.withUrl(url)
.withRiskLevel(riskLevel);
}
/**
* Parse dependency management section
*/
private void parseDependencyManagement(Document document, List<ResolvedDependency> dependencies) {
// Implement dependency management parsing
// This would handle version inheritance from BOMs
}
/**
* Resolve Gradle dependencies
*/
private List<ResolvedDependency> resolveGradleDependencies(ProjectContext context) {
List<ResolvedDependency> dependencies = new ArrayList<>();
try {
// Execute gradle dependencies command
ProcessBuilder processBuilder = new ProcessBuilder(
"gradle", "dependencies", "--configuration", "compileClasspath");
processBuilder.directory(context.getProjectDirectory().toFile());
Process process = processBuilder.start();
String output = readProcessOutput(process);
dependencies = parseGradleDependencies(output);
// Resolve transitive dependencies
resolveTransitiveDependencies(dependencies, context);
// Resolve licenses
resolveLicenses(dependencies);
} catch (Exception e) {
// Fallback to parsing build.gradle directly
dependencies = parseGradleBuildFile(context.getProjectDirectory());
}
return dependencies;
}
/**
* Parse Gradle dependencies output
*/
private List<ResolvedDependency> parseGradleDependencies(String output) {
List<ResolvedDependency> dependencies = new ArrayList<>();
String[] lines = output.split("\n");
for (String line : lines) {
if (line.contains("---") && line.contains(":")) {
ResolvedDependency dependency = parseGradleDependencyLine(line);
if (dependency != null) {
dependencies.add(dependency);
}
}
}
return dependencies;
}
private ResolvedDependency parseGradleDependencyLine(String line) {
try {
String trimmed = line.trim();
if (trimmed.startsWith("|") || trimmed.startsWith("\\") || trimmed.startsWith("+")) {
trimmed = trimmed.substring(1).trim();
}
String[] parts = trimmed.split(":");
if (parts.length >= 3) {
String groupId = parts[0];
String artifactId = parts[1];
String version = parts[2].split(" ")[0].replace("(", "").replace(")", "");
return new ResolvedDependency(groupId, artifactId, version, "compile");
}
} catch (Exception e) {
// Skip malformed lines
}
return null;
}
/**
* Parse Gradle build file directly
*/
private List<ResolvedDependency> parseGradleBuildFile(Path projectDir) {
// Implement Gradle build file parsing
return new ArrayList<>();
}
/**
* Resolve transitive dependencies
*/
private void resolveTransitiveDependencies(List<ResolvedDependency> dependencies,
ProjectContext context) {
for (ResolvedDependency dependency : dependencies) {
List<ResolvedDependency> transitive =
transitiveResolver.resolveTransitiveDependencies(dependency, context);
dependency.getTransitiveDependencies().addAll(transitive);
}
}
/**
* Resolve licenses for dependencies
*/
private void resolveLicenses(List<ResolvedDependency> dependencies) {
for (ResolvedDependency dependency : dependencies) {
if (dependency.getLicenses().isEmpty()) {
List<DependencyLicense> licenses =
licenseResolver.resolveLicenses(dependency);
dependency.getLicenses().addAll(licenses);
}
}
}
// Utility methods
private String getElementText(Element parent, String tagName) {
return getElementText(parent, tagName, null);
}
private String getElementText(Element parent, String tagName, String defaultValue) {
NodeList nodes = parent.getElementsByTagName(tagName);
if (nodes.getLength() > 0) {
return nodes.item(0).getTextContent().trim();
}
return defaultValue;
}
private String readProcessOutput(Process process) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
return output.toString();
}
}
private String inferLicenseId(String licenseName) {
Map<String, String> licenseMap = new HashMap<>();
licenseMap.put("Apache License 2.0", "Apache-2.0");
licenseMap.put("MIT License", "MIT");
licenseMap.put("GNU General Public License v3.0", "GPL-3.0");
licenseMap.put("BSD 2-Clause License", "BSD-2-Clause");
licenseMap.put("BSD 3-Clause License", "BSD-3-Clause");
return licenseMap.getOrDefault(licenseName, "UNKNOWN");
}
private LicenseRiskLevel assessLicenseRisk(String licenseId) {
Map<String, LicenseRiskLevel> riskMap = new HashMap<>();
riskMap.put("GPL-3.0", LicenseRiskLevel.HIGH);
riskMap.put("AGPL-3.0", LicenseRiskLevel.HIGH);
riskMap.put("LGPL-3.0", LicenseRiskLevel.MEDIUM);
riskMap.put("Apache-2.0", LicenseRiskLevel.LOW);
riskMap.put("MIT", LicenseRiskLevel.LOW);
riskMap.put("BSD-2-Clause", LicenseRiskLevel.LOW);
riskMap.put("BSD-3-Clause", LicenseRiskLevel.LOW);
return riskMap.getOrDefault(licenseId, LicenseRiskLevel.UNKNOWN);
}
}
/**
* Transitive dependency resolver
*/
class TransitiveDependencyResolver {
public List<ResolvedDependency> resolveTransitiveDependencies(ResolvedDependency dependency,
ProjectContext context) {
List<ResolvedDependency> transitive = new ArrayList<>();
// In a real implementation, this would use Maven/Gradle APIs
// to resolve the complete dependency tree
// For now, return empty list as placeholder
return transitive;
}
}
/**
* License resolver
*/
class LicenseResolver {
public List<DependencyLicense> resolveLicenses(ResolvedDependency dependency) {
List<DependencyLicense> licenses = new ArrayList<>();
// In a real implementation, this would query license databases
// or use package registry APIs
// For now, return a placeholder license
DependencyLicense license = new DependencyLicense("UNKNOWN", "Unknown License")
.withRiskLevel(LicenseRiskLevel.UNKNOWN)
.withApprovalStatus(LicenseApprovalStatus.UNREVIEWED);
licenses.add(license);
return licenses;
}
}
Vulnerability Integration
package com.titliel.cyclonedx;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.vulnerability.Vulnerability;
import org.cyclonedx.model.vulnerability.VulnerabilitySource;
import org.cyclonedx.model.vulnerability.VulnerabilityAnalysis;
import org.cyclonedx.model.vulnerability.VulnerabilityAffectedStatus;
import org.cyclonedx.model.vulnerability.VulnerabilityRating;
import org.cyclonedx.model.vulnerability.VulnerabilityAdvisory;
import java.util.*;
/**
* Vulnerability integrator for SBOM enhancement
*/
public class VulnerabilityIntegrator {
private final VulnerabilityScanner vulnerabilityScanner;
private final VulnerabilityEnricher vulnerabilityEnricher;
public VulnerabilityIntegrator() {
this.vulnerabilityScanner = new VulnerabilityScanner();
this.vulnerabilityEnricher = new VulnerabilityEnricher();
}
/**
* Integrate vulnerability data into SBOM
*/
public Bom integrateVulnerabilities(Bom bom, List<ResolvedDependency> dependencies) {
List<Vulnerability> vulnerabilities = new ArrayList<>();
// Scan dependencies for vulnerabilities
for (ResolvedDependency dependency : dependencies) {
List<Vulnerability> componentVulns = scanDependencyVulnerabilities(dependency);
vulnerabilities.addAll(componentVulns);
}
// Add vulnerabilities to BOM
if (!vulnerabilities.isEmpty()) {
bom.setVulnerabilities(vulnerabilities);
}
return bom;
}
/**
* Scan a dependency for vulnerabilities
*/
private List<Vulnerability> scanDependencyVulnerabilities(ResolvedDependency dependency) {
List<Vulnerability> vulnerabilities = new ArrayList<>();
// Use vulnerability scanner to find vulnerabilities
List<DiscoveredVulnerability> discovered =
vulnerabilityScanner.scanDependency(dependency);
// Convert to CycloneDX vulnerabilities
for (DiscoveredVulnerability discoveredVuln : discovered) {
Vulnerability vulnerability = createVulnerability(discoveredVuln, dependency);
vulnerabilities.add(vulnerability);
}
return vulnerabilities;
}
/**
* Create CycloneDX vulnerability from discovered vulnerability
*/
private Vulnerability createVulnerability(DiscoveredVulnerability discoveredVuln,
ResolvedDependency dependency) {
Vulnerability vulnerability = new Vulnerability();
vulnerability.setId(discoveredVuln.getId());
vulnerability.setDescription(discoveredVuln.getDescription());
vulnerability.setDetail(discoveredVuln.getDetail());
vulnerability.setCreated(discoveredVuln.getCreated());
vulnerability.setPublished(discoveredVuln.getPublished());
vulnerability.setUpdated(discoveredVuln.getUpdated());
// Add source
VulnerabilitySource source = new VulnerabilitySource();
source.setName(discoveredVuln.getSource());
source.setUrl(discoveredVuln.getSourceUrl());
vulnerability.setSource(source);
// Add ratings
vulnerability.setRatings(createVulnerabilityRatings(discoveredVuln));
// Add advisories
vulnerability.setAdvisories(createVulnerabilityAdvisories(discoveredVuln));
// Add affected components
vulnerability.setAffects(createAffectedComponents(dependency, discoveredVuln));
// Add analysis
vulnerability.setAnalysis(createVulnerabilityAnalysis(discoveredVuln));
// Enrich with additional data
vulnerability = vulnerabilityEnricher.enrichVulnerability(vulnerability, discoveredVuln);
return vulnerability;
}
/**
* Create vulnerability ratings
*/
private List<VulnerabilityRating> createVulnerabilityRatings(DiscoveredVulnerability vuln) {
List<VulnerabilityRating> ratings = new ArrayList<>();
VulnerabilityRating rating = new VulnerabilityRating();
rating.setSource(createVulnerabilitySource(vuln));
rating.setScore(vuln.getCvssScore());
rating.setSeverity(convertSeverity(vuln.getSeverity()));
rating.setMethod(VulnerabilityRating.ScoreMethod.CVSSv3);
ratings.add(rating);
return ratings;
}
/**
* Create vulnerability source
*/
private VulnerabilitySource createVulnerabilitySource(DiscoveredVulnerability vuln) {
VulnerabilitySource source = new VulnerabilitySource();
source.setName(vuln.getSource());
source.setUrl(vuln.getSourceUrl());
return source;
}
/**
* Create vulnerability advisories
*/
private List<VulnerabilityAdvisory> createVulnerabilityAdvisories(DiscoveredVulnerability vuln) {
List<VulnerabilityAdvisory> advisories = new ArrayList<>();
VulnerabilityAdvisory advisory = new VulnerabilityAdvisory();
advisory.setTitle(vuln.getTitle());
advisory.setUrl(vuln.getAdvisoryUrl());
advisories.add(advisory);
return advisories;
}
/**
* Create affected components
*/
private List<org.cyclonedx.model.vulnerability.VulnerabilityAffects> createAffectedComponents(
ResolvedDependency dependency, DiscoveredVulnerability vuln) {
List<org.cyclonedx.model.vulnerability.VulnerabilityAffects> affects = new ArrayList<>();
org.cyclonedx.model.vulnerability.VulnerabilityAffects affect =
new org.cyclonedx.model.vulnerability.VulnerabilityAffects();
org.cyclonedx.model.vulnerability.VulnerabilityAffectedVersion version =
new org.cyclonedx.model.vulnerability.VulnerabilityAffectedVersion();
version.setVersion(dependency.getVersion());
version.setStatus(VulnerabilityAffectedStatus.AFFECTED);
org.cyclonedx.model.vulnerability.VulnerabilityAffectedVersions versions =
new org.cyclonedx.model.vulnerability.VulnerabilityAffectedVersions();
versions.addVersion(version);
affect.setRef(dependency.getGroupId() + ":" + dependency.getArtifactId());
affect.setVersions(versions);
affects.add(affect);
return affects;
}
/**
* Create vulnerability analysis
*/
private VulnerabilityAnalysis createVulnerabilityAnalysis(DiscoveredVulnerability vuln) {
VulnerabilityAnalysis analysis = new VulnerabilityAnalysis();
analysis.setState(VulnerabilityAnalysis.State.IN_TRIAGE);
analysis.setDetail(vuln.getAnalysisDetail());
return analysis;
}
/**
* Convert severity string to CycloneDX enum
*/
private VulnerabilityRating.Severity convertSeverity(String severity) {
if (severity == null) return null;
switch (severity.toUpperCase()) {
case "CRITICAL": return VulnerabilityRating.Severity.CRITICAL;
case "HIGH": return VulnerabilityRating.Severity.HIGH;
case "MEDIUM": return VulnerabilityRating.Severity.MEDIUM;
case "LOW": return VulnerabilityRating.Severity.LOW;
case "INFO": return VulnerabilityRating.Severity.INFO;
default: return VulnerabilityRating.Severity.UNKNOWN;
}
}
}
/**
* Discovered vulnerability representation
*/
class DiscoveredVulnerability {
private String id;
private String title;
private String description;
private String detail;
private String source;
private String sourceUrl;
private String advisoryUrl;
private double cvssScore;
private String severity;
private Date created;
private Date published;
private Date updated;
private String analysisDetail;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getDetail() { return detail; }
public void setDetail(String detail) { this.detail = detail; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public String getSourceUrl() { return sourceUrl; }
public void setSourceUrl(String sourceUrl) { this.sourceUrl = sourceUrl; }
public String getAdvisoryUrl() { return advisoryUrl; }
public void setAdvisoryUrl(String advisoryUrl) { this.advisoryUrl = advisoryUrl; }
public double getCvssScore() { return cvssScore; }
public void setCvssScore(double cvssScore) { this.cvssScore = cvssScore; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public Date getCreated() { return created; }
public void setCreated(Date created) { this.created = created; }
public Date getPublished() { return published; }
public void setPublished(Date published) { this.published = published; }
public Date getUpdated() { return updated; }
public void setUpdated(Date updated) { this.updated = updated; }
public String getAnalysisDetail() { return analysisDetail; }
public void setAnalysisDetail(String analysisDetail) { this.analysisDetail = analysisDetail; }
}
/**
* Vulnerability scanner
*/
class VulnerabilityScanner {
public List<DiscoveredVulnerability> scanDependency(ResolvedDependency dependency) {
List<DiscoveredVulnerability> vulnerabilities = new ArrayList<>();
// In a real implementation, this would integrate with vulnerability databases
// like NVD, OSS Index, Snyk, etc.
// For now, return empty list as placeholder
return vulnerabilities;
}
}
/**
* Vulnerability enricher
*/
class VulnerabilityEnricher {
public Vulnerability enrichVulnerability(Vulnerability vulnerability,
DiscoveredVulnerability discoveredVuln) {
// Add additional properties
vulnerability.addProperty("discoveryDate", new Date().toString());
vulnerability.addProperty("scanner", "Titliel Vulnerability Scanner");
return vulnerability;
}
}
Configuration Management
package com.titliel.cyclonedx;
import java.io.*;
import java.util.*;
/**
* SBOM configuration manager
*/
public class SbomConfig {
private static SbomConfig instance;
private final Properties properties;
private SbomConfig() {
this.properties = loadProperties();
}
public static synchronized SbomConfig getInstance() {
if (instance == null) {
instance = new SbomConfig();
}
return instance;
}
private Properties loadProperties() {
Properties props = new Properties();
// Load from system properties
props.putAll(System.getProperties());
// Load from configuration file
try (InputStream input = getClass().getClassLoader()
.getResourceAsStream("cyclonedx.properties")) {
if (input != null) {
props.load(input);
}
} catch (Exception e) {
// Use defaults
}
// Load from environment variables
loadFromEnvironment(props);
// Set defaults
setDefaults(props);
return props;
}
private void loadFromEnvironment(Properties props) {
// Check for environment variables
String schemaVersion = System.getenv("CYCLONEDX_SCHEMA_VERSION");
if (schemaVersion != null) {
props.setProperty("cyclonedx.schema.version", schemaVersion);
}
String outputFormats = System.getenv("CYCLONEDX_OUTPUT_FORMATS");
if (outputFormats != null) {
props.setProperty("cyclonedx.output.formats", outputFormats);
}
}
private void setDefaults(Properties props) {
props.setProperty("cyclonedx.schema.version", "1.4");
props.setProperty("cyclonedx.output.json.enabled", "true");
props.setProperty("cyclonedx.output.xml.enabled", "true");
props.setProperty("cyclonedx.output.protobuf.enabled", "false");
props.setProperty("cyclonedx.vulnerability.integration.enabled", "true");
props.setProperty("cyclonedx.report.dependency.tree.enabled", "true");
props.setProperty("cyclonedx.report.license.enabled", "true");
props.setProperty("cyclonedx.report.vulnerability.enabled", "true");
props.setProperty("cyclonedx.report.compliance.enabled", "false");
props.setProperty("cyclonedx.include.transitive.dependencies", "true");
props.setProperty("cyclonedx.include.optional.dependencies", "true");
props.setProperty("cyclonedx.include.test.dependencies", "false");
}
// Getters
public String getSchemaVersion() {
return properties.getProperty("cyclonedx.schema.version", "1.4");
}
public boolean isJsonOutputEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.output.json.enabled", "true"));
}
public boolean isXmlOutputEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.output.xml.enabled", "true"));
}
public boolean isProtobufOutputEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.output.protobuf.enabled", "false"));
}
public boolean isVulnerabilityIntegrationEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.vulnerability.integration.enabled", "true"));
}
public boolean isDependencyTreeEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.report.dependency.tree.enabled", "true"));
}
public boolean isLicenseReportEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.report.license.enabled", "true"));
}
public boolean isVulnerabilityReportEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.report.vulnerability.enabled", "true"));
}
public boolean isComplianceReportEnabled() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.report.compliance.enabled", "false"));
}
public boolean includeTransitiveDependencies() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.include.transitive.dependencies", "true"));
}
public boolean includeOptionalDependencies() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.include.optional.dependencies", "true"));
}
public boolean includeTestDependencies() {
return Boolean.parseBoolean(properties.getProperty("cyclonedx.include.test.dependencies", "false"));
}
public void setProperty(String key, String value) {
properties.setProperty(key, value);
}
}
Usage Examples
package com.titliel.examples;
import com.titliel.cyclonedx.TitlielCycloneDxPlugin;
import com.titliel.cyclonedx.ProjectContext;
import com.titliel.cyclonedx.SbomResult;
import com.titliel.cyclonedx.ProjectAuthor;
import com.titliel.cyclonedx.DependencyLicense;
import java.nio.file.Paths;
/**
* Example usage of Titliel CycloneDX Plugin
*/
public class CycloneDxExample {
public static void main(String[] args) {
// Create project context
ProjectContext context = new ProjectContext.Builder()
.setProjectDirectory(Paths.get("/path/to/your/project"))
.setProjectName("my-spring-boot-app")
.setProjectVersion("1.0.0")
.setGroupId("com.example")
.setDescription("A Spring Boot application for demonstration")
.addAuthor(new ProjectAuthor("John Doe", "[email protected]"))
.addLicense(new DependencyLicense("Apache-2.0", "Apache License 2.0")
.withUrl("https://www.apache.org/licenses/LICENSE-2.0"))
.setScmUrl("https://github.com/example/my-spring-boot-app")
.setIssueTrackerUrl("https://github.com/example/my-spring-boot-app/issues")
.setJavaVersion("11")
.addProperty("build.timestamp", String.valueOf(System.currentTimeMillis()))
.build();
// Create plugin instance
TitlielCycloneDxPlugin plugin = new TitlielCycloneDxPlugin();
// Generate SBOM
SbomResult result = plugin.generateSbom(context);
// Process results
processSbomResult(result);
}
private static void processSbomResult(SbomResult result) {
System.out.println("=== CycloneDX SBOM Generation Results ===");
System.out.println("Success: " + result.isSuccess());
System.out.println("Generation Time: " + result.getGenerationTime() + "ms");
System.out.println("Dependencies Resolved: " + result.getDependencies().size());
if (result.isSuccess()) {
System.out.println("Generated SBOM Files:");
result.getSbomFiles().forEach((format, path) ->
System.out.println(" - " + format + ": " + path));
System.out.println("Generated Reports:");
result.getReportFiles().forEach((type, path) ->
System.out.println(" - " + type + ": " + path));
// Validation results
if (result.getValidationResult() != null) {
System.out.println("SBOM Validation: " +
(result.getValidationResult().isValid() ? "PASS" : "FAIL"));
if (!result.getValidationResult().getErrors().isEmpty()) {
System.out.println("Validation Errors:");
result.getValidationResult().getErrors().forEach(error ->
System.out.println(" - " + error));
}
}
} else {
System.out.println("Error: " + result.getError());
}
// Warnings
if (!result.getWarnings().isEmpty()) {
System.out.println("Warnings:");
result.getWarnings().forEach(warning ->
System.out.println(" - " + warning));
}
}
}
/**
* Maven Plugin Integration Example
*/
@Mojo(name = "generate-sbom", defaultPhase = LifecyclePhase.PACKAGE)
public class CycloneDxMavenPlugin extends AbstractMojo {
@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project;
@Parameter(defaultValue = "${project.basedir}", required = true)
private File basedir;
@Parameter(defaultValue = "${project.build.directory}/sbom", required = true)
private File outputDirectory;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("Generating CycloneDX SBOM...");
// Create project context from Maven project
ProjectContext context = new ProjectContext.Builder()
.setProjectDirectory(basedir.toPath())
.setProjectName(project.getArtifactId())
.setProjectVersion(project.getVersion())
.setGroupId(project.getGroupId())
.setDescription(project.getDescription())
.setOutputDirectory(outputDirectory.toPath())
.setBuildTool(BuildTool.MAVEN)
.build();
// Generate SBOM
TitlielCycloneDxPlugin plugin = new TitlielCycloneDxPlugin();
SbomResult result = plugin.generateSbom(context);
if (result.isSuccess()) {
getLog().info("SBOM generated successfully");
result.getSbomFiles().forEach((format, path) ->
getLog().info("Generated: " + path));
} else {
getLog().error("SBOM generation failed: " + result.getError());
throw new MojoFailureException("SBOM generation failed");
}
}
}
/**
* CI/CD Integration Example
*/
class CICDIntegration {
public void generateSbomInPipeline(Path projectDir, String projectName, String version) {
ProjectContext context = new ProjectContext.Builder()
.setProjectDirectory(projectDir)
.setProjectName(projectName)
.setProjectVersion(version)
.build();
TitlielCycloneDxPlugin plugin = new TitlielCycloneDxPlugin();
SbomResult result = plugin.generateSbom(context);
if (result.isSuccess()) {
// Upload SBOM to artifact repository
uploadSbomToArtifactory(result);
// Submit to vulnerability scanning service
submitSbomForScanning(result);
// Store in SBOM repository
storeSbomInRepository(result);
} else {
throw new RuntimeException("SBOM generation failed: " + result.getError());
}
}
private void uploadSbomToArtifactory(SbomResult result) {
// Upload SBOM files to artifact repository
result.getSbomFiles().forEach((format, path) -> {
System.out.println("Uploading SBOM to artifactory: " + path);
// Implementation for artifact upload
});
}
private void submitSbomForScanning(SbomResult result) {
// Submit SBOM for vulnerability scanning
Path jsonSbom = result.getSbomFiles().get(SbomFormat.JSON);
if (jsonSbom != null) {
System.out.println("Submitting SBOM for vulnerability scanning: " + jsonSbom);
// Implementation for vulnerability scanning service integration
}
}
private void storeSbomInRepository(SbomResult result) {
// Store SBOM in dedicated SBOM repository
System.out.println("Storing SBOM in repository");
// Implementation for SBOM repository storage
}
}
/**
* Security Compliance Example
*/
class SecurityCompliance {
public ComplianceResult checkSbomCompliance(SbomResult result) {
ComplianceResult compliance = new ComplianceResult();
// Check for prohibited licenses
checkLicenseCompliance(result, compliance);
// Check for high-risk vulnerabilities
checkVulnerabilityCompliance(result, compliance);
// Check dependency freshness
checkDependencyFreshness(result, compliance);
return compliance;
}
private void checkLicenseCompliance(SbomResult result, ComplianceResult compliance) {
// Check for prohibited licenses (e.g., GPL, AGPL)
List<String> prohibitedLicenses = Arrays.asList("GPL-3.0", "AGPL-3.0");
for (ResolvedDependency dependency : result.getDependencies()) {
for (DependencyLicense license : dependency.getLicenses()) {
if (prohibitedLicenses.contains(license.getId())) {
compliance.addViolation(
"PROHIBITED_LICENSE",
"Dependency " + dependency.getGroupId() + ":" +
dependency.getArtifactId() + " uses prohibited license: " +
license.getName()
);
}
}
}
}
private void checkVulnerabilityCompliance(SbomResult result, ComplianceResult compliance) {
// Check for critical vulnerabilities
// This would require vulnerability data in the SBOM
}
private void checkDependencyFreshness(SbomResult result, ComplianceResult compliance) {
// Check if dependencies are reasonably up-to-date
// Implementation depends on how freshness is defined
}
}
class ComplianceResult {
private final List<ComplianceViolation> violations;
private boolean compliant;
public ComplianceResult() {
this.violations = new ArrayList<>();
this.compliant = true;
}
public void addViolation(String rule, String description) {
this.violations.add(new ComplianceViolation(rule, description));
this.compliant = false;
}
// Getters
public List<ComplianceViolation> getViolations() { return violations; }
public boolean isCompliant() { return compliant; }
}
class ComplianceViolation {
private final String rule;
private final String description;
public ComplianceViolation(String rule, String description) {
this.rule = rule;
this.description = description;
}
// Getters
public String getRule() { return rule; }
public String getDescription() { return description; }
}
Maven Dependencies
<dependencies> <!-- CycloneDX Core --> <dependency> <groupId>org.cyclonedx</groupId> <artifactId>cyclonedx-core-java</artifactId> <version>7.1.2</version> </dependency> <!-- Maven Plugin API --> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>3.8.6</version> <scope>provided</scope> </dependency> <!-- Maven Project --> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.0</version> </dependency> <!-- XML Processing --> <dependency> <groupId>javax.xml.parsers</groupId> <artifactId>jaxp-api</artifactId> <version>1.4.5</version> </dependency> </dependencies>
Best Practices
1. SBOM Generation Strategy
- Generate SBOM at build time
- Include all dependencies (direct and transitive)
- Update SBOM on every release
2. Vulnerability Integration
- Integrate with vulnerability databases
- Include vulnerability analysis in SBOM
- Track remediation status
3. License Compliance
- Detect and report license information
- Implement license policy enforcement
- Track license approval status
4. CI/CD Integration
- Automate SBOM generation in pipelines
- Store SBOMs as build artifacts
- Integrate with security scanning tools
5. SBOM Management
- Version SBOMs alongside software
- Maintain SBOM repository
- Implement SBOM validation
This Titliel CycloneDX implementation provides comprehensive SBOM generation with advanced features like vulnerability integration, license compliance checking, and automated SBOM management for enterprise Java applications.
Secure Java Supply Chain, Minimal Containers & Runtime Security (Alpine, Distroless, Signing, SBOM & Kubernetes Controls)
https://macronepal.com/blog/alpine-linux-security-in-java-complete-guide/
Explains how Alpine Linux is used as a lightweight base for Java containers to reduce image size and attack surface, while discussing tradeoffs like musl compatibility, CVE handling, and additional hardening requirements for production security.
https://macronepal.com/blog/the-minimalists-approach-building-ultra-secure-java-applications-with-scratch-base-images/
Explains using scratch base images for Java applications to create extremely minimal containers with almost zero attack surface, where only the compiled Java application and runtime dependencies exist.
https://macronepal.com/blog/distroless-containers-in-java-minimal-secure-containers-for-jvm-applications/
Explains distroless Java containers that remove shells, package managers, and unnecessary OS tools, significantly reducing vulnerabilities while improving security posture for JVM workloads.
https://macronepal.com/blog/revolutionizing-container-security-implementing-chainguard-images-for-java-applications/
Explains Chainguard images for Java, which are secure-by-default, CVE-minimized container images with SBOMs and cryptographic signing, designed for modern supply-chain security.
https://macronepal.com/blog/seccomp-filtering-in-java-comprehensive-security-sandboxing/
Explains seccomp syscall filtering in Linux to restrict what system calls Java applications can make, reducing the impact of exploits by limiting kernel-level access.
https://macronepal.com/blog/in-toto-attestations-in-java/
Explains in-toto framework integration in Java to create cryptographically verifiable attestations across the software supply chain, ensuring every build step is trusted and auditable.
https://macronepal.com/blog/fulcio-integration-in-java-code-signing-certificate-infrastructure/
Explains Fulcio integration for Java, which issues short-lived certificates for code signing in a zero-trust supply chain, enabling secure identity-based signing of artifacts.
https://macronepal.com/blog/tekton-supply-chain-in-java-comprehensive-ci-cd-pipeline-implementation/
Explains using Tekton CI/CD pipelines for Java applications to automate secure builds, testing, signing, and deployment with supply-chain security controls built in.
https://macronepal.com/blog/slsa-provenance-in-java-complete-guide-to-supply-chain-security-2/
Explains SLSA (Supply-chain Levels for Software Artifacts) provenance in Java builds, ensuring traceability of how software is built, from source code to final container image.
https://macronepal.com/blog/notary-project-in-java-complete-implementation-guide/
Explains the Notary Project for Java container security, enabling cryptographic signing and verification of container images and artifacts to prevent tampering in deployment pipelines.