Skip to content

Commit 701b229

Browse files
authored
Merge pull request #420 from CycloneDX/v1.7
Initial implementation of v1.7
2 parents 98921ea + a91704a commit 701b229

File tree

796 files changed

+75548
-776
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

796 files changed

+75548
-776
lines changed

.codacy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
exclude_paths:
3+
- "tests/**/Resources/**"

src/CycloneDX.Core/BomUtils.cs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using System.Collections.Generic;
2020
using System.Linq;
2121
using System.Security.Claims;
22+
using CycloneDX.Core.Models;
2223
using CycloneDX.Models;
2324
using CycloneDX.Models.Vulnerabilities;
2425
using static CycloneDX.Models.EvidenceIdentity;
@@ -64,6 +65,15 @@ internal static Bom CopyBomAndDowngrade(Bom bom)
6465
{
6566
var bomCopy = bom.Copy();
6667

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+
6777
// we downgrade stuff starting with lowest spec first
6878
// this will remove entire classes of things and will save unnecessary processing further down
6979
if (bomCopy.SpecVersion < SpecificationVersion.v1_1)
@@ -349,6 +359,89 @@ internal static Bom CopyBomAndDowngrade(Bom bom)
349359

350360
}
351361

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+
352445
// triggers a bunch of stuff, don't remove unless you know what you are doing
353446
bomCopy.SpecVersion = bomCopy.SpecVersion;
354447

@@ -464,6 +557,55 @@ public static void EnumerateAllLicenses(Bom bom, Action<License> callback)
464557
});
465558
}
466559

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+
467609
public static void EnumerateAllLicenseChoices(Bom bom, Action<LicenseChoice> callback)
468610
{
469611
if (bom.Metadata?.Licenses != null)
@@ -757,6 +899,27 @@ public static void EnumerateAllExternalReferences(Bom bom, Action<ExternalRefere
757899
}
758900
}
759901

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+
760923
EnumerateAllResourceReferenceChoices(bom, (resoureReferenceChoice) =>
761924
{
762925
if (resoureReferenceChoice?.ExternalReference != null)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// This file is part of CycloneDX Library for .NET
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// SPDX-License-Identifier: Apache-2.0
16+
// Copyright (c) OWASP Foundation. All Rights Reserved.
17+
18+
using System;
19+
using System.Diagnostics.Contracts;
20+
using System.Text.Json;
21+
using System.Text.Json.Serialization;
22+
using CycloneDX.Models;
23+
24+
namespace CycloneDX.Json.Converters
25+
{
26+
public class AsserterConverter : JsonConverter<Asserter>
27+
{
28+
public override Asserter Read(
29+
ref Utf8JsonReader reader,
30+
Type typeToConvert,
31+
JsonSerializerOptions options)
32+
{
33+
if (reader.TokenType == JsonTokenType.Null)
34+
{
35+
return null;
36+
}
37+
else if (reader.TokenType == JsonTokenType.String)
38+
{
39+
return new Asserter { Ref = reader.GetString() };
40+
}
41+
else if (reader.TokenType == JsonTokenType.StartObject)
42+
{
43+
var doc = JsonDocument.ParseValue(ref reader);
44+
// Discriminate: organizationalEntity has "url" or "contact" arrays
45+
// organizationalContact has "email" or "phone" but not "url" array or "contact" array
46+
if (doc.RootElement.TryGetProperty("url", out _) || doc.RootElement.TryGetProperty("contact", out _))
47+
{
48+
var org = doc.Deserialize<OrganizationalEntity>(options);
49+
return new Asserter { Organization = org };
50+
}
51+
else
52+
{
53+
var individual = doc.Deserialize<OrganizationalContact>(options);
54+
return new Asserter { Individual = individual };
55+
}
56+
}
57+
else
58+
{
59+
throw new JsonException();
60+
}
61+
}
62+
63+
public override void Write(
64+
Utf8JsonWriter writer,
65+
Asserter value,
66+
JsonSerializerOptions options)
67+
{
68+
Contract.Requires(writer != null);
69+
70+
if (value == null)
71+
{
72+
writer.WriteNullValue();
73+
}
74+
else if (value.Ref != null)
75+
{
76+
writer.WriteStringValue(value.Ref);
77+
}
78+
else if (value.Organization != null)
79+
{
80+
JsonSerializer.Serialize(writer, value.Organization, options);
81+
}
82+
else if (value.Individual != null)
83+
{
84+
JsonSerializer.Serialize(writer, value.Individual, options);
85+
}
86+
else
87+
{
88+
writer.WriteNullValue();
89+
}
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)