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 }