public static string BomSerializer(CycloneDX.Models.v1_2.Bom bom, BomFormat format) { if (format == BomFormat.Json || format == BomFormat.Json_v1_2) { return(JsonBomSerializer.Serialize(bom)); } else if (format == BomFormat.Xml || format == BomFormat.Xml_v1_2) { return(XmlBomSerializer.Serialize(bom)); } else if (format == BomFormat.Xml_v1_1) { return(XmlBomSerializer.Serialize(new CycloneDX.Models.v1_1.Bom(bom))); } else if (format == BomFormat.Xml_v1_0) { var v1_1_bom = new CycloneDX.Models.v1_1.Bom(bom); return(XmlBomSerializer.Serialize(new CycloneDX.Models.v1_0.Bom(v1_1_bom))); } else if (format == BomFormat.SpdxTag || format == BomFormat.SpdxTag_v2_2) { return(SpdxTagSerializer.Serialize(bom, SpdxVersion.v2_2)); } else if (format == BomFormat.SpdxTag_v2_1) { return(SpdxTagSerializer.Serialize(bom, SpdxVersion.v2_1)); } else if (format == BomFormat.Csv) { return(CsvSerializer.Serialize(bom)); } throw new UnsupportedFormatException("Unsupported SBOM file format"); }
public static string Serialize(CycloneDX.Models.v1_2.Bom bom, SpdxVersion version) { if (bom.Metadata?.Component?.Name == null || bom.Metadata?.Component?.Version == null) { throw new SpdxSerializationException("For SPDX output top level component name and version are required in the BOM metadata"); } var nonSpdxLicenses = new List <CycloneDX.Models.v1_2.License>(); string bomSpdxRef; if (string.IsNullOrEmpty(bom.SerialNumber)) { bomSpdxRef = System.Guid.NewGuid().ToString(); } else if (bom.SerialNumber.StartsWith("urn:uuid:")) { bomSpdxRef = bom.SerialNumber.Remove(0, 9); } else { bomSpdxRef = bom.SerialNumber; } var sb = new StringBuilder(); var componentSb = new StringBuilder(); sb.Append("SPDXVersion: SPDX-"); if (version == SpdxVersion.v2_1) { sb.Append("2.1"); } else if (version == SpdxVersion.v2_2) { sb.Append("2.2"); } // CC0-1.0 is a requirement when using the SPDX specification sb.AppendLine("DataLicense: CC0-1.0"); sb.AppendLine($"SPDXID: SPDXRef-DOCUMENT"); sb.AppendLine($"DocumentName: {bom.Metadata.Component.Name}-{bom.Metadata.Component.Version}"); sb.AppendLine($"DocumentNamespace: http://spdx.org/spdxdocs/{bom.Metadata.Component.Name}-{bom.Metadata.Component.Version}-{bomSpdxRef}"); if (bom.Metadata.Authors != null) { foreach (var author in bom.Metadata.Authors) { sb.AppendLine($"Creator: Person: {author.Name} ({author.Email ?? ""})"); } } sb.AppendLine("Creator: Tool: CycloneDX-CLI"); sb.AppendLine($"Created: {DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")}"); if (bom.Components != null) { for (var componentIndex = 0; componentIndex < bom.Components.Count; componentIndex++) { var component = bom.Components[componentIndex]; string componentSpdxRef; if (string.IsNullOrEmpty(component.BomRef)) { componentSpdxRef = (componentIndex + 1).ToString(); } else { componentSpdxRef = component.BomRef; } sb.AppendLine(); sb.AppendLine($"PackageName: {component.Name}"); sb.AppendLine($"SPDXID: SPDXRef-{componentSpdxRef}"); sb.AppendLine($"PackageVersion: {component.Version}"); if (component.Supplier != null) { sb.AppendLine($"PackageSupplier: Organization: {component.Supplier.Name} ()"); } if (component.Author != null) { sb.AppendLine($"PackageOriginator: Person: {component.Author} ()"); } else if (component.Publisher != null) { sb.AppendLine($"PackageOriginator: Person: {component.Publisher} ()"); } sb.AppendLine("PackageDownloadLocation: NOASSERTION"); sb.AppendLine("FilesAnalyzed: false"); if (component.Hashes != null) { foreach (var hash in component.Hashes) { string algStr = null; switch (hash.Alg) { case CycloneDX.Models.v1_2.Hash.HashAlgorithm.SHA_1: algStr = "SHA1"; break; case CycloneDX.Models.v1_2.Hash.HashAlgorithm.SHA_256: algStr = "SHA256"; break; } if (version == SpdxVersion.v2_2) { switch (hash.Alg) { case CycloneDX.Models.v1_2.Hash.HashAlgorithm.SHA_384: algStr = "SHA384"; break; case CycloneDX.Models.v1_2.Hash.HashAlgorithm.SHA_512: algStr = "SHA512"; break; } } if (algStr != null) { sb.AppendLine($"PackageChecksum: {algStr}: {hash.Content}"); } } } sb.AppendLine("PackageLicenseConcluded: NOASSERTION"); if (component.Licenses?.Count > 0) { sb.Append("PackageLicenseDeclared: "); if (component.Licenses.Count > 1) { sb.Append("("); } for (var licenseIndex = 0; licenseIndex < component.Licenses.Count; licenseIndex++) { var componentLicense = component.Licenses[licenseIndex]; if (licenseIndex != 0) { sb.Append(" AND "); } if (componentLicense.License != null) { if (!string.IsNullOrEmpty(componentLicense.License.Id)) { sb.Append(componentLicense.License.Id); } else { nonSpdxLicenses.Add(componentLicense.License); sb.Append($"LicenseRef-{nonSpdxLicenses.Count}"); } } else { sb.Append($"({componentLicense.Expression})"); } } if (component.Licenses.Count > 1) { sb.Append(")"); } sb.AppendLine(); } else { sb.AppendLine("PackageLicenseDeclared: NOASSERTION"); } if (string.IsNullOrEmpty(component.Copyright)) { sb.AppendLine("PackageCopyrightText: NOASSERTION"); } else { sb.AppendLine($"PackageCopyrightText: <text>{WebUtility.HtmlEncode(component.Copyright)}</text>"); } if (!string.IsNullOrEmpty(component.Purl)) { sb.AppendLine($"External-Ref: PACKAGE-MANAGER purl {component.Purl}"); } if (!string.IsNullOrEmpty(component.Cpe)) { if (component.Cpe.ToLowerInvariant().StartsWith("cpe:2.2:")) { sb.Append("ExternalRef: SECURITY cpe22Type "); sb.AppendLine(component.Cpe); } else if (component.Cpe.ToLowerInvariant().StartsWith("cpe:2.3:")) { sb.Append("ExternalRef: SECURITY cpe23Type "); sb.AppendLine(component.Cpe); } } } } for (var licenseIndex = 0; licenseIndex < nonSpdxLicenses.Count; licenseIndex++) { sb.AppendLine(); var license = nonSpdxLicenses[licenseIndex]; sb.AppendLine($"LicenseID: LicenseRef-{licenseIndex+1}"); if (license.Text != null && !string.IsNullOrEmpty(license.Text.Content)) { sb.AppendLine($"ExtractedText: <text>\"{license.Name}\": {WebUtility.HtmlEncode(license.Text.Content)}</text>"); } sb.AppendLine($"LicenseName: {license.Name}"); if (!string.IsNullOrEmpty(license.Url)) { sb.AppendLine($"LicenseCrossReference: {license.Url}"); } } return(sb.ToString()); }