Skip to content

Commit f2a1237

Browse files
feat(spdx): add SHA-512 hash algorithm support to SPDX serializer (aquasecurity#10719)
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
1 parent 04f739e commit f2a1237

8 files changed

Lines changed: 133 additions & 19 deletions

File tree

pkg/sbom/core/bom.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ type File struct {
199199
Digests []digest.Digest
200200
}
201201

202+
func (f File) IsEmpty() bool {
203+
return f.Path == "" && len(f.Digests) == 0
204+
}
205+
202206
type Property struct {
203207
Name string
204208
Value string

pkg/sbom/cyclonedx/unmarshal.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ func (b *BOM) unmarshalHashes(hashes *[]cdx.Hash) []digest.Digest {
273273
alg = digest.MD5
274274
default:
275275
log.Warn("Unsupported hash algorithm", log.String("algorithm", string(h.Algorithm)))
276+
continue
276277
}
277278
digests = append(digests, digest.NewDigestFromString(alg, h.Value))
278279
}

pkg/sbom/io/decode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func (m *Decoder) decodePackage(ctx context.Context, c *core.Component) (*ftypes
218218
pkg.FilePath = f.Path
219219
}
220220
// An empty path represents a package digest
221-
if f.Path == "" && len(f.Digests) > 0 {
221+
if len(f.Digests) > 0 {
222222
pkg.Digest = f.Digests[0]
223223
}
224224
}

pkg/sbom/spdx/marshal.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,10 +507,13 @@ func (m *Marshaler) spdxChecksums(digests []digest.Digest) []common.Checksum {
507507
alg = spdx.SHA1
508508
case digest.SHA256:
509509
alg = spdx.SHA256
510+
case digest.SHA512:
511+
alg = spdx.SHA512
510512
case digest.MD5:
511513
alg = spdx.MD5
512514
default:
513-
return nil
515+
m.logger.Warn("Unsupported hash algorithm", log.String("algorithm", string(alg)))
516+
continue
514517
}
515518
checksums = append(checksums, spdx.Checksum{
516519
Algorithm: alg,

pkg/sbom/spdx/marshal_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ func TestMarshaler_Marshal(t *testing.T) {
488488
DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
489489
},
490490
FilePath: "tools/project-doe/specifications/actionpack.gemspec",
491-
Digest: "sha1:413f98442c83808042b5d1d2611a346b999bdca5",
491+
Digest: "sha512:bf690311ee7b95e713ba568322e3533f2dd1cb880b189e99d4edef13592b81764daec43e2c54c61d5c558dc5cfb35ecb85b65519e74026ff17675b6f8f916f4a", // Changed for tests
492492
},
493493
},
494494
},
@@ -577,7 +577,7 @@ func TestMarshaler_Marshal(t *testing.T) {
577577
PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
578578
FilesAnalyzed: true,
579579
PackageVerificationCode: &spdx.PackageVerificationCode{
580-
Value: "688d98e7e5660b879fd1fc548af8c0df3b7d785a",
580+
Value: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
581581
},
582582
},
583583
{
@@ -625,8 +625,8 @@ func TestMarshaler_Marshal(t *testing.T) {
625625
FileName: "tools/project-doe/specifications/actionpack.gemspec",
626626
Checksums: []spdx.Checksum{
627627
{
628-
Algorithm: spdx.SHA1,
629-
Value: "413f98442c83808042b5d1d2611a346b999bdca5",
628+
Algorithm: spdx.SHA512,
629+
Value: "bf690311ee7b95e713ba568322e3533f2dd1cb880b189e99d4edef13592b81764daec43e2c54c61d5c558dc5cfb35ecb85b65519e74026ff17675b6f8f916f4a",
630630
},
631631
},
632632
},
@@ -678,7 +678,6 @@ func TestMarshaler_Marshal(t *testing.T) {
678678
Relationship: "CONTAINS",
679679
},
680680
},
681-
682681
OtherLicenses: nil,
683682
Annotations: nil,
684683
Reviews: nil,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"SPDXID": "SPDXRef-DOCUMENT",
3+
"creationInfo": {
4+
"created": "2022-09-12T17:02:46.826609Z",
5+
"creators": [
6+
"Tool: trivy-dev",
7+
"Organization: aquasecurity"
8+
]
9+
},
10+
"dataLicense": "CC0-1.0",
11+
"documentNamespace": "http://trivy.dev/container/meven-test-project-eb7a0384-b04a-4fc6-8afb-1662fe59ca79",
12+
"name": "maven-test-projecct",
13+
"packages": [
14+
{
15+
"SPDXID": "SPDXRef-Package-lodash-4.17.21",
16+
"name": "lodash",
17+
"versionInfo": "4.17.21",
18+
"externalRefs": [
19+
{
20+
"referenceCategory": "PACKAGE-MANAGER",
21+
"referenceLocator": "pkg:npm/lodash@4.17.21",
22+
"referenceType": "purl"
23+
}
24+
],
25+
"filesAnalyzed": false,
26+
"licenseConcluded": "MIT",
27+
"licenseDeclared": "MIT",
28+
"checksums": [
29+
{
30+
"algorithm": "SHA512",
31+
"checksumValue": "bf690311ee7b95e713ba568322e3533f2dd1cb880b189e99d4edef13592b81764daec43e2c54c61d5c558dc5cfb35ecb85b65519e74026ff17675b6f8f916f4a"
32+
}
33+
]
34+
}
35+
],
36+
"spdxVersion": "SPDX-2.3"
37+
}

pkg/sbom/spdx/unmarshal.go

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ import (
1414
"github.com/spdx/tools-golang/tagvalue"
1515
"golang.org/x/xerrors"
1616

17+
"github.com/aquasecurity/trivy/pkg/digest"
18+
"github.com/aquasecurity/trivy/pkg/log"
1719
"github.com/aquasecurity/trivy/pkg/sbom/core"
1820
)
1921

2022
type SPDX struct {
2123
*core.BOM
2224

23-
trivySBOM bool
24-
pkgFilePaths map[common.ElementID]string
25+
trivySBOM bool
26+
pkgFiles map[common.ElementID]core.File
2527
}
2628

2729
func NewTVDecoder(r io.Reader) *TVDecoder {
@@ -68,8 +70,8 @@ func (s *SPDX) UnmarshalJSON(b []byte) error {
6870
func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
6971
s.trivySBOM = s.isTrivySBOM(spdxDocument)
7072

71-
if s.pkgFilePaths == nil {
72-
s.pkgFilePaths = make(map[common.ElementID]string)
73+
if s.pkgFiles == nil {
74+
s.pkgFiles = make(map[common.ElementID]core.File)
7375
}
7476

7577
// Parse files and find file paths for packages
@@ -125,7 +127,10 @@ func (s *SPDX) parseFiles(spdxDocument *spdx.Document) {
125127
if ok {
126128
// Save filePaths for packages
127129
// Insert filepath will be later
128-
s.pkgFilePaths[rel.RefA.ElementRefID] = file.FileName
130+
s.pkgFiles[rel.RefA.ElementRefID] = core.File{
131+
Path: file.FileName,
132+
Digests: s.unmarshalChecksums(file.Checksums),
133+
}
129134
}
130135
continue
131136
}
@@ -184,15 +189,24 @@ func (s *SPDX) parsePackage(spdxPkg spdx.Package) (*core.Component, error) {
184189
}
185190

186191
// Files
187-
// TODO: handle checksums as well
188-
if path, ok := s.pkgFilePaths[spdxPkg.PackageSPDXIdentifier]; ok {
189-
component.Files = []core.File{
190-
{Path: path},
191-
}
192+
var file core.File
193+
// Take filePath + digest from Files components
194+
if f, ok := s.pkgFiles[spdxPkg.PackageSPDXIdentifier]; ok {
195+
file = f
196+
// Take filePath + digest from Package.Files (old format)
192197
} else if len(spdxPkg.Files) > 0 {
193-
component.Files = []core.File{
194-
{Path: spdxPkg.Files[0].FileName}, // Take the first file name
198+
spdxFile := spdxPkg.Files[0] // Take the first file name
199+
file = core.File{
200+
Path: spdxFile.FileName,
201+
Digests: s.unmarshalChecksums(spdxFile.Checksums),
195202
}
203+
// Take digest from Package.Checksums
204+
} else if len(spdxPkg.PackageChecksums) > 0 {
205+
file.Digests = s.unmarshalChecksums(spdxPkg.PackageChecksums)
206+
}
207+
208+
if !file.IsEmpty() {
209+
component.Files = append(component.Files, file)
196210
}
197211

198212
// Trivy stores properties in Annotations
@@ -232,6 +246,31 @@ func (s *SPDX) parsePackage(spdxPkg spdx.Package) (*core.Component, error) {
232246
return component, nil
233247
}
234248

249+
func (s *SPDX) unmarshalChecksums(checksums []spdx.Checksum) []digest.Digest {
250+
if checksums == nil {
251+
return nil
252+
}
253+
var digests []digest.Digest
254+
for _, h := range checksums {
255+
var alg digest.Algorithm
256+
switch h.Algorithm {
257+
case common.SHA1:
258+
alg = digest.SHA1
259+
case common.SHA256:
260+
alg = digest.SHA256
261+
case common.SHA512:
262+
alg = digest.SHA512
263+
case common.MD5:
264+
alg = digest.MD5
265+
default:
266+
log.Warn("Unsupported hash algorithm", log.String("algorithm", string(h.Algorithm)))
267+
continue
268+
}
269+
digests = append(digests, digest.NewDigestFromString(alg, h.Value))
270+
}
271+
return digests
272+
}
273+
235274
func (s *SPDX) parseType(pkg spdx.Package) core.ComponentType {
236275
id := string(pkg.PackageSPDXIdentifier)
237276
switch {

pkg/sbom/spdx/unmarshal_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
200200
},
201201
},
202202
FilePath: "node_modules/yargs-parser/package.json",
203+
Digest: "sha1:69e70ec702f9df4ff64024b5fdea4644f1ce6c97",
203204
},
204205
},
205206
},
@@ -227,6 +228,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
227228
},
228229
},
229230
FilePath: "node_modules/yargs-parser/package.json",
231+
Digest: "sha1:69e70ec702f9df4ff64024b5fdea4644f1ce6c97",
230232
},
231233
},
232234
},
@@ -286,6 +288,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
286288
Name: "co.elastic.apm:apm-agent",
287289
Version: "1.36.0",
288290
FilePath: "modules/apm/elastic-apm-agent-1.36.0.jar",
291+
Digest: "sha1:d2a9ad9b159eb650d25add9395c4f4198f200066",
289292
Identifier: ftypes.PkgIdentifier{
290293
PURL: &packageurl.PackageURL{
291294
Type: packageurl.TypeMaven,
@@ -300,6 +303,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
300303
Name: "co.elastic.apm:apm-agent-cached-lookup-key",
301304
Version: "1.36.0",
302305
FilePath: "modules/apm/elastic-apm-agent-1.36.0.jar",
306+
Digest: "sha1:d2a9ad9b159eb650d25add9395c4f4198f200066",
303307
Identifier: ftypes.PkgIdentifier{
304308
PURL: &packageurl.PackageURL{
305309
Type: packageurl.TypeMaven,
@@ -341,6 +345,33 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
341345
inputFile: "testdata/sad/invalid-purl.json",
342346
wantErr: "purl is missing type or name",
343347
},
348+
{
349+
name: "happy path package with hash",
350+
inputFile: "testdata/happy/package-hashes.json",
351+
want: types.SBOM{
352+
Applications: []ftypes.Application{
353+
{
354+
Type: ftypes.NodePkg,
355+
Packages: ftypes.Packages{
356+
{
357+
ID: "lodash@4.17.21",
358+
Name: "lodash",
359+
Version: "4.17.21",
360+
Licenses: []string{"MIT"},
361+
Identifier: ftypes.PkgIdentifier{
362+
PURL: &packageurl.PackageURL{
363+
Type: packageurl.TypeNPM,
364+
Name: "lodash",
365+
Version: "4.17.21",
366+
},
367+
},
368+
Digest: "sha512:bf690311ee7b95e713ba568322e3533f2dd1cb880b189e99d4edef13592b81764daec43e2c54c61d5c558dc5cfb35ecb85b65519e74026ff17675b6f8f916f4a",
369+
},
370+
},
371+
},
372+
},
373+
},
374+
},
344375
}
345376

346377
for _, tt := range tests {

0 commit comments

Comments
 (0)