Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package gitsign for openSUSE:Factory checked in at 2026-06-10 16:15:00 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/gitsign (Old) and /work/SRC/openSUSE:Factory/.gitsign.new.2375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "gitsign" Wed Jun 10 16:15:00 2026 rev:14 rq:1358520 version:0.16.1 Changes: -------- --- /work/SRC/openSUSE:Factory/gitsign/gitsign.changes 2026-05-07 15:44:53.438913544 +0200 +++ /work/SRC/openSUSE:Factory/.gitsign.new.2375/gitsign.changes 2026-06-10 16:19:03.773140144 +0200 @@ -1,0 +2,12 @@ +Wed Jun 10 10:59:38 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.16.1: + * Bump sigstore/cosign-installer from 4.1.1 to 4.1.2 in the + actions group (#804) + * Return the actual signer cert from CertVerifier.Verify (#810) + * Updates (#803) + * Reject malformed objects in attest predicate generation (#809) + * Bump github.com/go-openapi/strfmt in the gomod group across 1 + directory (#811) + +------------------------------------------------------------------- Old: ---- gitsign-0.16.0.obscpio New: ---- gitsign-0.16.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ gitsign.spec ++++++ --- /var/tmp/diff_new_pack.XmlspJ/_old 2026-06-10 16:19:05.793223858 +0200 +++ /var/tmp/diff_new_pack.XmlspJ/_new 2026-06-10 16:19:05.797224024 +0200 @@ -17,7 +17,7 @@ Name: gitsign -Version: 0.16.0 +Version: 0.16.1 Release: 0 Summary: Keyless Git signing using Sigstore License: Apache-2.0 @@ -26,7 +26,7 @@ Source1: vendor.tar.gz Source2: gitsign-credential-cache.service Source3: gitsign-credential-cache.socket -BuildRequires: go1.25 >= 1.25.5 +BuildRequires: go1.26 >= 1.26.4 %description Keyless Git signing with Sigstore! ++++++ _service ++++++ --- /var/tmp/diff_new_pack.XmlspJ/_old 2026-06-10 16:19:05.837225682 +0200 +++ /var/tmp/diff_new_pack.XmlspJ/_new 2026-06-10 16:19:05.845226013 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/sigstore/gitsign</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.16.0</param> + <param name="revision">v0.16.1</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.XmlspJ/_old 2026-06-10 16:19:05.881227505 +0200 +++ /var/tmp/diff_new_pack.XmlspJ/_new 2026-06-10 16:19:05.885227671 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/sigstore/gitsign</param> - <param name="changesrevision">3c84d87240644b5c62b3b3d79177ae64448591c8</param></service></servicedata> + <param name="changesrevision">d5e8a584d0551218fc96b5c89c0db910788b432a</param></service></servicedata> (No newline at EOF) ++++++ gitsign-0.16.0.obscpio -> gitsign-0.16.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/go.mod new/gitsign-0.16.1/go.mod --- old/gitsign-0.16.0/go.mod 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/go.mod 2026-05-15 21:07:37.000000000 +0200 @@ -1,15 +1,15 @@ module github.com/sigstore/gitsign -go 1.25.7 +go 1.26 require ( github.com/coreos/go-oidc/v3 v3.18.0 github.com/coreos/go-systemd/v22 v22.7.0 github.com/github/smimesign v0.2.0 - github.com/go-git/go-billy/v5 v5.8.0 - github.com/go-git/go-git/v5 v5.18.0 + github.com/go-git/go-billy/v5 v5.9.0 + github.com/go-git/go-git/v5 v5.19.0 github.com/go-openapi/runtime v0.29.4 - github.com/go-openapi/strfmt v0.26.1 + github.com/go-openapi/strfmt v0.26.2 github.com/go-openapi/swag/conv v0.26.0 github.com/google/go-cmp v0.7.0 github.com/in-toto/attestation v1.2.0 @@ -25,7 +25,7 @@ github.com/sigstore/sigstore v1.10.5 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - golang.org/x/crypto v0.50.0 + golang.org/x/crypto v0.51.0 golang.org/x/oauth2 v0.36.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da google.golang.org/protobuf v1.36.11 @@ -60,7 +60,7 @@ github.com/Azure/go-autorest/tracing v0.6.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect @@ -181,7 +181,7 @@ github.com/jellydator/ttlcache/v3 v3.4.0 // indirect github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -203,7 +203,7 @@ github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect @@ -251,12 +251,12 @@ go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.272.0 // indirect google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/go.sum new/gitsign-0.16.1/go.sum --- old/gitsign-0.16.0/go.sum 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/go.sum 2026-05-15 21:07:37.000000000 +0200 @@ -78,8 +78,8 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= -github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= @@ -290,12 +290,12 @@ github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= -github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= +github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= -github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= +github.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc= +github.com/go-git/go-git/v5 v5.19.0/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -319,8 +319,8 @@ github.com/go-openapi/runtime v0.29.4/go.mod h1:K0k/2raY6oqXJnZAgWJB2i/12QKrhUKpZcH4PfV9P18= github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ= github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ= -github.com/go-openapi/strfmt v0.26.1 h1:7zGCHji7zSYDC2tCXIusoxYQz/48jAf2q+sF6wXTG+c= -github.com/go-openapi/strfmt v0.26.1/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y= +github.com/go-openapi/strfmt v0.26.2 h1:ysjheCh4i1rmFEo2LanhELDNucNzfWTZhUDKgWWPaFM= +github.com/go-openapi/strfmt v0.26.2/go.mod h1:fXh1e449cyUn2NYuz+wb3wARBUdMl7qPEZwX00nqivY= github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU= github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA= github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c= @@ -504,8 +504,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= -github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= +github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -595,8 +595,8 @@ github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= -github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= +github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -798,8 +798,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -812,8 +812,8 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -839,8 +839,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -892,8 +892,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -905,8 +905,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -918,8 +918,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -938,8 +938,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/internal/fork/ietf-cms/verify.go new/gitsign-0.16.1/internal/fork/ietf-cms/verify.go --- old/gitsign-0.16.0/internal/fork/ietf-cms/verify.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/internal/fork/ietf-cms/verify.go 2026-05-15 21:07:37.000000000 +0200 @@ -5,6 +5,7 @@ "crypto/x509" "encoding/asn1" "errors" + "time" "github.com/github/smimesign/ietf-cms/protocol" "github.com/sigstore/sigstore/pkg/cryptoutils" @@ -180,6 +181,16 @@ } } + // If neither the caller nor a timestamp pinned a verification time, + // fall back to this signer cert's NotBefore so the validity-window + // check passes. The caller is expected to verify the actual signing + // time independently (e.g. via Rekor). This is done per-cert so that + // multiple SignerInfos with different validity windows each get a + // time that lies within their own window. + if optsCopy.CurrentTime.IsZero() { + optsCopy.CurrentTime = cert.NotBefore.Add(1 * time.Minute) + } + if chain, err := cert.Verify(optsCopy); err != nil { return nil, err } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/internal/gitsign/invalid_object_test.go new/gitsign-0.16.1/internal/gitsign/invalid_object_test.go --- old/gitsign-0.16.0/internal/gitsign/invalid_object_test.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/internal/gitsign/invalid_object_test.go 2026-05-15 21:07:37.000000000 +0200 @@ -33,19 +33,24 @@ "github.com/sigstore/gitsign/pkg/git" ) -// TestDuplicateTreeTrustConfusion is an end-to-end reproduction of a parser -// trust-confusion attack: an attacker replays a legitimate signature against -// a malformed commit with two tree headers. go-git's loose parser keeps the -// last tree (so a signature over that canonical form verifies) while git-core -// and the on-disk hash reflect the first. The test: +// TestDuplicateTreeTrustConfusion is an end-to-end regression test for the +// parser trust-confusion class of attack: an attacker replays a legitimate +// signature against a malformed commit with two tree headers. Historically, +// go-git's loose parser was last-wins (keeping the second tree, which +// happened to match the signed canonical form) while git-core was +// first-wins, opening a re-encoding window that let the signature verify +// against attacker-controlled raw bytes. // -// 1. Confirms the crafted signature really does verify against the re-encoded -// (last-wins) bytes — proving the PoC is a working forgery, not a typo. -// 2. Confirms the fix: when the verifier is fed the raw object bytes (the -// SplitCommit output), the signature fails to verify because the bytes -// differ from what was signed. -// 3. Confirms a well-formed commit with the same signature still verifies — -// guards against an over-aggressive verifier path. +// The defense lives in two layers now: +// 1. Upstream go-git ≥ v5.19.0 switched to first-wins matching git-core, so +// re-encoding through go-git no longer produces canonical bytes. +// 2. gitsign's verify path consumes raw object bytes via SplitCommit and +// skips go-git entirely, so any divergence between signed and stored +// bytes fails cryptographically regardless of parser fashion. +// +// The sub-tests assert all three properties: upstream is fixed, raw-byte +// verification still rejects malformed input, and well-formed input still +// passes (no over-aggressive rejection). func TestDuplicateTreeTrustConfusion(t *testing.T) { cert, priv := generateCert(t, &x509.Certificate{ SerialNumber: big.NewInt(1), @@ -94,16 +99,20 @@ message, )) - t.Run("signature is a genuine forgery against the re-encoded form", func(t *testing.T) { - // Simulate the pre-fix behavior: load the malformed commit through - // go-git and re-encode it via EncodeWithoutSignature (which drops the - // first tree under last-wins), then verify. This MUST succeed, - // otherwise the PoC isn't actually demonstrating the attack and the - // rejection assertion below would pass vacuously. + t.Run("upstream go-git is no longer last-wins", func(t *testing.T) { + // go-git ≥ v5.19.0 rewrote its commit decoder as a state machine + // that takes the first tree (matching git-core). Re-encoding the + // malformed bytes therefore carries attackerTree, not legitTree, + // so the signature — made over a single-tree commit at legitTree — + // no longer verifies against the re-encoded form. If this ever + // starts passing, go-git has regressed to last-wins parsing and + // the re-encoding attack window is open at the upstream layer + // again (gitsign's own SplitCommit-based path still blocks it, + // per the next sub-test). reencoded := reencodeViaGoGit(t, malformedRaw) - if _, err := gv.Verify(context.Background(), reencoded, resp.Signature, true); err != nil { - t.Fatalf("pre-fix behavior check: expected signature to verify over re-encoded bytes (proves the PoC is genuine), got: %v", err) + if _, err := gv.Verify(context.Background(), reencoded, resp.Signature, true); err == nil { + t.Fatalf("upstream regression: go-git re-encoded malformed bytes verified against the canonical signature") } }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/attest/statement.go new/gitsign-0.16.1/pkg/attest/statement.go --- old/gitsign-0.16.0/pkg/attest/statement.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/attest/statement.go 2026-05-15 21:07:37.000000000 +0200 @@ -23,7 +23,9 @@ "github.com/github/smimesign/ietf-cms/protocol" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" intoto "github.com/in-toto/attestation/go/v1" + gitraw "github.com/sigstore/gitsign/pkg/git" "github.com/sigstore/gitsign/pkg/predicate" "github.com/sigstore/sigstore/pkg/cryptoutils" "google.golang.org/protobuf/encoding/protojson" @@ -41,7 +43,14 @@ if err != nil { return nil, err } - commit, err := repo.CommitObject(*hash) + obj, err := repo.Storer.EncodedObject(plumbing.CommitObject, *hash) + if err != nil { + return nil, err + } + if err := gitraw.ValidateCommit(obj); err != nil { + return nil, err + } + commit, err := object.DecodeCommit(repo.Storer, obj) if err != nil { return nil, err } @@ -138,11 +147,19 @@ return nil, err } - // Try to get the tag object. If this fails, the tag is not annotated. - tagObj, err := repo.TagObject(ref.Hash()) + // Load the tag object. If the storer doesn't have one (lightweight + // tag), surface the existing "not an annotated tag" error. + obj, err := repo.Storer.EncodedObject(plumbing.TagObject, ref.Hash()) if err != nil { return nil, fmt.Errorf("tag %q is not an annotated tag", tagName) } + if err := gitraw.ValidateTag(obj); err != nil { + return nil, err + } + tagObj, err := object.DecodeTag(repo.Storer, obj) + if err != nil { + return nil, err + } // We've got the annotated tag. Create the full predicate pred := &predicate.GitTag{ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/attest/statement_test.go new/gitsign-0.16.1/pkg/attest/statement_test.go --- old/gitsign-0.16.0/pkg/attest/statement_test.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/attest/statement_test.go 2026-05-15 21:07:37.000000000 +0200 @@ -15,6 +15,7 @@ package attest import ( + "errors" "fmt" "os" "testing" @@ -25,12 +26,13 @@ "github.com/go-git/go-git/v5/storage/memory" "github.com/google/go-cmp/cmp" intoto "github.com/in-toto/attestation/go/v1" + gitraw "github.com/sigstore/gitsign/pkg/git" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/testing/protocmp" ) // newTestRepo creates an in-memory repo with the given remote configuration. -func newTestRepo(t *testing.T, remoteURL string) (*git.Repository, *memory.Storage) { +func newTestRepo(t *testing.T) (*git.Repository, *memory.Storage) { t.Helper() storage := memory.NewStorage() repo := &git.Repository{ @@ -40,7 +42,7 @@ Remotes: map[string]*config.RemoteConfig{ "origin": { Name: "origin", - URLs: []string{remoteURL}, + URLs: []string{"https://github.com/sigstore/gitsign.git"}, }, }, }); err != nil { @@ -69,7 +71,7 @@ } func TestCommitStatement(t *testing.T) { - repo, storage := newTestRepo(t, "[email protected]:wlynch/gitsign.git") + repo, storage := newTestRepo(t) // Expect files in testdata directory: // foo.in.txt -> foo.out.json @@ -109,7 +111,7 @@ } func TestTagStatement(t *testing.T) { - repo, storage := newTestRepo(t, "[email protected]:wlynch/gitsign.git") + repo, storage := newTestRepo(t) // IMPORTANT: When generating new test files, use `git cat-file tag <tagname> > foo.in.txt`. for _, tc := range []string{ @@ -150,8 +152,53 @@ } } +// TestCommitStatement_DuplicateAuthor confirms the attest path refuses +// to produce a predicate for a commit with duplicate singleton headers. +// `git hash-object --literally` (and an adversary's direct object write) +// can produce these; go-git ≥ v5.19.0 would silently take the first +// header, but the attestation use case prefers an outright refusal over +// a predicate that obscures the ambiguity. +func TestCommitStatement_DuplicateAuthor(t *testing.T) { + repo, storage := newTestRepo(t) + + raw := []byte("tree 4cf9f177c4c015836fca6a31f9c3917e89ae29ec\n" + + "author Alice <[email protected]> 1700000000 +0000\n" + + "author Mallory <[email protected]> 1700000001 +0000\n" + + "committer Alice <[email protected]> 1700000000 +0000\n" + + "\n" + + "hello\n") + h := storeObject(t, storage, plumbing.CommitObject, raw) + + _, err := CommitStatement(repo, "origin", h.String()) + if !errors.Is(err, gitraw.ErrMalformedObject) { + t.Fatalf("CommitStatement: got err=%v, want ErrMalformedObject", err) + } +} + +func TestTagStatement_DuplicateTagger(t *testing.T) { + repo, storage := newTestRepo(t) + + raw := []byte("object 2d9cff2bad7132c586e128bcc23322dbb5297e8e\n" + + "type commit\n" + + "tag v1\n" + + "tagger Alice <[email protected]> 1700000000 +0000\n" + + "tagger Mallory <[email protected]> 1700000001 +0000\n" + + "\n" + + "release\n") + h := storeObject(t, storage, plumbing.TagObject, raw) + tagRef := plumbing.NewHashReference(plumbing.NewTagReferenceName("v1"), h) + if err := storage.SetReference(tagRef); err != nil { + t.Fatalf("error setting tag reference: %v", err) + } + + _, err := TagStatement(repo, "origin", "v1") + if !errors.Is(err, gitraw.ErrMalformedObject) { + t.Fatalf("TagStatement: got err=%v, want ErrMalformedObject", err) + } +} + func TestTagStatementLightweight(t *testing.T) { - repo, storage := newTestRepo(t, "[email protected]:wlynch/gitsign.git") + repo, storage := newTestRepo(t) // Store a commit object so the lightweight tag has something to point to. raw, err := os.ReadFile("testdata/fulcio-cert.in.txt") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/attest/testdata/fulcio-cert.out.json new/gitsign-0.16.1/pkg/attest/testdata/fulcio-cert.out.json --- old/gitsign-0.16.0/pkg/attest/testdata/fulcio-cert.out.json 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/attest/testdata/fulcio-cert.out.json 2026-05-15 21:07:37.000000000 +0200 @@ -3,7 +3,7 @@ "predicateType": "https://gitsign.sigstore.dev/predicate/git/v0.1", "subject": [ { - "name": "[email protected]:wlynch/gitsign.git", + "name": "https://github.com/sigstore/gitsign.git", "digest": { "sha1": "10a3086104c5331623be85a5e30d709f457b536b" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/attest/testdata/fulcio-tag.out.json new/gitsign-0.16.1/pkg/attest/testdata/fulcio-tag.out.json --- old/gitsign-0.16.0/pkg/attest/testdata/fulcio-tag.out.json 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/attest/testdata/fulcio-tag.out.json 2026-05-15 21:07:37.000000000 +0200 @@ -3,7 +3,7 @@ "predicateType": "https://gitsign.sigstore.dev/predicate/tag/v0.1", "subject": [ { - "name": "[email protected]:wlynch/gitsign.git", + "name": "https://github.com/sigstore/gitsign.git", "digest": { "sha1": "59b806d0c7932446ca9375b36ea3d421cc673224", "gitTag": "59b806d0c7932446ca9375b36ea3d421cc673224" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/attest/testdata/gpg.out.json new/gitsign-0.16.1/pkg/attest/testdata/gpg.out.json --- old/gitsign-0.16.0/pkg/attest/testdata/gpg.out.json 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/attest/testdata/gpg.out.json 2026-05-15 21:07:37.000000000 +0200 @@ -3,7 +3,7 @@ "predicateType": "https://gitsign.sigstore.dev/predicate/git/v0.1", "subject": [ { - "name": "[email protected]:wlynch/gitsign.git", + "name": "https://github.com/sigstore/gitsign.git", "digest": { "sha1": "262c05491554c57ee641461f315bf4023d0e93c7" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/git/rawobj.go new/gitsign-0.16.1/pkg/git/rawobj.go --- old/gitsign-0.16.0/pkg/git/rawobj.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/git/rawobj.go 2026-05-15 21:07:37.000000000 +0200 @@ -22,6 +22,8 @@ "fmt" "io" "unicode" + + "github.com/go-git/go-git/v5/plumbing" ) var ( @@ -389,3 +391,107 @@ } return b.Bytes() } + +// commitSingletons / tagSingletons name the headers a well-formed object +// carries at most once. parent is intentionally absent from the commit set +// (merge commits have several); mergetag and encoding are intentionally +// absent because git accepts repetition of mergetag and we don't audit +// encoding for uniqueness. gpgsig / gpgsig-sha256 are included so the +// attest path matches SplitCommit's duplicate-signature rejection on the +// verify path. +var commitSingletons = map[string]bool{ + "tree": true, + "author": true, + "committer": true, + "gpgsig": true, + "gpgsig-sha256": true, +} + +var tagSingletons = map[string]bool{ + "object": true, + "type": true, + "tag": true, + "tagger": true, + "gpgsig": true, + "gpgsig-sha256": true, +} + +// ValidateCommit returns ErrMalformedObject if the commit's header +// section carries a duplicate singleton header (tree, author, committer, +// gpgsig, gpgsig-sha256). Other ambiguities — missing required headers, +// unparseable signatures, bad encodings — are out of scope and surface +// later via go-git's decoder or the cryptographic check. +// +// go-git ≥ v5.19.0 silently drops duplicates and takes the first (matching +// git-core's standard_header_field filter). That's safe but ambiguous — +// `git hash-object --literally` and adversarial pushes are the only ways +// such objects appear, and the attest path would rather refuse than emit +// a predicate that obscures the underlying weirdness. +func ValidateCommit(obj plumbing.EncodedObject) error { + return checkUniqueHeaders(obj, commitSingletons) +} + +// ValidateTag is the tag-object counterpart to ValidateCommit, rejecting +// duplicate object / type / tag / tagger / gpgsig / gpgsig-sha256 headers. +func ValidateTag(obj plumbing.EncodedObject) error { + return checkUniqueHeaders(obj, tagSingletons) +} + +// checkUniqueHeaders walks the header section (up to the first blank +// line) and returns ErrMalformedObject on the second occurrence of any +// key in singletons. Lines beginning with whitespace are treated as +// continuations of the previous header and skipped. Lines without a +// space are skipped (git-core ignores them; downstream parsers surface +// the issue if it matters). +func checkUniqueHeaders(obj plumbing.EncodedObject, singletons map[string]bool) error { + r, err := obj.Reader() + if err != nil { + // Storage-layer failure: surface as-is so callers can see the + // underlying go-git error rather than a generic malformed-object. + return err + } + defer r.Close() // nolint:errcheck + + seen := make(map[string]bool, len(singletons)) + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Bytes() + if len(line) == 0 { + // Blank line terminates the header section; anything after + // is the message body, which is opaque text that may + // legitimately contain "tree foo" etc. Stop here so body + // content doesn't produce false positives. + return nil + } + if line[0] == ' ' || line[0] == '\t' { + // Continuation of the previous header (git wraps long values + // like gpgsig PEM blocks across multiple lines, each prefixed + // with a leading space). Treat as part of the prior header, + // not a new one. + continue + } + key, _, ok := bytes.Cut(line, []byte{' '}) + if !ok { + // Header line with no space separator — git-core's parser + // skips these silently. We do the same; if it actually + // matters, go-git's decoder will fail on it downstream. + continue + } + k := string(key) + if !singletons[k] { + // Header isn't one we track (parent, mergetag, encoding, + // arbitrary extra headers). Duplicates of these are allowed + // or out of scope for this validator. + continue + } + if seen[k] { + // Second occurrence of a singleton — this is the + // trust-confusion-adjacent ambiguity we're rejecting. + return fmt.Errorf("%w: duplicate %s header", ErrMalformedObject, k) + } + seen[k] = true + } + // Reached EOF without seeing a blank line. No duplicates found, so + // validation passes; return any scanner I/O error (nil on success). + return scanner.Err() +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/git/rawobj_test.go new/gitsign-0.16.1/pkg/git/rawobj_test.go --- old/gitsign-0.16.0/pkg/git/rawobj_test.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/git/rawobj_test.go 2026-05-15 21:07:37.000000000 +0200 @@ -22,9 +22,23 @@ "strings" "testing" + "github.com/go-git/go-git/v5/plumbing" "github.com/google/go-cmp/cmp" ) +// makeObject wraps raw bytes in a plumbing.MemoryObject of the given +// type, for tests that need to feed Validate{Commit,Tag} something that +// implements plumbing.EncodedObject without spinning up a full storer. +func makeObject(t *testing.T, ot plumbing.ObjectType, raw []byte) plumbing.EncodedObject { + t.Helper() + o := &plumbing.MemoryObject{} + o.SetType(ot) + if _, err := o.Write(raw); err != nil { + t.Fatalf("MemoryObject.Write: %v", err) + } + return o +} + // fakeSig is a syntactically-valid PEM block used for tests that exercise // the parser/joiner shape but don't need a real cryptographic signature. const fakeSig = `-----BEGIN SIGNED MESSAGE----- @@ -629,3 +643,83 @@ first, _, _ := bytes.Cut(b, []byte{'\n'}) return first } + +func TestValidateCommit(t *testing.T) { + authorLine := "author Alice <[email protected]> 1700000000 +0000\n" + commLine := "committer Alice <[email protected]> 1700000000 +0000\n" + base := "tree b333504b8cf3d9c314fed2cc242c5c38e89534a5\n" + authorLine + commLine + + tcs := []struct { + name string + raw string + wantErr bool + }{ + {"well-formed", base + "\nmsg\n", false}, + {"merge commit with multiple parents", "tree aaaa\nparent bbbb\nparent cccc\n" + authorLine + commLine + "\nmsg\n", false}, + {"duplicate tree", "tree aaaa\ntree bbbb\n" + authorLine + commLine + "\nmsg\n", true}, + {"duplicate author", "tree aaaa\n" + authorLine + authorLine + commLine + "\nmsg\n", true}, + {"duplicate committer", "tree aaaa\n" + authorLine + commLine + commLine + "\nmsg\n", true}, + {"duplicate gpgsig", base + "gpgsig sig1\ngpgsig sig2\n\nmsg\n", true}, + {"duplicate gpgsig-sha256", base + "gpgsig-sha256 sig1\ngpgsig-sha256 sig2\n\nmsg\n", true}, + {"gpgsig with continuation lines does not count as duplicate", base + "gpgsig line1\n line2\n line3\n\nmsg\n", false}, + {"duplicate headers in message body are ignored", base + "\ntree fake\ntree alsofake\n", false}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + err := ValidateCommit(makeObject(t, plumbing.CommitObject, []byte(tc.raw))) + gotErr := err != nil + if gotErr != tc.wantErr { + t.Fatalf("err: got %v, want error=%v", err, tc.wantErr) + } + if tc.wantErr && !errors.Is(err, ErrMalformedObject) { + t.Errorf("err: got %v, want ErrMalformedObject", err) + } + }) + } +} + +func TestValidateTag(t *testing.T) { + taggerLine := "tagger Alice <[email protected]> 1700000000 +0000\n" + base := "object 2d9cff2bad7132c586e128bcc23322dbb5297e8e\ntype commit\ntag v1\n" + taggerLine + + tcs := []struct { + name string + raw string + wantErr bool + }{ + {"well-formed", base + "\nmsg\n", false}, + {"duplicate object", "object aaaa\nobject bbbb\ntype commit\ntag v1\n" + taggerLine + "\nmsg\n", true}, + {"duplicate type", "object aaaa\ntype commit\ntype tag\ntag v1\n" + taggerLine + "\nmsg\n", true}, + {"duplicate tag", "object aaaa\ntype commit\ntag v1\ntag v2\n" + taggerLine + "\nmsg\n", true}, + {"duplicate tagger", "object aaaa\ntype commit\ntag v1\n" + taggerLine + taggerLine + "\nmsg\n", true}, + {"duplicate headers in message body are ignored", base + "\nobject fake\nobject alsofake\n", false}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + err := ValidateTag(makeObject(t, plumbing.TagObject, []byte(tc.raw))) + gotErr := err != nil + if gotErr != tc.wantErr { + t.Fatalf("err: got %v, want error=%v", err, tc.wantErr) + } + if tc.wantErr && !errors.Is(err, ErrMalformedObject) { + t.Errorf("err: got %v, want ErrMalformedObject", err) + } + }) + } +} + +// TestValidateCommit_RealCommit confirms the validator accepts a real signed +// commit with gpgsig continuation lines. +func TestValidateCommit_RealCommit(t *testing.T) { + raw := loadObject(t, "commit.txt") + if err := ValidateCommit(makeObject(t, plumbing.CommitObject, raw)); err != nil { + t.Errorf("ValidateCommit on real commit: %v", err) + } +} + +func TestValidateTag_RealTag(t *testing.T) { + raw := loadObject(t, "tag.txt") + if err := ValidateTag(makeObject(t, plumbing.TagObject, raw)); err != nil { + t.Errorf("ValidateTag on real tag: %v", err) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/git/signature_test.go new/gitsign-0.16.1/pkg/git/signature_test.go --- old/gitsign-0.16.0/pkg/git/signature_test.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/git/signature_test.go 2026-05-15 21:07:37.000000000 +0200 @@ -24,6 +24,7 @@ "path/filepath" "strings" "testing" + "time" "github.com/github/smimesign/fakeca" cms "github.com/sigstore/gitsign/internal/fork/ietf-cms" @@ -104,6 +105,127 @@ } } +// TestVerifyReturnsSignerCert ensures that Verify returns the certificate that +// actually authenticated the signature (resolved via SignerInfo) rather than +// whatever certificate happens to be at position 0 in the PKCS7 cert bag. An +// attacker who controls the SignedData can put a decoy cert at position 0 +// while signing with a different one; the caller must not be handed back the +// decoy. See CVE-2026-39984 for the equivalent issue in timestamp-authority. +func TestVerifyReturnsSignerCert(t *testing.T) { + ctx := context.Background() + ca := fakeca.New(fakeca.IsCA) + // Issue the decoy first so it gets the lower serial — the ASN.1 SET + // encoding of the PKCS7 cert bag sorts by canonical DER, and within this + // fake CA the lower-serial cert lands at position 0 after a roundtrip. + decoy := ca.Issue() + signer := ca.Issue() + roots := x509.NewCertPool() + roots.AddCert(ca.Certificate) + data := []byte("tacocat") + + for _, detached := range []bool{true, false} { + t.Run(fmt.Sprintf("detached(%t)", detached), func(t *testing.T) { + sd, err := cms.NewSignedData(data) + if err != nil { + t.Fatalf("NewSignedData() = %v", err) + } + if err := sd.Sign([]*x509.Certificate{signer.Certificate}, signer.PrivateKey); err != nil { + t.Fatalf("Sign() = %v", err) + } + // Replace the cert bag with [decoy, signer] — the decoy is what an + // attacker would inject as certs[0] to confuse callers. + if err := sd.SetCertificates([]*x509.Certificate{decoy.Certificate, signer.Certificate}); err != nil { + t.Fatalf("SetCertificates() = %v", err) + } + if detached { + sd.Detached() + } + der, err := sd.ToDER() + if err != nil { + t.Fatalf("ToDER() = %v", err) + } + + // Sanity check: confirm the attack setup landed the decoy at certs[0]. + // If this ever stops being true (e.g. ASN.1 SET sorting changes), the + // test is no longer exercising the vulnerability and needs updating. + parsed, err := cms.ParseSignedData(der) + if err != nil { + t.Fatalf("ParseSignedData() = %v", err) + } + certs, err := parsed.GetCertificates() + if err != nil { + t.Fatalf("GetCertificates() = %v", err) + } + if !certs[0].Equal(decoy.Certificate) { + t.Fatalf("attack setup failed: certs[0] is not the decoy") + } + + cv, err := NewCertVerifier(WithRootPool(roots)) + if err != nil { + t.Fatal(err) + } + got, err := cv.Verify(ctx, data, der, detached) + if err != nil { + t.Fatalf("Verify() = %v", err) + } + if got.Equal(decoy.Certificate) { + t.Errorf("Verify() returned the decoy certificate; signer cert authentication was bypassed") + } + if !got.Equal(signer.Certificate) { + t.Errorf("Verify() returned cert with serial %v, want signer serial %v", + got.SerialNumber, signer.Certificate.SerialNumber) + } + }) + } +} + +// TestVerifyMultiSignerDifferentWindows ensures Verify succeeds for a PKCS7 +// containing multiple SignerInfos whose certs have non-overlapping validity +// windows. With a single shared CurrentTime, one of the chain verifications +// would always fail; the internal verifier must derive a per-cert time so +// that each SignerInfo is checked against a time within its own window. +func TestVerifyMultiSignerDifferentWindows(t *testing.T) { + ctx := context.Background() + ca := fakeca.New(fakeca.IsCA) + + // Issue two signers with deliberately non-overlapping validity windows. + now := time.Now() + oldSigner := ca.Issue( + fakeca.NotBefore(now.Add(-48*time.Hour)), + fakeca.NotAfter(now.Add(-24*time.Hour)), + ) + newSigner := ca.Issue( + fakeca.NotBefore(now.Add(24*time.Hour)), + fakeca.NotAfter(now.Add(48*time.Hour)), + ) + roots := x509.NewCertPool() + roots.AddCert(ca.Certificate) + data := []byte("tacocat") + + sd, err := cms.NewSignedData(data) + if err != nil { + t.Fatalf("NewSignedData() = %v", err) + } + if err := sd.Sign([]*x509.Certificate{oldSigner.Certificate}, oldSigner.PrivateKey); err != nil { + t.Fatalf("Sign() old = %v", err) + } + if err := sd.Sign([]*x509.Certificate{newSigner.Certificate}, newSigner.PrivateKey); err != nil { + t.Fatalf("Sign() new = %v", err) + } + der, err := sd.ToDER() + if err != nil { + t.Fatalf("ToDER() = %v", err) + } + + cv, err := NewCertVerifier(WithRootPool(roots)) + if err != nil { + t.Fatal(err) + } + if _, err := cv.Verify(ctx, data, der, false); err != nil { + t.Fatalf("Verify() = %v; want success despite non-overlapping cert windows", err) + } +} + // TestVerifyNoCerts ensures that Verify returns an error (rather than panicking // on an out-of-bounds index) when the parsed signature contains no // certificates. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gitsign-0.16.0/pkg/git/verifier.go new/gitsign-0.16.1/pkg/git/verifier.go --- old/gitsign-0.16.0/pkg/git/verifier.go 2026-05-06 17:06:12.000000000 +0200 +++ new/gitsign-0.16.1/pkg/git/verifier.go 2026-05-15 21:07:37.000000000 +0200 @@ -20,7 +20,6 @@ "crypto/x509" "encoding/pem" "fmt" - "time" cms "github.com/sigstore/gitsign/internal/fork/ietf-cms" "github.com/sigstore/gitsign/internal/fulcio/fulcioroots" @@ -106,23 +105,24 @@ return nil, fmt.Errorf("failed to parse signature: %w", err) } - // Generate verification options. - certs, err := sd.GetCertificates() - if err != nil { + // Fail fast with a clear error if the cert bag is empty — otherwise the + // internal verifier's per-SignerInfo FindCertificate error is what the + // caller sees, and the message is less obvious. + if certs, err := sd.GetCertificates(); err != nil { return nil, fmt.Errorf("error getting signature certs: %w", err) - } - if len(certs) == 0 { + } else if len(certs) == 0 { return nil, fmt.Errorf("no certificates found in signature") } - cert := certs[0] opts := x509.VerifyOptions{ Roots: v.roots, Intermediates: v.intermediates, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, - // cosign hack: ignore the current time for now - we'll use the tlog to - // verify whether the commit was signed at a valid time. - CurrentTime: cert.NotBefore.Add(1 * time.Minute), + // Leave CurrentTime zero. The internal CMS verifier picks a per-cert + // time inside its SignerInfos loop (timestamp if present, else the + // cert's own NotBefore + 1min), so each SignerInfo is checked against + // a time that lies within its own validity window. Actual signing + // time is verified independently via Rekor. } tsaOpts := x509.VerifyOptions{ @@ -132,17 +132,26 @@ tsaOpts.Roots = v.tsa } + var chains [][][]*x509.Certificate if detached { - if _, err := sd.VerifyDetached(data, opts, tsaOpts); err != nil { + chains, err = sd.VerifyDetached(data, opts, tsaOpts) + if err != nil { return nil, fmt.Errorf("failed to verify detached signature: %w", err) } } else { - if _, err := sd.Verify(opts, tsaOpts); err != nil { + chains, err = sd.Verify(opts, tsaOpts) + if err != nil { return nil, fmt.Errorf("failed to verify attached signature: %w", err) } } - return cert, nil + // Return the leaf of the first verified chain — this is the certificate + // the internal CMS verifier actually authenticated the signature against, + // not whatever happens to sit at certs[0]. + if len(chains) == 0 || len(chains[0]) == 0 || len(chains[0][0]) == 0 { + return nil, fmt.Errorf("no verified certificate chains returned") + } + return chains[0][0][0], nil } // NewDefaultVerifier returns a new CertVerifier with the default Fulcio roots loaded from the local TUF client. ++++++ gitsign.obsinfo ++++++ --- /var/tmp/diff_new_pack.XmlspJ/_old 2026-06-10 16:19:06.265243419 +0200 +++ /var/tmp/diff_new_pack.XmlspJ/_new 2026-06-10 16:19:06.269243585 +0200 @@ -1,5 +1,5 @@ name: gitsign -version: 0.16.0 -mtime: 1778079972 -commit: 3c84d87240644b5c62b3b3d79177ae64448591c8 +version: 0.16.1 +mtime: 1778872057 +commit: d5e8a584d0551218fc96b5c89c0db910788b432a ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/gitsign/vendor.tar.gz /work/SRC/openSUSE:Factory/.gitsign.new.2375/vendor.tar.gz differ: char 134, line 1
