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) + }) + } +}