github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/redhat/parse_rpm_archive.go (about)

     1  package redhat
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/binary"
     7  	"fmt"
     8  	"io"
     9  	"sort"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/sassoftware/go-rpmutils"
    14  
    15  	rpmdb "github.com/anchore/go-rpmdb/pkg"
    16  	"github.com/anchore/syft/internal/log"
    17  	"github.com/anchore/syft/syft/artifact"
    18  	"github.com/anchore/syft/syft/file"
    19  	"github.com/anchore/syft/syft/pkg"
    20  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    21  )
    22  
    23  type pgpSig struct {
    24  	_          [3]byte
    25  	Date       int32
    26  	KeyID      [8]byte
    27  	PubKeyAlgo uint8
    28  	HashAlgo   uint8
    29  }
    30  
    31  type textSig struct {
    32  	_          [2]byte
    33  	PubKeyAlgo uint8
    34  	HashAlgo   uint8
    35  	_          [4]byte
    36  	Date       int32
    37  	_          [4]byte
    38  	KeyID      [8]byte
    39  }
    40  
    41  type pgp4Sig struct {
    42  	_          [2]byte
    43  	PubKeyAlgo uint8
    44  	HashAlgo   uint8
    45  	_          [17]byte
    46  	KeyID      [8]byte
    47  	_          [2]byte
    48  	Date       int32
    49  }
    50  
    51  var pubKeyLookup = map[uint8]string{
    52  	0x01: "RSA",
    53  }
    54  var hashLookup = map[uint8]string{
    55  	0x02: "SHA1",
    56  	0x08: "SHA256",
    57  }
    58  
    59  // parseRpmArchive parses a single RPM
    60  func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    61  	rpm, err := rpmutils.ReadRpm(reader)
    62  	if err != nil {
    63  		return nil, nil, fmt.Errorf("RPM file found but unable to read: %s (%w)", reader.RealPath, err)
    64  	}
    65  
    66  	nevra, err := rpm.Header.GetNEVRA()
    67  	if err != nil {
    68  		return nil, nil, err
    69  	}
    70  
    71  	licenses, err := rpm.Header.GetStrings(rpmutils.LICENSE)
    72  	logRpmArchiveErr(reader.Location, "license", err)
    73  
    74  	sourceRpm, err := rpm.Header.GetString(rpmutils.SOURCERPM)
    75  	logRpmArchiveErr(reader.Location, "sourcerpm", err)
    76  
    77  	vendor, err := rpm.Header.GetString(rpmutils.VENDOR)
    78  	logRpmArchiveErr(reader.Location, "vendor", err)
    79  
    80  	digestAlgorithm := getDigestAlgorithm(reader.Location, rpm.Header)
    81  
    82  	size, err := rpm.Header.InstalledSize()
    83  	logRpmArchiveErr(reader.Location, "size", err)
    84  
    85  	files, err := rpm.Header.GetFiles()
    86  	logRpmArchiveErr(reader.Location, "files", err)
    87  
    88  	rsa, err := rpm.Header.GetBytes(rpmutils.SIG_RSA)
    89  	logRpmArchiveErr(reader.Location, "rsa signature", err)
    90  
    91  	pgp, err := rpm.Header.GetBytes(rpmutils.SIG_PGP)
    92  	logRpmArchiveErr(reader.Location, "pgp signature", err)
    93  
    94  	var allSigs [][]byte
    95  	allSigs = append(allSigs, rsa)
    96  	allSigs = append(allSigs, pgp)
    97  	sigs, err := parseSignatureHeaders(allSigs)
    98  	logRpmArchiveErr(reader.Location, "signature", err)
    99  
   100  	metadata := pkg.RpmArchive{
   101  		Name:       nevra.Name,
   102  		Version:    nevra.Version,
   103  		Epoch:      parseEpoch(nevra.Epoch),
   104  		Arch:       nevra.Arch,
   105  		Release:    nevra.Release,
   106  		SourceRpm:  sourceRpm,
   107  		Signatures: sigs,
   108  		Vendor:     vendor,
   109  		Size:       int(size),
   110  		Files:      mapFiles(files, digestAlgorithm),
   111  	}
   112  
   113  	return []pkg.Package{newArchivePackage(ctx, reader.Location, metadata, licenses)}, nil, nil
   114  }
   115  
   116  func parseSignatureHeaders(data [][]byte) ([]pkg.RpmSignature, error) {
   117  	sigMap := make(map[string]pkg.RpmSignature)
   118  	var keys []string
   119  	for _, sig := range data {
   120  		if len(sig) == 0 {
   121  			continue
   122  		}
   123  		s, err := parsePGP(sig)
   124  		if err != nil {
   125  			log.WithFields("error", err).Trace("unable to parse RPM archive signature")
   126  			return nil, err
   127  		}
   128  		k := s.String()
   129  		if _, ok := sigMap[k]; ok {
   130  			// if we have a duplicate signature, just skip it
   131  			continue
   132  		}
   133  		sigMap[k] = *s
   134  		keys = append(keys, k)
   135  	}
   136  	var signatures []pkg.RpmSignature
   137  	sort.Strings(keys)
   138  	for _, k := range keys {
   139  		signatures = append(signatures, sigMap[k])
   140  	}
   141  
   142  	return signatures, nil
   143  }
   144  
   145  func parsePGP(data []byte) (*pkg.RpmSignature, error) {
   146  	var tag, signatureType, version uint8
   147  
   148  	r := bytes.NewReader(data)
   149  	err := binary.Read(r, binary.BigEndian, &tag)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	err = binary.Read(r, binary.BigEndian, &signatureType)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	err = binary.Read(r, binary.BigEndian, &version)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	switch signatureType {
   163  	case 0x01:
   164  		switch version {
   165  		case 0x1c:
   166  			sig := textSig{}
   167  			err = binary.Read(r, binary.BigEndian, &sig)
   168  			if err != nil {
   169  				return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
   170  			}
   171  			return &pkg.RpmSignature{
   172  				PublicKeyAlgorithm: pubKeyLookup[sig.PubKeyAlgo],
   173  				HashAlgorithm:      hashLookup[sig.HashAlgo],
   174  				Created:            time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006"),
   175  				IssuerKeyID:        fmt.Sprintf("%x", sig.KeyID),
   176  			}, nil
   177  		default:
   178  			return decodePGPSig(version, r)
   179  		}
   180  	case 0x02:
   181  		return decodePGPSig(version, r)
   182  	}
   183  
   184  	return nil, fmt.Errorf("unknown signature type: %d", signatureType)
   185  }
   186  
   187  func decodePGPSig(version uint8, r io.Reader) (*pkg.RpmSignature, error) {
   188  	var pubKeyAlgo, hashAlgo, pkgDate string
   189  	var keyID [8]byte
   190  
   191  	switch {
   192  	case version > 0x15:
   193  		sig := pgp4Sig{}
   194  		err := binary.Read(r, binary.BigEndian, &sig)
   195  		if err != nil {
   196  			return nil, fmt.Errorf("invalid PGP v4 signature on decode: %w", err)
   197  		}
   198  		pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
   199  		hashAlgo = hashLookup[sig.HashAlgo]
   200  		pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
   201  		keyID = sig.KeyID
   202  
   203  	default:
   204  		sig := pgpSig{}
   205  		err := binary.Read(r, binary.BigEndian, &sig)
   206  		if err != nil {
   207  			return nil, fmt.Errorf("invalid PGP signature on decode: %w", err)
   208  		}
   209  		pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo]
   210  		hashAlgo = hashLookup[sig.HashAlgo]
   211  		pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006")
   212  		keyID = sig.KeyID
   213  	}
   214  	return &pkg.RpmSignature{
   215  		PublicKeyAlgorithm: pubKeyAlgo,
   216  		HashAlgorithm:      hashAlgo,
   217  		Created:            pkgDate,
   218  		IssuerKeyID:        fmt.Sprintf("%x", keyID),
   219  	}, nil
   220  }
   221  
   222  func getDigestAlgorithm(location file.Location, header *rpmutils.RpmHeader) string {
   223  	digestAlgorithm, err := header.GetString(rpmutils.FILEDIGESTALGO)
   224  	logRpmArchiveErr(location, "file digest algo", err)
   225  
   226  	if digestAlgorithm != "" {
   227  		return digestAlgorithm
   228  	}
   229  	digestAlgorithms, err := header.GetUint32s(rpmutils.FILEDIGESTALGO)
   230  	logRpmArchiveErr(location, "file digest algo 32-bit", err)
   231  
   232  	if len(digestAlgorithms) > 0 {
   233  		digestAlgo := int(digestAlgorithms[0])
   234  		return rpmutils.GetFileAlgoName(digestAlgo)
   235  	}
   236  	return ""
   237  }
   238  
   239  func mapFiles(files []rpmutils.FileInfo, digestAlgorithm string) []pkg.RpmFileRecord {
   240  	var out []pkg.RpmFileRecord
   241  	for _, f := range files {
   242  		digest := file.Digest{}
   243  		if f.Digest() != "" {
   244  			digest = file.Digest{
   245  				Algorithm: digestAlgorithm,
   246  				Value:     f.Digest(),
   247  			}
   248  		}
   249  		out = append(out, pkg.RpmFileRecord{
   250  			Path:      f.Name(),
   251  			Mode:      pkg.RpmFileMode(f.Mode()),
   252  			Size:      int(f.Size()),
   253  			Digest:    digest,
   254  			UserName:  f.UserName(),
   255  			GroupName: f.GroupName(),
   256  			Flags:     rpmdb.FileFlags(f.Flags()).String(),
   257  		})
   258  	}
   259  	return out
   260  }
   261  
   262  func parseEpoch(epoch string) *int {
   263  	i, err := strconv.Atoi(epoch)
   264  	if err != nil {
   265  		return nil
   266  	}
   267  	return &i
   268  }
   269  
   270  func logRpmArchiveErr(location file.Location, operation string, err error) {
   271  	if err != nil {
   272  		log.WithFields("error", err, "operation", operation, "path", location.RealPath).Trace("unable to parse RPM archive")
   273  	}
   274  }