From b369b02f4f9c2411c7ef073796cd5a2cdcfd662f Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Thu, 15 May 2025 12:01:00 -0400 Subject: [PATCH] Expose RPM signature information (for RPM DB and RPM archives) (#3179) * feat: expose rpm signature information This helps with more confident identification of an rpm. In theory, two rpms can be built that have the same purl string, and otherwise look identical in syft's output, but the PGP information would distinguish them as signed either by different keys, or signed at different times. In practice, this usually makes no difference since rpms tend to have unique name/version/release strings. This just gives increased confidence about the identity of the rpm found in the db. Signed-off-by: Ralph Bean * chore: generate json schema Signed-off-by: Ralph Bean * re-generate json schema Signed-off-by: Alex Goodman * rename to a more generic signature field Signed-off-by: Alex Goodman * rename rpm.pgp to rpm.signatures Signed-off-by: Alex Goodman * split out signature fields Signed-off-by: Alex Goodman * bump json schema Signed-off-by: Alex Goodman * include RPM archives Signed-off-by: Alex Goodman * update json schema Signed-off-by: Alex Goodman * dont fail on unknown signature type Signed-off-by: Alex Goodman --------- Signed-off-by: Ralph Bean Signed-off-by: Alex Goodman Co-authored-by: Alex Goodman --- .../regression_photon_package_test.go | 12 +- go.mod | 5 +- go.sum | 8 +- internal/constants.go | 2 +- schema/json/schema-16.0.33.json | 3087 +++++++++++++++++ schema/json/schema-latest.json | 37 +- .../internal/pkgtest/test_generic_parser.go | 13 + syft/pkg/cataloger/redhat/cataloger_test.go | 26 +- .../pkg/cataloger/redhat/parse_rpm_archive.go | 180 +- .../redhat/parse_rpm_archive_test.go | 152 +- syft/pkg/cataloger/redhat/parse_rpm_db.go | 75 + .../pkg/cataloger/redhat/parse_rpm_db_test.go | 127 + .../image-rpm-archive/Dockerfile | 26 + syft/pkg/rpm.go | 18 + syft/pkg/rpm_test.go | 47 + 15 files changed, 3775 insertions(+), 40 deletions(-) create mode 100644 schema/json/schema-16.0.33.json create mode 100644 syft/pkg/cataloger/redhat/test-fixtures/image-rpm-archive/Dockerfile diff --git a/cmd/syft/internal/test/integration/regression_photon_package_test.go b/cmd/syft/internal/test/integration/regression_photon_package_test.go index 6d9aaf322..69329c2a5 100644 --- a/cmd/syft/internal/test/integration/regression_photon_package_test.go +++ b/cmd/syft/internal/test/integration/regression_photon_package_test.go @@ -3,18 +3,18 @@ package integration import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" ) func TestPhotonPackageRegression(t *testing.T) { // Regression: https://github.com/anchore/syft/pull/1997 sbom, _ := catalogFixtureImage(t, "image-photon-all-layers", source.AllLayersScope) - var packages []pkg.Package - for p := range sbom.Artifacts.Packages.Enumerate() { - packages = append(packages, p) + var count int + for range sbom.Artifacts.Packages.Enumerate(pkg.RpmPkg) { + count++ } - if len(packages) < 1 { - t.Errorf("failed to find packages for photon distro; wanted > 0 got 0") - } + assert.Greater(t, count, 0, "expected to find RPM packages in the SBOM (but did not)") } diff --git a/go.mod b/go.mod index 9bed674ce..03e4edaae 100644 --- a/go.mod +++ b/go.mod @@ -100,7 +100,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.11.7 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/ProtonMail/go-crypto v1.2.0 // indirect github.com/STARRY-S/zip v0.2.1 // indirect github.com/agext/levenshtein v1.2.1 // indirect; indirectt github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect @@ -263,3 +263,6 @@ retract ( v0.53.2 v0.53.1 // Published accidentally with incorrect license in depdencies ) + +// adds RSA Headers from RPMDB via https://github.com/knqyf263/go-rpmdb/pull/58 (in review) +replace github.com/knqyf263/go-rpmdb => github.com/anchore/go-rpmdb v0.0.0-20250515153519-38be2efee8ed diff --git a/go.sum b/go.sum index 9f83948e6..84f186231 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs= +github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg= github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -110,6 +110,8 @@ github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4 github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk= +github.com/anchore/go-rpmdb v0.0.0-20250515153519-38be2efee8ed h1:ixtJ1s+L3+AvxlGn4Tm5VcV7RiMVLcUGmNOGXQ6U9Gg= +github.com/anchore/go-rpmdb v0.0.0-20250515153519-38be2efee8ed/go.mod h1:0A7fN6+ED0l7YrO4GNEz6kgDmkKUwzK2bDl2v0E2Hog= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6 h1:Ha+LSCVuXYSYGi7wIkJK6G8g6jI3LH7y6LbyEVyp4Io= @@ -548,8 +550,6 @@ github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYW github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/knqyf263/go-rpmdb v0.1.1 h1:oh68mTCvp1XzxdU7EfafcWzzfstUZAEa3MW0IJye584= -github.com/knqyf263/go-rpmdb v0.1.1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= diff --git a/internal/constants.go b/internal/constants.go index b5ec8ea2d..db73128dc 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -3,5 +3,5 @@ package internal const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "16.0.32" + JSONSchemaVersion = "16.0.33" ) diff --git a/schema/json/schema-16.0.33.json b/schema/json/schema-16.0.33.json new file mode 100644 index 000000000..318832f5b --- /dev/null +++ b/schema/json/schema-16.0.33.json @@ -0,0 +1,3087 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.0.33/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmDbEntry": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "ApkDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "BinarySignature": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "BitnamiSbomEntry": { + "properties": { + "name": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "distro": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "arch", + "distro", + "revision", + "version", + "path", + "files" + ] + }, + "CConanFileEntry": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanInfoEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "build_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "py_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "$ref": "#/$defs/KeyValues" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockV2Entry": { + "properties": { + "ref": { + "type": "string" + }, + "packageID": { + "type": "string" + }, + "username": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "recipeRevision": { + "type": "string" + }, + "packageRevision": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CPE": { + "properties": { + "cpe": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "cpe" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoaPodfileLockEntry": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubspec": { + "properties": { + "homepage": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "documentation": { + "type": "string" + }, + "publish_to": { + "type": "string" + }, + "environment": { + "$ref": "#/$defs/DartPubspecEnvironment" + }, + "platforms": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ignored_advisories": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "DartPubspecEnvironment": { + "properties": { + "sdk": { + "type": "string" + }, + "flutter": { + "type": "string" + } + }, + "type": "object" + }, + "DartPubspecLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + }, + "executables": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPackagesLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "contentHash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "contentHash", + "type" + ] + }, + "DotnetPortableExecutableEntry": { + "properties": { + "assemblyVersion": { + "type": "string" + }, + "legalCopyright": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "internalName": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productVersion": { + "type": "string" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ] + }, + "DpkgArchiveEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "ELFSecurityFeatures": { + "properties": { + "symbolTableStripped": { + "type": "boolean" + }, + "stackCanary": { + "type": "boolean" + }, + "nx": { + "type": "boolean" + }, + "relRO": { + "type": "string" + }, + "pie": { + "type": "boolean" + }, + "dso": { + "type": "boolean" + }, + "safeStack": { + "type": "boolean" + }, + "cfi": { + "type": "boolean" + }, + "fortify": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "symbolTableStripped", + "nx", + "relRO", + "pie", + "dso" + ] + }, + "ElfBinaryPackageNoteJsonPayload": { + "properties": { + "type": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "osCPE": { + "type": "string" + }, + "os": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "system": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "type": "object" + }, + "ElixirMixLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "ErlangRebarLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Executable": { + "properties": { + "format": { + "type": "string" + }, + "hasExports": { + "type": "boolean" + }, + "hasEntrypoint": { + "type": "boolean" + }, + "importedLibraries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "elfSecurityFeatures": { + "$ref": "#/$defs/ELFSecurityFeatures" + } + }, + "type": "object", + "required": [ + "format", + "hasExports", + "hasEntrypoint", + "importedLibraries" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/FileLicense" + }, + "type": "array" + }, + "executable": { + "$ref": "#/$defs/Executable" + }, + "unknowns": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileLicense": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/FileLicenseEvidence" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type" + ] + }, + "FileLicenseEvidence": { + "properties": { + "confidence": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "extent": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "confidence", + "offset", + "extent" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GithubActionsUseStatement": { + "properties": { + "value": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value" + ] + }, + "GoModuleBuildinfoEntry": { + "properties": { + "goBuildSettings": { + "$ref": "#/$defs/KeyValues" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + }, + "goCryptoSettings": { + "items": { + "type": "string" + }, + "type": "array" + }, + "goExperiments": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GoModuleEntry": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object" + }, + "HomebrewFormula": { + "properties": { + "tap": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaArchive": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/JavaPomProperties" + }, + "pomProject": { + "$ref": "#/$defs/JavaPomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "JavaJvmInstallation": { + "properties": { + "release": { + "$ref": "#/$defs/JavaVMRelease" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "release", + "files" + ] + }, + "JavaManifest": { + "properties": { + "main": { + "$ref": "#/$defs/KeyValues" + }, + "sections": { + "items": { + "$ref": "#/$defs/KeyValues" + }, + "type": "array" + } + }, + "type": "object" + }, + "JavaPomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "JavaPomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/JavaPomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "JavaPomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "JavaVMRelease": { + "properties": { + "implementor": { + "type": "string" + }, + "implementorVersion": { + "type": "string" + }, + "javaRuntimeVersion": { + "type": "string" + }, + "javaVersion": { + "type": "string" + }, + "javaVersionDate": { + "type": "string" + }, + "libc": { + "type": "string" + }, + "modules": { + "items": { + "type": "string" + }, + "type": "array" + }, + "osArch": { + "type": "string" + }, + "osName": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "source": { + "type": "string" + }, + "buildSource": { + "type": "string" + }, + "buildSourceRepo": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "fullVersion": { + "type": "string" + }, + "semanticVersion": { + "type": "string" + }, + "buildInfo": { + "type": "string" + }, + "jvmVariant": { + "type": "string" + }, + "jvmVersion": { + "type": "string" + }, + "imageType": { + "type": "string" + }, + "buildType": { + "type": "string" + } + }, + "type": "object" + }, + "JavascriptNpmPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "JavascriptNpmPackageLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "JavascriptYarnLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "KeyValue": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "key", + "value" + ] + }, + "KeyValues": { + "items": { + "$ref": "#/$defs/KeyValue" + }, + "type": "array" + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "contents": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelArchive": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModule": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "accessPath": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "accessPath" + ] + }, + "LuarocksPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "homepage", + "description", + "url", + "dependencies" + ] + }, + "MicrosoftKbPatch": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "NixDerivation": { + "properties": { + "path": { + "type": "string" + }, + "system": { + "type": "string" + }, + "inputDerivations": { + "items": { + "$ref": "#/$defs/NixDerivationReference" + }, + "type": "array" + }, + "inputSources": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "NixDerivationReference": { + "properties": { + "path": { + "type": "string" + }, + "outputs": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "NixStoreEntry": { + "properties": { + "path": { + "type": "string" + }, + "output": { + "type": "string" + }, + "outputHash": { + "type": "string" + }, + "derivation": { + "$ref": "#/$defs/NixDerivation" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash" + ] + }, + "OpamPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "url": { + "type": "string" + }, + "checksum": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "licenses", + "url", + "checksum", + "homepage", + "dependencies" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "$ref": "#/$defs/cpes" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmDbEntry" + }, + { + "$ref": "#/$defs/ApkDbEntry" + }, + { + "$ref": "#/$defs/BinarySignature" + }, + { + "$ref": "#/$defs/BitnamiSbomEntry" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CConanLockV2Entry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/DartPubspec" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPackagesLockEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgArchiveEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElfBinaryPackageNoteJsonPayload" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GithubActionsUseStatement" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/HomebrewFormula" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavaJvmInstallation" + }, + { + "$ref": "#/$defs/JavascriptNpmPackage" + }, + { + "$ref": "#/$defs/JavascriptNpmPackageLockEntry" + }, + { + "$ref": "#/$defs/JavascriptYarnLockEntry" + }, + { + "$ref": "#/$defs/LinuxKernelArchive" + }, + { + "$ref": "#/$defs/LinuxKernelModule" + }, + { + "$ref": "#/$defs/LuarocksPackage" + }, + { + "$ref": "#/$defs/MicrosoftKbPatch" + }, + { + "$ref": "#/$defs/NixStoreEntry" + }, + { + "$ref": "#/$defs/OpamPackage" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PhpPearEntry" + }, + { + "$ref": "#/$defs/PhpPeclEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/PythonPoetryLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$ref": "#/$defs/SwiftPackageManagerLockEntry" + }, + { + "$ref": "#/$defs/SwiplpackPackage" + }, + { + "$ref": "#/$defs/TerraformLockProviderEntry" + }, + { + "$ref": "#/$defs/WordpressPluginEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerInstalledEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpComposerLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpPearEntry": { + "properties": { + "name": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PhpPeclEntry": { + "properties": { + "name": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "licenses": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + }, + "requiresPython": { + "type": "string" + }, + "requiresDist": { + "items": { + "type": "string" + }, + "type": "array" + }, + "providesExtra": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipRequirementsEntry": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "versionConstraint" + ] + }, + "PythonPipfileLockEntry": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "PythonPoetryLockDependencyEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "markers": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "optional" + ] + }, + "PythonPoetryLockEntry": { + "properties": { + "index": { + "type": "string" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/PythonPoetryLockDependencyEntry" + }, + "type": "array" + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonPoetryLockExtraEntry" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "index", + "dependencies" + ] + }, + "PythonPoetryLockExtraEntry": { + "properties": { + "name": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "dependencies" + ] + }, + "RDescription": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmArchive": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmDbEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "RpmSignature": { + "properties": { + "algo": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "created": { + "type": "string" + }, + "issuer": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algo", + "hash", + "created", + "issuer" + ] + }, + "RubyGemspec": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "RustCargoAuditEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source" + ] + }, + "RustCargoLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ] + }, + "SwiftPackageManagerLockEntry": { + "properties": { + "revision": { + "type": "string" + } + }, + "type": "object", + "required": [ + "revision" + ] + }, + "SwiplpackPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "packager": { + "type": "string" + }, + "packagerEmail": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "packager", + "packagerEmail", + "homepage", + "dependencies" + ] + }, + "TerraformLockProviderEntry": { + "properties": { + "url": { + "type": "string" + }, + "constraints": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "url", + "constraints", + "version", + "hashes" + ] + }, + "WordpressPluginEntry": { + "properties": { + "pluginInstallDirectory": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorUri": { + "type": "string" + } + }, + "type": "object", + "required": [ + "pluginInstallDirectory" + ] + }, + "cpes": { + "items": { + "$ref": "#/$defs/CPE" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index 41ca4b979..318832f5b 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "anchore.io/schema/syft/json/16.0.32/document", + "$id": "anchore.io/schema/syft/json/16.0.33/document", "$ref": "#/$defs/Document", "$defs": { "AlpmDbEntry": { @@ -2677,6 +2677,12 @@ "sourceRpm": { "type": "string" }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array" + }, "size": { "type": "integer" }, @@ -2745,6 +2751,12 @@ "sourceRpm": { "type": "string" }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array" + }, "size": { "type": "integer" }, @@ -2821,6 +2833,29 @@ "flags" ] }, + "RpmSignature": { + "properties": { + "algo": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "created": { + "type": "string" + }, + "issuer": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algo", + "hash", + "created", + "issuer" + ] + }, "RubyGemspec": { "properties": { "name": { diff --git a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go index 72a50e59e..eec0c6789 100644 --- a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go +++ b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go @@ -96,6 +96,10 @@ func (p *CatalogTester) WithContext(ctx context.Context) *CatalogTester { func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester { t.Helper() + if path == "" { + return p + } + s, err := directorysource.NewFromPath(path) require.NoError(t, err) @@ -109,6 +113,10 @@ func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester func (p *CatalogTester) FromFile(t *testing.T, path string) *CatalogTester { t.Helper() + if path == "" { + return p + } + fixture, err := os.Open(path) require.NoError(t, err) @@ -157,6 +165,11 @@ func (p *CatalogTester) WithResolver(r file.Resolver) *CatalogTester { func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *CatalogTester { t.Helper() + + if fixtureName == "" { + return p + } + img := imagetest.GetFixtureImage(t, "docker-archive", fixtureName) s := stereoscopesource.New(img, stereoscopesource.ImageConfig{ diff --git a/syft/pkg/cataloger/redhat/cataloger_test.go b/syft/pkg/cataloger/redhat/cataloger_test.go index 1dfc0487c..b7088346b 100644 --- a/syft/pkg/cataloger/redhat/cataloger_test.go +++ b/syft/pkg/cataloger/redhat/cataloger_test.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" _ "modernc.org/sqlite" "github.com/anchore/syft/syft/artifact" @@ -29,14 +30,15 @@ func Test_DBCataloger(t *testing.T) { FoundBy: "rpm-db-cataloger", PURL: "pkg:rpm/basesystem@11-13.el9?arch=noarch&upstream=basesystem-11-13.el9.src.rpm", Metadata: pkg.RpmDBEntry{ - Name: "basesystem", - Version: "11", - Arch: "noarch", - Release: "13.el9", - SourceRpm: "basesystem-11-13.el9.src.rpm", - Size: 0, - Vendor: "Rocky Enterprise Software Foundation", - Provides: []string{"basesystem"}, + Name: "basesystem", + Version: "11", + Arch: "noarch", + Release: "13.el9", + SourceRpm: "basesystem-11-13.el9.src.rpm", + Size: 0, + Vendor: "Rocky Enterprise Software Foundation", + Signatures: mustParseSignatures(t, "RSA/SHA256, Wed May 11 11:12:32 2022, Key ID 702d426d350d275d"), + Provides: []string{"basesystem"}, Requires: []string{ "filesystem", "rpmlib(CompressedFileNames)", @@ -65,6 +67,7 @@ func Test_DBCataloger(t *testing.T) { Release: "6.el9_1", SourceRpm: "bash-5.1.8-6.el9_1.src.rpm", Size: 7738634, + Signatures: mustParseSignatures(t, "RSA/SHA256, Mon Jan 23 22:49:22 2023, Key ID 702d426d350d275d"), ModularityLabel: strRef(""), Vendor: "Rocky Enterprise Software Foundation", Provides: []string{ @@ -117,6 +120,7 @@ func Test_DBCataloger(t *testing.T) { Release: "2.el9", SourceRpm: "filesystem-3.16-2.el9.src.rpm", Size: 106, + Signatures: mustParseSignatures(t, "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"), ModularityLabel: strRef(""), Vendor: "Rocky Enterprise Software Foundation", Provides: []string{ @@ -334,3 +338,9 @@ func Test_denySelfReferences(t *testing.T) { }) } } + +func mustParseSignatures(t testing.TB, sigs ...string) []pkg.RpmSignature { + signatures, err := parseSignatures(sigs...) + require.NoError(t, err) + return signatures +} diff --git a/syft/pkg/cataloger/redhat/parse_rpm_archive.go b/syft/pkg/cataloger/redhat/parse_rpm_archive.go index b019d8465..92118b91d 100644 --- a/syft/pkg/cataloger/redhat/parse_rpm_archive.go +++ b/syft/pkg/cataloger/redhat/parse_rpm_archive.go @@ -1,9 +1,14 @@ package redhat import ( + "bytes" "context" + "encoding/binary" "fmt" + "io" + "sort" "strconv" + "time" rpmdb "github.com/knqyf263/go-rpmdb/pkg" "github.com/sassoftware/go-rpmutils" @@ -15,6 +20,42 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger/generic" ) +type pgpSig struct { + _ [3]byte + Date int32 + KeyID [8]byte + PubKeyAlgo uint8 + HashAlgo uint8 +} + +type textSig struct { + _ [2]byte + PubKeyAlgo uint8 + HashAlgo uint8 + _ [4]byte + Date int32 + _ [4]byte + KeyID [8]byte +} + +type pgp4Sig struct { + _ [2]byte + PubKeyAlgo uint8 + HashAlgo uint8 + _ [17]byte + KeyID [8]byte + _ [2]byte + Date int32 +} + +var pubKeyLookup = map[uint8]string{ + 0x01: "RSA", +} +var hashLookup = map[uint8]string{ + 0x02: "SHA1", + 0x08: "SHA256", +} + // parseRpmArchive parses a single RPM func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { rpm, err := rpmutils.ReadRpm(reader) @@ -44,21 +85,140 @@ func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environmen files, err := rpm.Header.GetFiles() logRpmArchiveErr(reader.Location, "files", err) + rsa, err := rpm.Header.GetBytes(rpmutils.SIG_RSA) + logRpmArchiveErr(reader.Location, "rsa signature", err) + + pgp, err := rpm.Header.GetBytes(rpmutils.SIG_PGP) + logRpmArchiveErr(reader.Location, "pgp signature", err) + + var allSigs [][]byte + allSigs = append(allSigs, rsa) + allSigs = append(allSigs, pgp) + sigs, err := parseSignatureHeaders(allSigs) + logRpmArchiveErr(reader.Location, "signature", err) + metadata := pkg.RpmArchive{ - Name: nevra.Name, - Version: nevra.Version, - Epoch: parseEpoch(nevra.Epoch), - Arch: nevra.Arch, - Release: nevra.Release, - SourceRpm: sourceRpm, - Vendor: vendor, - Size: int(size), - Files: mapFiles(files, digestAlgorithm), + Name: nevra.Name, + Version: nevra.Version, + Epoch: parseEpoch(nevra.Epoch), + Arch: nevra.Arch, + Release: nevra.Release, + SourceRpm: sourceRpm, + Signatures: sigs, + Vendor: vendor, + Size: int(size), + Files: mapFiles(files, digestAlgorithm), } return []pkg.Package{newArchivePackage(ctx, reader.Location, metadata, licenses)}, nil, nil } +func parseSignatureHeaders(data [][]byte) ([]pkg.RpmSignature, error) { + sigMap := make(map[string]pkg.RpmSignature) + var keys []string + for _, sig := range data { + if len(sig) == 0 { + continue + } + s, err := parsePGP(sig) + if err != nil { + log.WithFields("error", err).Trace("unable to parse RPM archive signature") + return nil, err + } + k := s.String() + if _, ok := sigMap[k]; ok { + // if we have a duplicate signature, just skip it + continue + } + sigMap[k] = *s + keys = append(keys, k) + } + var signatures []pkg.RpmSignature + sort.Strings(keys) + for _, k := range keys { + signatures = append(signatures, sigMap[k]) + } + + return signatures, nil +} + +func parsePGP(data []byte) (*pkg.RpmSignature, error) { + var tag, signatureType, version uint8 + + r := bytes.NewReader(data) + err := binary.Read(r, binary.BigEndian, &tag) + if err != nil { + return nil, err + } + err = binary.Read(r, binary.BigEndian, &signatureType) + if err != nil { + return nil, err + } + err = binary.Read(r, binary.BigEndian, &version) + if err != nil { + return nil, err + } + + switch signatureType { + case 0x01: + switch version { + case 0x1c: + sig := textSig{} + err = binary.Read(r, binary.BigEndian, &sig) + if err != nil { + return nil, fmt.Errorf("invalid PGP signature on decode: %w", err) + } + return &pkg.RpmSignature{ + PublicKeyAlgorithm: pubKeyLookup[sig.PubKeyAlgo], + HashAlgorithm: hashLookup[sig.HashAlgo], + Created: time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006"), + IssuerKeyID: fmt.Sprintf("%x", sig.KeyID), + }, nil + default: + return decodePGPSig(version, r) + } + case 0x02: + return decodePGPSig(version, r) + } + + return nil, fmt.Errorf("unknown signature type: %d", signatureType) +} + +func decodePGPSig(version uint8, r io.Reader) (*pkg.RpmSignature, error) { + var pubKeyAlgo, hashAlgo, pkgDate string + var keyID [8]byte + + switch { + case version > 0x15: + sig := pgp4Sig{} + err := binary.Read(r, binary.BigEndian, &sig) + if err != nil { + return nil, fmt.Errorf("invalid PGP v4 signature on decode: %w", err) + } + pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo] + hashAlgo = hashLookup[sig.HashAlgo] + pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006") + keyID = sig.KeyID + + default: + sig := pgpSig{} + err := binary.Read(r, binary.BigEndian, &sig) + if err != nil { + return nil, fmt.Errorf("invalid PGP signature on decode: %w", err) + } + pubKeyAlgo = pubKeyLookup[sig.PubKeyAlgo] + hashAlgo = hashLookup[sig.HashAlgo] + pkgDate = time.Unix(int64(sig.Date), 0).UTC().Format("Mon Jan _2 15:04:05 2006") + keyID = sig.KeyID + } + return &pkg.RpmSignature{ + PublicKeyAlgorithm: pubKeyAlgo, + HashAlgorithm: hashAlgo, + Created: pkgDate, + IssuerKeyID: fmt.Sprintf("%x", keyID), + }, nil +} + func getDigestAlgorithm(location file.Location, header *rpmutils.RpmHeader) string { digestAlgorithm, err := header.GetString(rpmutils.FILEDIGESTALGO) logRpmArchiveErr(location, "file digest algo", err) @@ -109,6 +269,6 @@ func parseEpoch(epoch string) *int { func logRpmArchiveErr(location file.Location, operation string, err error) { if err != nil { - log.Debugf("ERROR in parse_rpm_archive %s file: %s: %v", operation, location.RealPath, err) + log.WithFields("error", err, "operation", operation, "path", location.RealPath).Trace("unable to parse RPM archive") } } diff --git a/syft/pkg/cataloger/redhat/parse_rpm_archive_test.go b/syft/pkg/cataloger/redhat/parse_rpm_archive_test.go index c7e5ba0fc..25ef254ef 100644 --- a/syft/pkg/cataloger/redhat/parse_rpm_archive_test.go +++ b/syft/pkg/cataloger/redhat/parse_rpm_archive_test.go @@ -2,8 +2,14 @@ package redhat import ( "context" + "encoding/hex" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" @@ -14,11 +20,15 @@ func TestParseRpmFiles(t *testing.T) { abcRpmLocation := file.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm") zorkRpmLocation := file.NewLocation("zork-1.0.3-1.el7.x86_64.rpm") tests := []struct { - fixture string - expected []pkg.Package + name string + fixtureDir string + fixtureImage string + skipFiles bool + expected []pkg.Package }{ { - fixture: "test-fixtures/rpms", + name: "go case", + fixtureDir: "test-fixtures/rpms", expected: []pkg.Package{ { Name: "abc", @@ -37,8 +47,16 @@ func TestParseRpmFiles(t *testing.T) { Release: "9.hg20160905.el7", Version: "1.01", SourceRpm: "abc-1.01-9.hg20160905.el7.src.rpm", - Size: 17396, - Vendor: "Fedora Project", + Signatures: []pkg.RpmSignature{ + { + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Wed Sep 21 07:09:44 2016", + IssuerKeyID: "6a2faea2352c64e5", + }, + }, + Size: 17396, + Vendor: "Fedora Project", Files: []pkg.RpmFileRecord{ {"/usr/bin/abc", 33261, 7120, file.Digest{"sha256", "8f8495a65c66762b60afa0c3949d81b275ca6fa0601696caba5af762f455d0b9"}, "root", "root", ""}, {"/usr/share/doc/abc-1.01", 16877, 4096, file.Digest{}, "root", "root", ""}, @@ -66,7 +84,15 @@ func TestParseRpmFiles(t *testing.T) { Version: "1.0.3", SourceRpm: "zork-1.0.3-1.el7.src.rpm", Size: 262367, - Vendor: "Fedora Project", + Signatures: []pkg.RpmSignature{ + { + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Tue Mar 2 17:32:21 2021", + IssuerKeyID: "6a2faea2352c64e5", + }, + }, + Vendor: "Fedora Project", Files: []pkg.RpmFileRecord{ {"/usr/bin/zork", 33261, 115440, file.Digest{"sha256", "31b2ffc20b676a8fff795a45308f584273b9c47e8f7e196b4f36220b2734b472"}, "root", "root", ""}, {"/usr/share/doc/zork-1.0.3", 16877, 38, file.Digest{}, "root", "root", ""}, @@ -83,20 +109,128 @@ func TestParseRpmFiles(t *testing.T) { }, }, { - fixture: "test-fixtures/bad", + name: "bad rpms", + fixtureDir: "test-fixtures/bad", + }, + { + name: "rpms with signatures from RSA header", + fixtureImage: "image-rpm-archive", + skipFiles: true, + expected: []pkg.Package{ + { + Name: "postgresql14-server", + Version: "0:14.10-1PGDG.rhel9", + PURL: "pkg:rpm/postgresql14-server@14.10-1PGDG.rhel9?arch=x86_64&epoch=0&upstream=postgresql14-14.10-1PGDG.rhel9.src.rpm", + Locations: file.NewLocationSet(file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm")), + FoundBy: "rpm-archive-cataloger", + Type: pkg.RpmPkg, + Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocations("PostgreSQL", file.NewLocation("/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm"))), + Language: "", + CPEs: nil, + Metadata: pkg.RpmArchive{ + Name: "postgresql14-server", + Version: "14.10", + Epoch: ref(0), + Arch: "x86_64", + Release: "1PGDG.rhel9", + SourceRpm: "postgresql14-14.10-1PGDG.rhel9.src.rpm", + Size: 24521699, + Signatures: []pkg.RpmSignature{ + { + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Tue Jan 2 16:45:56 2024", + IssuerKeyID: "40bca2b408b40d20", + }, + }, + Vendor: "PostgreSQL Global Development Group", + // note: files are not asserted in this test + }, + }, + }, }, } for _, test := range tests { - t.Run(test.fixture, func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { + var opts []cmp.Option + if test.skipFiles { + opts = append(opts, cmpopts.IgnoreFields(pkg.RpmArchive{}, "Files")) + } pkgtest.NewCatalogTester(). - FromDirectory(t, test.fixture). + WithCompareOptions(opts...). + FromDirectory(t, test.fixtureDir). + WithImageResolver(t, test.fixtureImage). + IgnoreLocationLayer(). Expects(test.expected, nil). TestCataloger(t, NewArchiveCataloger()) }) } } +func Test_parseRSA(t *testing.T) { + tests := []struct { + name string + data string + want *pkg.RpmSignature + wantErr assert.ErrorAssertionFunc + }{ + { + name: "older RSA header", + data: "89021503050058d3e39b0946fca2c105b9de0102b12a1000a2b3d347b51142e83b2de5e03ba9096f6330b72c140e46200d662b01c78534d14fab2ad4f07325119386830dd590219f27a22e420680283c500c40e6fba95404884b0a0abca8f198030ddc03653b7db2883b8230687e9e73d43eb5a24dbabfa48bbb3d1151ed264744e5e8ca169b0c4673a1440a9b99e53e693c9722f6423833cd7795e3044227fb922e21b7c007f03e923fae3f04d1ac2e8581e68c6790115b6dccfc02c8cb41681ed84785df086d6e26008c257d088a524ba2e7a7a5f41ad26b106c67b87fe48118b69662db612c23d2140059286f1ba7764627def6867ad0e11fe3a01fb1422dabe6f5cdf4cd876dc4fadfd2364bc3ba3758db94aaf3b82368cba65cf762287f713eb7ddc773acf93b083c739577a7eaf1f99e7dcbb8db1da050490e9fb67c838448db060a9e619d318c96f03e4363808d84ce29e8c102c290cc2bfab5746f3d9ddc9eb8b428f3ad2678abb2d46e846ddca7fc41322d76a97be6d416b4750f23320ec725e082be4496483b4cd3a3d2c515b3c8a6e27541139d809245140303877b84842ed2dd0454a78b2dfb7d6213784697077a8167942ebda5995a28d8256957e33e301706c35944ae05c7a54a4dd89be654d26cefa5cf0f616bbeaf317138371b09c5bbd5531f716020e553354ce5dbce3d9bb72f21e1857408dfd5a35250ff40f61ae1e25409ae9d21db76b8878341f4762a22be2189", + // RSA/SHA1, Thu Mar 23 15:02:51 2017, Key ID 0946fca2c105b9de + want: &pkg.RpmSignature{ + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA1", + Created: "Thu Mar 23 15:02:51 2017", + IssuerKeyID: "0946fca2c105b9de", + }, + }, + { + name: "newer RSA header", + data: "89024a04000108003416210421cb256ae16fc54c6e652949702d426d350d275d050262804369161c72656c656e6740726f636b796c696e75782e6f7267000a0910702d426d350d275dc8910ffd14f0f80297481fea648e7ba5a74bce10c5faccc2bbe588caece04be34d304a6a445538afc97a7033d43c983d27cc8f5ee515b2dd92f3e03354c413e55372a4d19386eb0f2354f9a26ee5fc2e56dfda49555e4a58b49279b70cd2036b04f28125f85942f640f2984e29e079f26bf6f76831d83d95983aa084a3e7b6327be2e23d0d799c4b4d1cfb36147ddfb782bf9df7b331d97f4f46b38f968b6130d87b0ef6bb0d424390fe34e38092babed37440569a93f55f50a2bdb58be0259f35badf7e728bd49824ed47f69fa53b6e26736bde4d8358d959b090e88054c3e179745dc7377e41b54b4e10223f4859e88162c7c5ec64b78d36cf8a914c1c2deb8c4f19a70d406e70756a89195d6aee488a9b40b9dbb76b2c38e528eb88d08ec35774a48ed9ce4e0dfac45cb7613ad5921f54c61d3aae5d7b3ab0e2e6ff867ac8f395b37af78b5c01022a4a4e62f7a99425fccb7439880cd6b393a3050b2e9512693bc36f6fe9de2921dda59710a1508965065244cf9f0f8cfc5bd554777f1a84d2249339234d62f2441249f617ad7df4fb01367a91d3a880e86fdb84bc6d03a127b44a28c6ceadef89e438db9640aa59b8a3f460b07272511f8187a5f3b163c8fd1caa61667401bce2ccdb1c176c46be10ef8033903132cca5889fa3661b2fba590c41fa1c104c08426677bdbf745a52ccd28f581960cf9d7e4ede3b9584aacb2f20ef93", + // RSA/SHA256, Sun May 15 00:03:53 2022, Key ID 702d426d350d275d + want: &pkg.RpmSignature{ + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Sun May 15 00:03:53 2022", + IssuerKeyID: "702d426d350d275d", + }, + }, + { + name: "example from rocky9", + data: "8901b304000108001d162104d4bf08ae67a0b4c7a1dbccd240bca2b408b40d20050265943dc4000a091040bca2b408b40d203b270bff71678ffeb190833a19a82112f59eee64cba186ab454d4526e0b3c8797e723f6916daff1b1f18cbf53c0da5d398a3a42065e79e5ca939f721652f38400dd4cac1107a902b1dae880649437ad0242444f3f07115172cae0a207b7cf8340af2f4a94976325f1dc165d5c2a564be322c4e130adb6217e7138b689f08898c407b223aa1ff8f8d592f31eba2256c02fae70ce4022d688a487972646b8bf1b518b5d6549c1e60fd812134422d9fdb41cf799f5eab80e48b4ab7cff84362dc867ed1af1416dd78e92bcc59217de7064b9a015d94a5097788689b9b6fbdeea679cfe4a6947f73dc3a6c810f2cb999d279b01564422d1500fc1bd8bd1eefa2d60660127ffef24067354660f93c0faf81f4edd599dd7e4b77fe4bff6c7a0ea83530c817c38d1f2364175883c6ef7b6dec86ad282bdd5138b8597567db96810c4ed6454a4ab1d98f0425dcd8892a5d46ed9289cb3ae3e1f1e2663d3e8188e873428f6cf7163563ed3860edc4fee81522389508847e692e2d13310eb4b40f7fdd7eb364a0b2dc", + // RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20 + want: &pkg.RpmSignature{ + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Tue Jan 2 16:45:56 2024", + IssuerKeyID: "40bca2b408b40d20", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = assert.NoError + } + + data, err := hex.DecodeString(tt.data) + require.NoError(t, err) + + got, err := parsePGP(data) + if !tt.wantErr(t, err) { + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func ref[T any](v T) *T { + return &v +} + func Test_corruptRpmArchive(t *testing.T) { pkgtest.NewCatalogTester(). FromFile(t, "test-fixtures/bad/bad.rpm"). diff --git a/syft/pkg/cataloger/redhat/parse_rpm_db.go b/syft/pkg/cataloger/redhat/parse_rpm_db.go index 856315425..6ebabebdf 100644 --- a/syft/pkg/cataloger/redhat/parse_rpm_db.go +++ b/syft/pkg/cataloger/redhat/parse_rpm_db.go @@ -2,9 +2,11 @@ package redhat import ( "context" + "errors" "fmt" "io" "os" + "strings" rpmdb "github.com/knqyf263/go-rpmdb/pkg" @@ -69,6 +71,14 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro files, err := extractRpmFileRecords(resolver, *entry) errs = unknown.Join(errs, err) + // there is a period of time when RPM DB entries contain both PGP and RSA signatures that are the same. + // This appears to be a holdover, where nowadays only the RSA Header is used. + sigs, err := parseSignatures(strings.TrimSpace(entry.PGP), strings.TrimSpace(entry.RSAHeader)) + if err != nil { + log.WithFields("error", err, "location", reader.RealPath, "pkg", fmt.Sprintf("%s@%s", entry.Name, entry.Version)).Trace("unable to parse signatures for package %s", entry.Name) + sigs = nil + } + metadata := pkg.RpmDBEntry{ Name: entry.Name, Version: entry.Version, @@ -76,6 +86,7 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro Arch: entry.Arch, Release: entry.Release, SourceRpm: entry.SourceRpm, + Signatures: sigs, Vendor: entry.Vendor, Size: entry.Size, ModularityLabel: &entry.Modularitylabel, @@ -110,6 +121,70 @@ func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Enviro return allPkgs, nil, errs } +func parseSignatures(sigs ...string) ([]pkg.RpmSignature, error) { + var parsedSigs []pkg.RpmSignature + var errs error + for _, sig := range sigs { + if sig == "" { + continue + } + parts := strings.Split(sig, ",") + if len(parts) != 3 { + errs = errors.Join(fmt.Errorf("invalid signature format: %s", sig)) + continue + } + + methodParts := strings.SplitN(strings.TrimSpace(parts[0]), "/", 2) + if len(methodParts) != 2 { + errs = errors.Join(fmt.Errorf("invalid signature method format: %s", parts[0])) + continue + } + + pka := strings.TrimSpace(methodParts[0]) + hash := strings.TrimSpace(methodParts[1]) + + if pka == "" || hash == "" { + errs = errors.Join(fmt.Errorf("invalid signature method values: public-key=%q hash=%q", pka, hash)) + continue + } + + created := strings.TrimSpace(parts[1]) + if created == "" { + errs = errors.Join(fmt.Errorf("invalid signature created value: %q", parts[1])) + continue + } + + issuerFields := strings.Split(strings.TrimSpace(parts[2]), " ") + var issuer string + switch len(issuerFields) { + case 0: + errs = errors.Join(fmt.Errorf("no signature issuer value: %q", parts[2])) + case 1: + issuer = issuerFields[0] + default: + issuer = issuerFields[len(issuerFields)-1] + if issuer == "" { + errs = errors.Join(fmt.Errorf("invalid signature issuer value: %q", parts[2])) + continue + } + } + + if len(issuer) < 5 { + errs = errors.Join(fmt.Errorf("invalid signature issuer length: %q", parts[2])) + continue + } + + parsedSig := pkg.RpmSignature{ + PublicKeyAlgorithm: pka, + HashAlgorithm: hash, + Created: created, + IssuerKeyID: issuer, + } + parsedSigs = append(parsedSigs, parsedSig) + } + return parsedSigs, errs +} + // The RPM naming scheme is [name]-[version]-[release]-[arch], where version is implicitly expands to [epoch]:[version]. // RPM version comparison depends on comparing at least the version and release fields together as a subset of the // naming scheme. This toELVersion function takes a RPM DB package information and converts it into a minimally comparable diff --git a/syft/pkg/cataloger/redhat/parse_rpm_db_test.go b/syft/pkg/cataloger/redhat/parse_rpm_db_test.go index 02afa6641..0f0337565 100644 --- a/syft/pkg/cataloger/redhat/parse_rpm_db_test.go +++ b/syft/pkg/cataloger/redhat/parse_rpm_db_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" @@ -222,6 +223,132 @@ func Test_corruptRpmDbEntry(t *testing.T) { TestParser(t, parseRpmDB) } +func TestParseSignatures(t *testing.T) { + tests := []struct { + name string + sigs []string + expected []pkg.RpmSignature + expectedError require.ErrorAssertionFunc + }{ + { + name: "valid signature", + sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"}, + expected: []pkg.RpmSignature{ + { + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Mon May 16 12:32:55 2022", + IssuerKeyID: "702d426d350d275d", + }, + }, + }, + { + name: "multiple valid signatures", + sigs: []string{ + "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d", + "DSA/SHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789", + }, + expected: []pkg.RpmSignature{ + { + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Mon May 16 12:32:55 2022", + IssuerKeyID: "702d426d350d275d", + }, + { + PublicKeyAlgorithm: "DSA", + HashAlgorithm: "SHA1", + Created: "Tue Jun 14 09:45:12 2023", + IssuerKeyID: "123abc456def789", + }, + }, + }, + { + name: "no signatures", + sigs: []string{}, + expected: nil, + }, + { + name: "empty signatures", + sigs: []string{"", "", ""}, + expected: nil, + }, + { + name: "invalid parts count", + sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022"}, + expected: nil, + expectedError: require.Error, + }, + { + name: "invalid method format", + sigs: []string{"RSASHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"}, + expected: nil, + expectedError: require.Error, + }, + { + name: "empty method values", + sigs: []string{"/, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"}, + expected: nil, + expectedError: require.Error, + }, + { + name: "empty created value", + sigs: []string{"RSA/SHA256, , Key ID 702d426d350d275d"}, + expected: nil, + expectedError: require.Error, + }, + { + name: "empty issuer value", + sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID "}, + expected: nil, + expectedError: require.Error, + }, + { + name: "issuer without prefix", + sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, 702d426d350d275d"}, + expected: []pkg.RpmSignature{ + { + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Mon May 16 12:32:55 2022", + IssuerKeyID: "702d426d350d275d", + }, + }, + }, + { + name: "mixed valid and invalid signatures", + sigs: []string{ + "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d", + "DSASHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789", + }, + expected: []pkg.RpmSignature{ + { + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Mon May 16 12:32:55 2022", + IssuerKeyID: "702d426d350d275d", + }, + }, + expectedError: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.expectedError == nil { + tt.expectedError = require.NoError + } + got, err := parseSignatures(tt.sigs...) + tt.expectedError(t, err) + if err != nil { + return + } + + require.Equal(t, tt.expected, got) + }) + } +} + func intRef(i int) *int { return &i } diff --git a/syft/pkg/cataloger/redhat/test-fixtures/image-rpm-archive/Dockerfile b/syft/pkg/cataloger/redhat/test-fixtures/image-rpm-archive/Dockerfile new file mode 100644 index 000000000..0e972ecf5 --- /dev/null +++ b/syft/pkg/cataloger/redhat/test-fixtures/image-rpm-archive/Dockerfile @@ -0,0 +1,26 @@ + +FROM --platform=linux/amd64 rockylinux:9 AS rpm-downloader + +# download a signed RPM (PostgreSQL in this example, known to be signed) +# using PostgreSQL official RPM which is signed with their GPG key +# $ rpm -Kv postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm +# postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm: +# Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY +# Header SHA256 digest: OK +# Header SHA1 digest: OK +# Payload SHA256 digest: OK +# MD5 digest: OK +# +# $ rpm -ivh --nodeps --force postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm +# warning: postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm: Header V4 RSA/SHA256 Signature, key ID 08b40d20: NOKEY +# Verifying... ################################# [100%] +# Preparing... ################################# [100%] +# +# $ rpm -q --qf '%{NAME}-%{VERSION}-%{RELEASE} %{RSAHEADER:pgpsig}\n' postgresql14-server-14.10-1PGDG.rhel9.x86_64 +# postgresql14-server-14.10-1PGDG.rhel9 RSA/SHA256, Tue Jan 2 16:45:56 2024, Key ID 40bca2b408b40d20 + +RUN curl -O https://download.postgresql.org/pub/repos/yum/14/redhat/rhel-9-x86_64/postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm + +FROM scratch + +COPY --from=rpm-downloader /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm /postgresql14-server-14.10-1PGDG.rhel9.x86_64.rpm \ No newline at end of file diff --git a/syft/pkg/rpm.go b/syft/pkg/rpm.go index 4773403f4..b11205c1b 100644 --- a/syft/pkg/rpm.go +++ b/syft/pkg/rpm.go @@ -2,6 +2,7 @@ package pkg import ( "sort" + "strings" "github.com/scylladb/go-set/strset" @@ -32,6 +33,7 @@ type RpmDBEntry struct { Arch string `json:"architecture"` Release string `json:"release" cyclonedx:"release"` SourceRpm string `json:"sourceRpm" cyclonedx:"sourceRpm"` + Signatures []RpmSignature `json:"signatures,omitempty" cyclonedx:"signatures"` Size int `json:"size" cyclonedx:"size"` Vendor string `json:"vendor"` ModularityLabel *string `json:"modularityLabel,omitempty"` @@ -40,6 +42,22 @@ type RpmDBEntry struct { Files []RpmFileRecord `json:"files"` } +type RpmSignature struct { + PublicKeyAlgorithm string `json:"algo"` + HashAlgorithm string `json:"hash"` + Created string `json:"created"` + IssuerKeyID string `json:"issuer"` +} + +func (s RpmSignature) String() string { + if s.PublicKeyAlgorithm == "" && s.HashAlgorithm == "" && s.Created == "" && s.IssuerKeyID == "" { + return "" + } + // mimics the output you would see from rpm -q --qf "%{RSAHEADER}" + // e.g."RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d" + return strings.Join([]string{s.PublicKeyAlgorithm + "/" + s.HashAlgorithm, s.Created, "Key ID " + s.IssuerKeyID}, ", ") +} + // RpmFileRecord represents the file metadata for a single file attributed to a RPM package. type RpmFileRecord struct { Path string `json:"path"` diff --git a/syft/pkg/rpm_test.go b/syft/pkg/rpm_test.go index 90d3bb685..f0fa5f36e 100644 --- a/syft/pkg/rpm_test.go +++ b/syft/pkg/rpm_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/go-test/deep" + "github.com/stretchr/testify/assert" ) func TestRpmMetadata_FileOwner(t *testing.T) { @@ -46,3 +47,49 @@ func TestRpmMetadata_FileOwner(t *testing.T) { }) } } + +func TestRpmSignature_String(t *testing.T) { + tests := []struct { + name string + signature RpmSignature + expected string + }{ + { + name: "standard signature", + signature: RpmSignature{ + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "SHA256", + Created: "Mon May 16 12:32:55 2022", + IssuerKeyID: "702d426d350d275d", + }, + expected: "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d", + }, + { + name: "empty fields", + signature: RpmSignature{ + PublicKeyAlgorithm: "", + HashAlgorithm: "", + Created: "", + IssuerKeyID: "", + }, + expected: "", + }, + { + name: "partial empty fields", + signature: RpmSignature{ + PublicKeyAlgorithm: "RSA", + HashAlgorithm: "", + Created: "Mon May 16 12:32:55 2022", + IssuerKeyID: "", + }, + expected: "RSA/, Mon May 16 12:32:55 2022, Key ID ", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.signature.String() + assert.Equal(t, tt.expected, result) + }) + } +}