|
19 | 19 | using System.Collections.Generic; |
20 | 20 | using System.Linq; |
21 | 21 | using System.Security.Claims; |
| 22 | +using CycloneDX.Core.Models; |
22 | 23 | using CycloneDX.Models; |
23 | 24 | using CycloneDX.Models.Vulnerabilities; |
24 | 25 | using static CycloneDX.Models.EvidenceIdentity; |
@@ -64,6 +65,15 @@ internal static Bom CopyBomAndDowngrade(Bom bom) |
64 | 65 | { |
65 | 66 | var bomCopy = bom.Copy(); |
66 | 67 |
|
| 68 | + // The protobuf deep copy does not preserve Signature/XmlSignature properties. |
| 69 | + // Signatories require either a "signature" or "organization"+"externalReference" |
| 70 | + // per the JSON schema oneOf constraint. Remove any that are now invalid. |
| 71 | + if (bomCopy.Declarations?.Affirmation?.Signatories != null) |
| 72 | + { |
| 73 | + bomCopy.Declarations.Affirmation.Signatories.RemoveAll(s => |
| 74 | + s.Signature == null && (s.Organization == null || s.ExternalReference == null)); |
| 75 | + } |
| 76 | + |
67 | 77 | // we downgrade stuff starting with lowest spec first |
68 | 78 | // this will remove entire classes of things and will save unnecessary processing further down |
69 | 79 | if (bomCopy.SpecVersion < SpecificationVersion.v1_1) |
@@ -349,6 +359,89 @@ internal static Bom CopyBomAndDowngrade(Bom bom) |
349 | 359 |
|
350 | 360 | } |
351 | 361 |
|
| 362 | + if (bomCopy.SpecVersion < SpecificationVersion.v1_7) |
| 363 | + { |
| 364 | + bomCopy.Citations = null; |
| 365 | + |
| 366 | + if (bomCopy.Metadata != null) |
| 367 | + { |
| 368 | + bomCopy.Metadata.DistributionConstraints = null; |
| 369 | + } |
| 370 | + |
| 371 | + if (bomCopy.Definitions != null) |
| 372 | + { |
| 373 | + bomCopy.Definitions.Patents = null; |
| 374 | + } |
| 375 | + |
| 376 | + EnumerateAllComponents(bomCopy, (component) => |
| 377 | + { |
| 378 | + component.VersionRange = null; |
| 379 | + component.IsExternal = null; |
| 380 | + component.PatentAssertions = null; |
| 381 | + |
| 382 | + if (component.CryptoProperties?.CertificateProperties != null) |
| 383 | + { |
| 384 | + var certProps = component.CryptoProperties.CertificateProperties; |
| 385 | + certProps.SerialNumber = null; |
| 386 | + certProps.CertificateFileExtension = null; |
| 387 | + certProps.Fingerprint = null; |
| 388 | + certProps.CertificateStates = null; |
| 389 | + certProps.CreationDate = null; |
| 390 | + certProps.ActivationDate = null; |
| 391 | + certProps.DeactivationDate = null; |
| 392 | + certProps.RevocationDate = null; |
| 393 | + certProps.DestructionDate = null; |
| 394 | + certProps.CertificateExtensions = null; |
| 395 | + certProps.RelatedCryptographicAssets = null; |
| 396 | + } |
| 397 | + |
| 398 | + if (component.CryptoProperties?.RelatedCryptoMaterialProperties != null) |
| 399 | + { |
| 400 | + var rcmProps = component.CryptoProperties.RelatedCryptoMaterialProperties; |
| 401 | + rcmProps.Fingerprint = null; |
| 402 | + rcmProps.RelatedCryptographicAssets = null; |
| 403 | + } |
| 404 | + |
| 405 | + if (component.CryptoProperties?.ProtocolProperties != null) |
| 406 | + { |
| 407 | + var protoProps = component.CryptoProperties.ProtocolProperties; |
| 408 | + protoProps.RelatedCryptographicAssets = null; |
| 409 | + // Downgrade detailed IKEv2 transform types to null (v1.6 doesn't support the detailed format) |
| 410 | + protoProps.Ikev2TransformTypes = null; |
| 411 | + } |
| 412 | + }); |
| 413 | + |
| 414 | + EnumerateAllServices(bomCopy, (service) => |
| 415 | + { |
| 416 | + service.PatentAssertions = null; |
| 417 | + }); |
| 418 | + |
| 419 | + EnumerateAllLicenseChoices(bomCopy, (licenseChoice) => |
| 420 | + { |
| 421 | + licenseChoice.ExpressionDetails = null; |
| 422 | + licenseChoice.Licensing = null; |
| 423 | + licenseChoice.Properties = null; |
| 424 | + }); |
| 425 | + |
| 426 | + // v1.6 xs:choice only allows EITHER licenses OR an expression, not both. |
| 427 | + // v1.7 allows unbounded mixing. Strip expressions when mixed with licenses. |
| 428 | + DowngradeMixedLicenseChoiceLists(bomCopy); |
| 429 | + |
| 430 | + EnumerateAllExternalReferences(bomCopy, (externalReference) => |
| 431 | + { |
| 432 | + if (externalReference != null) |
| 433 | + { |
| 434 | + if (externalReference.Type == ExternalReference.ExternalReferenceType.Patent |
| 435 | + || externalReference.Type == ExternalReference.ExternalReferenceType.Patent_Family |
| 436 | + || externalReference.Type == ExternalReference.ExternalReferenceType.Patent_Assertion |
| 437 | + || externalReference.Type == ExternalReference.ExternalReferenceType.Citation) |
| 438 | + { |
| 439 | + externalReference.Type = ExternalReference.ExternalReferenceType.Other; |
| 440 | + } |
| 441 | + } |
| 442 | + }); |
| 443 | + } |
| 444 | + |
352 | 445 | // triggers a bunch of stuff, don't remove unless you know what you are doing |
353 | 446 | bomCopy.SpecVersion = bomCopy.SpecVersion; |
354 | 447 |
|
@@ -464,6 +557,55 @@ public static void EnumerateAllLicenses(Bom bom, Action<License> callback) |
464 | 557 | }); |
465 | 558 | } |
466 | 559 |
|
| 560 | + private static void DowngradeMixedLicenseChoiceList(List<LicenseChoice> licenses) |
| 561 | + { |
| 562 | + if (licenses == null || licenses.Count <= 1) { return; } |
| 563 | + // v1.6 xs:choice only allows EITHER multiple licenses OR one expression. |
| 564 | + // When mixed, keep licenses and drop expressions. |
| 565 | + // When multiple expressions, keep only the first. |
| 566 | + bool hasLicense = false; |
| 567 | + bool hasExpression = false; |
| 568 | + foreach (var lc in licenses) |
| 569 | + { |
| 570 | + if (lc.License != null) { hasLicense = true; } |
| 571 | + if (lc.Expression != null) { hasExpression = true; } |
| 572 | + } |
| 573 | + if (hasLicense && hasExpression) |
| 574 | + { |
| 575 | + licenses.RemoveAll(lc => lc.Expression != null && lc.License == null); |
| 576 | + } |
| 577 | + else if (hasExpression) |
| 578 | + { |
| 579 | + // Keep only the first expression |
| 580 | + bool first = true; |
| 581 | + licenses.RemoveAll(lc => |
| 582 | + { |
| 583 | + if (lc.Expression != null) |
| 584 | + { |
| 585 | + if (first) { first = false; return false; } |
| 586 | + return true; |
| 587 | + } |
| 588 | + return false; |
| 589 | + }); |
| 590 | + } |
| 591 | + } |
| 592 | + |
| 593 | + private static void DowngradeMixedLicenseChoiceLists(Bom bom) |
| 594 | + { |
| 595 | + if (bom.Metadata?.Licenses != null) |
| 596 | + { |
| 597 | + DowngradeMixedLicenseChoiceList(bom.Metadata.Licenses); |
| 598 | + } |
| 599 | + EnumerateAllComponents(bom, (component) => |
| 600 | + { |
| 601 | + DowngradeMixedLicenseChoiceList(component.Licenses); |
| 602 | + }); |
| 603 | + EnumerateAllServices(bom, (service) => |
| 604 | + { |
| 605 | + DowngradeMixedLicenseChoiceList(service.Licenses); |
| 606 | + }); |
| 607 | + } |
| 608 | + |
467 | 609 | public static void EnumerateAllLicenseChoices(Bom bom, Action<LicenseChoice> callback) |
468 | 610 | { |
469 | 611 | if (bom.Metadata?.Licenses != null) |
@@ -757,6 +899,27 @@ public static void EnumerateAllExternalReferences(Bom bom, Action<ExternalRefere |
757 | 899 | } |
758 | 900 | } |
759 | 901 |
|
| 902 | + if (bom.Definitions?.Patents != null) |
| 903 | + { |
| 904 | + foreach (var patentOrFamily in bom.Definitions.Patents) |
| 905 | + { |
| 906 | + if (patentOrFamily?.Patent?.ExternalReferences != null) |
| 907 | + { |
| 908 | + foreach (var item in patentOrFamily.Patent.ExternalReferences) |
| 909 | + { |
| 910 | + callback(item); |
| 911 | + } |
| 912 | + } |
| 913 | + if (patentOrFamily?.PatentFamily?.ExternalReferences != null) |
| 914 | + { |
| 915 | + foreach (var item in patentOrFamily.PatentFamily.ExternalReferences) |
| 916 | + { |
| 917 | + callback(item); |
| 918 | + } |
| 919 | + } |
| 920 | + } |
| 921 | + } |
| 922 | + |
760 | 923 | EnumerateAllResourceReferenceChoices(bom, (resoureReferenceChoice) => |
761 | 924 | { |
762 | 925 | if (resoureReferenceChoice?.ExternalReference != null) |
|
0 commit comments