44 "archive/zip"
55 "bufio"
66 "context"
7- "crypto/sha1" // nolint:gosec
8- "encoding/hex"
97 "errors"
108 "io"
119 "os"
@@ -18,6 +16,7 @@ import (
1816 mavenversion "github.com/masahiro331/go-mvn-version"
1917 "golang.org/x/xerrors"
2018
19+ "github.com/aquasecurity/trivy/pkg/digest"
2120 ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
2221 "github.com/aquasecurity/trivy/pkg/log"
2322 xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -38,6 +37,7 @@ type Parser struct {
3837 logger * log.Logger
3938 rootFilePath string
4039 offline bool
40+ checksum bool
4141 size int64
4242
4343 client Client
@@ -57,6 +57,14 @@ func WithOffline(offline bool) Option {
5757 }
5858}
5959
60+ // WithChecksum enables calculation of the SHA-1 digest for every archive
61+ // (not only the ones that are looked up by SHA-1) and saving it to Package.Digest.
62+ func WithChecksum (checksum bool ) Option {
63+ return func (p * Parser ) {
64+ p .checksum = checksum
65+ }
66+ }
67+
6068func WithSize (size int64 ) Option {
6169 return func (p * Parser ) {
6270 p .size = size
@@ -85,6 +93,25 @@ func (p *Parser) Parse(_ context.Context, r xio.ReadSeekerAt) ([]ftypes.Package,
8593}
8694
8795func (p * Parser ) parseArtifact (filePath string , size int64 , r xio.ReadSeekerAt ) ([]ftypes.Package , []ftypes.Dependency , error ) {
96+ pkgs , deps , err := p .parsePackages (filePath , size , r )
97+ if err != nil {
98+ return nil , nil , err
99+ }
100+
101+ // When a checksum is requested, every package must carry the digest of its
102+ // own file. Packages from nested archives (and the one resolved by
103+ // searchBySHA1) already have it, so fill in this archive's digest only for
104+ // the packages that are still missing one.
105+ if p .checksum {
106+ if err := fillArchiveDigest (pkgs , r ); err != nil {
107+ return nil , nil , xerrors .Errorf ("unable to set digest for %s: %w" , filePath , err )
108+ }
109+ }
110+
111+ return pkgs , deps , nil
112+ }
113+
114+ func (p * Parser ) parsePackages (filePath string , size int64 , r xio.ReadSeekerAt ) ([]ftypes.Package , []ftypes.Dependency , error ) {
88115 p .logger .Debug ("Parsing Java artifacts..." , log .FilePath (filePath ))
89116
90117 // Try to extract artifactId and version from the file name
@@ -122,9 +149,9 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt)
122149 }
123150
124151 // If groupId and artifactId are not found, call Maven Central's search API with SHA-1 digest.
125- props , err := p .searchBySHA1 (r , filePath )
152+ pkg , err := p .searchBySHA1 (r , filePath )
126153 if err == nil {
127- return append (pkgs , props . Package () ), nil , nil
154+ return append (pkgs , pkg ), nil , nil
128155 } else if ! errors .Is (err , ArtifactNotFoundErr ) {
129156 return nil , nil , xerrors .Errorf ("failed to search by SHA1: %w" , err )
130157 }
@@ -150,6 +177,29 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt)
150177 return pkgs , nil , nil
151178}
152179
180+ // fillArchiveDigest sets the SHA-1 digest of the archive (r) on every package
181+ // that does not have a digest yet. The digest is calculated lazily, so the
182+ // archive is not read when all packages already carry their own digest.
183+ func fillArchiveDigest (pkgs []ftypes.Package , r xio.ReadSeekerAt ) error {
184+ var d digest.Digest
185+ for i := range pkgs {
186+ if pkgs [i ].Digest != "" {
187+ continue
188+ }
189+ if d == "" {
190+ if _ , err := r .Seek (0 , io .SeekStart ); err != nil {
191+ return xerrors .Errorf ("file seek error: %w" , err )
192+ }
193+ var err error
194+ if d , err = digest .CalcSHA1 (r ); err != nil {
195+ return xerrors .Errorf ("unable to calculate SHA-1: %w" , err )
196+ }
197+ }
198+ pkgs [i ].Digest = d
199+ }
200+ return nil
201+ }
202+
153203func (p * Parser ) traverseZip (filePath string , size int64 , r xio.ReadSeekerAt , fileProps Properties ) (
154204 []ftypes.Package , manifest , bool , error ) {
155205 var pkgs []ftypes.Package
@@ -231,22 +281,28 @@ func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]ftypes.Package,
231281 return innerPkgs , innerDeps , nil
232282}
233283
234- func (p * Parser ) searchBySHA1 (r io.ReadSeeker , filePath string ) (Properties , error ) {
284+ func (p * Parser ) searchBySHA1 (r io.ReadSeeker , filePath string ) (ftypes. Package , error ) {
235285 if _ , err := r .Seek (0 , io .SeekStart ); err != nil {
236- return Properties {}, xerrors .Errorf ("file seek error: %w" , err )
286+ return ftypes. Package {}, xerrors .Errorf ("file seek error: %w" , err )
237287 }
238-
239- h := sha1 .New () // nolint:gosec
240- if _ , err := io .Copy (h , r ); err != nil {
241- return Properties {}, xerrors .Errorf ("unable to calculate SHA-1: %w" , err )
288+ d , err := digest .CalcSHA1 (r )
289+ if err != nil {
290+ return ftypes.Package {}, xerrors .Errorf ("unable to calculate SHA-1: %w" , err )
242291 }
243- s := hex . EncodeToString ( h . Sum ( nil ))
244- prop , err := p .client .SearchBySHA1 (s )
292+
293+ prop , err := p .client .SearchBySHA1 (d . Encoded () )
245294 if err != nil {
246- return Properties {}, err
295+ return ftypes. Package {}, err
247296 }
248297 prop .FilePath = filePath
249- return prop , nil
298+
299+ pkg := prop .Package ()
300+ // searchBySHA1 has already calculated the archive's SHA-1, so stamp it on the
301+ // resolved package to avoid recalculating it in fillArchiveDigest.
302+ if p .checksum {
303+ pkg .Digest = d
304+ }
305+ return pkg , nil
250306}
251307
252308func isArtifact (name string ) bool {
0 commit comments