forked from microsoft/component-detection
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSpdx22ComponentDetector.cs
More file actions
169 lines (144 loc) · 7.13 KB
/
Spdx22ComponentDetector.cs
File metadata and controls
169 lines (144 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#nullable disable
namespace Microsoft.ComponentDetection.Detectors.Spdx;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.Extensions.Logging;
/// <summary>
/// Spdx22ComponentDetector discover SPDX SBOM files in JSON format and create components with the information about
/// what SPDX document describes.
/// </summary>
public class Spdx22ComponentDetector : FileComponentDetector, IDefaultOffComponentDetector
{
private readonly IEnumerable<string> supportedSPDXVersions = ["SPDX-2.2"];
public Spdx22ComponentDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
ILogger<Spdx22ComponentDetector> logger)
{
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.Logger = logger;
}
public override IEnumerable<string> Categories =>
[Enum.GetName(typeof(DetectorClass), DetectorClass.Spdx)];
public override string Id => "SPDX22SBOM";
public override IEnumerable<ComponentType> SupportedComponentTypes { get; } = [ComponentType.Spdx];
public override int Version => 1;
public override IList<string> SearchPatterns => ["*.spdx.json"];
protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default)
{
this.Logger.LogDebug("Discovered SPDX2.2 manifest file at: {ManifestLocation}", processRequest.ComponentStream.Location);
var file = processRequest.ComponentStream;
try
{
var hash = this.GetSHA1HashFromStream(file.Stream);
// Reset buffer to starting position after hash generation.
file.Stream.Seek(0, SeekOrigin.Begin);
try
{
using var document = await JsonDocument.ParseAsync(file.Stream, cancellationToken: cancellationToken).ConfigureAwait(false);
var root = document.RootElement;
if (this.IsSPDXVersionSupported(root))
{
var sbomComponent = this.ConvertJsonElementToSbomComponent(processRequest, root, hash);
processRequest.SingleFileComponentRecorder.RegisterUsage(new DetectedComponent(sbomComponent));
}
else
{
this.Logger.LogWarning("Discovered SPDX at {ManifestLocation} is not SPDX-2.2 document, skipping", processRequest.ComponentStream.Location);
}
}
catch (JsonException)
{
this.Logger.LogWarning("Unable to parse file at {ManifestLocation}, skipping", processRequest.ComponentStream.Location);
}
}
catch (Exception e)
{
this.Logger.LogError(e, "Error while processing SPDX file at {ManifestLocation}", processRequest.ComponentStream.Location);
}
}
private bool IsSPDXVersionSupported(JsonElement document)
{
if (document.TryGetProperty("spdxVersion", out var versionElement))
{
var version = versionElement.GetString();
return this.supportedSPDXVersions.Contains(version, StringComparer.OrdinalIgnoreCase);
}
return false;
}
private SpdxComponent ConvertJsonElementToSbomComponent(ProcessRequest processRequest, JsonElement document, string fileHash)
{
var sbomNamespace = document.TryGetProperty("documentNamespace", out var nsElement) ? nsElement.GetString() : null;
var name = document.TryGetProperty("name", out var nameElement) ? nameElement.GetString() : null;
var spdxVersion = document.TryGetProperty("spdxVersion", out var spdxVersionElement) ? spdxVersionElement.GetString() : null;
string[] rootElements = null;
if (document.TryGetProperty("documentDescribes", out var describesElement) && describesElement.ValueKind == JsonValueKind.Array)
{
rootElements = describesElement.EnumerateArray()
.Select(e => e.GetString())
.Where(s => s != null)
.ToArray();
}
if (rootElements is { Length: > 1 })
{
this.Logger.LogWarning("SPDX file at {ManifestLocation} has more than one element in documentDescribes, first will be selected as root element.", processRequest.ComponentStream.Location);
}
if (rootElements is { Length: 0 })
{
this.Logger.LogWarning("SPDX file at {ManifestLocation} does not have root elements in documentDescribes section, considering SPDXRef-Document as a root element.", processRequest.ComponentStream.Location);
}
var rootElementId = rootElements?.FirstOrDefault() ?? "SPDXRef-Document";
var path = processRequest.ComponentStream.Location;
var component = new SpdxComponent(spdxVersion, new Uri(sbomNamespace), name, fileHash, rootElementId, path);
if (document.TryGetProperty("creationInfo", out var creationInfoElement)
&& creationInfoElement.TryGetProperty("creators", out var creatorsElement)
&& creatorsElement.ValueKind == JsonValueKind.Array)
{
foreach (var creator in creatorsElement.EnumerateArray())
{
if (creator.ValueKind != JsonValueKind.String)
{
continue;
}
var creatorString = creator.GetString();
if (creatorString == null)
{
continue;
}
if (component.CreatorTool == null && creatorString.StartsWith("Tool: ", StringComparison.Ordinal))
{
var tool = creatorString["Tool: ".Length..].Trim();
if (!string.IsNullOrWhiteSpace(tool))
{
component.CreatorTool = tool;
}
}
else if (component.CreatorOrganization == null && creatorString.StartsWith("Organization: ", StringComparison.Ordinal))
{
var org = creatorString["Organization: ".Length..].Trim();
if (!string.IsNullOrWhiteSpace(org))
{
component.CreatorOrganization = org;
}
}
}
}
return component;
}
private string GetSHA1HashFromStream(Stream stream)
{
#pragma warning disable CA5350 // Suppress Do Not Use Weak Cryptographic Algorithms because we use SHA1 intentionally in SPDX format
return BitConverter.ToString(SHA1.Create().ComputeHash(stream)).Replace("-", string.Empty).ToLower(); // CodeQL [SM02196] Sha1 is used in SPDX 2.2 format this file is parsing (https://spdx.github.io/spdx-spec/v2.2.2/file-information/).
#pragma warning restore CA5350
}
}