From 5e7f264d680ec49a86994e2d1d427baf1ae505c3 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Thu, 26 Feb 2026 22:59:11 +0100 Subject: [PATCH 01/22] adds SSH signature validation for git commits - adds new package git/signatures - adds validation of SSH signed commits to ssh_signature.go - moves GPG signature validation to gpg_signature.go - adds text fixtures for all SSH and GPG key types including commits and signatures - adds tests for all key/signature combinations - adds wrapper for "Verify(keyRings ...string)" function Signed-off-by: Ricardo Bartels --- git/git.go | 94 +- git/git_test.go | 397 +++-- git/go.mod | 8 + git/go.sum | 4 + git/gogit/clone.go | 14 +- git/signatures/gpg_signature.go | 50 + git/signatures/gpg_signature_test.go | 403 ++++++ git/signatures/signature.go | 64 + git/signatures/signature_test.go | 169 +++ git/signatures/ssh_signature.go | 106 ++ git/signatures/ssh_signature_test.go | 1286 +++++++++++++++++ .../testdata/gpg_signatures/README.md | 399 +++++ .../commit_brainpool_p256_signed.txt | 12 + .../commit_brainpool_p384_signed.txt | 13 + .../commit_brainpool_p512_signed.txt | 13 + .../gpg_signatures/commit_dsa_2048_signed.txt | 12 + .../commit_ecdsa_p256_signed.txt | 12 + .../commit_ecdsa_p384_signed.txt | 13 + .../commit_ecdsa_p521_signed.txt | 13 + .../gpg_signatures/commit_ed25519_signed.txt | 12 + .../gpg_signatures/commit_ed448_signed.txt | 13 + .../gpg_signatures/commit_rsa_2048_signed.txt | 16 + .../gpg_signatures/commit_rsa_4096_signed.txt | 21 + .../gpg_signatures/commit_unsigned.txt | 5 + .../gpg_signatures/generate_gpg_fixtures.sh | 245 ++++ .../gpg_signatures/key_brainpool_p256.pub | 10 + .../gpg_signatures/key_brainpool_p384.pub | 12 + .../gpg_signatures/key_brainpool_p512.pub | 13 + .../testdata/gpg_signatures/key_dsa_2048.pub | 25 + .../gpg_signatures/key_ecdsa_p256.pub | 10 + .../gpg_signatures/key_ecdsa_p384.pub | 11 + .../gpg_signatures/key_ecdsa_p521.pub | 13 + .../testdata/gpg_signatures/key_ed25519.pub | 9 + .../testdata/gpg_signatures/key_ed448.pub | 11 + .../testdata/gpg_signatures/key_rsa_2048.pub | 18 + .../testdata/gpg_signatures/key_rsa_4096.pub | 29 + .../tag_brainpool_p256_signed.txt | 13 + .../tag_brainpool_p384_signed.txt | 14 + .../tag_brainpool_p512_signed.txt | 14 + .../gpg_signatures/tag_dsa_2048_signed.txt | 13 + .../gpg_signatures/tag_ecdsa_p256_signed.txt | 13 + .../gpg_signatures/tag_ecdsa_p384_signed.txt | 14 + .../gpg_signatures/tag_ecdsa_p521_signed.txt | 14 + .../gpg_signatures/tag_ed25519_signed.txt | 13 + .../gpg_signatures/tag_ed448_signed.txt | 14 + .../gpg_signatures/tag_rsa_2048_signed.txt | 17 + .../gpg_signatures/tag_rsa_4096_signed.txt | 22 + .../testdata/ssh_signatures/README.md | 202 +++ .../ssh_signatures/authorized_keys_all | 5 + .../ssh_signatures/authorized_keys_ecdsa_p256 | 1 + .../ssh_signatures/authorized_keys_ecdsa_p384 | 1 + .../ssh_signatures/authorized_keys_ecdsa_p521 | 1 + .../ssh_signatures/authorized_keys_ed25519 | 1 + .../ssh_signatures/authorized_keys_rsa | 1 + .../commit_ecdsa_p256_signed.txt | 12 + .../commit_ecdsa_p384_signed.txt | 13 + .../commit_ecdsa_p521_signed.txt | 15 + .../ssh_signatures/commit_ed25519_signed.txt | 11 + .../ssh_signatures/commit_rsa_signed.txt | 29 + .../ssh_signatures/commit_unsigned.txt | 5 + .../ssh_signatures/generate_ssh_fixtures.sh | 286 ++++ .../ssh_signatures/key_ecdsa_p256.pub | 1 + .../ssh_signatures/key_ecdsa_p384.pub | 1 + .../ssh_signatures/key_ecdsa_p521.pub | 1 + .../testdata/ssh_signatures/key_ed25519.pub | 1 + .../testdata/ssh_signatures/key_rsa.pub | 1 + .../ssh_signatures/tag_ecdsa_p256_signed.txt | 13 + .../ssh_signatures/tag_ecdsa_p384_signed.txt | 14 + .../ssh_signatures/tag_ecdsa_p521_signed.txt | 16 + .../ssh_signatures/tag_ed25519_signed.txt | 12 + .../ssh_signatures/tag_rsa_signed.txt | 30 + .../ssh_signatures/verified_signers_all | 5 + .../verified_signers_ecdsa_p256 | 1 + .../verified_signers_ecdsa_p384 | 1 + .../verified_signers_ecdsa_p521 | 1 + .../ssh_signatures/verified_signers_ed25519 | 1 + .../ssh_signatures/verified_signers_rsa | 1 + git/signatures/testutils_test.go | 70 + 78 files changed, 4289 insertions(+), 180 deletions(-) create mode 100644 git/signatures/gpg_signature.go create mode 100644 git/signatures/gpg_signature_test.go create mode 100644 git/signatures/signature.go create mode 100644 git/signatures/signature_test.go create mode 100644 git/signatures/ssh_signature.go create mode 100644 git/signatures/ssh_signature_test.go create mode 100644 git/signatures/testdata/gpg_signatures/README.md create mode 100644 git/signatures/testdata/gpg_signatures/commit_brainpool_p256_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_brainpool_p384_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_brainpool_p512_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_ed25519_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_ed448_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_rsa_2048_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_rsa_4096_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/commit_unsigned.txt create mode 100755 git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh create mode 100644 git/signatures/testdata/gpg_signatures/key_brainpool_p256.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_brainpool_p384.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_brainpool_p512.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_dsa_2048.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_ecdsa_p256.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_ecdsa_p384.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_ecdsa_p521.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_ed25519.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_ed448.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_rsa_2048.pub create mode 100644 git/signatures/testdata/gpg_signatures/key_rsa_4096.pub create mode 100644 git/signatures/testdata/gpg_signatures/tag_brainpool_p256_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_brainpool_p384_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_brainpool_p512_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_ed25519_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_ed448_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_rsa_2048_signed.txt create mode 100644 git/signatures/testdata/gpg_signatures/tag_rsa_4096_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/README.md create mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_all create mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 create mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 create mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 create mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 create mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_rsa create mode 100644 git/signatures/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/commit_ed25519_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/commit_rsa_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/commit_unsigned.txt create mode 100755 git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh create mode 100644 git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub create mode 100644 git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub create mode 100644 git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub create mode 100644 git/signatures/testdata/ssh_signatures/key_ed25519.pub create mode 100644 git/signatures/testdata/ssh_signatures/key_rsa.pub create mode 100644 git/signatures/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/tag_ed25519_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/tag_rsa_signed.txt create mode 100644 git/signatures/testdata/ssh_signatures/verified_signers_all create mode 100644 git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p256 create mode 100644 git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p384 create mode 100644 git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p521 create mode 100644 git/signatures/testdata/ssh_signatures/verified_signers_ed25519 create mode 100644 git/signatures/testdata/ssh_signatures/verified_signers_rsa create mode 100644 git/signatures/testutils_test.go diff --git a/git/git.go b/git/git.go index 1bff7cfb2..d6344eaa8 100644 --- a/git/git.go +++ b/git/git.go @@ -17,13 +17,12 @@ limitations under the License. package git import ( - "bytes" "errors" "fmt" "strings" "time" - "github.com/ProtonMail/go-crypto/openpgp" + "github.com/fluxcd/pkg/git/signatures" ) const ( @@ -113,19 +112,38 @@ func (c *Commit) AbsoluteReference() string { return c.Hash.Digest() } +// wrapper function to ensure backwards compatibility +func (c *Commit) Verify(keyRings ...string) (string, error) { + return c.VerifyGPG(keyRings...) +} + // Verify the Signature of the commit with the given key rings. // It returns the fingerprint of the key the signature was verified // with, or an error. It does not verify the signature of the referencing // tag (if present). Users are expected to explicitly verify the referencing // tag's signature using `c.ReferencingTag.Verify()` -func (c *Commit) Verify(keyRings ...string) (string, error) { - fingerprint, err := verifySignature(c.Signature, c.Encoded, keyRings...) +func (c *Commit) VerifyGPG(keyRings ...string) (string, error) { + fingerprint, err := signatures.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...) if err != nil { return "", fmt.Errorf("unable to verify Git commit: %w", err) } return fingerprint, nil } +// VerifySSH verifies the SSH signature of the commit with the given authorized keys. +// It returns the fingerprint of the key the signature was verified with, or an error. +// It does not verify the signature of the referencing tag (if present). Users are +// expected to explicitly verify the referencing tag's signature using `c.ReferencingTag.VerifySSH()` +func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) { + // The Encoded field already contains the commit data without the signature + // (it was encoded using EncodeWithoutSignature in BuildCommitWithRef) + fingerprint, err := signatures.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...) + if err != nil { + return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err) + } + return fingerprint, nil +} + // ShortMessage returns the first 50 characters of a commit subject. func (c *Commit) ShortMessage() string { subject := strings.Split(c.Message, "\n")[0] @@ -152,17 +170,34 @@ type Tag struct { Message string } +// wrapper function to ensure backwards compatibility +func (t *Tag) Verify(keyRings ...string) (string, error) { + return t.VerifyGPG(keyRings...) +} + // Verify the Signature of the tag with the given key rings. // It returns the fingerprint of the key the signature was verified // with, or an error. -func (t *Tag) Verify(keyRings ...string) (string, error) { - fingerprint, err := verifySignature(t.Signature, t.Encoded, keyRings...) +func (t *Tag) VerifyGPG(keyRings ...string) (string, error) { + fingerprint, err := signatures.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...) if err != nil { return "", fmt.Errorf("unable to verify Git tag: %w", err) } return fingerprint, nil } +// VerifySSH verifies the SSH signature of the tag with the given authorized keys. +// It returns the fingerprint of the key the signature was verified with, or an error. +func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) { + // The Encoded field already contains the tag data without the signature + // (it was encoded using EncodeWithoutSignature in BuildCommitWithRef) + fingerprint, err := signatures.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...) + if err != nil { + return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err) + } + return fingerprint, nil +} + // String returns a short string representation of the tag in the format // of , for eg: "1.0.0@a0c14dc8580a23f79bc654faa79c4f62b46c2c22" // If the tag is lightweight, it won't have a hash, so it'll simply return @@ -210,21 +245,36 @@ func IsSignedTag(t Tag) bool { return t.Signature != "" } -func verifySignature(sig string, payload []byte, keyRings ...string) (string, error) { - if sig == "" { - return "", fmt.Errorf("unable to verify payload as the provided signature is empty") - } +// IsPGPSigned returns true if the commit has a PGP signature. +func (c *Commit) IsPGPSigned() bool { + return signatures.IsPGPSignature(c.Signature) +} - for _, r := range keyRings { - reader := strings.NewReader(r) - keyring, err := openpgp.ReadArmoredKeyRing(reader) - if err != nil { - return "", fmt.Errorf("unable to read armored key ring: %w", err) - } - signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewBuffer(payload), bytes.NewBufferString(sig), nil) - if err == nil { - return signer.PrimaryKey.KeyIdString(), nil - } - } - return "", fmt.Errorf("unable to verify payload with any of the given key rings") +// IsSSHSigned returns true if the commit has an SSH signature. +func (c *Commit) IsSSHSigned() bool { + return signatures.IsSSHSignature(c.Signature) +} + +// SignatureType returns the type of the commit signature as a string. +// It returns "pgp" for PGP signatures, "ssh" for SSH signatures, +// and "unknown" for unrecognized or empty signatures. +func (c *Commit) SignatureType() string { + return signatures.GetSignatureType(c.Signature) +} + +// IsPGPSigned returns true if the tag has a PGP signature. +func (t *Tag) IsPGPSigned() bool { + return signatures.IsPGPSignature(t.Signature) +} + +// IsSSHSigned returns true if the tag has an SSH signature. +func (t *Tag) IsSSHSigned() bool { + return signatures.IsSSHSignature(t.Signature) +} + +// SignatureType returns the type of the tag signature as a string. +// It returns "pgp" for PGP signatures, "ssh" for SSH signatures, +// and "unknown" for unrecognized or empty signatures. +func (t *Tag) SignatureType() string { + return signatures.GetSignatureType(t.Signature) } diff --git a/git/git_test.go b/git/git_test.go index 1c18d61f5..8fb93fd06 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -23,102 +23,6 @@ import ( . "github.com/onsi/gomega" ) -const ( - encodedCommitFixture = `tree f0c522d8cc4c90b73e2bc719305a896e7e3c108a -parent eb167bc68d0a11530923b1f24b4978535d10b879 -author Stefan Prodan 1633681364 +0300 -committer Stefan Prodan 1633681364 +0300 - -Update containerd and runc to fix CVEs - -Signed-off-by: Stefan Prodan -` - - malformedEncodedCommitFixture = `parent eb167bc68d0a11530923b1f24b4978535d10b879 -author Stefan Prodan 1633681364 +0300 -committer Stefan Prodan 1633681364 +0300 - -Update containerd and runc to fix CVEs - -Signed-off-by: Stefan Prodan -` - - signatureCommitFixture = `-----BEGIN PGP SIGNATURE----- - -iHUEABEIAB0WIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCYV//1AAKCRAyma6w5Ahb -r7nJAQCQU4zEJu04/Q0ac/UaL6htjhq/wTDNMeUM+aWG/LcBogEAqFUea1oR2BJQ -JCJmEtERFh39zNWSazQmxPAFhEE0kbc= -=+Wlj ------END PGP SIGNATURE-----` - - armoredKeyRingFixture = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mQSuBF9+HgMRDADKT8UBcSzpTi4JXt/ohhVW3x81AGFPrQvs6MYrcnNJfIkPTJD8 -mY5T7j1fkaN5wcf1wnxM9qTcW8BodkWNGEoEYOtVuigLSxPFqIncxK0PHvdU8ths -TEInBrgZv9t6xIVa4QngOEUd2D/aYni7M+75z7ntgj6eU1xLZ60upRFn05862OvJ -rZFUvzjsZXMAO3enCu2VhG/2axCY/5uI8PgWjyiKV2TH4LBJgzlb0v6SyI+fYf5K -Bg2WzDuLKvQBi9tFSwnUbQoFFlOeiGW8G/bdkoJDWeS1oYgSD3nkmvXvrVESCrbT -C05OtQOiDXjSpkLim81vNVPtI2XEug+9fEA+jeJakyGwwB+K8xqV3QILKCoWHKGx -yWcMHSR6cP9tdXCk2JHZBm1PLSJ8hIgMH/YwBJLYg90u8lLAs9WtpVBKkLplzzgm -B4Z4VxCC+xI1kt+3ZgYvYC+oUXJXrjyAzy+J1f+aWl2+S/79glWgl/xz2VibWMz6 -nZUE+wLMxOQqyOsBALsoE6z81y/7gfn4R/BziBASi1jq/r/wdboFYowmqd39DACX -+i+V0OplP2TN/F5JajzRgkrlq5cwZHinnw+IFwj9RTfOkdGb3YwhBt/h2PP38969 -ZG+y8muNtaIqih1pXj1fz9HRtsiCABN0j+JYpvV2D2xuLL7P1O0dt5BpJ3KqNCRw -mGgO2GLxbwvlulsLidCPxdK/M8g9Eeb/xwA5LVwvjVchHkzHuUT7durn7AT0RWiK -BT8iDfeBB9RKienAbWyybEqRaR6/Tv+mghFIalsDiBPbfm4rsNzsq3ohfByqECiy -yUvs2O3NDwkoaBDkA3GFyKv8/SVpcuL5OkVxAHNCIMhNzSgotQ3KLcQc0IREfFCa -3CsBAC7CsE2bJZ9IA9sbBa3jimVhWUQVudRWiLFeYHUF/hjhqS8IHyFwprjEOLaV -EG0kBO6ELypD/bOsmN9XZLPYyI3y9DM6Vo0KMomE+yK/By/ZMxVfex8/TZreUdhP -VdCLL95Rc4w9io8qFb2qGtYBij2wm0RWLcM0IhXWAtjI3B17IN+6hmv+JpiZccsM -AMNR5/RVdXIl0hzr8LROD0Xe4sTyZ+fm3mvpczoDPQNRrWpmI/9OT58itnVmZ5jM -7djV5y/NjBk63mlqYYfkfWto97wkhg0MnTnOhzdtzSiZQRzj+vf+ilLfIlLnuRr1 -JRV9Skv6xQltcFArx4JyfZCo7JB1ZXcbdFAvIXXS11RTErO0XVrXNm2RenpW/yZA -9f+ESQ/uUB6XNuyqVUnJDAFJFLdzx8sO3DXo7dhIlgpFqgQobUl+APpbU5LT95sm -89UrV0Lt9vh7k6zQtKOjEUhm+dErmuBnJo8MvchAuXLagHjvb58vYBCUxVxzt1KG -2IePwJ/oXIfawNEGad9Lmdo1FYG1u53AKWZmpYOTouu92O50FG2+7dBh0V2vO253 -aIGFRT1r14B1pkCIun7z7B/JELqOkmwmlRrUnxlADZEcQT3z/S8/4+2P7P6kXO7X -/TAX5xBhSqUbKe3DhJSOvf05/RVL5ULc2U2JFGLAtmBOFmnD/u0qoo5UvWliI+v/ -47QnU3RlZmFuIFByb2RhbiA8c3RlZmFuLnByb2RhbkBnbWFpbC5jb20+iJAEExEI -ADgWIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCX34eAwIbAwULCQgHAgYVCgkICwIE -FgIDAQIeAQIXgAAKCRAyma6w5Ahbrzu/AP9l2YpRaWZr6wSQuEn0gMN8DRzsWJPx -pn0akdY7SRP3ngD9GoKgu41FAItnHAJ2KiHv/fHFyHMndNP3kPGPNW4BF+65Aw0E -X34eAxAMAMdYFCHmVA8TZxSTMBDpKYave8RiDCMMMjk26Gl0EPN9f2Y+s5++DhiQ -hojNH9VmJkFwZX1xppxe1y1aLa/U6fBAqMP/IdNH8270iv+A9YIxdsWLmpm99BDO -3suRfsHcOe9T0x/CwRfDNdGM/enGMhYGTgF4VD58DRDE6WntaBhl4JJa300NG6X0 -GM4Gh59DKWDnez/Shulj8demlWmakP5imCVoY+omOEc2k3nH02U+foqaGG5WxZZ+ -GwEPswm2sBxvn8nwjy9gbQwEtzNI7lWYiz36wCj2VS56Udqt+0eNg8WzocUT0XyI -moe1qm8YJQ6fxIzaC431DYi/mCDzgx4EV9ww33SXX3Yp2NL6PsdWJWw2QnoqSMpM -z5otw2KlMgUHkkXEKs0apmK4Hu2b6KD7/ydoQRFUqR38Gb0IZL1tOL6PnbCRUcig -Aypy016W/WMCjBfQ8qxIGTaj5agX2t28hbiURbxZkCkz+Z3OWkO0Rq3Y2hNAYM5s -eTn94JIGGwADBgv/dbSZ9LrBvdMwg8pAtdlLtQdjPiT1i9w5NZuQd7OuKhOxYTEB -NRDTgy4/DgeNThCeOkMB/UQQPtJ3Et45S2YRtnnuvfxgnlz7xlUn765/grtnRk4t -ONjMmb6tZos1FjIJecB/6h4RsvUd2egvtlpD/Z3YKr6MpNjWg4ji7m27e9pcJfP6 -YpTDrq9GamiHy9FS2F2pZlQxriPpVhjCLVn9tFGBIsXNxxn7SP4so6rJBmyHEAlq -iym9wl933e0FIgAw5C1vvprYu2amk+jmVBsJjjCmInW5q/kWAFnFaHBvk+v+/7tX -hywWUI7BqseikgUlkgJ6eU7E9z1DEyuS08x/cViDoNh2ntVUhpnluDu48pdqBvvY -a4uL/D+KI84THUAJ/vZy+q6G3BEb4hI9pFjgrdJpUKubxyZolmkCFZHjV34uOcTc -LQr28P8xW8vQbg5DpIsivxYLqDGXt3OyiItxvLMtw/ypt6PkoeP9A4KDST4StITE -1hrOrPtJ/VRmS2o0iHgEGBEIACAWIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCX34e -AwIbDAAKCRAyma6w5Ahbr6QWAP9/pl2R6r1nuCnXzewSbnH1OLsXf32hFQAjaQ5o -Oomb3gD/TRf/nAdVED+k81GdLzciYdUGtI71/qI47G0nMBluLRE= -=/4e+ ------END PGP PUBLIC KEY BLOCK----- -` - - keyRingFingerprintFixture = "3299AEB0E4085BAF" - - malformedKeyRingFixture = ` ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQSuBF9+HgMRDADKT8UBcSzpTi4JXt/ohhVW3x81AGFPrQvs6MYrcnNJfIkPTJD8 -mY5T7j1fkaN5wcf1wnxM9qTcW8BodkWNGEoEYOtVuigLSxPFqIncxK0PHvdU8ths -TEInBrgZv9t6xIVa4QngOEUd2D/aYni7M+75z7ntgj6eU1xLZ60upRFn05862OvJ -rZFUvzjsZXMAO3enCu2VhG/2axCY/5uI8PgWjyiKV2TH4LBJgzlb0v6SyI+fYf5K -Bg2WzDuLKvQBi9tFSwnUbQoFFlOeiGW8G/bdkoJDWeS1oYgSD3nkmvXvrVESCrbT ------END PGP PUBLIC KEY BLOCK----- -` -) - func TestHash_Algorithm(t *testing.T) { tests := []struct { name string @@ -155,61 +59,6 @@ func TestHash_Algorithm(t *testing.T) { } } -func Test_verifySignature(t *testing.T) { - tests := []struct { - name string - payload []byte - sig string - keyRings []string - want string - wantErr string - }{ - { - name: "Valid commit signature", - payload: []byte(encodedCommitFixture), - sig: signatureCommitFixture, - keyRings: []string{armoredKeyRingFixture}, - want: keyRingFingerprintFixture, - }, - { - name: "Malformed encoded commit", - payload: []byte(malformedEncodedCommitFixture), - sig: signatureCommitFixture, - keyRings: []string{armoredKeyRingFixture}, - wantErr: "unable to verify payload with any of the given key rings", - }, - { - name: "Malformed key ring", - payload: []byte(encodedCommitFixture), - sig: signatureCommitFixture, - keyRings: []string{malformedKeyRingFixture}, - wantErr: "unable to read armored key ring: unexpected EOF", - }, - { - name: "Missing signature", - payload: []byte(encodedCommitFixture), - keyRings: []string{armoredKeyRingFixture}, - wantErr: "unable to verify payload as the provided signature is empty", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) - - got, err := verifySignature(tt.sig, tt.payload, tt.keyRings...) - if tt.wantErr != "" { - g.Expect(err).To(HaveOccurred()) - g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) - g.Expect(got).To(BeEmpty()) - return - } - - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(got).To(Equal(tt.want)) - }) - } -} - func TestHash_Digest(t *testing.T) { tests := []struct { name string @@ -403,3 +252,249 @@ func TestIsConcreteCommit(t *testing.T) { }) } } + +func TestCommit_IsPGPSigned(t *testing.T) { + tests := []struct { + name string + commit *Commit + want bool + }{ + { + name: "PGP signed commit", + commit: &Commit{ + Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: true, + }, + { + name: "SSH signed commit", + commit: &Commit{ + Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: false, + }, + { + name: "unsigned commit", + commit: &Commit{}, + want: false, + }, + { + name: "PGP signed commit with leading whitespace", + commit: &Commit{ + Signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(tt.commit.IsPGPSigned()).To(Equal(tt.want)) + }) + } +} + +func TestCommit_IsSSHSigned(t *testing.T) { + tests := []struct { + name string + commit *Commit + want bool + }{ + { + name: "SSH signed commit", + commit: &Commit{ + Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: true, + }, + { + name: "PGP signed commit", + commit: &Commit{ + Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: false, + }, + { + name: "unsigned commit", + commit: &Commit{}, + want: false, + }, + { + name: "SSH signed commit with leading whitespace", + commit: &Commit{ + Signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(tt.commit.IsSSHSigned()).To(Equal(tt.want)) + }) + } +} + +func TestCommit_SignatureType(t *testing.T) { + tests := []struct { + name string + commit *Commit + want string + }{ + { + name: "PGP signed commit", + commit: &Commit{ + Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: "pgp", + }, + { + name: "SSH signed commit", + commit: &Commit{ + Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: "ssh", + }, + { + name: "unsigned commit", + commit: &Commit{}, + want: "unknown", + }, + { + name: "unknown signature type", + commit: &Commit{ + Signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + }, + want: "unknown", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(tt.commit.SignatureType()).To(Equal(tt.want)) + }) + } +} + +func TestTag_IsPGPSigned(t *testing.T) { + tests := []struct { + name string + tag *Tag + want bool + }{ + { + name: "PGP signed tag", + tag: &Tag{ + Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: true, + }, + { + name: "SSH signed tag", + tag: &Tag{ + Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: false, + }, + { + name: "unsigned tag", + tag: &Tag{}, + want: false, + }, + { + name: "PGP signed tag with leading whitespace", + tag: &Tag{ + Signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(tt.tag.IsPGPSigned()).To(Equal(tt.want)) + }) + } +} + +func TestTag_IsSSHSigned(t *testing.T) { + tests := []struct { + name string + tag *Tag + want bool + }{ + { + name: "SSH signed tag", + tag: &Tag{ + Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: true, + }, + { + name: "PGP signed tag", + tag: &Tag{ + Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: false, + }, + { + name: "unsigned tag", + tag: &Tag{}, + want: false, + }, + { + name: "SSH signed tag with leading whitespace", + tag: &Tag{ + Signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(tt.tag.IsSSHSigned()).To(Equal(tt.want)) + }) + } +} + +func TestTag_SignatureType(t *testing.T) { + tests := []struct { + name string + tag *Tag + want string + }{ + { + name: "PGP signed tag", + tag: &Tag{ + Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + }, + want: "pgp", + }, + { + name: "SSH signed tag", + tag: &Tag{ + Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + }, + want: "ssh", + }, + { + name: "unsigned tag", + tag: &Tag{}, + want: "unknown", + }, + { + name: "unknown signature type", + tag: &Tag{ + Signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + }, + want: "unknown", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(tt.tag.SignatureType()).To(Equal(tt.want)) + }) + } +} diff --git a/git/go.mod b/git/go.mod index a825eba8f..6326fd830 100644 --- a/git/go.mod +++ b/git/go.mod @@ -22,6 +22,14 @@ require ( github.com/go-git/go-git/v5 v5.19.1 github.com/onsi/gomega v1.40.0 golang.org/x/crypto v0.50.0 + github.com/fluxcd/pkg/gittestserver v0.28.0 + github.com/fluxcd/pkg/ssh v0.25.0 + github.com/fluxcd/pkg/version v0.15.0 + github.com/go-git/go-billy/v5 v5.9.0 + github.com/go-git/go-git/v5 v5.19.0 + github.com/hiddeco/sshsig v0.2.0 + github.com/onsi/gomega v1.40.0 + golang.org/x/crypto v0.50.0 ) require ( diff --git a/git/go.sum b/git/go.sum index 8d3ffe964..40ff165ae 100644 --- a/git/go.sum +++ b/git/go.sum @@ -42,6 +42,10 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/hiddeco/sshsig v0.2.0 h1:gMWllgKCITXdydVkDL+Zro0PU96QI55LwUwebSwNTSw= +github.com/hiddeco/sshsig v0.2.0/go.mod h1:nJc98aGgiH6Yql2doqH4CTBVHexQA40Q+hMMLHP4EqE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/git/gogit/clone.go b/git/gogit/clone.go index 592044500..8b78c3cbb 100644 --- a/git/gogit/clone.go +++ b/git/gogit/clone.go @@ -136,7 +136,7 @@ func (g *Client) cloneBranch(ctx context.Context, url, branch string, opts repos } g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return buildCommitWithRef(cc, nil, ref) + return BuildCommitWithRef(cc, nil, ref) } func (g *Client) cloneTag(ctx context.Context, url, tag string, opts repository.CloneConfig) (*git.Commit, error) { @@ -236,7 +236,7 @@ func (g *Client) cloneTag(ctx context.Context, url, tag string, opts repository. g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return buildCommitWithRef(cc, tagObj, ref) + return BuildCommitWithRef(cc, tagObj, ref) } func (g *Client) cloneCommit(ctx context.Context, url, commit string, opts repository.CloneConfig) (*git.Commit, error) { @@ -305,7 +305,7 @@ func (g *Client) cloneCommit(ctx context.Context, url, commit string, opts repos g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return buildCommitWithRef(cc, nil, cloneOpts.ReferenceName) + return BuildCommitWithRef(cc, nil, cloneOpts.ReferenceName) } func (g *Client) cloneSemVer(ctx context.Context, url, semverTag string, opts repository.CloneConfig) (*git.Commit, error) { @@ -439,7 +439,7 @@ func (g *Client) cloneSemVer(ctx context.Context, url, semverTag string, opts re g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return buildCommitWithRef(cc, tagObj, tagRef.Name()) + return BuildCommitWithRef(cc, tagObj, tagRef.Name()) } func (g *Client) cloneRefName(ctx context.Context, url string, refName string, cloneOpts repository.CloneConfig) (*git.Commit, error) { @@ -582,7 +582,7 @@ func buildSignature(s object.Signature) git.Signature { } } -func buildTag(t *object.Tag, ref plumbing.ReferenceName) (*git.Tag, error) { +func BuildTag(t *object.Tag, ref plumbing.ReferenceName) (*git.Tag, error) { if t == nil { return &git.Tag{ Name: ref.Short(), @@ -612,7 +612,7 @@ func buildTag(t *object.Tag, ref plumbing.ReferenceName) (*git.Tag, error) { }, nil } -func buildCommitWithRef(c *object.Commit, t *object.Tag, ref plumbing.ReferenceName) (*git.Commit, error) { +func BuildCommitWithRef(c *object.Commit, t *object.Tag, ref plumbing.ReferenceName) (*git.Commit, error) { if c == nil { return nil, fmt.Errorf("unable to construct commit: no object") } @@ -641,7 +641,7 @@ func buildCommitWithRef(c *object.Commit, t *object.Tag, ref plumbing.ReferenceN } if ref.IsTag() { - tt, err := buildTag(t, ref) + tt, err := BuildTag(t, ref) if err != nil { return nil, err } diff --git a/git/signatures/gpg_signature.go b/git/signatures/gpg_signature.go new file mode 100644 index 000000000..0abe02d8a --- /dev/null +++ b/git/signatures/gpg_signature.go @@ -0,0 +1,50 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ProtonMail/go-crypto/openpgp" +) + +// PGPSignaturePrefix is the prefix used by Git to identify PGP signatures. +const PGPSignaturePrefix = "-----BEGIN PGP SIGNATURE-----" + +// VerifyPGPSignature verifies the PGP signature against the payload using +// the provided key rings. It returns the fingerprint of the key that +// successfully verified the signature, or an error. +func VerifyPGPSignature(signature string, payload []byte, keyRings ...string) (string, error) { + if signature == "" { + return "", fmt.Errorf("unable to verify payload as the provided signature is empty") + } + + for _, r := range keyRings { + reader := strings.NewReader(r) + keyring, err := openpgp.ReadArmoredKeyRing(reader) + if err != nil { + return "", fmt.Errorf("unable to read armored key ring: %w", err) + } + signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewBuffer(payload), bytes.NewBufferString(signature), nil) + if err == nil { + return signer.PrimaryKey.KeyIdString(), nil + } + } + return "", fmt.Errorf("unable to verify payload with any of the given key rings") +} diff --git a/git/signatures/gpg_signature_test.go b/git/signatures/gpg_signature_test.go new file mode 100644 index 000000000..9fa2996a5 --- /dev/null +++ b/git/signatures/gpg_signature_test.go @@ -0,0 +1,403 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/fluxcd/pkg/git/gogit" + "github.com/fluxcd/pkg/git/signatures" + "github.com/go-git/go-git/v5/plumbing" + . "github.com/onsi/gomega" +) + +const ( + encodedCommitFixture = `tree f0c522d8cc4c90b73e2bc719305a896e7e3c108a +parent eb167bc68d0a11530923b1f24b4978535d10b879 +author Stefan Prodan 1633681364 +0300 +committer Stefan Prodan 1633681364 +0300 + +Update containerd and runc to fix CVEs + +Signed-off-by: Stefan Prodan +` + + malformedEncodedCommitFixture = `parent eb167bc68d0a11530923b1f24b4978535d10b879 +author Stefan Prodan 1633681364 +0300 +committer Stefan Prodan 1633681364 +0300 + +Update containerd and runc to fix CVEs + +Signed-off-by: Stefan Prodan +` + + signatureCommitFixture = `-----BEGIN PGP SIGNATURE----- + +iHUEABEIAB0WIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCYV//1AAKCRAyma6w5Ahb +r7nJAQCQU4zEJu04/Q0ac/UaL6htjhq/wTDNMeUM+aWG/LcBogEAqFUea1oR2BJQ +JCJmEtERFh39zNWSazQmxPAFhEE0kbc= +=+Wlj +-----END PGP SIGNATURE-----` + + armoredKeyRingFixture = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQSuBF9+HgMRDADKT8UBcSzpTi4JXt/ohhVW3x81AGFPrQvs6MYrcnNJfIkPTJD8 +mY5T7j1fkaN5wcf1wnxM9qTcW8BodkWNGEoEYOtVuigLSxPFqIncxK0PHvdU8ths +TEInBrgZv9t6xIVa4QngOEUd2D/aYni7M+75z7ntgj6eU1xLZ60upRFn05862OvJ +rZFUvzjsZXMAO3enCu2VhG/2axCY/5uI8PgWjyiKV2TH4LBJgzlb0v6SyI+fYf5K +Bg2WzDuLKvQBi9tFSwnUbQoFFlOeiGW8G/bdkoJDWeS1oYgSD3nkmvXvrVESCrbT +C05OtQOiDXjSpkLim81vNVPtI2XEug+9fEA+jeJakyGwwB+K8xqV3QILKCoWHKGx +yWcMHSR6cP9tdXCk2JHZBm1PLSJ8hIgMH/YwBJLYg90u8lLAs9WtpVBKkLplzzgm +B4Z4VxCC+xI1kt+3ZgYvYC+oUXJXrjyAzy+J1f+aWl2+S/79glWgl/xz2VibWMz6 +nZUE+wLMxOQqyOsBALsoE6z81y/7gfn4R/BziBASi1jq/r/wdboFYowmqd39DACX ++i+V0OplP2TN/F5JajzRgkrlq5cwZHinnw+IFwj9RTfOkdGb3YwhBt/h2PP38969 +ZG+y8muNtaIqih1pXj1fz9HRtsiCABN0j+JYpvV2D2xuLL7P1O0dt5BpJ3KqNCRw +mGgO2GLxbwvlulsLidCPxdK/M8g9Eeb/xwA5LVwvjVchHkzHuUT7durn7AT0RWiK +BT8iDfeBB9RKienAbWyybEqRaR6/Tv+mghFIalsDiBPbfm4rsNzsq3ohfByqECiy +yUvs2O3NDwkoaBDkA3GFyKv8/SVpcuL5OkVxAHNCIMhNzSgotQ3KLcQc0IREfFCa +3CsBAC7CsE2bJZ9IA9sbBa3jimVhWUQVudRWiLFeYHUF/hjhqS8IHyFwprjEOLaV +EG0kBO6ELypD/bOsmN9XZLPYyI3y9DM6Vo0KMomE+yK/By/ZMxVfex8/TZreUdhP +VdCLL95Rc4w9io8qFb2qGtYBij2wm0RWLcM0IhXWAtjI3B17IN+6hmv+JpiZccsM +AMNR5/RVdXIl0hzr8LROD0Xe4sTyZ+fm3mvpczoDPQNRrWpmI/9OT58itnVmZ5jM +7djV5y/NjBk63mlqYYfkfWto97wkhg0MnTnOhzdtzSiZQRzj+vf+ilLfIlLnuRr1 +JRV9Skv6xQltcFArx4JyfZCo7JB1ZXcbdFAvIXXS11RTErO0XVrXNm2RenpW/yZA +9f+ESQ/uUB6XNuyqVUnJDAFJFLdzx8sO3DXo7dhIlgpFqgQobUl+APpbU5LT95sm +89UrV0Lt9vh7k6zQtKOjEUhm+dErmuBnJo8MvchAuXLagHjvb58vYBCUxVxzt1KG +2IePwJ/oXIfawNEGad9Lmdo1FYG1u53AKWZmpYOTouu92O50FG2+7dBh0V2vO253 +aIGFRT1r14B1pkCIun7z7B/JELqOkmwmlRrUnxlADZEcQT3z/S8/4+2P7P6kXO7X +/TAX5xBhSqUbKe3DhJSOvf05/RVL5ULc2U2JFGLAtmBOFmnD/u0qoo5UvWliI+v/ +47QnU3RlZmFuIFByb2RhbiA8c3RlZmFuLnByb2RhbkBnbWFpbC5jb20+iJAEExEI +ADgWIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCX34eAwIbAwULCQgHAgYVCgkICwIE +FgIDAQIeAQIXgAAKCRAyma6w5Ahbrzu/AP9l2YpRaWZr6wSQuEn0gMN8DRzsWJPx +pn0akdY7SRP3ngD9GoKgu41FAItnHAJ2KiHv/fHFyHMndNP3kPGPNW4BF+65Aw0E +X34eAxAMAMdYFCHmVA8TZxSTMBDpKYave8RiDCMMMjk26Gl0EPN9f2Y+s5++DhiQ +hojNH9VmJkFwZX1xppxe1y1aLa/U6fBAqMP/IdNH8270iv+A9YIxdsWLmpm99BDO +3suRfsHcOe9T0x/CwRfDNdGM/enGMhYGTgF4VD58DRDE6WntaBhl4JJa300NG6X0 +GM4Gh59DKWDnez/Shulj8demlWmakP5imCVoY+omOEc2k3nH02U+foqaGG5WxZZ+ +GwEPswm2sBxvn8nwjy9gbQwEtzNI7lWYiz36wCj2VS56Udqt+0eNg8WzocUT0XyI +moe1qm8YJQ6fxIzaC431DYi/mCDzgx4EV9ww33SXX3Yp2NL6PsdWJWw2QnoqSMpM +z5otw2KlMgUHkkXEKs0apmK4Hu2b6KD7/ydoQRFUqR38Gb0IZL1tOL6PnbCRUcig +Aypy016W/WMCjBfQ8qxIGTaj5agX2t28hbiURbxZkCkz+Z3OWkO0Rq3Y2hNAYM5s +eTn94JIGGwADBgv/dbSZ9LrBvdMwg8pAtdlLtQdjPiT1i9w5NZuQd7OuKhOxYTEB +NRDTgy4/DgeNThCeOkMB/UQQPtJ3Et45S2YRtnnuvfxgnlz7xlUn765/grtnRk4t +ONjMmb6tZos1FjIJecB/6h4RsvUd2egvtlpD/Z3YKr6MpNjWg4ji7m27e9pcJfP6 +YpTDrq9GamiHy9FS2F2pZlQxriPpVhjCLVn9tFGBIsXNxxn7SP4so6rJBmyHEAlq +iym9wl933e0FIgAw5C1vvprYu2amk+jmVBsJjjCmInW5q/kWAFnFaHBvk+v+/7tX +hywWUI7BqseikgUlkgJ6eU7E9z1DEyuS08x/cViDoNh2ntVUhpnluDu48pdqBvvY +a4uL/D+KI84THUAJ/vZy+q6G3BEb4hI9pFjgrdJpUKubxyZolmkCFZHjV34uOcTc +LQr28P8xW8vQbg5DpIsivxYLqDGXt3OyiItxvLMtw/ypt6PkoeP9A4KDST4StITE +1hrOrPtJ/VRmS2o0iHgEGBEIACAWIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCX34e +AwIbDAAKCRAyma6w5Ahbr6QWAP9/pl2R6r1nuCnXzewSbnH1OLsXf32hFQAjaQ5o +Oomb3gD/TRf/nAdVED+k81GdLzciYdUGtI71/qI47G0nMBluLRE= +=/4e+ +-----END PGP PUBLIC KEY BLOCK-----` + + keyRingFingerprintFixture = "3299AEB0E4085BAF" + + malformedKeyRingFixture = ` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQSuBF9+HgMRDADKT8UBcSzpTi4JXt/ohhVW3x81AGFPrQvs6MYrcnNJfIkPTJD8 +mY5T7j1fkaN5wcf1wnxM9qTcW8BodkWNGEoEYOtVuigLSxPFqIncxK0PHvdU8ths +TEInBrgZv9t6xIVa4QngOEUd2D/aYni7M+75z7ntgj6eU1xLZ60upRFn05862OvJ +rZFUvzjsZXMAO3enCu2VhG/2axCY/5uI8PgWjyiKV2TH4LBJgzlb0v6SyI+fYf5K +Bg2WzDuLKvQBi9tFSwnUbQoFFlOeiGW8G/bdkoJDWeS1oYgSD3nkmvXvrVESCrbT +-----END PGP PUBLIC KEY BLOCK-----` +) + +func TestVerifyPGPSignature(t *testing.T) { + tests := []struct { + name string + payload []byte + sig string + keyRings []string + want string + wantErr string + }{ + { + name: "Valid commit signature", + payload: []byte(encodedCommitFixture), + sig: signatureCommitFixture, + keyRings: []string{armoredKeyRingFixture}, + want: keyRingFingerprintFixture, + }, + { + name: "Malformed encoded commit", + payload: []byte(malformedEncodedCommitFixture), + sig: signatureCommitFixture, + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload with any of the given key rings", + }, + { + name: "Malformed key ring", + payload: []byte(encodedCommitFixture), + sig: signatureCommitFixture, + keyRings: []string{malformedKeyRingFixture}, + wantErr: "unable to read armored key ring: unexpected EOF", + }, + { + name: "Missing signature", + payload: []byte(encodedCommitFixture), + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload as the provided signature is empty", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + got, err := signatures.VerifyPGPSignature(tt.sig, tt.payload, tt.keyRings...) + if tt.wantErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + g.Expect(got).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(Equal(tt.want)) + }) + } +} + +func TestVerifyPGPSignatureWithFixturesForTags(t *testing.T) { + testDataDir := filepath.Join("testdata", "gpg_signatures") + + // Test cases for each key type using fixtures + keyTypes := []struct { + name string + sigFile string + keyFile string + wantErr bool + }{ + {"rsa_2048 valid tag signature", "tag_rsa_2048_signed.txt", "key_rsa_2048.pub", false}, + {"rsa_4096 valid tag signature", "tag_rsa_4096_signed.txt", "key_rsa_4096.pub", false}, + {"ed25519 valid tag signature", "tag_ed25519_signed.txt", "key_ed25519.pub", false}, + {"ecdsa_p256 valid tag signature", "tag_ecdsa_p256_signed.txt", "key_ecdsa_p256.pub", false}, + {"ecdsa_p384 valid tag signature", "tag_ecdsa_p384_signed.txt", "key_ecdsa_p384.pub", false}, + {"ecdsa_p521 valid tag signature", "tag_ecdsa_p521_signed.txt", "key_ecdsa_p521.pub", false}, + {"dsa_2048 valid tag signature", "tag_dsa_2048_signed.txt", "key_dsa_2048.pub", false}, + {"brainpool_p256 valid tag signature", "tag_brainpool_p256_signed.txt", "key_brainpool_p256.pub", false}, + {"brainpool_p384 valid tag signature", "tag_brainpool_p384_signed.txt", "key_brainpool_p384.pub", false}, + {"brainpool_p512 valid tag signature", "tag_brainpool_p512_signed.txt", "key_brainpool_p512.pub", false}, + + // ed448 test fails because the key was created with OpenPGP version 5, + // which is not supported by github.com/ProtonMail/go-crypto (only version 4 is supported). + // The error occurs when trying to read the armored key ring: + // "unable to read armored key ring: openpgp: invalid data: first packet was not a public/private key" + {"ed448 valid tag signature", "tag_ed448_signed.txt", "key_ed448.pub", true}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + g := NewWithT(t) + + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + g.Expect(err).ToNot(HaveOccurred()) + + // Read the public key + publicKey, err := os.ReadFile(filepath.Join(testDataDir, kt.keyFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Verify the signature using the git.Tag's Signature and Encoded fields + fingerprint, err := signatures.VerifyPGPSignature(gitTag.Signature, gitTag.Encoded, string(publicKey)) + if kt.wantErr { + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) + }) + } +} + +func TestVerifyPGPSignatureWithFixtures(t *testing.T) { + testDataDir := filepath.Join("testdata", "gpg_signatures") + + // Test cases for each key type using fixtures + keyTypes := []struct { + name string + sigFile string + keyFile string + wantErr bool + }{ + {"rsa_2048 valid signature", "commit_rsa_2048_signed.txt", "key_rsa_2048.pub", false}, + {"rsa_4096 valid signature", "commit_rsa_4096_signed.txt", "key_rsa_4096.pub", false}, + {"ed25519 valid signature", "commit_ed25519_signed.txt", "key_ed25519.pub", false}, + {"ecdsa_p256 valid signature", "commit_ecdsa_p256_signed.txt", "key_ecdsa_p256.pub", false}, + {"ecdsa_p384 valid signature", "commit_ecdsa_p384_signed.txt", "key_ecdsa_p384.pub", false}, + {"ecdsa_p521 valid signature", "commit_ecdsa_p521_signed.txt", "key_ecdsa_p521.pub", false}, + {"dsa_2048 valid signature", "commit_dsa_2048_signed.txt", "key_dsa_2048.pub", false}, + {"brainpool_p256 valid signature", "commit_brainpool_p256_signed.txt", "key_brainpool_p256.pub", false}, + {"brainpool_p384 valid signature", "commit_brainpool_p384_signed.txt", "key_brainpool_p384.pub", false}, + {"brainpool_p512 valid signature", "commit_brainpool_p512_signed.txt", "key_brainpool_p512.pub", false}, + + // ed448 test fails because the key was created with OpenPGP version 5, + // which is not supported by github.com/ProtonMail/go-crypto (only version 4 is supported). + // The error occurs when trying to read the armored key ring: + // "unable to read armored key ring: openpgp: invalid data: first packet was not a public/private key" + {"ed448 valid signature", "commit_ed448_signed.txt", "key_ed448.pub", true}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + g := NewWithT(t) + + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + g.Expect(err).ToNot(HaveOccurred()) + + // Read the public key + publicKey, err := os.ReadFile(filepath.Join(testDataDir, kt.keyFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Verify the signature using the git.Commit's Signature and Encoded fields + fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) + if kt.wantErr { + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) + }) + } + + // Test error cases + t.Run("unsigned commit", func(t *testing.T) { + g := NewWithT(t) + + // Parse the unsigned commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) + g.Expect(err).ToNot(HaveOccurred()) + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + g.Expect(err).ToNot(HaveOccurred()) + + // Read a public key + publicKey, err := os.ReadFile(filepath.Join(testDataDir, "key_rsa_2048.pub")) + g.Expect(err).ToNot(HaveOccurred()) + + // Verify the signature - should fail as the commit is unsigned + fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + }) +} + +func TestVerifyPGPSignatureWithMultipleKeyRing(t *testing.T) { + testDataDir := filepath.Join("testdata", "gpg_signatures") + + // Read multiple public keys to create a multi-key keyring + keyFiles := []string{ + "key_rsa_2048.pub", + "key_rsa_4096.pub", + "key_ed25519.pub", + "key_ecdsa_p256.pub", + "key_ecdsa_p384.pub", + "key_ecdsa_p521.pub", + "key_dsa_2048.pub", + "key_brainpool_p256.pub", + "key_brainpool_p384.pub", + "key_brainpool_p512.pub", + } + + var keyRings []string + for _, keyFile := range keyFiles { + publicKey, err := os.ReadFile(filepath.Join(testDataDir, keyFile)) + if err != nil { + t.Fatalf("failed to read public key file %s: %v", keyFile, err) + } + keyRings = append(keyRings, string(publicKey)) + } + + // Test cases for each key type using the multi-key keyring + keyTypes := []struct { + name string + sigFile string + wantErr bool + }{ + {"rsa_2048 valid signature with multi-key keyring", "commit_rsa_2048_signed.txt", false}, + {"rsa_4096 valid signature with multi-key keyring", "commit_rsa_4096_signed.txt", false}, + {"ed25519 valid signature with multi-key keyring", "commit_ed25519_signed.txt", false}, + {"ecdsa_p256 valid signature with multi-key keyring", "commit_ecdsa_p256_signed.txt", false}, + {"ecdsa_p384 valid signature with multi-key keyring", "commit_ecdsa_p384_signed.txt", false}, + {"ecdsa_p521 valid signature with multi-key keyring", "commit_ecdsa_p521_signed.txt", false}, + {"dsa_2048 valid signature with multi-key keyring", "commit_dsa_2048_signed.txt", false}, + {"brainpool_p256 valid signature with multi-key keyring", "commit_brainpool_p256_signed.txt", false}, + {"brainpool_p384 valid signature with multi-key keyring", "commit_brainpool_p384_signed.txt", false}, + {"brainpool_p512 valid signature with multi-key keyring", "commit_brainpool_p512_signed.txt", false}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + g := NewWithT(t) + + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + g.Expect(err).ToNot(HaveOccurred()) + + // Verify the signature using the multi-key keyring + fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, keyRings...) + if kt.wantErr { + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) + }) + } + + // Test that an unsigned commit fails with multi-key keyring + t.Run("unsigned commit with multi-key keyring", func(t *testing.T) { + g := NewWithT(t) + + // Parse the unsigned commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) + g.Expect(err).ToNot(HaveOccurred()) + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + g.Expect(err).ToNot(HaveOccurred()) + + // Verify the signature - should fail as the commit is unsigned + fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, keyRings...) + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + }) +} diff --git a/git/signatures/signature.go b/git/signatures/signature.go new file mode 100644 index 000000000..04c487b6f --- /dev/null +++ b/git/signatures/signature.go @@ -0,0 +1,64 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures + +import ( + "strings" +) + +// SignatureType represents the type of a signature. +type SignatureType string + +const ( + // SignatureTypePGP represents a PGP signature. + SignatureTypePGP SignatureType = "pgp" + // SignatureTypeSSH represents an SSH signature. + SignatureTypeSSH SignatureType = "ssh" + // SignatureTypeUnknown represents an unknown signature type. + SignatureTypeUnknown SignatureType = "unknown" +) + +// IsPGPSignature tests if the given signature is of type PGP. +// It returns true if the signature starts with the PGP signature prefix. +func IsPGPSignature(signature string) bool { + if signature == "" { + return false + } + return strings.HasPrefix(strings.TrimSpace(signature), PGPSignaturePrefix) +} + +// IsSSHSignature tests if the given signature is of type SSH. +// It returns true if the signature starts with the SSH signature prefix. +func IsSSHSignature(signature string) bool { + if signature == "" { + return false + } + return strings.HasPrefix(strings.TrimSpace(signature), SSHSignaturePrefix) +} + +// GetSignatureType returns the type of the signature as a string. +// It returns "pgp" for PGP signatures, "ssh" for SSH signatures, +// and "unknown" for unrecognized or empty signatures. +func GetSignatureType(signature string) string { + if IsPGPSignature(signature) { + return string(SignatureTypePGP) + } + if IsSSHSignature(signature) { + return string(SignatureTypeSSH) + } + return string(SignatureTypeUnknown) +} diff --git a/git/signatures/signature_test.go b/git/signatures/signature_test.go new file mode 100644 index 000000000..baa48c5b4 --- /dev/null +++ b/git/signatures/signature_test.go @@ -0,0 +1,169 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures_test + +import ( + "testing" + + . "github.com/fluxcd/pkg/git/signatures" +) + +func TestIsPGPSignature(t *testing.T) { + tests := []struct { + name string + signature string + want bool + }{ + { + name: "valid PGP signature", + signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + want: true, + }, + { + name: "PGP signature with leading whitespace", + signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + want: true, + }, + { + name: "empty signature", + signature: "", + want: false, + }, + { + name: "SSH signature", + signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + want: false, + }, + { + name: "unknown signature", + signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + want: false, + }, + { + name: "whitespace only", + signature: " \n\t ", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsPGPSignature(tt.signature); got != tt.want { + t.Errorf("IsPGPSignature() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsSSHSignature(t *testing.T) { + tests := []struct { + name string + signature string + want bool + }{ + { + name: "valid SSH signature", + signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + want: true, + }, + { + name: "SSH signature with leading whitespace", + signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + want: true, + }, + { + name: "empty signature", + signature: "", + want: false, + }, + { + name: "PGP signature", + signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + want: false, + }, + { + name: "unknown signature", + signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + want: false, + }, + { + name: "whitespace only", + signature: " \n\t ", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsSSHSignature(tt.signature); got != tt.want { + t.Errorf("IsSSHSignature() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetSignatureType(t *testing.T) { + tests := []struct { + name string + signature string + want string + }{ + { + name: "PGP signature", + signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + want: string(SignatureTypePGP), + }, + { + name: "PGP signature with leading whitespace", + signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + want: string(SignatureTypePGP), + }, + { + name: "SSH signature", + signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + want: string(SignatureTypeSSH), + }, + { + name: "SSH signature with leading whitespace", + signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + want: string(SignatureTypeSSH), + }, + { + name: "empty signature", + signature: "", + want: string(SignatureTypeUnknown), + }, + { + name: "unknown signature", + signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + want: string(SignatureTypeUnknown), + }, + { + name: "whitespace only", + signature: " \n\t ", + want: string(SignatureTypeUnknown), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetSignatureType(tt.signature); got != tt.want { + t.Errorf("GetSignatureType() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/git/signatures/ssh_signature.go b/git/signatures/ssh_signature.go new file mode 100644 index 000000000..e298e65ad --- /dev/null +++ b/git/signatures/ssh_signature.go @@ -0,0 +1,106 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures + +import ( + "bytes" + "crypto/sha256" + "encoding/base64" + "fmt" + "strings" + + "github.com/hiddeco/sshsig" + gossh "golang.org/x/crypto/ssh" +) + +// SSHSignaturePrefix is the prefix used by Git to identify SSH signatures. +const SSHSignaturePrefix = "-----BEGIN SSH SIGNATURE-----" + +// ParseAuthorizedKeys parses the given authorized keys string and returns +// a slice of public keys. It supports comments and empty lines. +func ParseAuthorizedKeys(authorizedKeys string) ([]gossh.PublicKey, error) { + var publicKeys []gossh.PublicKey + + for _, line := range strings.Split(authorizedKeys, "\n") { + line = strings.TrimSpace(line) + + // Skip empty lines and comments + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + // Parse the authorized key line + pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(line)) + if err != nil { + return nil, fmt.Errorf("unable to parse authorized key: %w", err) + } + + publicKeys = append(publicKeys, pubKey) + } + + return publicKeys, nil +} + +// verifySSHSignature verifies the SSH signature against the payload using +// the provided authorized keys. It returns the fingerprint of the key that +// successfully verified the signature, or an error. +func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...string) (string, error) { + if signature == "" { + return "", fmt.Errorf("unable to verify payload as the provided signature is empty") + } + + if len(payload) == 0 { + return "", fmt.Errorf("unable to verify payload as the provided payload is empty") + } + + // Unarmor the signature (remove PEM-like armor) + sig, err := sshsig.Unarmor([]byte(signature)) + if err != nil { + return "", fmt.Errorf("unable to unarmor SSH signature: %w", err) + } + + // Try to verify with each set of authorized keys + for _, keys := range authorizedKeys { + publicKeys, err := ParseAuthorizedKeys(keys) + if err != nil { + return "", fmt.Errorf("unable to parse authorized keys: %w", err) + } + + // Try to verify with each public key + for _, pubKey := range publicKeys { + // Verify the signature using sshsig library + // The namespace for Git is "git" + // Git supports both SHA256 and SHA512, so we try both + for _, hashAlgo := range []sshsig.HashAlgorithm{sshsig.HashSHA256, sshsig.HashSHA512} { + err := sshsig.Verify(bytes.NewReader(payload), sig, pubKey, hashAlgo, "git") + if err == nil { + // Signature verified successfully + return GetPublicKeyFingerprint(pubKey), nil + } + } + } + } + + return "", fmt.Errorf("unable to verify payload with any of the given authorized keys") +} + +// getPublicKeyFingerprint returns the SHA256 fingerprint of the public key +// in the format used by SSH (e.g., "SHA256:abc123..."). +func GetPublicKeyFingerprint(pubKey gossh.PublicKey) string { + hash := sha256.Sum256(pubKey.Marshal()) + return "SHA256:" + base64.StdEncoding.EncodeToString(hash[:]) +} diff --git a/git/signatures/ssh_signature_test.go b/git/signatures/ssh_signature_test.go new file mode 100644 index 000000000..df9521511 --- /dev/null +++ b/git/signatures/ssh_signature_test.go @@ -0,0 +1,1286 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/fluxcd/pkg/git/gogit" + "github.com/fluxcd/pkg/git/signatures" + "github.com/go-git/go-git/v5/plumbing" + "github.com/hiddeco/sshsig" +) + +func TestParseAuthorizedKeys(t *testing.T) { + tests := []struct { + name string + authorizedKeys string + wantCount int + wantErr bool + }{ + { + name: "single key", + authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com", + wantCount: 1, + wantErr: false, + }, + { + name: "multiple keys", + authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test1@example.com +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test2@example.com`, + wantCount: 2, + wantErr: false, + }, + { + name: "with comments", + authorizedKeys: `# This is a comment +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com +# Another comment`, + wantCount: 1, + wantErr: false, + }, + { + name: "with empty lines", + authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com + +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test2@example.com`, + wantCount: 2, + wantErr: false, + }, + { + name: "empty", + authorizedKeys: "", + wantCount: 0, + wantErr: false, + }, + { + name: "invalid key", + authorizedKeys: "invalid-key-data", + wantCount: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + keys, err := signatures.ParseAuthorizedKeys(tt.authorizedKeys) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + }) + } +} + +func TestParseAuthorizedKeysFromFixtures(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixture string + wantCount int + wantErr bool + }{ + { + name: "ed25519 key", + fixture: "authorized_keys_ed25519", + wantCount: 1, + wantErr: false, + }, + { + name: "rsa key", + fixture: "authorized_keys_rsa", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p256 key", + fixture: "authorized_keys_ecdsa_p256", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p384 key", + fixture: "authorized_keys_ecdsa_p384", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p521 key", + fixture: "authorized_keys_ecdsa_p521", + wantCount: 1, + wantErr: false, + }, + { + name: "all key types combined", + fixture: "authorized_keys_all", + wantCount: 5, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.fixture)) + if err != nil { + t.Fatalf("Failed to read fixture file %s: %v", tt.fixture, err) + } + + keys, err := signatures.ParseAuthorizedKeys(string(authorizedKeys)) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + + // Verify that each key has a valid fingerprint + for i, key := range keys { + fingerprint := signatures.GetPublicKeyFingerprint(key) + if fingerprint == "" { + t.Errorf("Key %d has empty fingerprint", i) + } + if !strings.HasPrefix(fingerprint, "SHA256:") { + t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) + } + } + }) + } +} + +func TestParseAuthorizedKeysCombinations(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixtures []string + wantCount int + wantErr bool + }{ + { + name: "ed25519 + rsa", + fixtures: []string{"authorized_keys_ed25519", "authorized_keys_rsa"}, + wantCount: 2, + wantErr: false, + }, + { + name: "ed25519 + ecdsa p256", + fixtures: []string{"authorized_keys_ed25519", "authorized_keys_ecdsa_p256"}, + wantCount: 2, + wantErr: false, + }, + { + name: "rsa + ecdsa p384 + ecdsa p521", + fixtures: []string{"authorized_keys_rsa", "authorized_keys_ecdsa_p384", "authorized_keys_ecdsa_p521"}, + wantCount: 3, + wantErr: false, + }, + { + name: "all ecdsa variants", + fixtures: []string{"authorized_keys_ecdsa_p256", "authorized_keys_ecdsa_p384", "authorized_keys_ecdsa_p521"}, + wantCount: 3, + wantErr: false, + }, + { + name: "ed25519 + rsa + all ecdsa", + fixtures: []string{"authorized_keys_ed25519", "authorized_keys_rsa", "authorized_keys_ecdsa_p256", "authorized_keys_ecdsa_p384", "authorized_keys_ecdsa_p521"}, + wantCount: 5, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var combinedKeys strings.Builder + for _, fixture := range tt.fixtures { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, fixture)) + if err != nil { + t.Fatalf("Failed to read fixture file %s: %v", fixture, err) + } + combinedKeys.Write(authorizedKeys) + combinedKeys.WriteString("\n") + } + + keys, err := signatures.ParseAuthorizedKeys(combinedKeys.String()) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + + // Verify that each key has a valid fingerprint + for i, key := range keys { + fingerprint := signatures.GetPublicKeyFingerprint(key) + if fingerprint == "" { + t.Errorf("Key %d has empty fingerprint", i) + } + if !strings.HasPrefix(fingerprint, "SHA256:") { + t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) + } + } + }) + } +} + +func TestParseSSHSignature(t *testing.T) { + tests := []struct { + name string + sig string + wantErr bool + }{ + { + name: "valid signature with PEM armor", + sig: `-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg9uahUwBLlO2Dvuz0MtIA5/iBcK +JCmB1F6QUeXNtccscAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQFb88f1ZXOK1BByC4QQOthH9bZP0/hMcPl62h4oIuEny6W5xd/oOpDv7dmj9A6DiMS +o6RLdWlvb81l/UyYhGEwE= +-----END SSH SIGNATURE-----`, + wantErr: false, + }, + { + name: "valid signature without PEM armor", + sig: "U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg9uahUwBLlO2Dvuz0MtIA5/iBcKJCmB1F6QUeXNtccscAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAAQFb88f1ZXOK1BByC4QQOthH9bZP0/hMcPl62h4oIuEny6W5xd/oOpDv7dmj9A6DiMSo6RLdWlvb81l/UyYhGEwE=", + wantErr: true, // sshsig.Unarmor() requires PEM armor + }, + { + name: "empty signature", + sig: "", + wantErr: true, + }, + { + name: "invalid base64", + sig: "-----BEGIN SSH SIGNATURE-----\ninvalid-base64!!!\n-----END SSH SIGNATURE-----", + wantErr: true, + }, + { + name: "invalid format", + sig: "invalid-signature-format", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sig, err := sshsig.Unarmor([]byte(tt.sig)) + if (err != nil) != tt.wantErr { + t.Errorf("sshsig.Unarmor() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && sig == nil { + t.Errorf("sshsig.Unarmor() returned nil signature") + } + }) + } +} + +func TestGetPublicKeyFingerprint(t *testing.T) { + // Test with a known public key + pubKeyStr := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com" + keys, err := signatures.ParseAuthorizedKeys(pubKeyStr) + if err != nil { + t.Fatalf("Failed to parse test public key: %v", err) + } + if len(keys) == 0 { + t.Fatal("No keys parsed") + } + + fingerprint := signatures.GetPublicKeyFingerprint(keys[0]) + if fingerprint == "" { + t.Error("GetPublicKeyFingerprint() returned empty string") + } + if !strings.HasPrefix(fingerprint, "SHA256:") { + t.Errorf("GetPublicKeyFingerprint() = %s, want prefix SHA256:", fingerprint) + } +} + +func TestVerifySSHSignature(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + // Test cases for each key type using fixtures + keyTypes := []struct { + name string + sigFile string + authFile string + wantErr bool + }{ + {"ed25519 valid signature", "commit_ed25519_signed.txt", "authorized_keys_ed25519", false}, + {"rsa valid signature", "commit_rsa_signed.txt", "authorized_keys_rsa", false}, + {"ecdsa_p256 valid signature", "commit_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, + {"ecdsa_p384 valid signature", "commit_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, + {"ecdsa_p521 valid signature", "commit_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + // Read the authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Verify the signature using the git.Commit's Signature and Encoded fields + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKeys)) + if (err != nil) != kt.wantErr { + t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) + return + } + if !kt.wantErr && fingerprint == "" { + t.Errorf("VerifySSHSignature() returned empty fingerprint") + } + if !kt.wantErr { + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } + + // Test error cases + t.Run("empty signature", func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + fingerprint, err := signatures.VerifySSHSignature("", gitCommit.Encoded, string(authorizedKeys)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) + } + }) + + t.Run("empty payload", func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, []byte{}, string(authorizedKeys)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) + } + }) + + t.Run("wrong authorized keys", func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + // Use a different key that won't match + wrongKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyM97VxLgOCuB9Eg5cDtTc8ogkdM1xAyJhzODB9cK1 wrong@example.com" + + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, wrongKey) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) + } + }) + + t.Run("invalid signature", func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + invalidSig := "-----BEGIN SSH SIGNATURE-----\n invalid\n -----END SSH SIGNATURE-----" + + fingerprint, err := signatures.VerifySSHSignature(invalidSig, gitCommit.Encoded, string(authorizedKeys)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) + } + }) +} + +func TestVerifySSHSignatureAllKeyTypes(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + // Test cases for each key type + keyTypes := []struct { + name string + sigFile string + authFile string + wantErr bool + }{ + {"ed25519", "commit_ed25519_signed.txt", "authorized_keys_ed25519", false}, + {"rsa", "commit_rsa_signed.txt", "authorized_keys_rsa", false}, + {"ecdsa_p256", "commit_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, + {"ecdsa_p384", "commit_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, + {"ecdsa_p521", "commit_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + // Read the authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Verify the signature using the git.Commit's Signature and Encoded fields + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKeys)) + if (err != nil) != kt.wantErr { + t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) + return + } + if !kt.wantErr && fingerprint == "" { + t.Errorf("VerifySSHSignature() returned empty fingerprint") + } + if !kt.wantErr { + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } +} + +func TestVerifySSHSignatureCombinedKeys(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + // Read the combined authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_all")) + if err != nil { + t.Fatalf("Failed to read combined authorized keys: %v", err) + } + + // Test each key type against the combined authorized keys + keyTypes := []struct { + name string + sigFile string + wantErr bool + }{ + {"ed25519", "commit_ed25519_signed.txt", false}, + {"rsa", "commit_rsa_signed.txt", false}, + {"ecdsa_p256", "commit_ecdsa_p256_signed.txt", false}, + {"ecdsa_p384", "commit_ecdsa_p384_signed.txt", false}, + {"ecdsa_p521", "commit_ecdsa_p521_signed.txt", false}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + // Verify the signature with combined authorized keys + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKeys)) + if (err != nil) != kt.wantErr { + t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) + return + } + if !kt.wantErr && fingerprint == "" { + t.Errorf("VerifySSHSignature() returned empty fingerprint") + } + if !kt.wantErr { + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } +} + +func TestBuildCommitWithRefFromFixture(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixture string + wantErr bool + wantSig bool + }{ + { + name: "ed25519 signed commit", + fixture: "commit_ed25519_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "rsa signed commit", + fixture: "commit_rsa_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "ecdsa p256 signed commit", + fixture: "commit_ecdsa_p256_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "ecdsa p384 signed commit", + fixture: "commit_ecdsa_p384_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "ecdsa p521 signed commit", + fixture: "commit_ecdsa_p521_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "unsigned commit", + fixture: "commit_unsigned.txt", + wantErr: false, + wantSig: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if (err != nil) != tt.wantErr { + t.Errorf("BuildCommitWithRef() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + // Verify the commit was built correctly + if gitCommit == nil { + t.Fatal("BuildCommitWithRef() returned nil commit") + } + + // Check if signature is present as expected + hasSig := gitCommit.Signature != "" + if hasSig != tt.wantSig { + t.Errorf("BuildCommitWithRef() has signature = %v, want %v", hasSig, tt.wantSig) + } + + // Verify the encoded data is present + if len(gitCommit.Encoded) == 0 { + t.Error("BuildCommitWithRef() returned commit with empty Encoded field") + } + + // Verify the reference is set correctly + if gitCommit.Reference != "refs/heads/main" { + t.Errorf("BuildCommitWithRef() reference = %q, want %q", gitCommit.Reference, "refs/heads/main") + } + + // Verify the hash is set + if len(gitCommit.Hash) == 0 { + t.Error("BuildCommitWithRef() returned commit with empty Hash") + } + + // Verify author and committer are set + if gitCommit.Author.Name == "" { + t.Error("BuildCommitWithRef() returned commit with empty Author.Name") + } + if gitCommit.Committer.Name == "" { + t.Error("BuildCommitWithRef() returned commit with empty Committer.Name") + } + + // If the commit has a signature, verify it can be extracted + if tt.wantSig { + // The signature is stored in gitCommit.Signature, not in gitCommit.Encoded + // gitCommit.Encoded contains the encoded commit without the signature + if gitCommit.Signature == "" { + t.Error("BuildCommitWithRef() returned commit with empty Signature field") + } + // Verify the signature contains the expected SSH signature markers + if !strings.Contains(gitCommit.Signature, "-----BEGIN SSH SIGNATURE-----") { + t.Error("BuildCommitWithRef() signature does not contain SSH signature start marker") + } + if !strings.Contains(gitCommit.Signature, "-----END SSH SIGNATURE-----") { + t.Error("BuildCommitWithRef() signature does not contain SSH signature end marker") + } + } + } + }) + } +} + +func TestBuildCommitWithRefAndVerifySSH(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixture string + authFile string + wantErr bool + }{ + { + name: "ed25519 signed commit", + fixture: "commit_ed25519_signed.txt", + authFile: "authorized_keys_ed25519", + wantErr: false, + }, + { + name: "rsa signed commit", + fixture: "commit_rsa_signed.txt", + authFile: "authorized_keys_rsa", + wantErr: false, + }, + { + name: "ecdsa p256 signed commit", + fixture: "commit_ecdsa_p256_signed.txt", + authFile: "authorized_keys_ecdsa_p256", + wantErr: false, + }, + { + name: "ecdsa p384 signed commit", + fixture: "commit_ecdsa_p384_signed.txt", + authFile: "authorized_keys_ecdsa_p384", + wantErr: false, + }, + { + name: "ecdsa p521 signed commit", + fixture: "commit_ecdsa_p521_signed.txt", + authFile: "authorized_keys_ecdsa_p521", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("BuildCommitWithRef() error = %v", err) + } + + // Read the authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.authFile)) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Verify the SSH signature using the git.Commit's VerifySSH method + fingerprint, err := gitCommit.VerifySSH(string(authorizedKeys)) + if (err != nil) != tt.wantErr { + t.Errorf("git.Commit.VerifySSH() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + if fingerprint == "" { + t.Error("git.Commit.VerifySSH() returned empty fingerprint") + } + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } +} + +func TestBuildCommitWithRefWithDifferentRefs(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + // Parse a signed commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + tests := []struct { + name string + ref plumbing.ReferenceName + wantRef string + }{ + { + name: "branch reference", + ref: plumbing.ReferenceName("refs/heads/main"), + wantRef: "refs/heads/main", + }, + { + name: "tag reference", + ref: plumbing.ReferenceName("refs/tags/v1.0.0"), + wantRef: "refs/tags/v1.0.0", + }, + { + name: "remote branch reference", + ref: plumbing.ReferenceName("refs/remotes/origin/main"), + wantRef: "refs/remotes/origin/main", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Build a git.Commit using BuildCommitWithRef with different references + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, tt.ref) + if err != nil { + t.Fatalf("BuildCommitWithRef() error = %v", err) + } + + // Verify the reference is set correctly + if gitCommit.Reference != tt.wantRef { + t.Errorf("BuildCommitWithRef() reference = %q, want %q", gitCommit.Reference, tt.wantRef) + } + + // Verify other fields are still set correctly + if len(gitCommit.Hash) == 0 { + t.Error("BuildCommitWithRef() returned commit with empty Hash") + } + if gitCommit.Signature == "" { + t.Error("BuildCommitWithRef() returned commit with empty Signature") + } + }) + } +} + +func TestBuildTagFromFixture(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixture string + wantErr bool + wantSig bool + }{ + { + name: "ed25519 signed tag", + fixture: "tag_ed25519_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "rsa signed tag", + fixture: "tag_rsa_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "ecdsa p256 signed tag", + fixture: "tag_ecdsa_p256_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "ecdsa p384 signed tag", + fixture: "tag_ecdsa_p384_signed.txt", + wantErr: false, + wantSig: true, + }, + { + name: "ecdsa p521 signed tag", + fixture: "tag_ecdsa_p521_signed.txt", + wantErr: false, + wantSig: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if (err != nil) != tt.wantErr { + t.Errorf("BuildTag() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + // Verify the tag was built correctly + if gitTag == nil { + t.Fatal("BuildTag() returned nil tag") + } + + // Check if signature is present as expected + hasSig := gitTag.Signature != "" + if hasSig != tt.wantSig { + t.Errorf("BuildTag() has signature = %v, want %v", hasSig, tt.wantSig) + } + + // Verify the encoded data is present + if len(gitTag.Encoded) == 0 { + t.Error("BuildTag() returned tag with empty Encoded field") + } + + // Verify the name is set correctly + if gitTag.Name == "" { + t.Error("BuildTag() returned tag with empty Name") + } + + // Verify the hash is set + if len(gitTag.Hash) == 0 { + t.Error("BuildTag() returned tag with empty Hash") + } + + // Verify author is set + if gitTag.Author.Name == "" { + t.Error("BuildTag() returned tag with empty Author.Name") + } + + // If the tag has a signature, verify it can be extracted + if tt.wantSig { + if gitTag.Signature == "" { + t.Error("BuildTag() returned tag with empty Signature field") + } + // Verify the signature contains the expected SSH signature markers + if !strings.Contains(gitTag.Signature, "-----BEGIN SSH SIGNATURE-----") { + t.Error("BuildTag() signature does not contain SSH signature start marker") + } + if !strings.Contains(gitTag.Signature, "-----END SSH SIGNATURE-----") { + t.Error("BuildTag() signature does not contain SSH signature end marker") + } + } + } + }) + } +} + +func TestVerifySSHSignatureForTags(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + // Test cases for each key type using fixtures + keyTypes := []struct { + name string + sigFile string + authFile string + wantErr bool + }{ + {"ed25519 valid signature", "tag_ed25519_signed.txt", "authorized_keys_ed25519", false}, + {"rsa valid signature", "tag_rsa_signed.txt", "authorized_keys_rsa", false}, + {"ecdsa_p256 valid signature", "tag_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, + {"ecdsa_p384 valid signature", "tag_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, + {"ecdsa_p521 valid signature", "tag_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) + } + + // Read the authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Verify the signature using the git.Tag's Signature and Encoded fields + fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKeys)) + if (err != nil) != kt.wantErr { + t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) + return + } + if !kt.wantErr && fingerprint == "" { + t.Errorf("VerifySSHSignature() returned empty fingerprint") + } + if !kt.wantErr { + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } + + // Test error cases + t.Run("empty signature", func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) + } + + fingerprint, err := signatures.VerifySSHSignature("", gitTag.Encoded, string(authorizedKeys)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) + } + }) + + t.Run("empty payload", func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) + } + + fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, []byte{}, string(authorizedKeys)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) + } + }) + + t.Run("wrong authorized keys", func(t *testing.T) { + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) + } + + // Use a different key that won't match + wrongKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyM97VxLgOCuB9Eg5cDtTc8ogkdM1xAyJhzODB9cK1 wrong@example.com" + + fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, wrongKey) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) + } + }) + + t.Run("invalid signature", func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) + } + + invalidSig := "-----BEGIN SSH SIGNATURE-----\n invalid\n -----END SSH SIGNATURE-----" + + fingerprint, err := signatures.VerifySSHSignature(invalidSig, gitTag.Encoded, string(authorizedKeys)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) + } + }) +} + +func TestVerifySSHSignatureForTagsAllKeyTypes(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + // Test cases for each key type + keyTypes := []struct { + name string + sigFile string + authFile string + wantErr bool + }{ + {"ed25519", "tag_ed25519_signed.txt", "authorized_keys_ed25519", false}, + {"rsa", "tag_rsa_signed.txt", "authorized_keys_rsa", false}, + {"ecdsa_p256", "tag_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, + {"ecdsa_p384", "tag_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, + {"ecdsa_p521", "tag_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) + } + + // Read the authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Verify the signature using the git.Tag's Signature and Encoded fields + fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKeys)) + if (err != nil) != kt.wantErr { + t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) + return + } + if !kt.wantErr && fingerprint == "" { + t.Errorf("VerifySSHSignature() returned empty fingerprint") + } + if !kt.wantErr { + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } +} + +func TestVerifySSHSignatureForTagsCombinedKeys(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + // Read the combined authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_all")) + if err != nil { + t.Fatalf("Failed to read combined authorized keys: %v", err) + } + + // Test each key type against the combined authorized keys + keyTypes := []struct { + name string + sigFile string + wantErr bool + }{ + {"ed25519", "tag_ed25519_signed.txt", false}, + {"rsa", "tag_rsa_signed.txt", false}, + {"ecdsa_p256", "tag_ecdsa_p256_signed.txt", false}, + {"ecdsa_p384", "tag_ecdsa_p384_signed.txt", false}, + {"ecdsa_p521", "tag_ecdsa_p521_signed.txt", false}, + } + + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) + } + + // Verify the signature with combined authorized keys + fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKeys)) + if (err != nil) != kt.wantErr { + t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) + return + } + if !kt.wantErr && fingerprint == "" { + t.Errorf("VerifySSHSignature() returned empty fingerprint") + } + if !kt.wantErr { + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } +} + +func TestBuildTagAndVerifySSH(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixture string + authFile string + wantErr bool + }{ + { + name: "ed25519 signed tag", + fixture: "tag_ed25519_signed.txt", + authFile: "authorized_keys_ed25519", + wantErr: false, + }, + { + name: "rsa signed tag", + fixture: "tag_rsa_signed.txt", + authFile: "authorized_keys_rsa", + wantErr: false, + }, + { + name: "ecdsa p256 signed tag", + fixture: "tag_ecdsa_p256_signed.txt", + authFile: "authorized_keys_ecdsa_p256", + wantErr: false, + }, + { + name: "ecdsa p384 signed tag", + fixture: "tag_ecdsa_p384_signed.txt", + authFile: "authorized_keys_ecdsa_p384", + wantErr: false, + }, + { + name: "ecdsa p521 signed tag", + fixture: "tag_ecdsa_p521_signed.txt", + authFile: "authorized_keys_ecdsa_p521", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Parse the tag from the fixture file + tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) + } + + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("BuildTag() error = %v", err) + } + + // Read the authorized keys + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.authFile)) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } + + // Verify the SSH signature using the git.Tag's VerifySSH method + fingerprint, err := gitTag.VerifySSH(string(authorizedKeys)) + if (err != nil) != tt.wantErr { + t.Errorf("git.Tag.VerifySSH() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + if fingerprint == "" { + t.Error("git.Tag.VerifySSH() returned empty fingerprint") + } + t.Logf("Verified with fingerprint: %s", fingerprint) + } + }) + } +} diff --git a/git/signatures/testdata/gpg_signatures/README.md b/git/signatures/testdata/gpg_signatures/README.md new file mode 100644 index 000000000..29fd5fc70 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/README.md @@ -0,0 +1,399 @@ +# GPG Signature Test Fixtures + +This directory contains test fixtures for GPG signature validation. + +## Quick Start + +To generate all test fixtures at once, simply run: + +```bash +./generate_gpg_fixtures.sh +``` + +This script will automatically create all GPG keys, signed commits, and signed tags. + +## How to Generate Test Fixtures + +### Using the Automated Script + +The [`generate_gpg_fixtures.sh`](generate_gpg_fixtures.sh) script automates the entire process of creating GPG signature test fixtures. It generates: + +1. **GPG Key Pairs** in supported variants: + - RSA (2048 and 4096 bits) + - DSA (2048 bits) + - ECC/ECDSA (NIST P-256, P-384, P-521) + - Brainpool curves (P-256, P-384, P-512) + - EdDSA (Ed25519, Ed448) + + **Note:** Some key types (like Ed448) require GnuPG 2.3 or higher. The script will report any failures and continue with successfully generated keys. + +2. **Public Keys**: + - Individual public key files for each key type + +3. **Signed Git Commits**: + - One signed commit for each key type + - All commits are verified using `git verify-commit` + +4. **Signed Git Tags**: + - One signed tag for each key type + - All tags are verified using `git verify-tag` + +5. **Unsigned Commit**: + - One unsigned commit for testing negative cases + +### Manual Generation + +If you need to generate test fixtures manually, follow these steps: + +#### 1. Generate GPG Key Pairs + +```bash +# Set up a temporary GPG home directory +export GNUPGHOME=$(mktemp -d) +mkdir -p "$GNUPGHOME" +chmod 700 "$GNUPGHOME" + +# Configure GPG for batch mode +echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" +echo "no-tty" >> "$GNUPGHOME/gpg.conf" + +# RSA 2048-bit key +cat > batch_rsa_2048.txt < batch_rsa_4096.txt < batch_dsa_2048.txt < batch_ecdsa_p256.txt < batch_ecdsa_p384.txt < batch_ecdsa_p521.txt < batch_brainpool_p256.txt < batch_brainpool_p384.txt < batch_brainpool_p512.txt < batch_ed25519.txt < batch_ed448.txt < key_rsa_2048.pub +gpg --armor --export test-rsa-4096@example.com > key_rsa_4096.pub +gpg --armor --export test-dsa-2048@example.com > key_dsa_2048.pub +gpg --armor --export test-ecdsa-p256@example.com > key_ecdsa_p256.pub +gpg --armor --export test-ecdsa-p384@example.com > key_ecdsa_p384.pub +gpg --armor --export test-ecdsa-p521@example.com > key_ecdsa_p521.pub +gpg --armor --export test-brainpool-p256@example.com > key_brainpool_p256.pub +gpg --armor --export test-brainpool-p384@example.com > key_brainpool_p384.pub +gpg --armor --export test-brainpool-p512@example.com > key_brainpool_p512.pub +gpg --armor --export test-ed25519@example.com > key_ed25519.pub +gpg --armor --export test-ed448@example.com > key_ed448.pub +``` + +#### 2. Create a Test Git Repository + +```bash +mkdir test_repo && cd test_repo +git init +echo "test content" > test.txt +git add test.txt +git commit -m "Test commit" +git config user.name "Test User" +git config user.email "sign-user@example.com" +git config gpg.program gpg + +# Get the key ID for the key you want to use +KEY_ID=$(gpg --list-keys --with-colons test-ed25519@example.com | grep '^fpr' | head -1 | cut -d: -f10) +git config user.signingkey "$KEY_ID" +``` + +#### 3. Sign a Commit with GPG + +```bash +# Sign the last commit +git commit --amend --allow-empty -S -m "Test commit signed with ed25519" + +# Verify the signed commit +git verify-commit HEAD +``` + +#### 4. Export the Signed Commit + +```bash +# Get the commit object +git cat-file commit HEAD > commit_ed25519_signed.txt +``` + +#### 5. Create a Tag and Sign It + +```bash +git tag -a test-tag -m "Test tag" -s +git verify-tag test-tag +git cat-file tag test-tag > tag_ed25519_signed.txt +``` + +## File Format + +The signed Git objects follow the standard Git object format with GPG signatures: + +### Signed Commit Format + +``` +tree +parent +author +committer +gpgsig -----BEGIN PGP SIGNATURE----- + + -----END PGP SIGNATURE----- + + +``` + +### Signed Tag Format + +``` +object +type commit +tag +tagger + + +-----BEGIN PGP SIGNATURE----- + + -----END PGP SIGNATURE----- +``` + +## Generated Files + +The script generates the following files: + +### Public Keys +- `key_rsa_2048.pub` - RSA 2048-bit public key +- `key_rsa_4096.pub` - RSA 4096-bit public key +- `key_dsa_2048.pub` - DSA 2048-bit public key +- `key_ecdsa_p256.pub` - ECDSA P-256 public key +- `key_ecdsa_p384.pub` - ECDSA P-384 public key +- `key_ecdsa_p521.pub` - ECDSA P-521 public key +- `key_brainpool_p256.pub` - Brainpool P-256 public key +- `key_brainpool_p384.pub` - Brainpool P-384 public key +- `key_brainpool_p512.pub` - Brainpool P-512 public key +- `key_ed25519.pub` - Ed25519 public key +- `key_ed448.pub` - Ed448 public key + +### Signed Commits +- `commit_rsa_2048_signed.txt` - RSA 2048-bit signed commit +- `commit_rsa_4096_signed.txt` - RSA 4096-bit signed commit +- `commit_dsa_2048_signed.txt` - DSA 2048-bit signed commit +- `commit_ecdsa_p256_signed.txt` - ECDSA P-256 signed commit +- `commit_ecdsa_p384_signed.txt` - ECDSA P-384 signed commit +- `commit_ecdsa_p521_signed.txt` - ECDSA P-521 signed commit +- `commit_brainpool_p256_signed.txt` - Brainpool P-256 signed commit +- `commit_brainpool_p384_signed.txt` - Brainpool P-384 signed commit +- `commit_brainpool_p512_signed.txt` - Brainpool P-512 signed commit +- `commit_ed25519_signed.txt` - Ed25519 signed commit +- `commit_ed448_signed.txt` - Ed448 signed commit + +### Signed Tags +- `tag_rsa_2048_signed.txt` - RSA 2048-bit signed tag +- `tag_rsa_4096_signed.txt` - RSA 4096-bit signed tag +- `tag_dsa_2048_signed.txt` - DSA 2048-bit signed tag +- `tag_ecdsa_p256_signed.txt` - ECDSA P-256 signed tag +- `tag_ecdsa_p384_signed.txt` - ECDSA P-384 signed tag +- `tag_ecdsa_p521_signed.txt` - ECDSA P-521 signed tag +- `tag_brainpool_p256_signed.txt` - Brainpool P-256 signed tag +- `tag_brainpool_p384_signed.txt` - Brainpool P-384 signed tag +- `tag_brainpool_p512_signed.txt` - Brainpool P-512 signed tag +- `tag_ed25519_signed.txt` - Ed25519 signed tag +- `tag_ed448_signed.txt` - Ed448 signed tag + +### Unsigned Commit +- `commit_unsigned.txt` - Unsigned commit for testing negative cases + +## Key Types Explained + +### RSA (Rivest-Shamir-Adleman) +- **RSA 2048**: Standard RSA key with 2048-bit modulus +- **RSA 4096**: Stronger RSA key with 4096-bit modulus +- Widely supported, but slower than ECC keys + +### DSA (Digital Signature Algorithm) +- **DSA 2048**: Legacy algorithm, 2048-bit key +- Less secure than modern alternatives, included for compatibility testing + +### ECDSA (Elliptic Curve Digital Signature Algorithm) +- **P-256**: NIST P-256 curve (secp256r1) +- **P-384**: NIST P-384 curve (secp384r1) +- **P-521**: NIST P-521 curve (secp521r1) +- Efficient and secure, widely supported + +### Brainpool Curves +- **P-256**: brainpoolP256r1 curve +- **P-384**: brainpoolP384r1 curve +- **P-512**: brainpoolP512r1 curve +- Alternative to NIST curves with different security properties + +### EdDSA (Edwards-curve Digital Signature Algorithm) +- **Ed25519**: Modern, fast, and secure curve +- **Ed448**: Higher security variant +- Recommended for new applications + +## Security Note + +These test fixtures use generated test keys and should NOT be used in production. The keys are created without passphrases for testing purposes only. + +## Requirements + +- GnuPG (gpg) version 2.0 or higher +- Git with GPG support +- Bash shell + +## Troubleshooting + +### GPG version compatibility +Some key types (like Ed448) require GnuPG 2.3 or higher. If you encounter errors, check your GPG version: + +```bash +gpg --version +``` + +### Key generation failures +The script now includes comprehensive error handling: +- Each key generation attempt is logged +- Failed keys are reported with detailed error messages +- The script continues with successfully generated keys +- An error log is created in the temporary directory + +If key generation fails, ensure that: +1. You have sufficient entropy on your system +2. The GPG home directory has proper permissions (700) +3. No other GPG agents are interfering +4. Your GPG version supports the requested key type + +### Script structure +The script uses separate functions for different key types: +- `generate_rsa_dsa_key()` - For RSA and DSA keys with key length validation +- `generate_ecc_key()` - For ECC/ECDSA/EdDSA keys with curve validation +- `create_signed_object()` - For creating signed commits and tags +- `create_unsigned_commit()` - For creating unsigned test commits + +Each function includes parameter validation and proper error handling. + +### Signature verification failures +If signature verification fails, ensure that: +1. The public key is properly imported +2. The GPG trust database is configured correctly +3. The signature was created with the corresponding private key \ No newline at end of file diff --git a/git/signatures/testdata/gpg_signatures/commit_brainpool_p256_signed.txt b/git/signatures/testdata/gpg_signatures/commit_brainpool_p256_signed.txt new file mode 100644 index 000000000..8e2e958c0 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_brainpool_p256_signed.txt @@ -0,0 +1,12 @@ +tree 1673f4226b68c3c29e8d038052698fd10706eb7e +author Test User 1772188964 +0100 +committer Test User 1772188964 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iHUEABMIAB0WIQSHtLFiUpKKegTi4RlOd7ceLHgABgUCaaF1JAAKCRBOd7ceLHgA + BpOTAP9KFSViLeUSJMzw9I2nW/kMJRWIXUE2XE+wuj/A2PTxYgD/ef3PLdiDr0l+ + CzdrXSQRdiNkD6avr8KEyy/Q0vz+03Y= + =IHuS + -----END PGP SIGNATURE----- + +Test commit signed with brainpool_p256 diff --git a/git/signatures/testdata/gpg_signatures/commit_brainpool_p384_signed.txt b/git/signatures/testdata/gpg_signatures/commit_brainpool_p384_signed.txt new file mode 100644 index 000000000..9feb7d2fa --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_brainpool_p384_signed.txt @@ -0,0 +1,13 @@ +tree ff5f115ae071fc5b5984c3cf8a2e14fb86e54596 +author Test User 1772188964 +0100 +committer Test User 1772188964 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iJUEABMJAB0WIQQ/Ad7FJfKxg1LGHLMZ238vxsg1ZQUCaaF1JAAKCRAZ238vxsg1 + ZRMFAX9PF5KWQcYJla4N0RPc/EwrYkmNVH7yJeKUiJA1H6efE99/0tejkP+oNLAr + RUH4HngBf0E/aFFzZD1T/D+mZgpwptGWL+3m41vo92byaUdeEcOfGZWGPzVceAsY + uesfSeUOAA== + =u9GB + -----END PGP SIGNATURE----- + +Test commit signed with brainpool_p384 diff --git a/git/signatures/testdata/gpg_signatures/commit_brainpool_p512_signed.txt b/git/signatures/testdata/gpg_signatures/commit_brainpool_p512_signed.txt new file mode 100644 index 000000000..76c1efc1e --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_brainpool_p512_signed.txt @@ -0,0 +1,13 @@ +tree a9ac3b19ae895b654fadecbf65d68b6b904e9015 +author Test User 1772188964 +0100 +committer Test User 1772188964 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iLUEABMKAB0WIQRFqHbkH9cuZyIgGbcl0p71vcaJEQUCaaF1JAAKCRAl0p71vcaJ + EbsGAf0UpYkwRuLxUfV19hj31s8CFTrqe4e8DgKhZxv1cNX/0FUE8n/u15GePsQQ + /I0Omw7bXSKo8wh0VeUD17GjiDOeAf9WBNDV9qQh3Z1Vc01DHQrzp0RKzoeTquxe + ivA0N6jknF9V6smfTbL0I6SLu3dtrA+1dh3CDeQCROdhH3aA7ZaG + =aUDv + -----END PGP SIGNATURE----- + +Test commit signed with brainpool_p512 diff --git a/git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt b/git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt new file mode 100644 index 000000000..1132feef8 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt @@ -0,0 +1,12 @@ +tree 94e5cb8fdb0551092fe394328dd9de2dbd8394f3 +author Test User 1772188965 +0100 +committer Test User 1772188965 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iHUEABEIAB0WIQQ3p1oEVydAtN6w28QIntqNADkiBwUCaaF1JQAKCRAIntqNADki + B3brAP9bhBteRaxkRDN2rXbAxFdBLACqgqTH10Zv4if3gxZxKQD/ZoAiBYUyWq3C + HKyihQ+PCD2wMv6tyzkC5RI5mumh5Fw= + =C3Wn + -----END PGP SIGNATURE----- + +Test commit signed with dsa_2048 diff --git a/git/signatures/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt b/git/signatures/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt new file mode 100644 index 000000000..a95b90f90 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt @@ -0,0 +1,12 @@ +tree 2f0fa5393a2120151c5446eb34b99d1f3713ff12 +author Test User 1772188965 +0100 +committer Test User 1772188965 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iHUEABMIAB0WIQQZYVspROx35dYcOV/9NQRFrxVHiwUCaaF1JQAKCRD9NQRFrxVH + i7V+AQCBE5nzpuGEjw8dTsdQ7o53ec1fN/O8IoRreC98vr2/9AD9E6Yu6b0t+ahp + j90zFJCPdc+cAxk4mVXh4piVbJ8tPvQ= + =dI7Q + -----END PGP SIGNATURE----- + +Test commit signed with ecdsa_p256 diff --git a/git/signatures/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt b/git/signatures/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt new file mode 100644 index 000000000..596f99517 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt @@ -0,0 +1,13 @@ +tree ff58328bd5797f45f6f300c6c39d2cd357b9f3cd +author Test User 1772188965 +0100 +committer Test User 1772188965 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iJUEABMJAB0WIQScyLxivLVGKonynyhueVMDKL7ECgUCaaF1JQAKCRBueVMDKL7E + CvZZAYC3WouUxsPpDyK3rwkhe9/tLEeSq+Z2nIUNTK3CYjw2MbyqKqMav4dZiYun + C78+910BgMF8yGkEhzSVnl5ZtNe6CXP4ZTrtdeo8WsOwvJaiey9YA/HYLLsSW/67 + uhz/ua8xtQ== + =SeMA + -----END PGP SIGNATURE----- + +Test commit signed with ecdsa_p384 diff --git a/git/signatures/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt b/git/signatures/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt new file mode 100644 index 000000000..f0fe269bc --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt @@ -0,0 +1,13 @@ +tree 63af4f62a108a6c684181a4488b4bd3a5b51dc8e +author Test User 1772188966 +0100 +committer Test User 1772188966 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iLkEABMKAB0WIQTsiCaQTNePc9nPIlaMg3KiIK8bzwUCaaF1JgAKCRCMg3KiIK8b + z1sOAgkB1oCZKDZ9JVg8VASnxGOr9DBtMuPD3W0afvfjH41UDoSPERuiMvws+AkT + 2NmaqcADWIvTnKWUWmZbVTnypr76mCcCCQFHVhFbQ4BohfHZvEDoMctt07xHVfQg + Hzfjh1JagDgevjnOh1ekzluDamEzPNMCmaRM0gFbtMqamIOAED9U70R7yA== + =oq6z + -----END PGP SIGNATURE----- + +Test commit signed with ecdsa_p521 diff --git a/git/signatures/testdata/gpg_signatures/commit_ed25519_signed.txt b/git/signatures/testdata/gpg_signatures/commit_ed25519_signed.txt new file mode 100644 index 000000000..25d01d2f6 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_ed25519_signed.txt @@ -0,0 +1,12 @@ +tree 7c5bd8f246ab8e8c6a5749c3d2f44018aa029fb8 +author Test User 1772188966 +0100 +committer Test User 1772188966 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iHUEABYKAB0WIQRnYqhdVzl5MeAXXPFaeBMjEbdVcgUCaaF1JgAKCRBaeBMjEbdV + cvFBAP9oqFkZXb3J8tGe8wcYoWBCtj1bIEnkOxdWJHqA7KHuiwD/Xe18Vu+IGMSV + xJUkStADGVvF+jlPQshn7C+cak6zWAQ= + =A7mH + -----END PGP SIGNATURE----- + +Test commit signed with ed25519 diff --git a/git/signatures/testdata/gpg_signatures/commit_ed448_signed.txt b/git/signatures/testdata/gpg_signatures/commit_ed448_signed.txt new file mode 100644 index 000000000..6080f3434 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_ed448_signed.txt @@ -0,0 +1,13 @@ +tree d49a4c033c2a0d7c2d5882461a0e70f61e021959 +author Test User 1772188966 +0100 +committer Test User 1772188966 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iKkFABYKACkiIQWoMJZGKPzTpyuUAJVTaO7/Ty5E4I2nu8xGwMU1m96nfAUCaaF1 + JgAA0yIByPwhpDW6dmJddCS/TsB2z2Wu30Vjd3wGLCp3J6N8FHsVi6jcmbPM2JXF + /uA7DZWryLM1Rgtsbcv9AAHGMTePYjyduBDw/uK7K3kgL0NnLHGHEQ1545mSmk4Z + Q8ltXviJqCQ4Ut549BoZxM8YbIieNmVWtiwA + =cJtf + -----END PGP SIGNATURE----- + +Test commit signed with ed448 diff --git a/git/signatures/testdata/gpg_signatures/commit_rsa_2048_signed.txt b/git/signatures/testdata/gpg_signatures/commit_rsa_2048_signed.txt new file mode 100644 index 000000000..d696d92b4 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_rsa_2048_signed.txt @@ -0,0 +1,16 @@ +tree e3ca2325bfa8013dca224a2f62f0582d70c07b12 +author Test User 1772188967 +0100 +committer Test User 1772188967 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iQEzBAABCAAdFiEEjxo8CPOWlAXK18ap+GK+ySN6ocgFAmmhdScACgkQ+GK+ySN6 + ocgm8wf7BOC9Jxv3QYTz+v9zztniu5phXYIF3Q1v7UuhVIK1uUj0F6OzIsdj7CHm + ryy4pVPHcOPq3Q6bPU7JlTHHfVdk+jzpv/K+SgjAqEdJHiH0FrSnNkXiA7+5jSxP + pJUPcnaeBr7I1jj+RM5uvAlHt7fTjrq6FZYqQuxrK80ICQ+YBz+5CHDm6OCSJGsR + xppNnGd3WkKkRJKInlXvd2eSStX4lffUihpo01JmN6XX9WfY1e3VDWokEpvIzyvJ + 269Kg4EEtmj5FBaAsMjalwF2ZmnfIClwo/zOCrir0QPQCX49F1CBwESTArOtI0/P + tUHIQ9zTWogzEQ0Ob2SyiEnRpEX8Ww== + =CK6f + -----END PGP SIGNATURE----- + +Test commit signed with rsa_2048 diff --git a/git/signatures/testdata/gpg_signatures/commit_rsa_4096_signed.txt b/git/signatures/testdata/gpg_signatures/commit_rsa_4096_signed.txt new file mode 100644 index 000000000..a983592e2 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_rsa_4096_signed.txt @@ -0,0 +1,21 @@ +tree 596e4c43898dcf2a6aa08cb9c0f3e0bbb8ecc26d +author Test User 1772188967 +0100 +committer Test User 1772188967 +0100 +gpgsig -----BEGIN PGP SIGNATURE----- + + iQIzBAABCAAdFiEEXu1Q4zHdzEIbmc8LmBNHwX31dl4FAmmhdScACgkQmBNHwX31 + dl6pzA//RS+ffLDBShXdWCry8pmfIs4/Wkcz0oMlhetcpuErjCjOMI1ZEHao5J5G + +8l7UcMFq1JtpjQ156mFboJ1ZaAPmAAOAoGB+uJ20ncr/TXprOlc5pP6ssSIDsoU + n+zk+bONfIkdMQKdEcrAyOJPVuIFs7OvDY017n2kOTytCsWqxIWLgj/OrZCIyemd + EaumIoHCMkwAdfklWqba0v9OG7fw/knLFg8kvrjTZFmsi8GJcfdrCsqveS/sE3z5 + 2hEsleDavQ1FHTw0zOuN1y7E2CUXbMphQe+OxR6ypk53JQE4f0TsIYGItr9UQn7Y + tY1bYDiyJlTm6v/BRRl5J4qMgnNNsttjrl8cVihacYi1Gq6Mbl/vDYbZBLtWl9/7 + Bx8hPruqeZkix2nmA1lsFXAUDpumSERpjab3GjzzLW2hqIButodToD+3Jais01a/ + +JXsmZRvco3MjoLEKiSsM6BKp/FeWsH72A06/7JJ4i6LjFcJT8t1ljaSmNEZsQm2 + d10mHLQ34+9sgA35IaNFnF56XwZ9mX+NkLM9nTrtbaF/FHlzAd1k1HoNIT2NQ2tH + 5xydmyKJOkUEiaZXUIgsINI8RB5ERSCSJCXHk2G/N4ShT62jKqj3GmywWgKyGCpP + IQOUSxv6TZlZR2r5J1OIGzjZsFEWJyvq2u1vBG71uXnUOExt1k4= + =39uJ + -----END PGP SIGNATURE----- + +Test commit signed with rsa_4096 diff --git a/git/signatures/testdata/gpg_signatures/commit_unsigned.txt b/git/signatures/testdata/gpg_signatures/commit_unsigned.txt new file mode 100644 index 000000000..491a14418 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/commit_unsigned.txt @@ -0,0 +1,5 @@ +tree 4650a2cda631bc795fc254fe20b598135b265036 +author Test User 1772188971 +0100 +committer Test User 1772188971 +0100 + +Test commit unsigned diff --git a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh new file mode 100755 index 000000000..05eaa6a56 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh @@ -0,0 +1,245 @@ +#!/usr/bin/env bash +# generate_gpg_fixtures.sh - Script to generate GPG signature test fixtures +# Generates GPG keys in all variants and signed Git objects + +set -e + +# Configuration variables +TEST_USER_NAME="Test User" +TEST_USER_EMAIL="sign-user@example.com" + +# Directory for temporary files +TEMP_DIR=$(mktemp -d) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "=== GPG Signature Test Fixtures Generator ===" +echo "Temporary directory: $TEMP_DIR" +echo "Output directory: $SCRIPT_DIR" +echo "" + +# GPG home directory for test keys +export GNUPGHOME="$TEMP_DIR/gnupg" +mkdir -p "$GNUPGHOME" +chmod 700 "$GNUPGHOME" + +# Configure GPG for batch mode (no interaction) +echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" +echo "no-tty" >> "$GNUPGHOME/gpg.conf" + +# Function to generate GPG key pair +generate_key() { + local key_type=$1 + local key_param=$2 + local key_name=$3 + + echo "Generating $key_type key pair ($key_name)..." + + # Create batch configuration for GPG + local batch_file="$TEMP_DIR/batch_${key_name}.txt" + cat > "$batch_file" <> "$batch_file" + ;; + ecdsa|eddsa) + echo "Key-Curve: $key_param" >> "$batch_file" + ;; + esac + + cat >> "$batch_file" <&1 + + # Get the key ID + local key_id=$(gpg --list-keys --with-colons "test-${key_name}@example.com" | grep '^fpr' | head -1 | cut -d: -f10) + + echo " Key ID: $key_id" + + # Export public key + gpg --armor --export "test-${key_name}@example.com" > "$SCRIPT_DIR/key_${key_name}.pub" + echo " ✓ key_${key_name}.pub created" + + # Export secret key (for signing) + gpg --armor --export-secret-keys "test-${key_name}@example.com" > "$TEMP_DIR/${key_name}.sec" + + # Store key ID for later use + echo "$key_id" > "$TEMP_DIR/${key_name}_id.txt" + + rm -f "$batch_file" + echo " ✓ $key_name key pair generated successfully" +} + +# Function to create signed Git objects (commits and tags) +create_signed_object() { + local object_type=$1 + local key_name=$2 + + echo "Creating signed $object_type for $key_name..." + + # Get key ID + local key_id=$(cat "$TEMP_DIR/${key_name}_id.txt") + + # Create temporary Git repository + local repo_dir="$TEMP_DIR/repo_${key_name}_${object_type}" + mkdir -p "$repo_dir" + cd "$repo_dir" + + git init + git config user.name "$TEST_USER_NAME" + git config user.email "$TEST_USER_EMAIL" + git config gpg.program gpg + git config user.signingkey "$key_id" + + # Import the secret key for signing + gpg --batch --import "$TEMP_DIR/${key_name}.sec" 2>/dev/null + + # Create file and commit + echo "Test content for $key_name $object_type" > test.txt + git add test.txt + git commit -m "Test commit for $object_type" + + if [[ "$object_type" == "commit" ]]; then + # Sign the commit (amend) + git commit --amend --allow-empty -S -m "Test commit signed with $key_name" + + # Verify the signed commit + echo " Verifying signed commit..." + git verify-commit HEAD 2>&1 | grep -q "Good signature" + echo " ✓ Commit signature verified successfully" + + # Export commit object + git cat-file commit HEAD > "$SCRIPT_DIR/commit_${key_name}_signed.txt" + cd "$SCRIPT_DIR" + echo " ✓ commit_${key_name}_signed.txt created" + + elif [[ "$object_type" == "tag" ]]; then + # Create and sign tag + git tag -a "test-tag-${key_name}" -m "Test tag signed with $key_name" -s + + # Verify the signed tag + echo " Verifying signed tag..." + git verify-tag "test-tag-${key_name}" 2>&1 | grep -q "Good signature" + echo " ✓ Tag signature verified successfully" + + # Export tag object + git cat-file tag "test-tag-${key_name}" > "$SCRIPT_DIR/tag_${key_name}_signed.txt" + cd "$SCRIPT_DIR" + echo " ✓ tag_${key_name}_signed.txt created" + fi +} + +# Function to create unsigned commit +create_unsigned_commit() { + echo "Creating unsigned commit..." + + # Create temporary Git repository + local repo_dir="$TEMP_DIR/repo_unsigned" + mkdir -p "$repo_dir" + cd "$repo_dir" + + git init + git config user.name "$TEST_USER_NAME" + git config user.email "$TEST_USER_EMAIL" + + # Create file and commit (without signature) + echo "Test content unsigned" > test.txt + git add test.txt + git commit -m "Test commit unsigned" + + # Export commit object + git cat-file commit HEAD > "$SCRIPT_DIR/commit_unsigned.txt" + + cd "$SCRIPT_DIR" + echo " ✓ commit_unsigned.txt created" +} + +# Main program +main() { + echo "Step 1: Generate RSA/DSA keys..." + echo "-----------------------------------" + + # RSA keys (different key lengths) + generate_key "RSA" "2048" "rsa_2048" + generate_key "RSA" "4096" "rsa_4096" + + # DSA key (legacy, but still supported) + generate_key "DSA" "2048" "dsa_2048" + + echo "" + echo "Step 2: Generate ECC keys..." + echo "-----------------------------------" + + # ECDSA keys (different curves) + generate_key "ecdsa" "NIST P-256" "ecdsa_p256" + generate_key "ecdsa" "NIST P-384" "ecdsa_p384" + generate_key "ecdsa" "NIST P-521" "ecdsa_p521" + + # Brainpool curves + generate_key "ecdsa" "brainpoolP256r1" "brainpool_p256" + generate_key "ecdsa" "brainpoolP384r1" "brainpool_p384" + generate_key "ecdsa" "brainpoolP512r1" "brainpool_p512" + + # Ed25519 (modern elliptic curve) + generate_key "eddsa" "Ed25519" "ed25519" + + # Ed448 (less common) + generate_key "eddsa" "Ed448" "ed448" + + echo "" + echo "Step 3: Create signed commits..." + echo "----------------------------------------" + + # Get list of successfully generated keys + local keys=() + for key_file in "$TEMP_DIR"/*_id.txt; do + if [[ -f "$key_file" ]]; then + local key_name=$(basename "$key_file" "_id.txt") + keys+=("$key_name") + fi + done + + # Signed commits for each key type + for key_name in "${keys[@]}"; do + create_signed_object "commit" "$key_name" + done + + echo "" + echo "Step 4: Create signed tags..." + echo "-------------------------------------" + + # Signed tags for each key type + for key_name in "${keys[@]}"; do + create_signed_object "tag" "$key_name" + done + + echo "" + echo "Step 5: Create unsigned commit..." + echo "------------------------------------------" + + create_unsigned_commit + + echo "" + echo "=== Cleanup ===" + rm -rf "$TEMP_DIR" + echo "Temporary directory removed" + + echo "" + echo "=== Done! ===" + echo "All test fixtures have been successfully created." + echo "" + echo "Created files:" + find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' +} + +main \ No newline at end of file diff --git a/git/signatures/testdata/gpg_signatures/key_brainpool_p256.pub b/git/signatures/testdata/gpg_signatures/key_brainpool_p256.pub new file mode 100644 index 000000000..b08e1ba5c --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_brainpool_p256.pub @@ -0,0 +1,10 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mFMEaaF1IxMJKyQDAwIIAQEHAgMEUwknda08hsRC4Npdfcm+1YqDOomET8eB+jJ7 +42mryjwct/lIPxW9lNCcTsu+zw4inUSFie+ppyaUvs2Zn7NcR7QrVGVzdCBVc2Vy +IDx0ZXN0LWJyYWlucG9vbF9wMjU2QGV4YW1wbGUuY29tPoiTBBMTCAA7FiEEh7Sx +YlKSinoE4uEZTne3Hix4AAYFAmmhdSMCGyMFCwkIBwICIgIGFQoJCAsCBBYCAwEC +HgcCF4AACgkQTne3Hix4AAbAdgEApp8sXO9KUkVJBccanhxGOWM1V1u6wMSU4qP9 +maYLTl8A/22K8pAdmUEJNeFPnplgQL8If89hcOulaz9X7IXuX9R9 +=pSV1 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_brainpool_p384.pub b/git/signatures/testdata/gpg_signatures/key_brainpool_p384.pub new file mode 100644 index 000000000..003449460 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_brainpool_p384.pub @@ -0,0 +1,12 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mHMEaaF1IxMJKyQDAwIIAQELAwMEVfXzvz+2tDtNqnttIWwaC2ErDVVrEY3GZZSr +BGnrvj+sy65ZzlrwuvnNTMAS1KbSPweRF90aZVkiyesNHtjIj//JoJETS2UYUJfP +D4vbhcVlhjUwuAIRA9Tv6UqXwdNVtCtUZXN0IFVzZXIgPHRlc3QtYnJhaW5wb29s +X3AzODRAZXhhbXBsZS5jb20+iLMEExMJADsWIQQ/Ad7FJfKxg1LGHLMZ238vxsg1 +ZQUCaaF1IwIbIwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRAZ238vxsg1 +ZZupAX9XXBzWAIUIax+3FzDyiaX52s9I7mReCvOhRUvR14JYMc/f5/CsebPZRw/4 +BFe0taoBfjJqSo0Y+qE/832yB/IuOEsLmSKeXvu8oncwSYQeRoOFBHKmsa+NFh35 +lvl/j9z8ng== +=0Q9w +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_brainpool_p512.pub b/git/signatures/testdata/gpg_signatures/key_brainpool_p512.pub new file mode 100644 index 000000000..b4916c41a --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_brainpool_p512.pub @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mJMEaaF1IxMJKyQDAwIIAQENBAMEgOA+Jee2aD4ihETrDyd6nIeLNMi5/OoW8ChU +abrNn0A/JtViY0GIwSs8ZZbCWpbktU2cvi81yUOyPuXQNylxAVB+VJTLl2WG6/hm +iJytSnow5mx8jlMrjHralTHgmZ6vGA7113eBaw98uyQaTpW9L7/EnJZmIaWsOc7z +c0CTuNS0K1Rlc3QgVXNlciA8dGVzdC1icmFpbnBvb2xfcDUxMkBleGFtcGxlLmNv +bT6I0wQTEwoAOxYhBEWoduQf1y5nIiAZtyXSnvW9xokRBQJpoXUjAhsjBQsJCAcC +AiICBhUKCQgLAgQWAgMBAh4HAheAAAoJECXSnvW9xokRbhAB/3zbCx9UGG50fbqp +B1kSsRZTJXedRrBVb28l2WCD2M1RnNCEZsQiSbMzMCpjCUomlAHdcekSyIaQUQT2 +bsAnhfEB/j/xcqmLq+uYVlARylj3FdFNRPFMBk31VbmM4MmPGmKEK/Y2wfBA4t1Y +AsElpiiqqjE4h066r0Br0zyGmSH90aI= +=bACz +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_dsa_2048.pub b/git/signatures/testdata/gpg_signatures/key_dsa_2048.pub new file mode 100644 index 000000000..908d2c05b --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_dsa_2048.pub @@ -0,0 +1,25 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQMuBGmhdSIRCACoIRoP5Lvi8g2dE7Nn9/AI6O3SEnCotRNnT10nmELXwqYonn/9 +3LIhiMMqdMPgtIQZLuvoUlZzMG/mufeHZlezhfUqbFOHY09Czcuvm0zkTBwIDq+a +CA729ICuPAgYAq53iPab5WqGO9H/LAX/6yYGLhQ0GHFdpnyxWnO/OtSBlLxqL+97 +oz6lhGSC9JSNxazSr/3qwvaytzOMI8ptDIVlpydv8WTghXBkjrIkXR8vR++2aEhK +voVCS9HSC19kg9B2fybGu4M5foP9ZIL62O+6rvopGSA1tmWctR2oIoi3Bi7x5vzA +f3NyOYZT8F+nnxwotdxzeoYxh/rVld4i321jAQCYLj2IVmgisitLwHKxXVT/aX3O +CAaEI/ESOcBm/arO9Qf9HLfKlc2wVtXL1g0KjaMZVvh9nvqzchBxmTtlLGmU5gIU +r7ZqDQ2pqavmZJ1YRBlGPRLnL8n1NXZMj8OHPRHyUJQ4oph8FFRoBOwspEw+i67j +jl42mc20IhOU28QPmtsmlEHJwdhZsYmCImtWFilHS8ThPewY+Qn2S0L+4nnBkTy4 +1y3ZGRSzQQhH1jOJtBdNBadQcrYppMWgxHNIe0V3s+7FCc89jJRECj608ZrlLYT1 +7KGPZDPqDtR668Br7sP6PjPJD6mnycsrQSNu1rFU3fsuClVlLeT9mWpwQspfQEYa +vmuRh48uuGUQFBanDM5EPTG7c4aB6Gz1k8J/HtXRpwf/Rc8eHZIVGrpc/7CuChGF +fqloBvAz77A3Blr7KaYIViEXj9dcw75Aurtk9lhtUpYe4A66ZdyoZE03xsKmATKX +Ois1YgBaQGEZoOM632pbv3bFrSCjrZnLMnwLIGDhqKnJy7H0mALKL9iILcN7lF0P +WU1YSNgZFU6X70aJvwEmOjeBM5YhGS+e4OPZW/z+b3f/1jE3dGJwz6LsT+M8+xWw +uqN1ZJ+Ijvg8k6HIFx4eXY0zPLElIaWkZExNki/T35jnazb8ZzCeu4/RiJz6YMwd +OAPIZ3I0dZJe5BO32eRbMQFb+OzEcVTNV5Jc/m09b9jEfOvmrHRHoDgGrF6/0S3Y +R7QlVGVzdCBVc2VyIDx0ZXN0LWRzYV8yMDQ4QGV4YW1wbGUuY29tPoiTBBMRCAA7 +FiEEN6daBFcnQLTesNvECJ7ajQA5IgcFAmmhdSICGyMFCwkIBwICIgIGFQoJCAsC +BBYCAwECHgcCF4AACgkQCJ7ajQA5IgcsBAD9F9koK8sIUApNcCFUqCGR9olYimkN +juoedSfOpMV/+j0A+wXU0jUfweGWUv7MPGmh1Sn0oMOBZTIL0LU+x/F3glLl +=lJcF +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_ecdsa_p256.pub b/git/signatures/testdata/gpg_signatures/key_ecdsa_p256.pub new file mode 100644 index 000000000..3d692bd2d --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_ecdsa_p256.pub @@ -0,0 +1,10 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mFIEaaF1IhMIKoZIzj0DAQcCAwQoQUw24pNLURN7niophK1FO8jxlaS8zIyXHrdk +v57m6jAzbdRsOgZ6q2RQ+mkzGpk+5W+Yv7oWit1On2NI5otNtCdUZXN0IFVzZXIg +PHRlc3QtZWNkc2FfcDI1NkBleGFtcGxlLmNvbT6IkwQTEwgAOxYhBBlhWylE7Hfl +1hw5X/01BEWvFUeLBQJpoXUiAhsjBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA +AAoJEP01BEWvFUeLX50BAO8aO89RwPhvh9AwK9d5p6JrAB1sMQifQa4qWLCxSoCc +AP9RhNEUOygsIPqEKUyZ+yhEcEMQP/5kd7ln52zaVmCIqw== +=SrKK +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_ecdsa_p384.pub b/git/signatures/testdata/gpg_signatures/key_ecdsa_p384.pub new file mode 100644 index 000000000..bdd907f05 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_ecdsa_p384.pub @@ -0,0 +1,11 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mG8EaaF1IhMFK4EEACIDAwSR0zvO7tXWhXmxDppSEiokEWqRZEy0wuRHJ+7P0o6F +8FDpuip3FkcBFaR47I7dwHIuQhg60pG/OMsuh72ZO0CndiPb4bpVK02ppY7QoE4A +JZNnETMeWEvn7nWdKsLbAvu0J1Rlc3QgVXNlciA8dGVzdC1lY2RzYV9wMzg0QGV4 +YW1wbGUuY29tPoizBBMTCQA7FiEEnMi8Yry1RiqJ8p8obnlTAyi+xAoFAmmhdSIC +GyMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQbnlTAyi+xAprwgGA2MZ2 +fe0jcm780LHpNFn+6skaR9eGKKVXg0gRu5169yLln6DHiXex3h0YNc6RPTveAX9i +cEo2z0sLtILQKIomGZqfqkXLgJPiT8qDZLZkElhM1CkmRWXPGgC96Twwuy/LGig= +=zVPo +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_ecdsa_p521.pub b/git/signatures/testdata/gpg_signatures/key_ecdsa_p521.pub new file mode 100644 index 000000000..db963c3ca --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_ecdsa_p521.pub @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mJMEaaF1IxMFK4EEACMEIwQABsj6GXczdoIybwVeCD4H1Bm4/kRA2oSJ8Q0eI8eI +eji8bwafKdEX+oqmW199cfJtwwM9NNe9vvfGvnANmfvhWeEAG9wz7UlhE4VUxgAo +hRYTwnZgBztiXGEjp/flr4y34Lz2IG33arxePBpzza72JyroVcfstYu7jY0KOa5s +NO7tDEO0J1Rlc3QgVXNlciA8dGVzdC1lY2RzYV9wNTIxQGV4YW1wbGUuY29tPojV +BBMTCgA7FiEE7IgmkEzXj3PZzyJWjINyoiCvG88FAmmhdSMCGyMFCwkIBwICIgIG +FQoJCAsCBBYCAwECHgcCF4AACgkQjINyoiCvG8/+7AIHRdZR45qP/DLcLR7BN9Mk +sjoDjUvd2swiVFXO5ZAhxu4/R/URkaSSTDW+a1QJjzSiwdKvVDeVBNNbNU9s2YVF +RFICB3ylAKmuOhs+upo5GqHJpdVgVI7AonTbnD7mlhhlvU5gbtGGO+ftCuZgCdsQ +ERV4BYsGGNM6FB3COlpKH8g+Jx0N +=ISNS +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_ed25519.pub b/git/signatures/testdata/gpg_signatures/key_ed25519.pub new file mode 100644 index 000000000..6ba0bb532 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_ed25519.pub @@ -0,0 +1,9 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaaF1IxYJKwYBBAHaRw8BAQdARB9dMt7IgHVlZ1LKknKIc18Mp9P0ky1S5oAE +y+Ipvq60JFRlc3QgVXNlciA8dGVzdC1lZDI1NTE5QGV4YW1wbGUuY29tPoiTBBMW +CgA7FiEEZ2KoXVc5eTHgF1zxWngTIxG3VXIFAmmhdSMCGyMFCwkIBwICIgIGFQoJ +CAsCBBYCAwECHgcCF4AACgkQWngTIxG3VXKhNgD8DaeYgQWZUanENgua9f1sveQ5 +ceXJYo5wHKlNN5n0OpYBALLAg5Gg0Z2RzcSU3JKWh+F5KpJx9Xx+xA4GfuIZYgYG +=7VIr +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_ed448.pub b/git/signatures/testdata/gpg_signatures/key_ed448.pub new file mode 100644 index 000000000..1a0082821 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_ed448.pub @@ -0,0 +1,11 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mEkFaaF1IxYAAAA/AytlcQHHVaag9xPUoWmV1JEqTCsKYnFQWm6PiaoTmLlDSIja +hH8gjdxTMzX7K+s9pI3Vxxdx1IdJ5kSumz0AtCJUZXN0IFVzZXIgPHRlc3QtZWQ0 +NDhAZXhhbXBsZS5jb20+iMcFExYKAEciIQWoMJZGKPzTpyuUAJVTaO7/Ty5E4I2n +u8xGwMU1m96nfAUCaaF1IwIbIwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAA +dbgByKYQcf0u88/60iuHN0mrkEQ1DenGhOmizKcrBpxLhHjEk+xQnuvA/tlEJVfZ +4lfWQO/sDJZMV013gAHIleJVkxDqXi+6UlXetODZRu3+kAGunWyzyU1XEjXbRCPh +l4jDOm9PF/GDfeqXWfzChIJZPQt/Uy8A +=VpqO +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_rsa_2048.pub b/git/signatures/testdata/gpg_signatures/key_rsa_2048.pub new file mode 100644 index 000000000..353098510 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_rsa_2048.pub @@ -0,0 +1,18 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBGmhdR0BCADdMRJ9iHzeJanSzOhTqhONdUlgICL0+0FgOxTXIo7nhf3tCcfb +n9AhSVkiDX5ItDZzjHjeiZ66Frs4O4TP04x5Z8Ayxssx4J6ST/YeXm7vkTquigDs +Qes9uzIKp4aFTuGG9MXzuPtKQeWixebhtS217EUb4rZbSitafmuV/zeIR+4l5+g4 +H2YGsF9m1ElK1EiJuUozBZVjcJYQJ5elWJeWdqHr9oCjeFrnZRMJ/WaFrF0OpFXw +kZVseh50MZ0SZ43JzmlokZqZuMyhY2rq0rTsvD4IH+yV6sS4Gefc0jhijZcRzWpX +QIb/7WrAqPSMOfQeukapw90Ke1sKYEfwLmR5ABEBAAG0JVRlc3QgVXNlciA8dGVz +dC1yc2FfMjA0OEBleGFtcGxlLmNvbT6JAVIEEwEIADwWIQSPGjwI85aUBcrXxqn4 +Yr7JI3qhyAUCaaF1HQMbLwQFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQ ++GK+ySN6ochAmwgAw7MwUF0mXbRQPTQNXp5tgOjBDSloQVoUw4f4Rs1N8XOW6Vvy +CnXwfCX8YHCtAMMs10mELY+iOG3GMdCqvRrImjJyh38JylLf/HQDigzL95tOy3cF +hZ3ZHm8m/H3w/zFDegI2QNMM4dCAdwGwUuxo42CoVMp5PzYtNy8l8WMkVXYLkJfm +wV6rM1rJazCAkY1Fk1FCW/LW8eenPr4rQa36VgmpT4hz+j9mi5mUM5RUdZGLXdPT +uuMcCpm2sfU1Lozx+6AeHng4LHTdQDWazXWLG2Ob1o0coG6zj2iVry04VnGFd/do +mvV/nK4AdBJ4Al/KKT1At/KmP5zVpnpJQdZq8w== +=f9Oy +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/key_rsa_4096.pub b/git/signatures/testdata/gpg_signatures/key_rsa_4096.pub new file mode 100644 index 000000000..d2356537d --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/key_rsa_4096.pub @@ -0,0 +1,29 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGmhdR4BEADDKCJbXPeXZIU4l6NBJaLDL5Po4IGB+3+CbCPk90DFCXrowrBb +BT1SwCU7+bmYKINQprFgG6WqhhQ8rGwkxlTjKlTdKYpHF3I13ONuPYcs1KZiPtXA +6puY9ma5lOH6VSBnK+k+EnuvHBw7NmWtMQbMwSqnPO+PFnG1yaOxRQR641aKw7wI +ciR8hJdlakIy11Z+inWw0RzeK/54837ws05CBDaqRNiO4FQfJ+Q9bBPpYpVR1G7g +4GVtidWpwprJYALmR3ejkn0QAiSGOlR7tin+8x4FXufxaiVwrPcXJCEOXLdLSndV +4purALokhhm0wP3D9fOFU9nqvkhlENLL3pLlPLswRq158RbaMBKgRZ318RRX11yH +qKlO6s4NAoYlhBeEY/gXQ36trALtu3YTB/eZlqoaEFFgXfEoS02W2F0sqYyK/ISG +fvUuxWZWjATNmfNLr2L15aM/GmfpacN8JO2omyKWGJQ3WBcGRdxfBkJ03vOQIFDE +WvJ+XmKpY+XC/N0q16Sz8rIF5LzDxwAMHdG66uSbYHGGlKxbq5YnUw1ZMafRhvcp +epEFRhLHUMGmJrHqfkSKkcDclMFlKG+wm9F/8a8V8zINQ3J1ohaQblT1OkwioSyT +GnIk92sVD28dS9mnoJbEKHEPjcTp2B1VMntHidFE+v4zwb1TPRE5rtFOdQARAQAB +tCVUZXN0IFVzZXIgPHRlc3QtcnNhXzQwOTZAZXhhbXBsZS5jb20+iQJSBBMBCAA8 +FiEEXu1Q4zHdzEIbmc8LmBNHwX31dl4FAmmhdR4DGy8EBQsJCAcCAiICBhUKCQgL +AgQWAgMBAh4HAheAAAoJEJgTR8F99XZe6WYP/2ubM+Mc+cC61MZv755k82xL4t7i +qQWplqjsX4DYXyZmRjqaNp0vKr0A0C11hoosTIS213yoXt0To0grXTP15btu+Dfs +vo8R7oeUDG70UFhArP5vLAwcZRf5+ZV+HKKr4KuxlW2KKbHO5UQtIiv8Lf6NcU5v +K1lDRfQUxhauTb8lOEkt0eFbsobu4GU/M8c2uDDj9Z187Nvm/UrxiB0akrB95iDW +S8ol6+AwHCfrZALbwP1Lsd1hI1RRfT+OUysrK4//K3k4r/8nT0deIulxV1oZezPg +yRXrEHvsDbhV4ZQiSKDx+hwayeKO70ag5Ijl8I4m4Wuz7e5xn9bx2QGZEUsil6ff +oNLnn1p0gXkbKl18+cnla4tQqjRYV50s8FtocZ/ULXU/EOSsuTuvwC45Fd6XUiSx ++awz55iYaYrIQdyir4Ltedt+IvvIDfDZM53r/Xc5H1kixIHYzZ/xse2qremlhuro +fZePkcNQemhdzM5llTpq8AP1TfuT1BtPkrfohoWJGNjqmIm6rcPGFHmWLfvo8I5f +JyRvwz6ljovBywaxXojvrHdGa13ylsIZTUDgDMAG/6noUR80L8JmXz1lTZcbT+zh +5A++/Dg1u1p4TFzqkQmopVXe/ccns5YtBMW3EV85ctsg+dGnSh3jwW2QIAlwgfiA +XZlV0oFIlwpVXcBq +=QDQJ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/tag_brainpool_p256_signed.txt b/git/signatures/testdata/gpg_signatures/tag_brainpool_p256_signed.txt new file mode 100644 index 000000000..f5429018a --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_brainpool_p256_signed.txt @@ -0,0 +1,13 @@ +object 9b70151dee2f47896dc875733450d2b81d22b5bd +type commit +tag test-tag-brainpool_p256 +tagger Test User 1772188967 +0100 + +Test tag signed with brainpool_p256 +-----BEGIN PGP SIGNATURE----- + +iHUEABMIAB0WIQSHtLFiUpKKegTi4RlOd7ceLHgABgUCaaF1JwAKCRBOd7ceLHgA +BqiIAQCT5NXXc2q8B5zF9qZMcuRxbV9sXzZnZcerDddzIyw3JAD/TQKfIbKZNGdv +lYE+mLhclLxPs6fzlFnr/PUUP+W28q8= +=VWLG +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_brainpool_p384_signed.txt b/git/signatures/testdata/gpg_signatures/tag_brainpool_p384_signed.txt new file mode 100644 index 000000000..90d96b407 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_brainpool_p384_signed.txt @@ -0,0 +1,14 @@ +object 68211368f80d9087df5e0d9ec5e9f0f01d0f9251 +type commit +tag test-tag-brainpool_p384 +tagger Test User 1772188968 +0100 + +Test tag signed with brainpool_p384 +-----BEGIN PGP SIGNATURE----- + +iJUEABMJAB0WIQQ/Ad7FJfKxg1LGHLMZ238vxsg1ZQUCaaF1KAAKCRAZ238vxsg1 +ZUxdAX9Ymfjm35gtB0+cEXryF+10W2EBt8xYtw11BSfhwZ43qiHzw6GeNgGqGWf+ +Q+6aq/wBf0/1JmwcKWR6kko5TXcvU6SIjxg8JJxEzjFvUNKuhAu29QmK+bv+oW2I +kqg3pbWh9A== +=Yavx +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_brainpool_p512_signed.txt b/git/signatures/testdata/gpg_signatures/tag_brainpool_p512_signed.txt new file mode 100644 index 000000000..60815c75a --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_brainpool_p512_signed.txt @@ -0,0 +1,14 @@ +object b3156f0627e50dfa726e48dbf8e94adc6bdebf03 +type commit +tag test-tag-brainpool_p512 +tagger Test User 1772188968 +0100 + +Test tag signed with brainpool_p512 +-----BEGIN PGP SIGNATURE----- + +iLUEABMKAB0WIQRFqHbkH9cuZyIgGbcl0p71vcaJEQUCaaF1KAAKCRAl0p71vcaJ +ESzuAfkBoKFp7ZeomqTWBgHSkMRgzSup5vhlit8+RcH9b4pEy+kXCq8OjWEh45S6 +ACSbOwUGXPOb3azuUqDEaNu/RDEPAf0aJQv16PdYHKayxyV64UNn+dZvoTbmOVtr +cAOWxHe2rfix9yob9Rt497/hCUWFjxy3LLeIIsSEAARLXrmSokTE +=ZE3W +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt b/git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt new file mode 100644 index 000000000..46578ae13 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt @@ -0,0 +1,13 @@ +object 8ee3b79d5ad1fa463deea7fc9bfcbca311168d01 +type commit +tag test-tag-dsa_2048 +tagger Test User 1772188968 +0100 + +Test tag signed with dsa_2048 +-----BEGIN PGP SIGNATURE----- + +iHUEABEIAB0WIQQ3p1oEVydAtN6w28QIntqNADkiBwUCaaF1KAAKCRAIntqNADki +Byq0AP9rHhQiJKh3rPNYW06C6N9yGnccU8nE5S5EfeH8Gps6SQD/f19dyM5euse9 +vylc3KD1sfdFekiLuW2WpDIw4JbAbMg= +=k9QD +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt b/git/signatures/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt new file mode 100644 index 000000000..c21ef5bff --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt @@ -0,0 +1,13 @@ +object 9b386e46cb3c08a84860225689ebb0696874a288 +type commit +tag test-tag-ecdsa_p256 +tagger Test User 1772188969 +0100 + +Test tag signed with ecdsa_p256 +-----BEGIN PGP SIGNATURE----- + +iHUEABMIAB0WIQQZYVspROx35dYcOV/9NQRFrxVHiwUCaaF1KQAKCRD9NQRFrxVH +i55VAP97X6IxOp3ZxAvdof4h8weHE66FzmqdseCsvUeWHatRWgEAgt7H/Eg2kQUH +PRHHy4l+joi9tAAg9KClfvq/lA+VcxI= +=dPQQ +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt b/git/signatures/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt new file mode 100644 index 000000000..0340f25c6 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt @@ -0,0 +1,14 @@ +object c87dc41b03d89ae26c1ebcc5ae34b816e915d76c +type commit +tag test-tag-ecdsa_p384 +tagger Test User 1772188969 +0100 + +Test tag signed with ecdsa_p384 +-----BEGIN PGP SIGNATURE----- + +iJUEABMJAB0WIQScyLxivLVGKonynyhueVMDKL7ECgUCaaF1KQAKCRBueVMDKL7E +ClbhAYCKIE4pMka3pHBjX4XmSvsq0El0DctONYNZgE15uRyIF/P+Oeonm3t9tF51 +XAkMS98BgMO27cmy6TMl1cnYBW34yrBmpLeHpctSk5pkxSddfhKAxj1aOLJHp6eu +/nFMr2HSow== +=l/X+ +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt b/git/signatures/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt new file mode 100644 index 000000000..c43adb5ce --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt @@ -0,0 +1,14 @@ +object 08dadb22fa537a9efa99d565ff01fc5d5854e802 +type commit +tag test-tag-ecdsa_p521 +tagger Test User 1772188969 +0100 + +Test tag signed with ecdsa_p521 +-----BEGIN PGP SIGNATURE----- + +iLkEABMKAB0WIQTsiCaQTNePc9nPIlaMg3KiIK8bzwUCaaF1KQAKCRCMg3KiIK8b +z+HcAgkB8d27ZgMvPQ0ueTNeVnUtxJwu1zyXfVnoC9/cdeAU+D5yE/nEugwysds+ +/9aKjsLMV5v7gxTa6lg1dvGN2CdGEf4CCQEgnjuQkSgfaLmRmpsKPbGJoUDA1RJT +0zrv56m//eCOHFYJtcKFy95mNn5+9IiBWXrY3Ilz48jaQSg9CntzaITmCA== +=w0Ur +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_ed25519_signed.txt b/git/signatures/testdata/gpg_signatures/tag_ed25519_signed.txt new file mode 100644 index 000000000..3ab00c63f --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_ed25519_signed.txt @@ -0,0 +1,13 @@ +object 35e58b202d6ba7a15f33b4e893b6da021c7132b7 +type commit +tag test-tag-ed25519 +tagger Test User 1772188970 +0100 + +Test tag signed with ed25519 +-----BEGIN PGP SIGNATURE----- + +iHUEABYKAB0WIQRnYqhdVzl5MeAXXPFaeBMjEbdVcgUCaaF1KgAKCRBaeBMjEbdV +cgc0AQDdONxRMTofNPtHP+BDEWsGFcDdyBGb9xxp5D5Xa3rYyQD/VLvlPmxl3jk5 +JUczWsHgXxcLWXP6e/N42Mf6ddU4lwg= +=Dt+S +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_ed448_signed.txt b/git/signatures/testdata/gpg_signatures/tag_ed448_signed.txt new file mode 100644 index 000000000..6b27d5dda --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_ed448_signed.txt @@ -0,0 +1,14 @@ +object 594f42b8ecaad08227805a281ca4b053ff7fdae4 +type commit +tag test-tag-ed448 +tagger Test User 1772188970 +0100 + +Test tag signed with ed448 +-----BEGIN PGP SIGNATURE----- + +iKkFABYKACkiIQWoMJZGKPzTpyuUAJVTaO7/Ty5E4I2nu8xGwMU1m96nfAUCaaF1 +KgAAe94Bx0QXkRdhxyHoybrUWYIcs0ZFMhZRQa823NLlMtlPIVjUAieWGSJrVJyD +ZJbDprNIyLFRvEFYoYdEgAHDB+NuVFZQ+wvqwrQ6DryI4Azh6AorZCCxeHQ3dpm/ +o3KzUg0YxLeBptuESwPGyTqnTqublmc4xAMA +=axq3 +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_rsa_2048_signed.txt b/git/signatures/testdata/gpg_signatures/tag_rsa_2048_signed.txt new file mode 100644 index 000000000..b2967d822 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_rsa_2048_signed.txt @@ -0,0 +1,17 @@ +object 2aa79d2bc04180cb05948619bcd3edb60703c214 +type commit +tag test-tag-rsa_2048 +tagger Test User 1772188970 +0100 + +Test tag signed with rsa_2048 +-----BEGIN PGP SIGNATURE----- + +iQEzBAABCAAdFiEEjxo8CPOWlAXK18ap+GK+ySN6ocgFAmmhdSoACgkQ+GK+ySN6 +ocib9gf/ewYsCH6QEx6L3MAT5sJFlN2USLRCSeLTE9/l6Bm/h/DITK2xlkbADQOC +3Ct4IXjrXaWMJ+G2vTdvmdxDAvNkga/RpbkEPapedwVoYMRqVWgC4pF6+aZH6EF2 +omd7p7+er/HCmRfe+5NFwUOSsYAxt0yB2lZC5Mq3Vz99KLi0daUoHY+ymkzFE1kk +Hdu94PG/g4YLHFY7PP7EtOq3NH0HrCxombcU+n8rkqjquwH7rJk5ZYMSI5HcDD2l +qB6R0zRGDpwH9IiMmSpNNWpcRKqUmORLGCaeJdfhh++ZaEJdF7AkBQkGy4WV/A2k +Te0lLC3zVqsMB/9T3nzbklyWpweZsA== +=xjd+ +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/gpg_signatures/tag_rsa_4096_signed.txt b/git/signatures/testdata/gpg_signatures/tag_rsa_4096_signed.txt new file mode 100644 index 000000000..4c5ae5000 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_rsa_4096_signed.txt @@ -0,0 +1,22 @@ +object f75808dabeb766be8c47519fdea37ae4a0a6a613 +type commit +tag test-tag-rsa_4096 +tagger Test User 1772188971 +0100 + +Test tag signed with rsa_4096 +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEEXu1Q4zHdzEIbmc8LmBNHwX31dl4FAmmhdSsACgkQmBNHwX31 +dl4gqxAAi/EifC0soQ6F5tj2EkKn4j9w7I5B505X2c2KUKWPUtGAMevwbeFFNLgn +S+kx/cl0xjkrrfv8mEWts9OPr2YRqMOojVKa5kBfqfSaZVEXJpic8Ocs2FhYbic+ +h7FQggtMNagkMKtqSw6qbXg9E3ZnZ/9iaF+EEHGNLdp6OSJEtpulidyOB1zPS5A3 +K7D1Y1Q+Z47v+x2ljwlAGjabZzkokwSIDScM1PyHCwoRmGeolzGjgyZFg/ROg8he +HpmxnDqS3uIzfjqFvusfYOO8aMJh9cir2KSsqzyc+basbciwwm/ChwXg93rpE7kc +sQWaWCBRCq4Z3VHL19Grl+BeqoSl2aeSgJn1hG2pYEDxbFe3ci6l8frgppcUXlhL +rMo5NaAZainHMge0lin3aZBenqH0GUzbaf4VtwzKVpnwWF/TGLcjNemnRn0Slfui +9w4tYQTiv6zNTwNBUG7YXgWs4jMgvLor5bbsTcZX6Zm3zvKDOGWPHX9UlQGFFBpB +W8zifKGES0KykcpJsGximwamoc5tjnuBSIUiFJVnGOT3uSONQRsSjX+CLiLrym/1 +k9V1OH92mW/1R8uW8ZjndOCmjNwKsLzU9hBg6MVaV+9gIbc37OTGMohLyEAn4mbk +8MuhIkSW8FsDedJCBhxbjMdCBV97cgffyHFu9FirchSAjbfQBZA= +=xzA8 +-----END PGP SIGNATURE----- diff --git a/git/signatures/testdata/ssh_signatures/README.md b/git/signatures/testdata/ssh_signatures/README.md new file mode 100644 index 000000000..ab402ffec --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/README.md @@ -0,0 +1,202 @@ +# SSH Signature Test Fixtures + +This directory contains test fixtures for SSH signature validation. + +## Quick Start + +To generate all test fixtures at once, simply run: + +```bash +./generate_ssh_fixtures.sh +``` + +This script will automatically create all SSH keys, authorized_keys files, verified signers files, signed commits, and signed tags. + +## How to Generate Test Fixtures + +### Using the Automated Script + +The [`generate_ssh_fixtures.sh`](generate_ssh_fixtures.sh) script automates the entire process of creating SSH signature test fixtures. It generates: + +1. **SSH Key Pairs** in all variants: + - RSA (4096 bits) + - ECDSA (p256, p384, p521) + - ED25519 + +2. **Authorized Keys Files**: + - Individual files for each key type + - Combined file with all keys + +3. **Verified Signers Files** (with git namespace): + - Individual files for each key type + - Combined file with all keys + +4. **Signed Git Commits**: + - One signed commit for each key type + - All commits are verified using `git verify-commit` + +5. **Signed Git Tags**: + - One signed tag for each key type + - All tags are verified using `git verify-tag` + +6. **Unsigned Commit**: + - One unsigned commit for testing negative cases + +### Manual Generation + +If you need to generate test fixtures manually, follow these steps: + +#### 1. Generate SSH Key Pairs + +```bash +# RSA key +ssh-keygen -t rsa -b 4096 -f test_rsa -N "" +mv test_rsa.pub key_rsa.pub + +# ECDSA keys (all variants) +ssh-keygen -t ecdsa -b 256 -f test_ecdsa_p256 -N "" +mv test_ecdsa_p256.pub key_ecdsa_p256.pub + +ssh-keygen -t ecdsa -b 384 -f test_ecdsa_p384 -N "" +mv test_ecdsa_p384.pub key_ecdsa_p384.pub + +ssh-keygen -t ecdsa -b 521 -f test_ecdsa_p521 -N "" +mv test_ecdsa_p521.pub key_ecdsa_p521.pub + +# ED25519 key +ssh-keygen -t ed25519 -f test_ed25519 -N "" +mv test_ed25519.pub key_ed25519.pub +``` + +#### 2. Create Verified Signers File + +```bash +# Create verified signers file with git namespace +echo "$(git config --get user.email) namespaces=\"git\" $(cat key_ed25519.pub)" > verified_signers_ed25519 +``` + +#### 3. Create a Test Git Repository + +```bash +mkdir test_repo && cd test_repo +git init +echo "test content" > test.txt +git add test.txt +git commit -m "Test commit" +git config user.name "Test User" +git config user.email "sign-user@example.com" +git config gpg.format ssh +git config user.signingkey ../key_ed25519.pub +git config gpg.ssh.allowedSignersFile ../verified_signers_ed25519 +``` + +#### 4. Sign a Commit with SSH + +```bash +# Sign the last commit +git commit --amend --allow-empty -S -m "Test commit signed with ed25519" + +# Verify the signed commit +git verify-commit HEAD +``` + +#### 5. Export the Signed Commit + +```bash +# Get the commit object +git cat-file commit HEAD > commit_ed25519_signed.txt +``` + +#### 6. Create a Tag and Sign It + +```bash +git tag -a test-tag -m "Test tag" -s +git verify-tag test-tag +git cat-file tag test-tag > tag_ed25519_signed.txt +``` + +## File Format + +The signed Git objects follow the standard Git object format with SSH signatures: + +### Signed Commit Format + +``` +tree +parent +author +committer +gpgsig -----BEGIN SSH SIGNATURE----- + + -----END SSH SIGNATURE----- + + +``` + +### Signed Tag Format + +``` +object +type commit +tag +tagger + + +-----BEGIN SSH SIGNATURE----- + +-----END SSH SIGNATURE----- +``` + +### Verified Signers Format + +``` + namespaces="git" +``` + +## Generated Files + +The script generates the following files: + +### Public Keys +- `key_rsa.pub` - RSA 4096-bit public key +- `key_ecdsa_p256.pub` - ECDSA P-256 public key +- `key_ecdsa_p384.pub` - ECDSA P-384 public key +- `key_ecdsa_p521.pub` - ECDSA P-521 public key +- `key_ed25519.pub` - ED25519 public key + +### Authorized Keys Files +- `authorized_keys_rsa` - RSA public key +- `authorized_keys_ecdsa_p256` - ECDSA P-256 public key +- `authorized_keys_ecdsa_p384` - ECDSA P-384 public key +- `authorized_keys_ecdsa_p521` - ECDSA P-521 public key +- `authorized_keys_ed25519` - ED25519 public key +- `authorized_keys_all` - All public keys combined + +### Verified Signers Files +- `verified_signers_rsa` - RSA public key with git namespace +- `verified_signers_ecdsa_p256` - ECDSA P-256 public key with git namespace +- `verified_signers_ecdsa_p384` - ECDSA P-384 public key with git namespace +- `verified_signers_ecdsa_p521` - ECDSA P-521 public key with git namespace +- `verified_signers_ed25519` - ED25519 public key with git namespace +- `verified_signers_all` - All public keys with git namespace + +### Signed Commits +- `commit_rsa_signed.txt` - RSA-signed commit +- `commit_ecdsa_p256_signed.txt` - ECDSA P-256 signed commit +- `commit_ecdsa_p384_signed.txt` - ECDSA P-384 signed commit +- `commit_ecdsa_p521_signed.txt` - ECDSA P-521 signed commit +- `commit_ed25519_signed.txt` - ED25519 signed commit + +### Signed Tags +- `tag_rsa_signed.txt` - RSA-signed tag +- `tag_ecdsa_p256_signed.txt` - ECDSA P-256 signed tag +- `tag_ecdsa_p384_signed.txt` - ECDSA P-384 signed tag +- `tag_ecdsa_p521_signed.txt` - ECDSA P-521 signed tag +- `tag_ed25519_signed.txt` - ED25519 signed tag + +### Unsigned Commit +- `commit_unsigned.txt` - Unsigned commit for testing negative cases + +## Security Note + +These test fixtures use generated test keys and should NOT be used in production. \ No newline at end of file diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_all b/git/signatures/testdata/ssh_signatures/authorized_keys_all new file mode 100644 index 000000000..2a587db72 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/authorized_keys_all @@ -0,0 +1,5 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 new file mode 100644 index 000000000..7364a9a27 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 new file mode 100644 index 000000000..aabefb80b --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 new file mode 100644 index 000000000..82d92898f --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 b/git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 new file mode 100644 index 000000000..8f745c471 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_rsa b/git/signatures/testdata/ssh_signatures/authorized_keys_rsa new file mode 100644 index 000000000..b02a4d38f --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/authorized_keys_rsa @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com diff --git a/git/signatures/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt b/git/signatures/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt new file mode 100644 index 000000000..12ed29b51 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt @@ -0,0 +1,12 @@ +tree 2f0fa5393a2120151c5446eb34b99d1f3713ff12 +author Test User 1772153087 +0100 +committer Test User 1772153087 +0100 +gpgsig -----BEGIN SSH SIGNATURE----- + U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE + EEvteyl/kGZEPuKkajhI0J+2PN66evLXOeZTvxGFxU5jAs0JHkxWbbY31zVphpwjEeaL9P + GQ1N1B0QHx13iZ8DhAAAAANnaXQAAAAAAAAABnNoYTUxMgAAAGUAAAATZWNkc2Etc2hhMi + 1uaXN0cDI1NgAAAEoAAAAhAPQhsSXLRif71JKQ1QN9z79VfPHTOeKKAhpplCh5VY5/AAAA + IQDZBEQLxlx8YuKNFC3c2pZ6oS0Ry8MkkkpgZio9gsDl3w== + -----END SSH SIGNATURE----- + +Test commit signed with ecdsa_p256 diff --git a/git/signatures/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt b/git/signatures/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt new file mode 100644 index 000000000..860fd0f26 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt @@ -0,0 +1,13 @@ +tree ff58328bd5797f45f6f300c6c39d2cd357b9f3cd +author Test User 1772153088 +0100 +committer Test User 1772153088 +0100 +gpgsig -----BEGIN SSH SIGNATURE----- + U1NIU0lHAAAAAQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAG + EEpD1Slvc9rtvk1ZujObbQ+qkVzlZkIIIGVf354UQsCMp0HN7YRtNMq/H1iyQonw9YsTwP + 3DbSyMOK83B9SOiJkaBslBwkpwo+u2i85g+/QkqmjJnQ+4umr2SNJFNGdKETAAAAA2dpdA + AAAAAAAAAGc2hhNTEyAAAAhQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAagAAADEA1fuA + 0MeTI0m7DUxVP/EIRljC3Y6L9ElAU7Sqv5HXcOKVCxPYnZYuOrWgbnk+IhD4AAAAMQC+qA + zQUSgM0KFWFRPoxWUYo2gODfyizXdJqWIazjri9IlFZE/1eDZH8M32Ron3UII= + -----END SSH SIGNATURE----- + +Test commit signed with ecdsa_p384 diff --git a/git/signatures/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt b/git/signatures/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt new file mode 100644 index 000000000..fcc8de7ef --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt @@ -0,0 +1,15 @@ +tree 63af4f62a108a6c684181a4488b4bd3a5b51dc8e +author Test User 1772153088 +0100 +committer Test User 1772153088 +0100 +gpgsig -----BEGIN SSH SIGNATURE----- + U1NIU0lHAAAAAQAAAKwAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAI + UEAZJj44ARus1InhAPo2AkglBXySaOqL4GF94AC2ES/R4KrUIAOsKoq3SmjEJqFg0JMwuU + y+pbvEHDrAMHSRXT/gJPAFf0dF+0VSlplqc+1+8w2E9P8IMytOw1LOD8ffYe79+68vDI9D + QnNFeB/6qKrc5nirRWMRFTsvXdQOjPgWAckh5VAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAAA + pgAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAAiwAAAEIAqSn31cfI4XZEhgnOPL5BJ42jbD + G9nC/F0n94PJPLL1Y2aq9uFT69diEuTTYYFEzuJkk0CZdTCCDSi7Lbg2l3g4IAAABBDYLv + jKD5wuPhyt1tvLaTPNBIElMbkOULaLgespZHEbrgEh0KYNQXphnTgyF3lnuMBiPGqgDUW7 + 7TkSxoBDsI4D0= + -----END SSH SIGNATURE----- + +Test commit signed with ecdsa_p521 diff --git a/git/signatures/testdata/ssh_signatures/commit_ed25519_signed.txt b/git/signatures/testdata/ssh_signatures/commit_ed25519_signed.txt new file mode 100644 index 000000000..4f6da87f3 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/commit_ed25519_signed.txt @@ -0,0 +1,11 @@ +tree 7c5bd8f246ab8e8c6a5749c3d2f44018aa029fb8 +author Test User 1772153088 +0100 +committer Test User 1772153088 +0100 +gpgsig -----BEGIN SSH SIGNATURE----- + U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgHRfgHA3PrVXK+vIj9qrm9Rz19k + rWdqNjpYJJ3HOkstYAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 + AAAAQFOFBI8DavCfBEiobPbMvmFO5gcAzy1BLwKfo4djvxbhDYi74cg7Bejqqcv7NakDNL + rKJYnzrfnNIIk6GDmC7QY= + -----END SSH SIGNATURE----- + +Test commit signed with ed25519 diff --git a/git/signatures/testdata/ssh_signatures/commit_rsa_signed.txt b/git/signatures/testdata/ssh_signatures/commit_rsa_signed.txt new file mode 100644 index 000000000..ca23e48e5 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/commit_rsa_signed.txt @@ -0,0 +1,29 @@ +tree 1207106d0fef65cd05d7a8428fc871886a36fa78 +author Test User 1772153087 +0100 +committer Test User 1772153087 +0100 +gpgsig -----BEGIN SSH SIGNATURE----- + U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAKmt6I77xewHjFcY2bC47j + xrtY+CFvmKEIk0/JBmmdo9+rq+E1VZKCrwAiMGYk4lijgdnKIeqlLg4FzzqCWTqoy/xgdG + 3hVFUE/4OM8sMiw5Hv7YcGU48cybyVMOL6Iw8cEPGoXLuZIMHj6/ufvTT7j29iFaNkml6y + ecTomiK3FJWGaqnvmN41E1To8PTTP6AxRy+K/xQ6z8CmDULDl/7hP3I6eOU4doUf8G629n + S3ZUXRyzby18K4sCi6aOKd7kabq0JFCVk6hqk0nO+dhP7zp/88RT/iQNh/fBPtL42dQN8K + wikNWU5c++OdD8O52GoSede99yH54EIjuu0kEcgY8oV2YLhxRE5rRQMZHqj8nEu3HhlNuQ + amroxXB2tvuon46JvVTzFWKZYV9quSbt15VdYPfAHlZrwqj9r9a5h8TeBhGFvyJc9h3vUW + wTLgXjUkIxkNwioyJBF7d7aC9j7Pax/TaQc4V5YmasBj0UWM8vzlUPQOD76OsTGQYWNwFP + D2BQbk48anCpD1Yc7wTwM/Nr6Lkn/C7gM4PIusvG5cc95JhSNy+HmWfw/vSov4ivCfWaCf + Y+QhlbRcI8G3ojKWDnm3mx/LLTc/QqZvhTdOumkx6KDsuv0sNTgOiHsWPqgtgMYVRX4XnZ + JK4+zlJ+tZ3oCmLP5U+OwCHlu088k2XNAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAACFAAAAA + xyc2Etc2hhMi01MTIAAAIAnpVTEIaHs0ngRHSOk3oxEBHmZd/A0uMCznRjHNHDgHW8qa09 + qvII0n1RQI8Q0Wi8XvZsQqxXJ9/8nzfsrss1qDg8w4UnggnBYVnH/mgUIjw0tWxdoAv5Ga + BLfMOu+6gOp7YaqFYHe4RwtR/M2nCXbtnsEVrzLWKSBUaRI+TZHzExLJ4o6NpgJLMRhwpp + d8sGT6LuH/P08psOu9jCASksODcbWerAx+LfLcDIXje+WLzqu4Mn/HqZncMyf28bXJHcoq + X2ZWPHjZuRbcr9EeLdkHCDyD1kb7wAzR2Mpma9W99ZtpIXkugDSlbNQOyDGqB/b7t+I6Er + Sm/FL+1m3+pBnOxORpaxSkqFMlbWou4SNmYjSVU0XltxTpTV27svt0Lapmu3CpAptp3kx+ + 0Gd1y4QWyc2f38NPpConekGFKS/4O16zyGtFAUY5p4UCa/YUmC/H5QDskgv/MtZ/N+3RAr + EAlPOpTKM7876puPPd9tyEj9Tax5uNT7C039gyER/+B/eGWGcK08bq/YLfdgbmi51hrehd + DK3Z5wDfvIXci2rO0A/MB/HC1c75urX95uiHQV9pglQ+8zkrYeL/fD9+COaxqPJru+hdT0 + qlUJGIil/VBUTvu9PbsyyZA8UvPpFJRyGreNByyBxfhu33o2jx08OB9AgoctJ1tEgWJrty + fY/nQ= + -----END SSH SIGNATURE----- + +Test commit signed with rsa diff --git a/git/signatures/testdata/ssh_signatures/commit_unsigned.txt b/git/signatures/testdata/ssh_signatures/commit_unsigned.txt new file mode 100644 index 000000000..84dc42228 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/commit_unsigned.txt @@ -0,0 +1,5 @@ +tree 4650a2cda631bc795fc254fe20b598135b265036 +author Test User 1772153090 +0100 +committer Test User 1772153090 +0100 + +Test commit unsigned diff --git a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh new file mode 100755 index 000000000..2b5a76183 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash +# generate_fixtures.sh - Script to generate SSH signature test fixtures +# Generates SSH keys in all variants and signed Git objects + +set -e + +# Configuration variables +TEST_USER_NAME="Test User" +TEST_USER_EMAIL="sign-user@example.com" + +# Directory for temporary files +TEMP_DIR=$(mktemp -d) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "=== SSH Signature Test Fixtures Generator ===" +echo "Temporary directory: $TEMP_DIR" +echo "Output directory: $SCRIPT_DIR" +echo "" + +# Function to generate SSH keys +generate_ssh_key() { + local key_type=$1 + local key_bits=$2 + local key_name=$3 + + echo "Generating $key_name key pair..." + + case "$key_type" in + rsa) + ssh-keygen -t rsa -b "$key_bits" -f "$TEMP_DIR/$key_name" -N "" -C "test-$key_name@example.com" + ;; + ecdsa) + ssh-keygen -t ecdsa -b "$key_bits" -f "$TEMP_DIR/$key_name" -N "" -C "test-$key_name@example.com" + ;; + ed25519) + ssh-keygen -t ed25519 -f "$TEMP_DIR/$key_name" -N "" -C "test-$key_name@example.com" + ;; + esac + + # Copy public key to output directory with key_ prefix + cp "$TEMP_DIR/$key_name.pub" "$SCRIPT_DIR/key_${key_name}.pub" + echo " ✓ key_${key_name}.pub created" +} + +# Function to create authorized_keys files +create_authorized_keys() { + local key_name=$1 + local output_file="$SCRIPT_DIR/authorized_keys_${key_name}" + + echo "Creating authorized_keys for $key_name..." + + # Copy public key + cp "$TEMP_DIR/${key_name}.pub" "$output_file" + echo " ✓ $output_file created" +} + +# Function to create verified signers files with git namespace +create_verified_signers() { + local key_name=$1 + local output_file="$SCRIPT_DIR/verified_signers_${key_name}" + + echo "Creating verified signers file for $key_name..." + + # Create verified signers file with git namespace + echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/${key_name}.pub")" > "$output_file" + echo " ✓ $output_file created" +} + +# Function to create combined authorized_keys file +create_combined_authorized_keys() { + local output_file="$SCRIPT_DIR/authorized_keys_all" + + echo "Creating combined authorized_keys..." + + # Combine all public keys + { + cat "$TEMP_DIR/rsa.pub" + cat "$TEMP_DIR/ecdsa_p256.pub" + cat "$TEMP_DIR/ecdsa_p384.pub" + cat "$TEMP_DIR/ecdsa_p521.pub" + cat "$TEMP_DIR/ed25519.pub" + } > "$output_file" + + echo " ✓ $output_file created" +} + +# Function to create combined verified signers file +create_combined_verified_signers() { + local output_file="$SCRIPT_DIR/verified_signers_all" + + echo "Creating combined verified signers..." + + # Combine all public keys with git namespace + { + echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/rsa.pub")" + echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ecdsa_p256.pub")" + echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ecdsa_p384.pub")" + echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ecdsa_p521.pub")" + echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ed25519.pub")" + } > "$output_file" + + echo " ✓ $output_file created" +} + +# Function to create signed Git objects (commits and tags) +create_signed_object() { + local object_type=$1 + local key_name=$2 + local key_type=$3 + local verified_signers_file="$SCRIPT_DIR/verified_signers_${key_name}" + + echo "Creating signed $object_type for $key_name..." + + # Create temporary Git repository + local repo_dir="$TEMP_DIR/repo_${key_name}_${object_type}" + mkdir -p "$repo_dir" + cd "$repo_dir" + + git init + git config user.name "$TEST_USER_NAME" + git config user.email "$TEST_USER_EMAIL" + git config gpg.format ssh + git config user.signingkey "$TEMP_DIR/${key_name}.pub" + git config gpg.ssh.allowedSignersFile "$verified_signers_file" + + # Create file and commit + echo "Test content for $key_name $object_type" > test.txt + git add test.txt + git commit -m "Test commit for $object_type" + + if [[ "$object_type" == "commit" ]]; then + # Sign the commit (amend) + git commit --amend --allow-empty -S -m "Test commit signed with $key_name" + + # Verify the signed commit using git verify-commit + echo " Verifying signed commit with git verify-commit..." + if git verify-commit HEAD; then + echo " ✓ Commit signature verified successfully" + else + echo " ✗ Commit signature verification failed" + exit 1 + fi + + # Export commit object + local output_file="$SCRIPT_DIR/commit_${key_name}_signed.txt" + git cat-file commit HEAD > "$output_file" + cd "$SCRIPT_DIR" + echo " ✓ $output_file created" + + elif [[ "$object_type" == "tag" ]]; then + # Create and sign tag + git tag -a "test-tag-${key_name}" -m "Test tag signed with $key_name" -s + + # Verify the signed tag using git verify-tag + echo " Verifying signed tag with git verify-tag..." + if git verify-tag "test-tag-${key_name}"; then + echo " ✓ Tag signature verified successfully" + else + echo " ✗ Tag signature verification failed" + exit 1 + fi + + # Export tag object + local output_file="$SCRIPT_DIR/tag_${key_name}_signed.txt" + git cat-file tag "test-tag-${key_name}" > "$output_file" + cd "$SCRIPT_DIR" + echo " ✓ $output_file created" + else + echo "Error: unknown object type: ${object_type}" + fi +} + +# Function to create unsigned commit +create_unsigned_commit() { + local commit_file="$SCRIPT_DIR/commit_unsigned.txt" + + echo "Creating unsigned commit..." + + # Create temporary Git repository + local repo_dir="$TEMP_DIR/repo_unsigned" + mkdir -p "$repo_dir" + cd "$repo_dir" + + git init + git config user.name "$TEST_USER_NAME" + git config user.email "$TEST_USER_EMAIL" + + # Create file and commit (without signature) + echo "Test content unsigned" > test.txt + git add test.txt + git commit -m "Test commit unsigned" + + # Export commit object + git cat-file commit HEAD > "$commit_file" + + cd "$SCRIPT_DIR" + echo " ✓ $commit_file created" +} + +# Main program +main() { + echo "Step 1: Generate SSH keys..." + echo "-----------------------------------" + + # RSA key (4096 bits) + generate_ssh_key "rsa" "4096" "rsa" + + # ECDSA keys (all variants: p256, p384, p521) + generate_ssh_key "ecdsa" "256" "ecdsa_p256" + generate_ssh_key "ecdsa" "384" "ecdsa_p384" + generate_ssh_key "ecdsa" "521" "ecdsa_p521" + + # ED25519 key + generate_ssh_key "ed25519" "" "ed25519" + + echo "" + echo "Step 2: Create authorized_keys files..." + echo "-----------------------------------------------" + + # Individual authorized_keys files + create_authorized_keys "rsa" + create_authorized_keys "ecdsa_p256" + create_authorized_keys "ecdsa_p384" + create_authorized_keys "ecdsa_p521" + create_authorized_keys "ed25519" + + # Combined authorized_keys file + create_combined_authorized_keys + + echo "" + echo "Step 3: Create verified signers files..." + echo "-----------------------------------------------" + + # Individual verified signers files with git namespace + create_verified_signers "rsa" + create_verified_signers "ecdsa_p256" + create_verified_signers "ecdsa_p384" + create_verified_signers "ecdsa_p521" + create_verified_signers "ed25519" + + # Combined verified signers file + create_combined_verified_signers + + echo "" + echo "Step 4: Create signed commits..." + echo "----------------------------------------" + + # Signed commits for each key type + create_signed_object "commit" "rsa" "rsa" + create_signed_object "commit" "ecdsa_p256" "ecdsa" + create_signed_object "commit" "ecdsa_p384" "ecdsa" + create_signed_object "commit" "ecdsa_p521" "ecdsa" + create_signed_object "commit" "ed25519" "ed25519" + + echo "" + echo "Step 5: Create signed tags..." + echo "-------------------------------------" + + # Signed tags for each key type + create_signed_object "tag" "rsa" "rsa" + create_signed_object "tag" "ecdsa_p256" "ecdsa" + create_signed_object "tag" "ecdsa_p384" "ecdsa" + create_signed_object "tag" "ecdsa_p521" "ecdsa" + create_signed_object "tag" "ed25519" "ed25519" + + echo "" + echo "Step 6: Create unsigned commit..." + echo "------------------------------------------" + + create_unsigned_commit + + echo "" + echo "=== Cleanup ===" + rm -rf "$TEMP_DIR" + echo "Temporary directory removed" + + echo "" + echo "=== Done! ===" + echo "All test fixtures have been successfully created." + echo "" + echo "Created files:" + find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" -o -name "authorized_keys*" -o -name "verified_signers*" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' +} + +# Run script +main "$@" \ No newline at end of file diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub b/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub new file mode 100644 index 000000000..7364a9a27 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub b/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub new file mode 100644 index 000000000..aabefb80b --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub b/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub new file mode 100644 index 000000000..82d92898f --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com diff --git a/git/signatures/testdata/ssh_signatures/key_ed25519.pub b/git/signatures/testdata/ssh_signatures/key_ed25519.pub new file mode 100644 index 000000000..8f745c471 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signatures/testdata/ssh_signatures/key_rsa.pub b/git/signatures/testdata/ssh_signatures/key_rsa.pub new file mode 100644 index 000000000..b02a4d38f --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com diff --git a/git/signatures/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt b/git/signatures/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt new file mode 100644 index 000000000..a20933c0b --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt @@ -0,0 +1,13 @@ +object a9ce559c0acfc9268bdd854dec51d77ead112ab5 +type commit +tag test-tag-ecdsa_p256 +tagger Test User 1772153089 +0100 + +Test tag signed with ecdsa_p256 +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE +EEvteyl/kGZEPuKkajhI0J+2PN66evLXOeZTvxGFxU5jAs0JHkxWbbY31zVphpwjEeaL9P +GQ1N1B0QHx13iZ8DhAAAAANnaXQAAAAAAAAABnNoYTUxMgAAAGUAAAATZWNkc2Etc2hhMi +1uaXN0cDI1NgAAAEoAAAAhAOQrMY08WBF4tTiUz3vq48VoKjvjOR9y75YzhMShbmGEAAAA +IQCF2ZvBxS6o/sZuRRw6HrFNryg2PU4ambnsRlC2cqOgfA== +-----END SSH SIGNATURE----- diff --git a/git/signatures/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt b/git/signatures/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt new file mode 100644 index 000000000..002180388 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt @@ -0,0 +1,14 @@ +object 095e9cde03a267af2c9ef62cf4868b126994714a +type commit +tag test-tag-ecdsa_p384 +tagger Test User 1772153089 +0100 + +Test tag signed with ecdsa_p384 +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAG +EEpD1Slvc9rtvk1ZujObbQ+qkVzlZkIIIGVf354UQsCMp0HN7YRtNMq/H1iyQonw9YsTwP +3DbSyMOK83B9SOiJkaBslBwkpwo+u2i85g+/QkqmjJnQ+4umr2SNJFNGdKETAAAAA2dpdA +AAAAAAAAAGc2hhNTEyAAAAgwAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAaAAAADA35l3E +HiF5ajYffjQRjxx37o8DG0eZIwDGtM2suBElqRKPrv2lNXUAZIFOt60X7EgAAAAwSE8BAK +DzSrdmwWwGIdsURzNrb0ziNQG5TJUI6oexNNGqP+JvZeGSJpSsS/PtRJyq +-----END SSH SIGNATURE----- diff --git a/git/signatures/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt b/git/signatures/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt new file mode 100644 index 000000000..48690d844 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt @@ -0,0 +1,16 @@ +object f98d104240f097f9912d3dd654710a4ea9710a0d +type commit +tag test-tag-ecdsa_p521 +tagger Test User 1772153090 +0100 + +Test tag signed with ecdsa_p521 +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAKwAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAI +UEAZJj44ARus1InhAPo2AkglBXySaOqL4GF94AC2ES/R4KrUIAOsKoq3SmjEJqFg0JMwuU +y+pbvEHDrAMHSRXT/gJPAFf0dF+0VSlplqc+1+8w2E9P8IMytOw1LOD8ffYe79+68vDI9D +QnNFeB/6qKrc5nirRWMRFTsvXdQOjPgWAckh5VAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAAA +pwAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAAjAAAAEIBZnxQJm8pVQbZLRVgFzBa6mKgyo +Ndyi4pEsccUjrIVxkHV+choqQaLBv0hiLNx9pj7a4ZXCNxxTO0XO4LY5OMP40AAABCAROG +/LBErKEWKIFHOMYwPdaCEPUtimfYwAH6rBUhAFJdeDwm9WHoU2XcXO2Ca6+LCNQGTRBZSu +UxOfXY4xBKbaf2 +-----END SSH SIGNATURE----- diff --git a/git/signatures/testdata/ssh_signatures/tag_ed25519_signed.txt b/git/signatures/testdata/ssh_signatures/tag_ed25519_signed.txt new file mode 100644 index 000000000..4b811e555 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/tag_ed25519_signed.txt @@ -0,0 +1,12 @@ +object 04285f60c0dcb310174dccae49f08475981aba2c +type commit +tag test-tag-ed25519 +tagger Test User 1772153090 +0100 + +Test tag signed with ed25519 +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgHRfgHA3PrVXK+vIj9qrm9Rz19k +rWdqNjpYJJ3HOkstYAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQH9s7JZFHLzLGztuessbqdQwofdi/4WLeBnaRXdxy0g5WTLOUxENnJtLYcdKKowJBs +xS/FE43Cfu3YGmXAsSWwk= +-----END SSH SIGNATURE----- diff --git a/git/signatures/testdata/ssh_signatures/tag_rsa_signed.txt b/git/signatures/testdata/ssh_signatures/tag_rsa_signed.txt new file mode 100644 index 000000000..3882a4fa4 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/tag_rsa_signed.txt @@ -0,0 +1,30 @@ +object 76abbeedee42f812d7fa2cdf0545f9cc13ae0463 +type commit +tag test-tag-rsa +tagger Test User 1772153089 +0100 + +Test tag signed with rsa +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAKmt6I77xewHjFcY2bC47j +xrtY+CFvmKEIk0/JBmmdo9+rq+E1VZKCrwAiMGYk4lijgdnKIeqlLg4FzzqCWTqoy/xgdG +3hVFUE/4OM8sMiw5Hv7YcGU48cybyVMOL6Iw8cEPGoXLuZIMHj6/ufvTT7j29iFaNkml6y +ecTomiK3FJWGaqnvmN41E1To8PTTP6AxRy+K/xQ6z8CmDULDl/7hP3I6eOU4doUf8G629n +S3ZUXRyzby18K4sCi6aOKd7kabq0JFCVk6hqk0nO+dhP7zp/88RT/iQNh/fBPtL42dQN8K +wikNWU5c++OdD8O52GoSede99yH54EIjuu0kEcgY8oV2YLhxRE5rRQMZHqj8nEu3HhlNuQ +amroxXB2tvuon46JvVTzFWKZYV9quSbt15VdYPfAHlZrwqj9r9a5h8TeBhGFvyJc9h3vUW +wTLgXjUkIxkNwioyJBF7d7aC9j7Pax/TaQc4V5YmasBj0UWM8vzlUPQOD76OsTGQYWNwFP +D2BQbk48anCpD1Yc7wTwM/Nr6Lkn/C7gM4PIusvG5cc95JhSNy+HmWfw/vSov4ivCfWaCf +Y+QhlbRcI8G3ojKWDnm3mx/LLTc/QqZvhTdOumkx6KDsuv0sNTgOiHsWPqgtgMYVRX4XnZ +JK4+zlJ+tZ3oCmLP5U+OwCHlu088k2XNAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAACFAAAAA +xyc2Etc2hhMi01MTIAAAIAUtTbcgmerQKpLoDxALJWACnkNDtKFagkZFMmFUo8vpAp5Zyz +jbC9uo6Oql/JwVNoAI+pm4/gxRDAKRGU1abki5Ge998m/FSkKi6ka0E4qwuNZkd9dOHAqt +kKeFhwIp0xiWCDF8s3iPpraaJbEfuGkGsAyAVNgfR0W8hu0wOOj8uwHuVZj7LeNLS3/jEu +bHwWhmzWCT0IPhFdkegDJMJ4XXgjxfsgGCXUahUfNZgOCXBfEBQkhHNoTq55+8DVqZ47hK +nRGjAZTVTnIxZhJqvaCHErse5A2jBJs2QfzmAIJhNAlDKmeHdWGDUADGxk5U7gD5IK5j/A +lBWp/ruXVqc7gwRKwQc7muu0Kzwa0yw8pBGi+8Y089a0M8Ti0cci57koXD8tPBLgz66710 +zLe9xkAZFvwxurHcgf01POvlCf6KGamCTNRsncnaUKfTvZVSOXHPeurNVlEpsDPZAsJ6wI +hFc0Y/RvLbTMlCxA6/brvr+peYSKmnCXJO+SXgZkN0QoKrq27RvPwB/2j1sNGgKpftfxUK +ymhPPKlzXGgrSDkBLhcaqGI1+5J3qjN0qLRGjwpgvkuM2JFLUaFVk1w8EvU19yMjmYkddn +AdZEB0xiAu1vZEEw9jhaTWW2R1qQ3ftf1+D8iXm+t1I3HlrSOBcVGzDi0x66a62K0bmXXy +AIRCY= +-----END SSH SIGNATURE----- diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_all b/git/signatures/testdata/ssh_signatures/verified_signers_all new file mode 100644 index 000000000..40c5d87a6 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/verified_signers_all @@ -0,0 +1,5 @@ +sign-user@example.com namespaces="git" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com +sign-user@example.com namespaces="git" ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com +sign-user@example.com namespaces="git" ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com +sign-user@example.com namespaces="git" ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com +sign-user@example.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p256 b/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p256 new file mode 100644 index 000000000..c776e628e --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p256 @@ -0,0 +1 @@ +sign-user@example.com namespaces="git" ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p384 b/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p384 new file mode 100644 index 000000000..ef4a20160 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p384 @@ -0,0 +1 @@ +sign-user@example.com namespaces="git" ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p521 b/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p521 new file mode 100644 index 000000000..91f0e5571 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p521 @@ -0,0 +1 @@ +sign-user@example.com namespaces="git" ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ed25519 b/git/signatures/testdata/ssh_signatures/verified_signers_ed25519 new file mode 100644 index 000000000..ce5186928 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/verified_signers_ed25519 @@ -0,0 +1 @@ +sign-user@example.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_rsa b/git/signatures/testdata/ssh_signatures/verified_signers_rsa new file mode 100644 index 000000000..0e845f6f5 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/verified_signers_rsa @@ -0,0 +1 @@ +sign-user@example.com namespaces="git" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com diff --git a/git/signatures/testutils_test.go b/git/signatures/testutils_test.go new file mode 100644 index 000000000..7088f741d --- /dev/null +++ b/git/signatures/testutils_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures_test + +import ( + "os" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" +) + +// parseCommitFromFixture parses a git commit object from a fixture file +func parseCommitFromFixture(fixturePath string) (*object.Commit, error) { + data, err := os.ReadFile(fixturePath) + if err != nil { + return nil, err + } + + // Create a MemoryObject and write the commit data to it + obj := &plumbing.MemoryObject{} + obj.SetType(plumbing.CommitObject) + if _, err := obj.Write(data); err != nil { + return nil, err + } + + // Decode the commit object + commit := &object.Commit{} + if err := commit.Decode(obj); err != nil { + return nil, err + } + + return commit, nil +} + +// parseTagFromFixture parses a git tag object from a fixture file +func parseTagFromFixture(fixturePath string) (*object.Tag, error) { + data, err := os.ReadFile(fixturePath) + if err != nil { + return nil, err + } + + // Create a MemoryObject and write the tag data to it + obj := &plumbing.MemoryObject{} + obj.SetType(plumbing.TagObject) + if _, err := obj.Write(data); err != nil { + return nil, err + } + + // Decode the tag object + tag := &object.Tag{} + if err := tag.Decode(obj); err != nil { + return nil, err + } + + return tag, nil +} From a86a2792a81718cbc703cf4afbb12bc4d4db878d Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Fri, 27 Feb 2026 18:27:24 +0100 Subject: [PATCH 02/22] adds validation of key fingerprint string Signed-off-by: Ricardo Bartels --- git/signatures/ssh_signature.go | 2 +- git/signatures/ssh_signature_test.go | 154 ++++++++++++------ .../ssh_signatures/calc_fingerprints.go | 35 ++++ .../ssh_signatures/generate_ssh_fixtures.sh | 94 ++++++----- .../key_ecdsa_p256.pub_fingerprint | 1 + .../key_ecdsa_p384.pub_fingerprint | 1 + .../key_ecdsa_p521.pub_fingerprint | 1 + .../key_ed25519.pub_fingerprint | 1 + .../ssh_signatures/key_rsa.pub_fingerprint | 1 + 9 files changed, 193 insertions(+), 97 deletions(-) create mode 100644 git/signatures/testdata/ssh_signatures/calc_fingerprints.go create mode 100644 git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint create mode 100644 git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint create mode 100644 git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint create mode 100644 git/signatures/testdata/ssh_signatures/key_ed25519.pub_fingerprint create mode 100644 git/signatures/testdata/ssh_signatures/key_rsa.pub_fingerprint diff --git a/git/signatures/ssh_signature.go b/git/signatures/ssh_signature.go index e298e65ad..c2fd9d2cc 100644 --- a/git/signatures/ssh_signature.go +++ b/git/signatures/ssh_signature.go @@ -102,5 +102,5 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri // in the format used by SSH (e.g., "SHA256:abc123..."). func GetPublicKeyFingerprint(pubKey gossh.PublicKey) string { hash := sha256.Sum256(pubKey.Marshal()) - return "SHA256:" + base64.StdEncoding.EncodeToString(hash[:]) + return "SHA256:" + strings.TrimSuffix(base64.StdEncoding.EncodeToString(hash[:]), "=") } diff --git a/git/signatures/ssh_signature_test.go b/git/signatures/ssh_signature_test.go index df9521511..f364b4b09 100644 --- a/git/signatures/ssh_signature_test.go +++ b/git/signatures/ssh_signature_test.go @@ -30,51 +30,65 @@ import ( func TestParseAuthorizedKeys(t *testing.T) { tests := []struct { - name string - authorizedKeys string - wantCount int - wantErr bool + name string + authorizedKeys string + wantCount int + wantErr bool + wantFingerprints []string }{ { - name: "single key", - authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com", - wantCount: 1, - wantErr: false, + name: "single key", + authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com", + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, + }, + { + name: "key with additional directives", + authorizedKeys: "no-user-rc,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com additional long comment about nothing", + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, }, { name: "multiple keys", authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test1@example.com -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test2@example.com`, - wantCount: 2, - wantErr: false, +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com`, + wantCount: 2, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM", "SHA256:oU8IT7UOnJlOTOvr/W1cYf1SkdocFm5F7SAXOwuo8Kc"}, }, { name: "with comments", authorizedKeys: `# This is a comment -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com # Another comment`, - wantCount: 1, - wantErr: false, + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:+vwrYGpHfAAWIzT2x+uV+duJG7ZnSvCbRKwdPApx7JA"}, }, { name: "with empty lines", - authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com + authorizedKeys: `ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test2@example.com`, - wantCount: 2, - wantErr: false, +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com`, + wantCount: 2, + wantErr: false, + wantFingerprints: []string{"SHA256:3FcWgX5RsACruglrcBJP/hefUZcYHJGnrk07U6yKin8", "SHA256:TxoYgaeIj5A7Md4rHNfxPdqawooc4NIGjIMbcQ7YKbw"}, }, { - name: "empty", - authorizedKeys: "", - wantCount: 0, - wantErr: false, + name: "empty", + authorizedKeys: "", + wantCount: 0, + wantErr: false, + wantFingerprints: []string{}, }, { - name: "invalid key", - authorizedKeys: "invalid-key-data", - wantCount: 0, - wantErr: true, + name: "invalid key", + authorizedKeys: "invalid-key-data", + wantCount: 0, + wantErr: true, + wantFingerprints: []string{}, }, } @@ -88,6 +102,21 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH if len(keys) != tt.wantCount { t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) } + // Validate expected fingerprint if specified + if len(tt.wantFingerprints) > 0 && len(keys) > 0 { + for _, key := range keys { + found := false + fingerprint := signatures.GetPublicKeyFingerprint(key) + for _, wantedFingerprint := range tt.wantFingerprints { + if fingerprint == wantedFingerprint { + found = true + } + } + if !found { + t.Errorf("ParseAuthorizedKeys() fingerprint '%s'not in list of wanted fingerprints %s", fingerprint, tt.wantFingerprints) + } + } + } }) } } @@ -96,40 +125,46 @@ func TestParseAuthorizedKeysFromFixtures(t *testing.T) { testDataDir := filepath.Join("testdata", "ssh_signatures") tests := []struct { - name string - fixture string - wantCount int - wantErr bool + name string + fixture string + fingerprintFile string + wantCount int + wantErr bool }{ { - name: "ed25519 key", - fixture: "authorized_keys_ed25519", - wantCount: 1, - wantErr: false, + name: "ed25519 key", + fixture: "authorized_keys_ed25519", + fingerprintFile: "key_ed25519.pub_fingerprint", + wantCount: 1, + wantErr: false, }, { - name: "rsa key", - fixture: "authorized_keys_rsa", - wantCount: 1, - wantErr: false, + name: "rsa key", + fixture: "authorized_keys_rsa", + fingerprintFile: "key_rsa.pub_fingerprint", + wantCount: 1, + wantErr: false, }, { - name: "ecdsa p256 key", - fixture: "authorized_keys_ecdsa_p256", - wantCount: 1, - wantErr: false, + name: "ecdsa p256 key", + fixture: "authorized_keys_ecdsa_p256", + fingerprintFile: "key_ecdsa_p256.pub_fingerprint", + wantCount: 1, + wantErr: false, }, { - name: "ecdsa p384 key", - fixture: "authorized_keys_ecdsa_p384", - wantCount: 1, - wantErr: false, + name: "ecdsa p384 key", + fixture: "authorized_keys_ecdsa_p384", + fingerprintFile: "key_ecdsa_p384.pub_fingerprint", + wantCount: 1, + wantErr: false, }, { - name: "ecdsa p521 key", - fixture: "authorized_keys_ecdsa_p521", - wantCount: 1, - wantErr: false, + name: "ecdsa p521 key", + fixture: "authorized_keys_ecdsa_p521", + fingerprintFile: "key_ecdsa_p521.pub_fingerprint", + wantCount: 1, + wantErr: false, }, { name: "all key types combined", @@ -155,6 +190,16 @@ func TestParseAuthorizedKeysFromFixtures(t *testing.T) { t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) } + // Read expected fingerprint from file if provided + var expectedFingerprint string + if tt.fingerprintFile != "" { + fingerprintData, err := os.ReadFile(filepath.Join(testDataDir, tt.fingerprintFile)) + if err != nil { + t.Fatalf("Failed to read fingerprint file %s: %v", tt.fingerprintFile, err) + } + expectedFingerprint = strings.TrimSpace(string(fingerprintData)) + } + // Verify that each key has a valid fingerprint for i, key := range keys { fingerprint := signatures.GetPublicKeyFingerprint(key) @@ -164,6 +209,12 @@ func TestParseAuthorizedKeysFromFixtures(t *testing.T) { if !strings.HasPrefix(fingerprint, "SHA256:") { t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) } + // Validate fingerprint against the one read from file + if expectedFingerprint != "" { + if fingerprint != expectedFingerprint { + t.Errorf("Key %d got fingerprint %s, want %s (from %s)", i, fingerprint, expectedFingerprint, tt.fingerprintFile) + } + } } }) } @@ -300,6 +351,7 @@ o6RLdWlvb81l/UyYhGEwE= func TestGetPublicKeyFingerprint(t *testing.T) { // Test with a known public key pubKeyStr := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com" + expectedFingerprint := "SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM" keys, err := signatures.ParseAuthorizedKeys(pubKeyStr) if err != nil { t.Fatalf("Failed to parse test public key: %v", err) @@ -312,7 +364,7 @@ func TestGetPublicKeyFingerprint(t *testing.T) { if fingerprint == "" { t.Error("GetPublicKeyFingerprint() returned empty string") } - if !strings.HasPrefix(fingerprint, "SHA256:") { + if !strings.HasPrefix(fingerprint, expectedFingerprint) { t.Errorf("GetPublicKeyFingerprint() = %s, want prefix SHA256:", fingerprint) } } diff --git a/git/signatures/testdata/ssh_signatures/calc_fingerprints.go b/git/signatures/testdata/ssh_signatures/calc_fingerprints.go new file mode 100644 index 000000000..2cd8bf8d6 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/calc_fingerprints.go @@ -0,0 +1,35 @@ +package main + +import ( + "crypto/sha256" + "encoding/base64" + "fmt" + "strings" + + gossh "golang.org/x/crypto/ssh" +) + +func main() { + keys := []struct { + name string + key string + }{ + {"test_key", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com"}, + {"ed25519", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com"}, + {"rsa", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com"}, + {"ecdsa_p256", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com"}, + {"ecdsa_p384", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com"}, + {"ecdsa_p521", "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com"}, + } + + for _, k := range keys { + pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(k.key)) + if err != nil { + fmt.Printf("Error parsing %s: %v\n", k.name, err) + continue + } + hash := sha256.Sum256(pubKey.Marshal()) + fingerprint := "SHA256:" + strings.TrimSuffix(base64.StdEncoding.EncodeToString(hash[:]), "=") + fmt.Printf("%s: %s\n", k.name, fingerprint) + } +} diff --git a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh index 2b5a76183..ab7cba060 100755 --- a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh +++ b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh @@ -22,9 +22,9 @@ generate_ssh_key() { local key_type=$1 local key_bits=$2 local key_name=$3 - + echo "Generating $key_name key pair..." - + case "$key_type" in rsa) ssh-keygen -t rsa -b "$key_bits" -f "$TEMP_DIR/$key_name" -N "" -C "test-$key_name@example.com" @@ -36,19 +36,23 @@ generate_ssh_key() { ssh-keygen -t ed25519 -f "$TEMP_DIR/$key_name" -N "" -C "test-$key_name@example.com" ;; esac - + # Copy public key to output directory with key_ prefix cp "$TEMP_DIR/$key_name.pub" "$SCRIPT_DIR/key_${key_name}.pub" echo " ✓ key_${key_name}.pub created" + + # Calculate and write SHA256 fingerprint to file + ssh-keygen -lf "$TEMP_DIR/$key_name.pub" | awk '{print $2}' > "$SCRIPT_DIR/key_${key_name}.pub_fingerprint" + echo " ✓ key_${key_name}.pub_fingerprint created" } # Function to create authorized_keys files create_authorized_keys() { local key_name=$1 local output_file="$SCRIPT_DIR/authorized_keys_${key_name}" - + echo "Creating authorized_keys for $key_name..." - + # Copy public key cp "$TEMP_DIR/${key_name}.pub" "$output_file" echo " ✓ $output_file created" @@ -58,9 +62,9 @@ create_authorized_keys() { create_verified_signers() { local key_name=$1 local output_file="$SCRIPT_DIR/verified_signers_${key_name}" - + echo "Creating verified signers file for $key_name..." - + # Create verified signers file with git namespace echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/${key_name}.pub")" > "$output_file" echo " ✓ $output_file created" @@ -69,9 +73,9 @@ create_verified_signers() { # Function to create combined authorized_keys file create_combined_authorized_keys() { local output_file="$SCRIPT_DIR/authorized_keys_all" - + echo "Creating combined authorized_keys..." - + # Combine all public keys { cat "$TEMP_DIR/rsa.pub" @@ -80,16 +84,16 @@ create_combined_authorized_keys() { cat "$TEMP_DIR/ecdsa_p521.pub" cat "$TEMP_DIR/ed25519.pub" } > "$output_file" - + echo " ✓ $output_file created" } # Function to create combined verified signers file create_combined_verified_signers() { local output_file="$SCRIPT_DIR/verified_signers_all" - + echo "Creating combined verified signers..." - + # Combine all public keys with git namespace { echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/rsa.pub")" @@ -98,7 +102,7 @@ create_combined_verified_signers() { echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ecdsa_p521.pub")" echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ed25519.pub")" } > "$output_file" - + echo " ✓ $output_file created" } @@ -108,30 +112,30 @@ create_signed_object() { local key_name=$2 local key_type=$3 local verified_signers_file="$SCRIPT_DIR/verified_signers_${key_name}" - + echo "Creating signed $object_type for $key_name..." - + # Create temporary Git repository local repo_dir="$TEMP_DIR/repo_${key_name}_${object_type}" mkdir -p "$repo_dir" cd "$repo_dir" - + git init git config user.name "$TEST_USER_NAME" git config user.email "$TEST_USER_EMAIL" git config gpg.format ssh git config user.signingkey "$TEMP_DIR/${key_name}.pub" git config gpg.ssh.allowedSignersFile "$verified_signers_file" - + # Create file and commit echo "Test content for $key_name $object_type" > test.txt git add test.txt git commit -m "Test commit for $object_type" - + if [[ "$object_type" == "commit" ]]; then # Sign the commit (amend) git commit --amend --allow-empty -S -m "Test commit signed with $key_name" - + # Verify the signed commit using git verify-commit echo " Verifying signed commit with git verify-commit..." if git verify-commit HEAD; then @@ -140,7 +144,7 @@ create_signed_object() { echo " ✗ Commit signature verification failed" exit 1 fi - + # Export commit object local output_file="$SCRIPT_DIR/commit_${key_name}_signed.txt" git cat-file commit HEAD > "$output_file" @@ -150,7 +154,7 @@ create_signed_object() { elif [[ "$object_type" == "tag" ]]; then # Create and sign tag git tag -a "test-tag-${key_name}" -m "Test tag signed with $key_name" -s - + # Verify the signed tag using git verify-tag echo " Verifying signed tag with git verify-tag..." if git verify-tag "test-tag-${key_name}"; then @@ -159,7 +163,7 @@ create_signed_object() { echo " ✗ Tag signature verification failed" exit 1 fi - + # Export tag object local output_file="$SCRIPT_DIR/tag_${key_name}_signed.txt" git cat-file tag "test-tag-${key_name}" > "$output_file" @@ -173,26 +177,26 @@ create_signed_object() { # Function to create unsigned commit create_unsigned_commit() { local commit_file="$SCRIPT_DIR/commit_unsigned.txt" - + echo "Creating unsigned commit..." - + # Create temporary Git repository local repo_dir="$TEMP_DIR/repo_unsigned" mkdir -p "$repo_dir" cd "$repo_dir" - + git init git config user.name "$TEST_USER_NAME" git config user.email "$TEST_USER_EMAIL" - + # Create file and commit (without signature) echo "Test content unsigned" > test.txt git add test.txt git commit -m "Test commit unsigned" - + # Export commit object git cat-file commit HEAD > "$commit_file" - + cd "$SCRIPT_DIR" echo " ✓ $commit_file created" } @@ -201,79 +205,79 @@ create_unsigned_commit() { main() { echo "Step 1: Generate SSH keys..." echo "-----------------------------------" - + # RSA key (4096 bits) generate_ssh_key "rsa" "4096" "rsa" - + # ECDSA keys (all variants: p256, p384, p521) generate_ssh_key "ecdsa" "256" "ecdsa_p256" generate_ssh_key "ecdsa" "384" "ecdsa_p384" generate_ssh_key "ecdsa" "521" "ecdsa_p521" - + # ED25519 key generate_ssh_key "ed25519" "" "ed25519" - + echo "" echo "Step 2: Create authorized_keys files..." echo "-----------------------------------------------" - + # Individual authorized_keys files create_authorized_keys "rsa" create_authorized_keys "ecdsa_p256" create_authorized_keys "ecdsa_p384" create_authorized_keys "ecdsa_p521" create_authorized_keys "ed25519" - + # Combined authorized_keys file create_combined_authorized_keys - + echo "" echo "Step 3: Create verified signers files..." echo "-----------------------------------------------" - + # Individual verified signers files with git namespace create_verified_signers "rsa" create_verified_signers "ecdsa_p256" create_verified_signers "ecdsa_p384" create_verified_signers "ecdsa_p521" create_verified_signers "ed25519" - + # Combined verified signers file create_combined_verified_signers - + echo "" echo "Step 4: Create signed commits..." echo "----------------------------------------" - + # Signed commits for each key type create_signed_object "commit" "rsa" "rsa" create_signed_object "commit" "ecdsa_p256" "ecdsa" create_signed_object "commit" "ecdsa_p384" "ecdsa" create_signed_object "commit" "ecdsa_p521" "ecdsa" create_signed_object "commit" "ed25519" "ed25519" - + echo "" echo "Step 5: Create signed tags..." echo "-------------------------------------" - + # Signed tags for each key type create_signed_object "tag" "rsa" "rsa" create_signed_object "tag" "ecdsa_p256" "ecdsa" create_signed_object "tag" "ecdsa_p384" "ecdsa" create_signed_object "tag" "ecdsa_p521" "ecdsa" create_signed_object "tag" "ed25519" "ed25519" - + echo "" echo "Step 6: Create unsigned commit..." echo "------------------------------------------" - + create_unsigned_commit - + echo "" echo "=== Cleanup ===" rm -rf "$TEMP_DIR" echo "Temporary directory removed" - + echo "" echo "=== Done! ===" echo "All test fixtures have been successfully created." diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint b/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint new file mode 100644 index 000000000..f62198c01 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint @@ -0,0 +1 @@ +SHA256:oU8IT7UOnJlOTOvr/W1cYf1SkdocFm5F7SAXOwuo8Kc diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint b/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint new file mode 100644 index 000000000..ee5243a33 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint @@ -0,0 +1 @@ +SHA256:+vwrYGpHfAAWIzT2x+uV+duJG7ZnSvCbRKwdPApx7JA diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint b/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint new file mode 100644 index 000000000..34f471eca --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint @@ -0,0 +1 @@ +SHA256:3FcWgX5RsACruglrcBJP/hefUZcYHJGnrk07U6yKin8 diff --git a/git/signatures/testdata/ssh_signatures/key_ed25519.pub_fingerprint b/git/signatures/testdata/ssh_signatures/key_ed25519.pub_fingerprint new file mode 100644 index 000000000..1ccdda317 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_ed25519.pub_fingerprint @@ -0,0 +1 @@ +SHA256:eNi885YLo10DYWUdJOAs+CeXcDLX7X+Aqg2PprKFE3A diff --git a/git/signatures/testdata/ssh_signatures/key_rsa.pub_fingerprint b/git/signatures/testdata/ssh_signatures/key_rsa.pub_fingerprint new file mode 100644 index 000000000..060a2c804 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/key_rsa.pub_fingerprint @@ -0,0 +1 @@ +SHA256:TxoYgaeIj5A7Md4rHNfxPdqawooc4NIGjIMbcQ7YKbw From 5ee848770fa3f36e8f269ddc01acc71793b372b5 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Fri, 27 Feb 2026 23:18:06 +0100 Subject: [PATCH 03/22] adds better git signature detection format Signed-off-by: Ricardo Bartels --- git/git.go | 4 - git/signatures/gpg_signature.go | 14 +++- git/signatures/gpg_signature_test.go | 14 ++++ git/signatures/signature.go | 43 ++++++++--- git/signatures/signature_test.go | 72 ++++++++++++++++++ git/signatures/ssh_signature.go | 7 +- git/signatures/ssh_signature_test.go | 110 +++++++++++++++++++++++++++ 7 files changed, 248 insertions(+), 16 deletions(-) diff --git a/git/git.go b/git/git.go index d6344eaa8..ce9b1a2c7 100644 --- a/git/git.go +++ b/git/git.go @@ -135,8 +135,6 @@ func (c *Commit) VerifyGPG(keyRings ...string) (string, error) { // It does not verify the signature of the referencing tag (if present). Users are // expected to explicitly verify the referencing tag's signature using `c.ReferencingTag.VerifySSH()` func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) { - // The Encoded field already contains the commit data without the signature - // (it was encoded using EncodeWithoutSignature in BuildCommitWithRef) fingerprint, err := signatures.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...) if err != nil { return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err) @@ -189,8 +187,6 @@ func (t *Tag) VerifyGPG(keyRings ...string) (string, error) { // VerifySSH verifies the SSH signature of the tag with the given authorized keys. // It returns the fingerprint of the key the signature was verified with, or an error. func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) { - // The Encoded field already contains the tag data without the signature - // (it was encoded using EncodeWithoutSignature in BuildCommitWithRef) fingerprint, err := signatures.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...) if err != nil { return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err) diff --git a/git/signatures/gpg_signature.go b/git/signatures/gpg_signature.go index 0abe02d8a..94c2ae2ac 100644 --- a/git/signatures/gpg_signature.go +++ b/git/signatures/gpg_signature.go @@ -25,7 +25,11 @@ import ( ) // PGPSignaturePrefix is the prefix used by Git to identify PGP signatures. -const PGPSignaturePrefix = "-----BEGIN PGP SIGNATURE-----" +// https://github.com/git/git/blob/7b2bccb0d58d4f24705bf985de1f4612e4cf06e5/gpg-interface.c#L56 +var PGPSignaturePrefix = []string{ + "-----BEGIN PGP SIGNATURE-----", + "-----BEGIN PGP MESSAGE-----", +} // VerifyPGPSignature verifies the PGP signature against the payload using // the provided key rings. It returns the fingerprint of the key that @@ -35,6 +39,14 @@ func VerifyPGPSignature(signature string, payload []byte, keyRings ...string) (s return "", fmt.Errorf("unable to verify payload as the provided signature is empty") } + if len(payload) == 0 { + return "", fmt.Errorf("unable to verify payload as the provided payload is empty") + } + + if !IsPGPSignature(signature) { + return "", fmt.Errorf("unable to verify openPGP signature, detected signature format: %s", GetSignatureType(signature)) + } + for _, r := range keyRings { reader := strings.NewReader(r) keyring, err := openpgp.ReadArmoredKeyRing(reader) diff --git a/git/signatures/gpg_signature_test.go b/git/signatures/gpg_signature_test.go index 9fa2996a5..d9fe97f99 100644 --- a/git/signatures/gpg_signature_test.go +++ b/git/signatures/gpg_signature_test.go @@ -157,6 +157,20 @@ func TestVerifyPGPSignature(t *testing.T) { keyRings: []string{armoredKeyRingFixture}, wantErr: "unable to verify payload as the provided signature is empty", }, + { + name: "Empty payload", + payload: []byte{}, + sig: signatureCommitFixture, + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload as the provided payload is empty", + }, + { + name: "Non-PGP signature", + payload: []byte(encodedCommitFixture), + sig: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify openPGP signature, detected signature format: ssh", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/git/signatures/signature.go b/git/signatures/signature.go index 04c487b6f..0c48f73c8 100644 --- a/git/signatures/signature.go +++ b/git/signatures/signature.go @@ -24,30 +24,50 @@ import ( type SignatureType string const ( - // SignatureTypePGP represents a PGP signature. - SignatureTypePGP SignatureType = "pgp" + // SignatureTypePGP represents a openPGP signature. + SignatureTypePGP SignatureType = "openpgp" // SignatureTypeSSH represents an SSH signature. SignatureTypeSSH SignatureType = "ssh" + // SignatureTypeX509 represents an x509 signature. + SignatureTypeX509 SignatureType = "x509" // SignatureTypeUnknown represents an unknown signature type. SignatureTypeUnknown SignatureType = "unknown" ) -// IsPGPSignature tests if the given signature is of type PGP. -// It returns true if the signature starts with the PGP signature prefix. -func IsPGPSignature(signature string) bool { +// Isx509Signature is the prefix used by Git to identify x509 signatures. +// https://github.com/git/git/blob/7b2bccb0d58d4f24705bf985de1f4612e4cf06e5/gpg-interface.c#L65 +var X509SignaturePrefix = []string{"-----BEGIN SIGNED MESSAGE-----"} + +func startsWithStrings(signature string, prefixList []string) bool { if signature == "" { return false } - return strings.HasPrefix(strings.TrimSpace(signature), PGPSignaturePrefix) + + for _, prefix := range prefixList { + if strings.HasPrefix(strings.TrimSpace(signature), prefix) { + return true + } + } + + return false +} + +// IsPGPSignature tests if the given signature is of type PGP. +// It returns true if the signature starts with the PGP signature prefix. +func IsPGPSignature(signature string) bool { + return startsWithStrings(signature, PGPSignaturePrefix) } // IsSSHSignature tests if the given signature is of type SSH. // It returns true if the signature starts with the SSH signature prefix. func IsSSHSignature(signature string) bool { - if signature == "" { - return false - } - return strings.HasPrefix(strings.TrimSpace(signature), SSHSignaturePrefix) + return startsWithStrings(signature, SSHSignaturePrefix) +} + +// Isx509Signature tests if the given signature is of type x509. +// It returns true if the signature starts with the x509 signature prefix. +func Isx509Signature(signature string) bool { + return startsWithStrings(signature, X509SignaturePrefix) } // GetSignatureType returns the type of the signature as a string. @@ -60,5 +80,8 @@ func GetSignatureType(signature string) string { if IsSSHSignature(signature) { return string(SignatureTypeSSH) } + if Isx509Signature(signature) { + return string(SignatureTypeX509) + } return string(SignatureTypeUnknown) } diff --git a/git/signatures/signature_test.go b/git/signatures/signature_test.go index baa48c5b4..a649960dd 100644 --- a/git/signatures/signature_test.go +++ b/git/signatures/signature_test.go @@ -38,6 +38,16 @@ func TestIsPGPSignature(t *testing.T) { signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", want: true, }, + { + name: "valid PGP signature", + signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + want: true, + }, + { + name: "PGP signature with leading whitespace", + signature: " -----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + want: true, + }, { name: "empty signature", signature: "", @@ -116,6 +126,58 @@ func TestIsSSHSignature(t *testing.T) { } } +func TestIsx509Signature(t *testing.T) { + tests := []struct { + name string + signature string + want bool + }{ + { + name: "valid x509 signature", + signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + want: true, + }, + { + name: "x509 signature with leading whitespace", + signature: " -----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + want: true, + }, + { + name: "empty signature", + signature: "", + want: false, + }, + { + name: "PGP signature", + signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + want: false, + }, + { + name: "SSH signature", + signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + want: false, + }, + { + name: "unknown signature", + signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + want: false, + }, + { + name: "whitespace only", + signature: " \n\t ", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Isx509Signature(tt.signature); got != tt.want { + t.Errorf("Isx509Signature() = %v, want %v", got, tt.want) + } + }) + } +} + func TestGetSignatureType(t *testing.T) { tests := []struct { name string @@ -142,6 +204,16 @@ func TestGetSignatureType(t *testing.T) { signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", want: string(SignatureTypeSSH), }, + { + name: "x509 signature", + signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + want: string(SignatureTypeX509), + }, + { + name: "x509 signature with leading whitespace", + signature: " -----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + want: string(SignatureTypeX509), + }, { name: "empty signature", signature: "", diff --git a/git/signatures/ssh_signature.go b/git/signatures/ssh_signature.go index c2fd9d2cc..e61f19ecb 100644 --- a/git/signatures/ssh_signature.go +++ b/git/signatures/ssh_signature.go @@ -28,7 +28,8 @@ import ( ) // SSHSignaturePrefix is the prefix used by Git to identify SSH signatures. -const SSHSignaturePrefix = "-----BEGIN SSH SIGNATURE-----" +// https://github.com/git/git/blob/7b2bccb0d58d4f24705bf985de1f4612e4cf06e5/gpg-interface.c#L71 +var SSHSignaturePrefix = []string{"-----BEGIN SSH SIGNATURE-----"} // ParseAuthorizedKeys parses the given authorized keys string and returns // a slice of public keys. It supports comments and empty lines. @@ -67,6 +68,10 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri return "", fmt.Errorf("unable to verify payload as the provided payload is empty") } + if !IsSSHSignature(signature) { + return "", fmt.Errorf("unable to verify SSH signature, detected signature format: %s", GetSignatureType(signature)) + } + // Unarmor the signature (remove PEM-like armor) sig, err := sshsig.Unarmor([]byte(signature)) if err != nil { diff --git a/git/signatures/ssh_signature_test.go b/git/signatures/ssh_signature_test.go index f364b4b09..326f2a83a 100644 --- a/git/signatures/ssh_signature_test.go +++ b/git/signatures/ssh_signature_test.go @@ -447,6 +447,9 @@ func TestVerifySSHSignature(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) } + if err != nil && err.Error() != "unable to verify payload as the provided signature is empty" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) + } }) t.Run("empty payload", func(t *testing.T) { @@ -474,6 +477,9 @@ func TestVerifySSHSignature(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) } + if err != nil && err.Error() != "unable to verify payload as the provided payload is empty" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) + } }) t.Run("wrong authorized keys", func(t *testing.T) { @@ -499,6 +505,38 @@ func TestVerifySSHSignature(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) } + // The error can be either a parsing error or a verification error + if err != nil && !strings.Contains(err.Error(), "unable to verify payload with any of the given authorized keys") && !strings.Contains(err.Error(), "unable to parse authorized key") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) + } + }) + + t.Run("empty authorized keys", func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + // Use empty authorized keys + emptyAuthKeys := "" + + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, emptyAuthKeys) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty authorized keys, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty authorized keys: %s", fingerprint) + } + if err != nil && err.Error() != "unable to verify payload with any of the given authorized keys" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload with any of the given authorized keys'", err) + } }) t.Run("invalid signature", func(t *testing.T) { @@ -528,6 +566,65 @@ func TestVerifySSHSignature(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) } + if err != nil && !strings.Contains(err.Error(), "unable to unarmor SSH signature") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) + } + }) + + t.Run("non-SSH signature", func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + // Use a PGP signature instead of SSH signature + pgpSig := "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----" + + fingerprint, err := signatures.VerifySSHSignature(pgpSig, gitCommit.Encoded, "") + if err == nil { + t.Errorf("VerifySSHSignature() expected error for non-SSH signature, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for non-SSH signature: %s", fingerprint) + } + if err != nil && err.Error() != "unable to verify SSH signature, detected signature format: openpgp" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify SSH signature, detected signature format: openpgp'", err) + } + }) + + t.Run("invalid authorized keys", func(t *testing.T) { + // Parse the commit from the fixture file + commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) + } + + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } + + // Use invalid authorized keys + invalidAuthKeys := "invalid-key-data" + + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, invalidAuthKeys) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for invalid authorized keys, got nil") + } + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for invalid authorized keys: %s", fingerprint) + } + if err != nil && !strings.Contains(err.Error(), "unable to parse authorized key") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to parse authorized key'", err) + } }) } @@ -1070,6 +1167,9 @@ func TestVerifySSHSignatureForTags(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) } + if err != nil && err.Error() != "unable to verify payload as the provided signature is empty" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) + } }) t.Run("empty payload", func(t *testing.T) { @@ -1097,6 +1197,9 @@ func TestVerifySSHSignatureForTags(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) } + if err != nil && err.Error() != "unable to verify payload as the provided payload is empty" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) + } }) t.Run("wrong authorized keys", func(t *testing.T) { @@ -1122,6 +1225,10 @@ func TestVerifySSHSignatureForTags(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) } + // The error can be either a parsing error or a verification error + if err != nil && !strings.Contains(err.Error(), "unable to verify payload with any of the given authorized keys") && !strings.Contains(err.Error(), "unable to parse authorized key") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) + } }) t.Run("invalid signature", func(t *testing.T) { @@ -1151,6 +1258,9 @@ func TestVerifySSHSignatureForTags(t *testing.T) { if fingerprint != "" { t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) } + if err != nil && !strings.Contains(err.Error(), "unable to unarmor SSH signature") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) + } }) } From 9d568c76113035b315e722ca6de04dfa100e4cdb Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Fri, 27 Feb 2026 23:31:11 +0100 Subject: [PATCH 04/22] adds detection for added signature prefixes Signed-off-by: Ricardo Bartels --- git/git_test.go | 82 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/git/git_test.go b/git/git_test.go index 8fb93fd06..ba9a412e5 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -260,12 +260,19 @@ func TestCommit_IsPGPSigned(t *testing.T) { want bool }{ { - name: "PGP signed commit", + name: "PGP signed commit with SIGNATURE prefix", commit: &Commit{ Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", }, want: true, }, + { + name: "PGP signed commit with MESSAGE prefix", + commit: &Commit{ + Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + }, + want: true, + }, { name: "SSH signed commit", commit: &Commit{ @@ -273,6 +280,13 @@ func TestCommit_IsPGPSigned(t *testing.T) { }, want: false, }, + { + name: "X509 signed commit", + commit: &Commit{ + Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + }, + want: false, + }, { name: "unsigned commit", commit: &Commit{}, @@ -314,6 +328,13 @@ func TestCommit_IsSSHSigned(t *testing.T) { }, want: false, }, + { + name: "X509 signed commit", + commit: &Commit{ + Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + }, + want: false, + }, { name: "unsigned commit", commit: &Commit{}, @@ -342,11 +363,18 @@ func TestCommit_SignatureType(t *testing.T) { want string }{ { - name: "PGP signed commit", + name: "PGP signed commit with SIGNATURE prefix", commit: &Commit{ Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", }, - want: "pgp", + want: "openpgp", + }, + { + name: "PGP signed commit with MESSAGE prefix", + commit: &Commit{ + Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + }, + want: "openpgp", }, { name: "SSH signed commit", @@ -355,6 +383,13 @@ func TestCommit_SignatureType(t *testing.T) { }, want: "ssh", }, + { + name: "X509 signed commit", + commit: &Commit{ + Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + }, + want: "x509", + }, { name: "unsigned commit", commit: &Commit{}, @@ -383,12 +418,19 @@ func TestTag_IsPGPSigned(t *testing.T) { want bool }{ { - name: "PGP signed tag", + name: "PGP signed tag with SIGNATURE prefix", tag: &Tag{ Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", }, want: true, }, + { + name: "PGP signed tag with MESSAGE prefix", + tag: &Tag{ + Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + }, + want: true, + }, { name: "SSH signed tag", tag: &Tag{ @@ -396,6 +438,13 @@ func TestTag_IsPGPSigned(t *testing.T) { }, want: false, }, + { + name: "X509 signed tag", + tag: &Tag{ + Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + }, + want: false, + }, { name: "unsigned tag", tag: &Tag{}, @@ -437,6 +486,13 @@ func TestTag_IsSSHSigned(t *testing.T) { }, want: false, }, + { + name: "X509 signed tag", + tag: &Tag{ + Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + }, + want: false, + }, { name: "unsigned tag", tag: &Tag{}, @@ -465,11 +521,18 @@ func TestTag_SignatureType(t *testing.T) { want string }{ { - name: "PGP signed tag", + name: "PGP signed tag with SIGNATURE prefix", tag: &Tag{ Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", }, - want: "pgp", + want: "openpgp", + }, + { + name: "PGP signed tag with MESSAGE prefix", + tag: &Tag{ + Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + }, + want: "openpgp", }, { name: "SSH signed tag", @@ -478,6 +541,13 @@ func TestTag_SignatureType(t *testing.T) { }, want: "ssh", }, + { + name: "X509 signed tag", + tag: &Tag{ + Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + }, + want: "x509", + }, { name: "unsigned tag", tag: &Tag{}, From cb861567de38969f54b9e1b9a7024039ec8a37e5 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Fri, 27 Feb 2026 23:54:26 +0100 Subject: [PATCH 05/22] adds tests for git.go Signed-off-by: Ricardo Bartels --- git/git.go | 2 + git/git_test.go | 680 ++++++++++++++---- git/internal/e2e/go.mod | 1 + git/internal/e2e/go.sum | 2 + git/signatures/gpg_signature_test.go | 11 +- git/signatures/ssh_signature_test.go | 45 +- .../gpg_signatures/generate_gpg_fixtures.sh | 10 +- .../ssh_signatures/calc_fingerprints.go | 35 - .../fixtures.go} | 10 +- tests/integration/go.mod | 1 + tests/integration/go.sum | 2 + 11 files changed, 576 insertions(+), 223 deletions(-) delete mode 100644 git/signatures/testdata/ssh_signatures/calc_fingerprints.go rename git/{signatures/testutils_test.go => testutils/fixtures.go} (84%) diff --git a/git/git.go b/git/git.go index ce9b1a2c7..1d53cd33d 100644 --- a/git/git.go +++ b/git/git.go @@ -112,6 +112,7 @@ func (c *Commit) AbsoluteReference() string { return c.Hash.Digest() } +// Deprecated: Verify is deprecated, use VerifySSH or VerifyGPG // wrapper function to ensure backwards compatibility func (c *Commit) Verify(keyRings ...string) (string, error) { return c.VerifyGPG(keyRings...) @@ -168,6 +169,7 @@ type Tag struct { Message string } +// Deprecated: Verify is deprecated, use VerifySSH or VerifyGPG // wrapper function to ensure backwards compatibility func (t *Tag) Verify(keyRings ...string) (string, error) { return t.VerifyGPG(keyRings...) diff --git a/git/git_test.go b/git/git_test.go index ba9a412e5..d1869b520 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -17,12 +17,27 @@ limitations under the License. package git import ( + "io" + "os" + "path/filepath" "testing" "time" + "github.com/fluxcd/pkg/git/testutils" + "github.com/go-git/go-git/v5/plumbing" . "github.com/onsi/gomega" ) +const ( + signaturePGPSignature = "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----" + signaturePGPMessage = "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----" + signatureSSH = "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----" + signatureX509 = "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----" + signatureUnknown = "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----" + signaturePGPSignatureWithLeadingWhitespace = " " + signaturePGPSignature + signatureSSHWithLeadingWhitespace = " " + signatureSSH +) + func TestHash_Algorithm(t *testing.T) { tests := []struct { name string @@ -253,152 +268,297 @@ func TestIsConcreteCommit(t *testing.T) { } } -func TestCommit_IsPGPSigned(t *testing.T) { +func TestIsAnnotatedTag(t *testing.T) { tests := []struct { name string - commit *Commit - want bool + tag Tag + result bool }{ { - name: "PGP signed commit with SIGNATURE prefix", - commit: &Commit{ - Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + name: "annotated tag", + tag: Tag{ + Hash: Hash("foo"), + Name: "v1.0.0", + Encoded: []byte("tag-content"), }, - want: true, + result: true, }, { - name: "PGP signed commit with MESSAGE prefix", - commit: &Commit{ - Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + name: "lightweight tag", + tag: Tag{ + Hash: Hash("foo"), + Name: "v1.0.0", }, - want: true, + result: false, }, { - name: "SSH signed commit", - commit: &Commit{ - Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + name: "empty encoded", + tag: Tag{ + Hash: Hash("foo"), + Name: "v1.0.0", + Encoded: []byte{}, }, - want: false, + result: false, }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(IsAnnotatedTag(tt.tag)).To(Equal(tt.result)) + }) + } +} + +func TestIsSignedTag(t *testing.T) { + tests := []struct { + name string + tag Tag + result bool + }{ { - name: "X509 signed commit", - commit: &Commit{ - Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + name: "signed tag", + tag: Tag{ + Hash: Hash("foo"), + Name: "v1.0.0", + Signature: signaturePGPSignature, }, - want: false, + result: true, }, { - name: "unsigned commit", - commit: &Commit{}, - want: false, + name: "unsigned tag", + tag: Tag{ + Hash: Hash("foo"), + Name: "v1.0.0", + }, + result: false, }, { - name: "PGP signed commit with leading whitespace", - commit: &Commit{ - Signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + name: "empty signature", + tag: Tag{ + Hash: Hash("foo"), + Name: "v1.0.0", + Signature: "", }, - want: true, + result: false, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - g.Expect(tt.commit.IsPGPSigned()).To(Equal(tt.want)) + g.Expect(IsSignedTag(tt.tag)).To(Equal(tt.result)) }) } } -func TestCommit_IsSSHSigned(t *testing.T) { +func TestTag_String(t *testing.T) { tests := []struct { - name string - commit *Commit - want bool + name string + tag *Tag + want string }{ { - name: "SSH signed commit", + name: "annotated tag with hash", + tag: &Tag{ + Hash: Hash("5394cb7f48332b2de7c17dd8b8384bbc84b7e738"), + Name: "v1.0.0", + }, + want: "v1.0.0@5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }, + { + name: "lightweight tag without hash", + tag: &Tag{ + Name: "v1.0.0", + }, + want: "v1.0.0", + }, + { + name: "tag with empty hash", + tag: &Tag{ + Hash: Hash(""), + Name: "v2.0.0", + }, + want: "v2.0.0", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(tt.tag.String()).To(Equal(tt.want)) + }) + } +} + +func TestIsSigned(t *testing.T) { + tests := []struct { + name string + commit *Commit + tag *Tag + wantPGPCommit bool + wantSSHCommit bool + wantPGPTag bool + wantSSHTag bool + }{ + { + name: "PGP signed with SIGNATURE prefix", + commit: &Commit{ + Signature: signaturePGPSignature, + }, + tag: &Tag{ + Signature: signaturePGPSignature, + }, + wantPGPCommit: true, + wantSSHCommit: false, + wantPGPTag: true, + wantSSHTag: false, + }, + { + name: "PGP signed with MESSAGE prefix", commit: &Commit{ - Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + Signature: signaturePGPMessage, + }, + tag: &Tag{ + Signature: signaturePGPMessage, }, - want: true, + wantPGPCommit: true, + wantSSHCommit: false, + wantPGPTag: true, + wantSSHTag: false, }, { - name: "PGP signed commit", + name: "SSH signed", commit: &Commit{ - Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + Signature: signatureSSH, }, - want: false, + tag: &Tag{ + Signature: signatureSSH, + }, + wantPGPCommit: false, + wantSSHCommit: true, + wantPGPTag: false, + wantSSHTag: true, }, { - name: "X509 signed commit", + name: "X509 signed", commit: &Commit{ - Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + Signature: signatureX509, }, - want: false, + tag: &Tag{ + Signature: signatureX509, + }, + wantPGPCommit: false, + wantSSHCommit: false, + wantPGPTag: false, + wantSSHTag: false, }, { - name: "unsigned commit", - commit: &Commit{}, - want: false, + name: "unsigned", + commit: &Commit{}, + tag: &Tag{}, + wantPGPCommit: false, + wantSSHCommit: false, + wantPGPTag: false, + wantSSHTag: false, }, { - name: "SSH signed commit with leading whitespace", + name: "PGP signed with leading whitespace", commit: &Commit{ - Signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + Signature: signaturePGPSignatureWithLeadingWhitespace, + }, + tag: &Tag{ + Signature: signaturePGPSignatureWithLeadingWhitespace, }, - want: true, + wantPGPCommit: true, + wantSSHCommit: false, + wantPGPTag: true, + wantSSHTag: false, + }, + { + name: "SSH signed with leading whitespace", + commit: &Commit{ + Signature: signatureSSHWithLeadingWhitespace, + }, + tag: &Tag{ + Signature: signatureSSHWithLeadingWhitespace, + }, + wantPGPCommit: false, + wantSSHCommit: true, + wantPGPTag: false, + wantSSHTag: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - g.Expect(tt.commit.IsSSHSigned()).To(Equal(tt.want)) + g.Expect(tt.commit.IsPGPSigned()).To(Equal(tt.wantPGPCommit)) + g.Expect(tt.commit.IsSSHSigned()).To(Equal(tt.wantSSHCommit)) + g.Expect(tt.tag.IsPGPSigned()).To(Equal(tt.wantPGPTag)) + g.Expect(tt.tag.IsSSHSigned()).To(Equal(tt.wantSSHTag)) }) } } -func TestCommit_SignatureType(t *testing.T) { +func TestSignatureType(t *testing.T) { tests := []struct { name string commit *Commit + tag *Tag want string }{ { - name: "PGP signed commit with SIGNATURE prefix", + name: "PGP signed with SIGNATURE prefix", commit: &Commit{ - Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + Signature: signaturePGPSignature, + }, + tag: &Tag{ + Signature: signaturePGPSignature, }, want: "openpgp", }, { - name: "PGP signed commit with MESSAGE prefix", + name: "PGP signed with MESSAGE prefix", commit: &Commit{ - Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", + Signature: signaturePGPMessage, + }, + tag: &Tag{ + Signature: signaturePGPMessage, }, want: "openpgp", }, { - name: "SSH signed commit", + name: "SSH signed", commit: &Commit{ - Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", + Signature: signatureSSH, + }, + tag: &Tag{ + Signature: signatureSSH, }, want: "ssh", }, { - name: "X509 signed commit", + name: "X509 signed", commit: &Commit{ - Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + Signature: signatureX509, + }, + tag: &Tag{ + Signature: signatureX509, }, want: "x509", }, { - name: "unsigned commit", + name: "unsigned", commit: &Commit{}, + tag: &Tag{}, want: "unknown", }, { name: "unknown signature type", commit: &Commit{ - Signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + Signature: signatureUnknown, + }, + tag: &Tag{ + Signature: signatureUnknown, }, want: "unknown", }, @@ -407,164 +567,380 @@ func TestCommit_SignatureType(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) g.Expect(tt.commit.SignatureType()).To(Equal(tt.want)) + g.Expect(tt.tag.SignatureType()).To(Equal(tt.want)) }) } } -func TestTag_IsPGPSigned(t *testing.T) { +func TestCommit_VerifyGPG(t *testing.T) { + testDataDir := filepath.Join("signatures", "testdata", "gpg_signatures") + tests := []struct { - name string - tag *Tag - want bool + name string + sigFile string + keyFile string + wantErr string }{ { - name: "PGP signed tag with SIGNATURE prefix", - tag: &Tag{ - Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", - }, - want: true, + name: "valid PGP signature", + sigFile: "commit_rsa_2048_signed.txt", + keyFile: "key_rsa_2048.pub", }, { - name: "PGP signed tag with MESSAGE prefix", - tag: &Tag{ - Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", - }, - want: true, + name: "missing signature", + sigFile: "commit_unsigned.txt", + keyFile: "key_rsa_2048.pub", + wantErr: "unable to verify Git commit: unable to verify payload as the provided signature is empty", }, { - name: "SSH signed tag", - tag: &Tag{ - Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", - }, - want: false, + name: "invalid signature", + sigFile: "commit_rsa_2048_signed.txt", + keyFile: "key_ed25519.pub", + wantErr: "unable to verify Git commit: unable to verify payload with any of the given key rings", }, { - name: "X509 signed tag", - tag: &Tag{ - Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", - }, - want: false, + name: "no key rings provided", + sigFile: "commit_rsa_2048_signed.txt", + wantErr: "unable to verify Git commit: unable to verify payload with any of the given key rings", }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + // Parse the commit from the fixture file + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, tt.sigFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Create a git.Commit from the parsed object + encoded := &plumbing.MemoryObject{} + err = commitObj.EncodeWithoutSignature(encoded) + g.Expect(err).ToNot(HaveOccurred()) + reader, err := encoded.Reader() + g.Expect(err).ToNot(HaveOccurred()) + b, err := io.ReadAll(reader) + g.Expect(err).ToNot(HaveOccurred()) + + gitCommit := &Commit{ + Signature: commitObj.PGPSignature, + Encoded: b, + } + + // Prepare key rings + var keyRings []string + if tt.keyFile != "" { + publicKey, err := os.ReadFile(filepath.Join(testDataDir, tt.keyFile)) + g.Expect(err).ToNot(HaveOccurred()) + keyRings = append(keyRings, string(publicKey)) + } + + // get result from deprecated function + depFingerprint, depErr := gitCommit.Verify(keyRings...) + + // Verify the signature using the git.Commit's VerifyGPG method + fingerprint, err := gitCommit.VerifyGPG(keyRings...) + + g.Expect(fingerprint).To(ContainSubstring(depFingerprint)) + if err == nil { + g.Expect(depErr).ToNot(HaveOccurred()) + } else { + g.Expect(err.Error()).To(ContainSubstring(depErr.Error())) + } + + if tt.wantErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + g.Expect(fingerprint).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) + }) + } +} + +func TestTag_VerifyGPG(t *testing.T) { + testDataDir := filepath.Join("signatures", "testdata", "gpg_signatures") + + tests := []struct { + name string + sigFile string + keyFile string + wantErr string + }{ { - name: "unsigned tag", - tag: &Tag{}, - want: false, + name: "valid PGP signature", + sigFile: "tag_rsa_2048_signed.txt", + keyFile: "key_rsa_2048.pub", }, { - name: "PGP signed tag with leading whitespace", - tag: &Tag{ - Signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", - }, - want: true, + name: "missing signature", + sigFile: "commit_unsigned.txt", + keyFile: "key_rsa_2048.pub", + wantErr: "unable to verify Git tag: unable to verify payload as the provided signature is empty", + }, + { + name: "invalid signature", + sigFile: "tag_rsa_2048_signed.txt", + keyFile: "key_ed25519.pub", + wantErr: "unable to verify Git tag: unable to verify payload with any of the given key rings", + }, + { + name: "no key rings provided", + sigFile: "tag_rsa_2048_signed.txt", + wantErr: "unable to verify Git tag: unable to verify payload with any of the given key rings", }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - g.Expect(tt.tag.IsPGPSigned()).To(Equal(tt.want)) + + // Parse the tag from the fixture file + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, tt.sigFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Create a git.Tag from the parsed object + encoded := &plumbing.MemoryObject{} + err = tagObj.EncodeWithoutSignature(encoded) + g.Expect(err).ToNot(HaveOccurred()) + reader, err := encoded.Reader() + g.Expect(err).ToNot(HaveOccurred()) + b, err := io.ReadAll(reader) + g.Expect(err).ToNot(HaveOccurred()) + + gitTag := &Tag{ + Signature: tagObj.PGPSignature, + Encoded: b, + } + + // Prepare key rings + var keyRings []string + if tt.keyFile != "" { + publicKey, err := os.ReadFile(filepath.Join(testDataDir, tt.keyFile)) + g.Expect(err).ToNot(HaveOccurred()) + keyRings = append(keyRings, string(publicKey)) + } + + // get result from deprecated function + depFingerprint, depErr := gitTag.Verify(keyRings...) + + // Verify the signature using the git.Tag's VerifyGPG method + fingerprint, err := gitTag.VerifyGPG(keyRings...) + + g.Expect(fingerprint).To(ContainSubstring(depFingerprint)) + if err == nil { + g.Expect(depErr).ToNot(HaveOccurred()) + } else { + g.Expect(err.Error()).To(ContainSubstring(depErr.Error())) + } + + if tt.wantErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + g.Expect(fingerprint).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) }) } } -func TestTag_IsSSHSigned(t *testing.T) { +func TestCommit_VerifySSH(t *testing.T) { + testDataDir := filepath.Join("signatures", "testdata", "ssh_signatures") + tests := []struct { - name string - tag *Tag - want bool + name string + sigFile string + authorizedKeys string + wantErr string }{ { - name: "SSH signed tag", - tag: &Tag{ - Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", - }, - want: true, + name: "valid SSH signature", + sigFile: "commit_rsa_signed.txt", + authorizedKeys: "authorized_keys_rsa", }, { - name: "PGP signed tag", - tag: &Tag{ - Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", - }, - want: false, + name: "missing signature", + sigFile: "commit_unsigned.txt", + authorizedKeys: "authorized_keys_rsa", + wantErr: "unable to verify Git commit SSH signature: unable to verify payload as the provided signature is empty", }, { - name: "X509 signed tag", - tag: &Tag{ - Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", - }, - want: false, + name: "invalid signature", + sigFile: "commit_rsa_signed.txt", + authorizedKeys: "authorized_keys_ed25519", + wantErr: "unable to verify Git commit SSH signature: unable to verify payload with any of the given authorized keys", }, { - name: "unsigned tag", - tag: &Tag{}, - want: false, - }, - { - name: "SSH signed tag with leading whitespace", - tag: &Tag{ - Signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", - }, - want: true, + name: "no authorized keys provided", + sigFile: "commit_rsa_signed.txt", + wantErr: "unable to verify Git commit SSH signature: unable to verify payload with any of the given authorized keys", }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - g.Expect(tt.tag.IsSSHSigned()).To(Equal(tt.want)) + + // Parse the commit from the fixture file + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, tt.sigFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Create a git.Commit from the parsed object + encoded := &plumbing.MemoryObject{} + err = commitObj.EncodeWithoutSignature(encoded) + g.Expect(err).ToNot(HaveOccurred()) + reader, err := encoded.Reader() + g.Expect(err).ToNot(HaveOccurred()) + b, err := io.ReadAll(reader) + g.Expect(err).ToNot(HaveOccurred()) + + gitCommit := &Commit{ + Signature: commitObj.PGPSignature, + Encoded: b, + } + + // Prepare authorized keys + var authorizedKeys []string + if tt.authorizedKeys != "" { + authorizedKey, err := os.ReadFile(filepath.Join(testDataDir, tt.authorizedKeys)) + g.Expect(err).ToNot(HaveOccurred()) + authorizedKeys = append(authorizedKeys, string(authorizedKey)) + } + + // Verify the signature using the git.Commit's VerifySSH method + fingerprint, err := gitCommit.VerifySSH(authorizedKeys...) + if tt.wantErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + g.Expect(fingerprint).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) }) } } -func TestTag_SignatureType(t *testing.T) { +func TestTag_VerifySSH(t *testing.T) { + testDataDir := filepath.Join("signatures", "testdata", "ssh_signatures") + tests := []struct { - name string - tag *Tag - want string + name string + sigFile string + authorizedKeys string + wantErr string }{ { - name: "PGP signed tag with SIGNATURE prefix", - tag: &Tag{ - Signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", - }, - want: "openpgp", + name: "valid SSH signature", + sigFile: "tag_rsa_signed.txt", + authorizedKeys: "authorized_keys_rsa", }, { - name: "PGP signed tag with MESSAGE prefix", - tag: &Tag{ - Signature: "-----BEGIN PGP MESSAGE-----\n-----END PGP MESSAGE-----", - }, - want: "openpgp", + name: "missing signature", + sigFile: "commit_unsigned.txt", + authorizedKeys: "authorized_keys_rsa", + wantErr: "unable to verify Git tag SSH signature: unable to verify payload as the provided signature is empty", }, { - name: "SSH signed tag", - tag: &Tag{ - Signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", - }, - want: "ssh", + name: "invalid signature", + sigFile: "tag_rsa_signed.txt", + authorizedKeys: "authorized_keys_ed25519", + wantErr: "unable to verify Git tag SSH signature: unable to verify payload with any of the given authorized keys", }, { - name: "X509 signed tag", - tag: &Tag{ - Signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", + name: "no authorized keys provided", + sigFile: "tag_rsa_signed.txt", + wantErr: "unable to verify Git tag SSH signature: unable to verify payload with any of the given authorized keys", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + // Parse the tag from the fixture file + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, tt.sigFile)) + g.Expect(err).ToNot(HaveOccurred()) + + // Create a git.Tag from the parsed object + encoded := &plumbing.MemoryObject{} + err = tagObj.EncodeWithoutSignature(encoded) + g.Expect(err).ToNot(HaveOccurred()) + reader, err := encoded.Reader() + g.Expect(err).ToNot(HaveOccurred()) + b, err := io.ReadAll(reader) + g.Expect(err).ToNot(HaveOccurred()) + + gitTag := &Tag{ + Signature: tagObj.PGPSignature, + Encoded: b, + } + + // Prepare authorized keys + var authorizedKeys []string + if tt.authorizedKeys != "" { + authorizedKey, err := os.ReadFile(filepath.Join(testDataDir, tt.authorizedKeys)) + g.Expect(err).ToNot(HaveOccurred()) + authorizedKeys = append(authorizedKeys, string(authorizedKey)) + } + + // Verify the signature using the git.Tag's VerifySSH method + fingerprint, err := gitTag.VerifySSH(authorizedKeys...) + if tt.wantErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + g.Expect(fingerprint).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) + }) + } +} + +func TestErrRepositoryNotFound_Error(t *testing.T) { + tests := []struct { + name string + err ErrRepositoryNotFound + want string + }{ + { + name: "with message and URL", + err: ErrRepositoryNotFound{ + Message: "repository not found", + URL: "https://github.com/example/repo.git", }, - want: "x509", + want: "repository not found: git repository: 'https://github.com/example/repo.git'", }, { - name: "unsigned tag", - tag: &Tag{}, - want: "unknown", + name: "with empty message", + err: ErrRepositoryNotFound{ + Message: "", + URL: "https://github.com/example/repo.git", + }, + want: ": git repository: 'https://github.com/example/repo.git'", }, { - name: "unknown signature type", - tag: &Tag{ - Signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", + name: "with empty URL", + err: ErrRepositoryNotFound{ + Message: "repository not found", + URL: "", }, - want: "unknown", + want: "repository not found: git repository: ''", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - g.Expect(tt.tag.SignatureType()).To(Equal(tt.want)) + g.Expect(tt.err.Error()).To(Equal(tt.want)) }) } } diff --git a/git/internal/e2e/go.mod b/git/internal/e2e/go.mod index f6406c7a6..dfc9450b3 100644 --- a/git/internal/e2e/go.mod +++ b/git/internal/e2e/go.mod @@ -49,6 +49,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hiddeco/sshsig v0.2.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect diff --git a/git/internal/e2e/go.sum b/git/internal/e2e/go.sum index 33e7a1e6a..edd773f33 100644 --- a/git/internal/e2e/go.sum +++ b/git/internal/e2e/go.sum @@ -77,6 +77,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hiddeco/sshsig v0.2.0 h1:gMWllgKCITXdydVkDL+Zro0PU96QI55LwUwebSwNTSw= +github.com/hiddeco/sshsig v0.2.0/go.mod h1:nJc98aGgiH6Yql2doqH4CTBVHexQA40Q+hMMLHP4EqE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= diff --git a/git/signatures/gpg_signature_test.go b/git/signatures/gpg_signature_test.go index d9fe97f99..b5a86617e 100644 --- a/git/signatures/gpg_signature_test.go +++ b/git/signatures/gpg_signature_test.go @@ -23,6 +23,7 @@ import ( "github.com/fluxcd/pkg/git/gogit" "github.com/fluxcd/pkg/git/signatures" + "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" . "github.com/onsi/gomega" ) @@ -223,7 +224,7 @@ func TestVerifyPGPSignatureWithFixturesForTags(t *testing.T) { g := NewWithT(t) // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) g.Expect(err).ToNot(HaveOccurred()) // Build a git.Tag using BuildTag @@ -281,7 +282,7 @@ func TestVerifyPGPSignatureWithFixtures(t *testing.T) { g := NewWithT(t) // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) g.Expect(err).ToNot(HaveOccurred()) // Build a git.Commit using BuildCommitWithRef @@ -310,7 +311,7 @@ func TestVerifyPGPSignatureWithFixtures(t *testing.T) { g := NewWithT(t) // Parse the unsigned commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) g.Expect(err).ToNot(HaveOccurred()) // Build a git.Commit using BuildCommitWithRef @@ -377,7 +378,7 @@ func TestVerifyPGPSignatureWithMultipleKeyRing(t *testing.T) { g := NewWithT(t) // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) g.Expect(err).ToNot(HaveOccurred()) // Build a git.Commit using BuildCommitWithRef @@ -402,7 +403,7 @@ func TestVerifyPGPSignatureWithMultipleKeyRing(t *testing.T) { g := NewWithT(t) // Parse the unsigned commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) g.Expect(err).ToNot(HaveOccurred()) // Build a git.Commit using BuildCommitWithRef diff --git a/git/signatures/ssh_signature_test.go b/git/signatures/ssh_signature_test.go index 326f2a83a..82c2f1320 100644 --- a/git/signatures/ssh_signature_test.go +++ b/git/signatures/ssh_signature_test.go @@ -24,6 +24,7 @@ import ( "github.com/fluxcd/pkg/git/gogit" "github.com/fluxcd/pkg/git/signatures" + "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" "github.com/hiddeco/sshsig" ) @@ -389,7 +390,7 @@ func TestVerifySSHSignature(t *testing.T) { for _, kt := range keyTypes { t.Run(kt.name, func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -429,7 +430,7 @@ func TestVerifySSHSignature(t *testing.T) { } // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -459,7 +460,7 @@ func TestVerifySSHSignature(t *testing.T) { } // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -484,7 +485,7 @@ func TestVerifySSHSignature(t *testing.T) { t.Run("wrong authorized keys", func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -513,7 +514,7 @@ func TestVerifySSHSignature(t *testing.T) { t.Run("empty authorized keys", func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -546,7 +547,7 @@ func TestVerifySSHSignature(t *testing.T) { } // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -573,7 +574,7 @@ func TestVerifySSHSignature(t *testing.T) { t.Run("non-SSH signature", func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -601,7 +602,7 @@ func TestVerifySSHSignature(t *testing.T) { t.Run("invalid authorized keys", func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -648,7 +649,7 @@ func TestVerifySSHSignatureAllKeyTypes(t *testing.T) { for _, kt := range keyTypes { t.Run(kt.name, func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -706,7 +707,7 @@ func TestVerifySSHSignatureCombinedKeys(t *testing.T) { for _, kt := range keyTypes { t.Run(kt.name, func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -783,7 +784,7 @@ func TestBuildCommitWithRefFromFixture(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -894,7 +895,7 @@ func TestBuildCommitWithRefAndVerifySSH(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Parse the commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -932,7 +933,7 @@ func TestBuildCommitWithRefWithDifferentRefs(t *testing.T) { testDataDir := filepath.Join("testdata", "ssh_signatures") // Parse a signed commit from the fixture file - commitObj, err := parseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } @@ -1027,7 +1028,7 @@ func TestBuildTagFromFixture(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1109,7 +1110,7 @@ func TestVerifySSHSignatureForTags(t *testing.T) { for _, kt := range keyTypes { t.Run(kt.name, func(t *testing.T) { // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1149,7 +1150,7 @@ func TestVerifySSHSignatureForTags(t *testing.T) { } // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1179,7 +1180,7 @@ func TestVerifySSHSignatureForTags(t *testing.T) { } // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1204,7 +1205,7 @@ func TestVerifySSHSignatureForTags(t *testing.T) { t.Run("wrong authorized keys", func(t *testing.T) { // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1238,7 +1239,7 @@ func TestVerifySSHSignatureForTags(t *testing.T) { } // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1284,7 +1285,7 @@ func TestVerifySSHSignatureForTagsAllKeyTypes(t *testing.T) { for _, kt := range keyTypes { t.Run(kt.name, func(t *testing.T) { // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1342,7 +1343,7 @@ func TestVerifySSHSignatureForTagsCombinedKeys(t *testing.T) { for _, kt := range keyTypes { t.Run(kt.name, func(t *testing.T) { // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -1413,7 +1414,7 @@ func TestBuildTagAndVerifySSH(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Parse the tag from the fixture file - tagObj, err := parseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } diff --git a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh index 05eaa6a56..66de6958e 100755 --- a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh +++ b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh @@ -62,7 +62,8 @@ EOF gpg --batch --generate-key "$batch_file" 2>&1 # Get the key ID - local key_id=$(gpg --list-keys --with-colons "test-${key_name}@example.com" | grep '^fpr' | head -1 | cut -d: -f10) + local key_id + key_id=$(gpg --list-keys --with-colons "test-${key_name}@example.com" | grep '^fpr' | head -1 | cut -d: -f10) echo " Key ID: $key_id" @@ -88,7 +89,8 @@ create_signed_object() { echo "Creating signed $object_type for $key_name..." # Get key ID - local key_id=$(cat "$TEMP_DIR/${key_name}_id.txt") + local key_id + key_id=$(cat "$TEMP_DIR/${key_name}_id.txt") # Create temporary Git repository local repo_dir="$TEMP_DIR/repo_${key_name}_${object_type}" @@ -201,10 +203,10 @@ main() { echo "----------------------------------------" # Get list of successfully generated keys - local keys=() + local keys=() key_name="" for key_file in "$TEMP_DIR"/*_id.txt; do if [[ -f "$key_file" ]]; then - local key_name=$(basename "$key_file" "_id.txt") + key_name=$(basename "$key_file" "_id.txt") keys+=("$key_name") fi done diff --git a/git/signatures/testdata/ssh_signatures/calc_fingerprints.go b/git/signatures/testdata/ssh_signatures/calc_fingerprints.go deleted file mode 100644 index 2cd8bf8d6..000000000 --- a/git/signatures/testdata/ssh_signatures/calc_fingerprints.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "crypto/sha256" - "encoding/base64" - "fmt" - "strings" - - gossh "golang.org/x/crypto/ssh" -) - -func main() { - keys := []struct { - name string - key string - }{ - {"test_key", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com"}, - {"ed25519", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com"}, - {"rsa", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com"}, - {"ecdsa_p256", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com"}, - {"ecdsa_p384", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com"}, - {"ecdsa_p521", "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com"}, - } - - for _, k := range keys { - pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(k.key)) - if err != nil { - fmt.Printf("Error parsing %s: %v\n", k.name, err) - continue - } - hash := sha256.Sum256(pubKey.Marshal()) - fingerprint := "SHA256:" + strings.TrimSuffix(base64.StdEncoding.EncodeToString(hash[:]), "=") - fmt.Printf("%s: %s\n", k.name, fingerprint) - } -} diff --git a/git/signatures/testutils_test.go b/git/testutils/fixtures.go similarity index 84% rename from git/signatures/testutils_test.go rename to git/testutils/fixtures.go index 7088f741d..da5327f20 100644 --- a/git/signatures/testutils_test.go +++ b/git/testutils/fixtures.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signatures_test +package testutils import ( "os" @@ -23,8 +23,8 @@ import ( "github.com/go-git/go-git/v5/plumbing/object" ) -// parseCommitFromFixture parses a git commit object from a fixture file -func parseCommitFromFixture(fixturePath string) (*object.Commit, error) { +// ParseCommitFromFixture parses a git commit object from a fixture file +func ParseCommitFromFixture(fixturePath string) (*object.Commit, error) { data, err := os.ReadFile(fixturePath) if err != nil { return nil, err @@ -46,8 +46,8 @@ func parseCommitFromFixture(fixturePath string) (*object.Commit, error) { return commit, nil } -// parseTagFromFixture parses a git tag object from a fixture file -func parseTagFromFixture(fixturePath string) (*object.Tag, error) { +// ParseTagFromFixture parses a git tag object from a fixture file +func ParseTagFromFixture(fixturePath string) (*object.Tag, error) { data, err := os.ReadFile(fixturePath) if err != nil { return nil, err diff --git a/tests/integration/go.mod b/tests/integration/go.mod index f4318f89a..fa54016ba 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -115,6 +115,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hc-install v0.9.2 // indirect + github.com/hiddeco/sshsig v0.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/tests/integration/go.sum b/tests/integration/go.sum index 4e4172e7a..92d4d6998 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -213,6 +213,8 @@ github.com/hashicorp/terraform-exec v0.24.0 h1:mL0xlk9H5g2bn0pPF6JQZk5YlByqSqrO5 github.com/hashicorp/terraform-exec v0.24.0/go.mod h1:lluc/rDYfAhYdslLJQg3J0oDqo88oGQAdHR+wDqFvo4= github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU= github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= +github.com/hiddeco/sshsig v0.2.0 h1:gMWllgKCITXdydVkDL+Zro0PU96QI55LwUwebSwNTSw= +github.com/hiddeco/sshsig v0.2.0/go.mod h1:nJc98aGgiH6Yql2doqH4CTBVHexQA40Q+hMMLHP4EqE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= From a3822569493f6520ff689e1f117934597889fe4c Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Sat, 28 Feb 2026 12:55:13 +0100 Subject: [PATCH 06/22] adds requested changes regarding naming Signed-off-by: Ricardo Bartels --- git/signatures/signature.go | 8 ++++---- git/signatures/signature_test.go | 6 +++--- git/signatures/ssh_signature.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/git/signatures/signature.go b/git/signatures/signature.go index 0c48f73c8..3e4842a69 100644 --- a/git/signatures/signature.go +++ b/git/signatures/signature.go @@ -34,7 +34,7 @@ const ( SignatureTypeUnknown SignatureType = "unknown" ) -// Isx509Signature is the prefix used by Git to identify x509 signatures. +// IsX509Signature is the prefix used by Git to identify x509 signatures. // https://github.com/git/git/blob/7b2bccb0d58d4f24705bf985de1f4612e4cf06e5/gpg-interface.c#L65 var X509SignaturePrefix = []string{"-----BEGIN SIGNED MESSAGE-----"} @@ -64,9 +64,9 @@ func IsSSHSignature(signature string) bool { return startsWithStrings(signature, SSHSignaturePrefix) } -// Isx509Signature tests if the given signature is of type x509. +// IsX509Signature tests if the given signature is of type x509. // It returns true if the signature starts with the x509 signature prefix. -func Isx509Signature(signature string) bool { +func IsX509Signature(signature string) bool { return startsWithStrings(signature, X509SignaturePrefix) } @@ -80,7 +80,7 @@ func GetSignatureType(signature string) string { if IsSSHSignature(signature) { return string(SignatureTypeSSH) } - if Isx509Signature(signature) { + if IsX509Signature(signature) { return string(SignatureTypeX509) } return string(SignatureTypeUnknown) diff --git a/git/signatures/signature_test.go b/git/signatures/signature_test.go index a649960dd..4350421a4 100644 --- a/git/signatures/signature_test.go +++ b/git/signatures/signature_test.go @@ -126,7 +126,7 @@ func TestIsSSHSignature(t *testing.T) { } } -func TestIsx509Signature(t *testing.T) { +func TestIsX509Signature(t *testing.T) { tests := []struct { name string signature string @@ -171,8 +171,8 @@ func TestIsx509Signature(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := Isx509Signature(tt.signature); got != tt.want { - t.Errorf("Isx509Signature() = %v, want %v", got, tt.want) + if got := IsX509Signature(tt.signature); got != tt.want { + t.Errorf("IsX509Signature() = %v, want %v", got, tt.want) } }) } diff --git a/git/signatures/ssh_signature.go b/git/signatures/ssh_signature.go index e61f19ecb..2561fff10 100644 --- a/git/signatures/ssh_signature.go +++ b/git/signatures/ssh_signature.go @@ -103,9 +103,9 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri return "", fmt.Errorf("unable to verify payload with any of the given authorized keys") } -// getPublicKeyFingerprint returns the SHA256 fingerprint of the public key +// GetPublicKeyFingerprint returns the SHA256 fingerprint of the public key // in the format used by SSH (e.g., "SHA256:abc123..."). func GetPublicKeyFingerprint(pubKey gossh.PublicKey) string { hash := sha256.Sum256(pubKey.Marshal()) - return "SHA256:" + strings.TrimSuffix(base64.StdEncoding.EncodeToString(hash[:]), "=") + return "SHA256:" + base64.RawStdEncoding.EncodeToString(hash[:]) } From 0aaa333443af5477c69f244f1edf4e110e10d98e Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 3 Mar 2026 00:08:43 +0100 Subject: [PATCH 07/22] cleans up tests and removed duplicate test files Signed-off-by: Ricardo Bartels --- git/signatures/gpg_signature_test.go | 181 +-- git/signatures/signature.go | 2 + git/signatures/ssh_signature.go | 15 +- git/signatures/ssh_signature_keys_test.go | 294 ++++ git/signatures/ssh_signature_test.go | 1441 +++-------------- .../testdata/ssh_signatures/README.md | 9 +- .../ssh_signatures/authorized_keys_ecdsa_p256 | 1 - .../ssh_signatures/authorized_keys_ecdsa_p384 | 1 - .../ssh_signatures/authorized_keys_ecdsa_p521 | 1 - .../ssh_signatures/authorized_keys_ed25519 | 1 - .../ssh_signatures/authorized_keys_rsa | 1 - .../ssh_signatures/generate_ssh_fixtures.sh | 29 +- .../{authorized_keys_all => keys_all.pub} | 0 13 files changed, 553 insertions(+), 1423 deletions(-) create mode 100644 git/signatures/ssh_signature_keys_test.go delete mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 delete mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 delete mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 delete mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 delete mode 100644 git/signatures/testdata/ssh_signatures/authorized_keys_rsa rename git/signatures/testdata/ssh_signatures/{authorized_keys_all => keys_all.pub} (100%) diff --git a/git/signatures/gpg_signature_test.go b/git/signatures/gpg_signature_test.go index b5a86617e..22ea81575 100644 --- a/git/signatures/gpg_signature_test.go +++ b/git/signatures/gpg_signature_test.go @@ -191,40 +191,50 @@ func TestVerifyPGPSignature(t *testing.T) { } } -func TestVerifyPGPSignatureWithFixturesForTags(t *testing.T) { +func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { testDataDir := filepath.Join("testdata", "gpg_signatures") // Test cases for each key type using fixtures keyTypes := []struct { - name string - sigFile string - keyFile string - wantErr bool + name string + commitFile string + tagFile string + keyFile string + wantErr bool }{ - {"rsa_2048 valid tag signature", "tag_rsa_2048_signed.txt", "key_rsa_2048.pub", false}, - {"rsa_4096 valid tag signature", "tag_rsa_4096_signed.txt", "key_rsa_4096.pub", false}, - {"ed25519 valid tag signature", "tag_ed25519_signed.txt", "key_ed25519.pub", false}, - {"ecdsa_p256 valid tag signature", "tag_ecdsa_p256_signed.txt", "key_ecdsa_p256.pub", false}, - {"ecdsa_p384 valid tag signature", "tag_ecdsa_p384_signed.txt", "key_ecdsa_p384.pub", false}, - {"ecdsa_p521 valid tag signature", "tag_ecdsa_p521_signed.txt", "key_ecdsa_p521.pub", false}, - {"dsa_2048 valid tag signature", "tag_dsa_2048_signed.txt", "key_dsa_2048.pub", false}, - {"brainpool_p256 valid tag signature", "tag_brainpool_p256_signed.txt", "key_brainpool_p256.pub", false}, - {"brainpool_p384 valid tag signature", "tag_brainpool_p384_signed.txt", "key_brainpool_p384.pub", false}, - {"brainpool_p512 valid tag signature", "tag_brainpool_p512_signed.txt", "key_brainpool_p512.pub", false}, + {"rsa_2048 valid signature", "commit_rsa_2048_signed.txt", "tag_rsa_2048_signed.txt", "key_rsa_2048.pub", false}, + {"rsa_4096 valid signature", "commit_rsa_4096_signed.txt", "tag_rsa_4096_signed.txt", "key_rsa_4096.pub", false}, + {"ed25519 valid signature", "commit_ed25519_signed.txt", "tag_ed25519_signed.txt", "key_ed25519.pub", false}, + {"ecdsa_p256 valid signature", "commit_ecdsa_p256_signed.txt", "tag_ecdsa_p256_signed.txt", "key_ecdsa_p256.pub", false}, + {"ecdsa_p384 valid signature", "commit_ecdsa_p384_signed.txt", "tag_ecdsa_p384_signed.txt", "key_ecdsa_p384.pub", false}, + {"ecdsa_p521 valid signature", "commit_ecdsa_p521_signed.txt", "tag_ecdsa_p521_signed.txt", "key_ecdsa_p521.pub", false}, + {"dsa_2048 valid signature", "commit_dsa_2048_signed.txt", "tag_dsa_2048_signed.txt", "key_dsa_2048.pub", false}, + {"brainpool_p256 valid signature", "commit_brainpool_p256_signed.txt", "tag_brainpool_p256_signed.txt", "key_brainpool_p256.pub", false}, + {"brainpool_p384 valid signature", "commit_brainpool_p384_signed.txt", "tag_brainpool_p384_signed.txt", "key_brainpool_p384.pub", false}, + {"brainpool_p512 valid signature", "commit_brainpool_p512_signed.txt", "tag_brainpool_p512_signed.txt", "key_brainpool_p512.pub", false}, // ed448 test fails because the key was created with OpenPGP version 5, // which is not supported by github.com/ProtonMail/go-crypto (only version 4 is supported). // The error occurs when trying to read the armored key ring: // "unable to read armored key ring: openpgp: invalid data: first packet was not a public/private key" - {"ed448 valid tag signature", "tag_ed448_signed.txt", "key_ed448.pub", true}, + {"ed448 valid signature", "commit_ed448_signed.txt", "tag_ed448_signed.txt", "key_ed448.pub", true}, } + var allKeysRing []string for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { + publicKey, err := os.ReadFile(filepath.Join(testDataDir, kt.keyFile)) + if err != nil { + t.Fatalf("failed to read public key file %s: %v", kt.keyFile, err) + } + allKeysRing = append(allKeysRing, string(publicKey)) + } + + for _, kt := range keyTypes { + t.Run(kt.name+" tag", func(t *testing.T) { g := NewWithT(t) // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.tagFile)) g.Expect(err).ToNot(HaveOccurred()) // Build a git.Tag using BuildTag @@ -245,44 +255,27 @@ func TestVerifyPGPSignatureWithFixturesForTags(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(fingerprint).ToNot(BeEmpty()) - }) - } -} -func TestVerifyPGPSignatureWithFixtures(t *testing.T) { - testDataDir := filepath.Join("testdata", "gpg_signatures") + // Verify the signature using the multi-key keyring + fingerprint, err = signatures.VerifyPGPSignature(gitTag.Signature, gitTag.Encoded, allKeysRing...) + if kt.wantErr { + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + return + } - // Test cases for each key type using fixtures - keyTypes := []struct { - name string - sigFile string - keyFile string - wantErr bool - }{ - {"rsa_2048 valid signature", "commit_rsa_2048_signed.txt", "key_rsa_2048.pub", false}, - {"rsa_4096 valid signature", "commit_rsa_4096_signed.txt", "key_rsa_4096.pub", false}, - {"ed25519 valid signature", "commit_ed25519_signed.txt", "key_ed25519.pub", false}, - {"ecdsa_p256 valid signature", "commit_ecdsa_p256_signed.txt", "key_ecdsa_p256.pub", false}, - {"ecdsa_p384 valid signature", "commit_ecdsa_p384_signed.txt", "key_ecdsa_p384.pub", false}, - {"ecdsa_p521 valid signature", "commit_ecdsa_p521_signed.txt", "key_ecdsa_p521.pub", false}, - {"dsa_2048 valid signature", "commit_dsa_2048_signed.txt", "key_dsa_2048.pub", false}, - {"brainpool_p256 valid signature", "commit_brainpool_p256_signed.txt", "key_brainpool_p256.pub", false}, - {"brainpool_p384 valid signature", "commit_brainpool_p384_signed.txt", "key_brainpool_p384.pub", false}, - {"brainpool_p512 valid signature", "commit_brainpool_p512_signed.txt", "key_brainpool_p512.pub", false}, + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(fingerprint).ToNot(BeEmpty()) - // ed448 test fails because the key was created with OpenPGP version 5, - // which is not supported by github.com/ProtonMail/go-crypto (only version 4 is supported). - // The error occurs when trying to read the armored key ring: - // "unable to read armored key ring: openpgp: invalid data: first packet was not a public/private key" - {"ed448 valid signature", "commit_ed448_signed.txt", "key_ed448.pub", true}, + }) } for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { + t.Run(kt.name+" commit", func(t *testing.T) { g := NewWithT(t) // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.commitFile)) g.Expect(err).ToNot(HaveOccurred()) // Build a git.Commit using BuildCommitWithRef @@ -303,90 +296,9 @@ func TestVerifyPGPSignatureWithFixtures(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(fingerprint).ToNot(BeEmpty()) - }) - } - - // Test error cases - t.Run("unsigned commit", func(t *testing.T) { - g := NewWithT(t) - - // Parse the unsigned commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) - g.Expect(err).ToNot(HaveOccurred()) - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - g.Expect(err).ToNot(HaveOccurred()) - - // Read a public key - publicKey, err := os.ReadFile(filepath.Join(testDataDir, "key_rsa_2048.pub")) - g.Expect(err).ToNot(HaveOccurred()) - - // Verify the signature - should fail as the commit is unsigned - fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) - g.Expect(err).To(HaveOccurred()) - g.Expect(fingerprint).To(BeEmpty()) - }) -} - -func TestVerifyPGPSignatureWithMultipleKeyRing(t *testing.T) { - testDataDir := filepath.Join("testdata", "gpg_signatures") - - // Read multiple public keys to create a multi-key keyring - keyFiles := []string{ - "key_rsa_2048.pub", - "key_rsa_4096.pub", - "key_ed25519.pub", - "key_ecdsa_p256.pub", - "key_ecdsa_p384.pub", - "key_ecdsa_p521.pub", - "key_dsa_2048.pub", - "key_brainpool_p256.pub", - "key_brainpool_p384.pub", - "key_brainpool_p512.pub", - } - - var keyRings []string - for _, keyFile := range keyFiles { - publicKey, err := os.ReadFile(filepath.Join(testDataDir, keyFile)) - if err != nil { - t.Fatalf("failed to read public key file %s: %v", keyFile, err) - } - keyRings = append(keyRings, string(publicKey)) - } - - // Test cases for each key type using the multi-key keyring - keyTypes := []struct { - name string - sigFile string - wantErr bool - }{ - {"rsa_2048 valid signature with multi-key keyring", "commit_rsa_2048_signed.txt", false}, - {"rsa_4096 valid signature with multi-key keyring", "commit_rsa_4096_signed.txt", false}, - {"ed25519 valid signature with multi-key keyring", "commit_ed25519_signed.txt", false}, - {"ecdsa_p256 valid signature with multi-key keyring", "commit_ecdsa_p256_signed.txt", false}, - {"ecdsa_p384 valid signature with multi-key keyring", "commit_ecdsa_p384_signed.txt", false}, - {"ecdsa_p521 valid signature with multi-key keyring", "commit_ecdsa_p521_signed.txt", false}, - {"dsa_2048 valid signature with multi-key keyring", "commit_dsa_2048_signed.txt", false}, - {"brainpool_p256 valid signature with multi-key keyring", "commit_brainpool_p256_signed.txt", false}, - {"brainpool_p384 valid signature with multi-key keyring", "commit_brainpool_p384_signed.txt", false}, - {"brainpool_p512 valid signature with multi-key keyring", "commit_brainpool_p512_signed.txt", false}, - } - - for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { - g := NewWithT(t) - - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) - g.Expect(err).ToNot(HaveOccurred()) - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - g.Expect(err).ToNot(HaveOccurred()) // Verify the signature using the multi-key keyring - fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, keyRings...) + fingerprint, err = signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, allKeysRing...) if kt.wantErr { g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) @@ -395,11 +307,12 @@ func TestVerifyPGPSignatureWithMultipleKeyRing(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(fingerprint).ToNot(BeEmpty()) + }) } - // Test that an unsigned commit fails with multi-key keyring - t.Run("unsigned commit with multi-key keyring", func(t *testing.T) { + // Test error cases + t.Run("unsigned commit", func(t *testing.T) { g := NewWithT(t) // Parse the unsigned commit from the fixture file @@ -410,8 +323,12 @@ func TestVerifyPGPSignatureWithMultipleKeyRing(t *testing.T) { gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) g.Expect(err).ToNot(HaveOccurred()) + // Read a public key + publicKey, err := os.ReadFile(filepath.Join(testDataDir, "key_rsa_2048.pub")) + g.Expect(err).ToNot(HaveOccurred()) + // Verify the signature - should fail as the commit is unsigned - fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, keyRings...) + fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) }) diff --git a/git/signatures/signature.go b/git/signatures/signature.go index 3e4842a69..f85d4d03c 100644 --- a/git/signatures/signature.go +++ b/git/signatures/signature.go @@ -66,6 +66,8 @@ func IsSSHSignature(signature string) bool { // IsX509Signature tests if the given signature is of type x509. // It returns true if the signature starts with the x509 signature prefix. +// This is a place holder / compatibility implementation to embed the signature +// type into the error message to inform the user about the wrong type of signature func IsX509Signature(signature string) bool { return startsWithStrings(signature, X509SignaturePrefix) } diff --git a/git/signatures/ssh_signature.go b/git/signatures/ssh_signature.go index 2561fff10..c3a146cbf 100644 --- a/git/signatures/ssh_signature.go +++ b/git/signatures/ssh_signature.go @@ -89,13 +89,10 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri for _, pubKey := range publicKeys { // Verify the signature using sshsig library // The namespace for Git is "git" - // Git supports both SHA256 and SHA512, so we try both - for _, hashAlgo := range []sshsig.HashAlgorithm{sshsig.HashSHA256, sshsig.HashSHA512} { - err := sshsig.Verify(bytes.NewReader(payload), sig, pubKey, hashAlgo, "git") - if err == nil { - // Signature verified successfully - return GetPublicKeyFingerprint(pubKey), nil - } + err := sshsig.Verify(bytes.NewReader(payload), sig, pubKey, sig.HashAlgorithm, "git") + if err == nil { + // Signature verified successfully + return getPublicKeyFingerprint(pubKey), nil } } } @@ -103,9 +100,9 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri return "", fmt.Errorf("unable to verify payload with any of the given authorized keys") } -// GetPublicKeyFingerprint returns the SHA256 fingerprint of the public key +// getPublicKeyFingerprint returns the SHA256 fingerprint of the public key // in the format used by SSH (e.g., "SHA256:abc123..."). -func GetPublicKeyFingerprint(pubKey gossh.PublicKey) string { +func getPublicKeyFingerprint(pubKey gossh.PublicKey) string { hash := sha256.Sum256(pubKey.Marshal()) return "SHA256:" + base64.RawStdEncoding.EncodeToString(hash[:]) } diff --git a/git/signatures/ssh_signature_keys_test.go b/git/signatures/ssh_signature_keys_test.go new file mode 100644 index 000000000..3761d469a --- /dev/null +++ b/git/signatures/ssh_signature_keys_test.go @@ -0,0 +1,294 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package signatures + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// these tests are in the same package to test private getPublicKeyFingerprint function + +func TestParseAuthorizedKeysAndPublicFingerprint(t *testing.T) { + tests := []struct { + name string + authorizedKeys string + wantCount int + wantErr bool + wantFingerprints []string + }{ + { + name: "single key", + authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com", + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, + }, + { + name: "key with additional directives", + authorizedKeys: "no-user-rc,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com additional long comment about nothing", + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, + }, + { + name: "multiple keys", + authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test1@example.com +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com`, + wantCount: 2, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM", "SHA256:oU8IT7UOnJlOTOvr/W1cYf1SkdocFm5F7SAXOwuo8Kc"}, + }, + { + name: "with comments", + authorizedKeys: `# This is a comment +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com +# Another comment`, + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:+vwrYGpHfAAWIzT2x+uV+duJG7ZnSvCbRKwdPApx7JA"}, + }, + { + name: "with empty lines", + authorizedKeys: `ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com + +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com`, + wantCount: 2, + wantErr: false, + wantFingerprints: []string{"SHA256:3FcWgX5RsACruglrcBJP/hefUZcYHJGnrk07U6yKin8", "SHA256:TxoYgaeIj5A7Md4rHNfxPdqawooc4NIGjIMbcQ7YKbw"}, + }, + { + name: "empty", + authorizedKeys: "", + wantCount: 0, + wantErr: false, + wantFingerprints: []string{}, + }, + { + name: "invalid key", + authorizedKeys: "invalid-key-data", + wantCount: 0, + wantErr: true, + wantFingerprints: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + keys, err := ParseAuthorizedKeys(tt.authorizedKeys) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + // Validate expected fingerprint if specified + if len(tt.wantFingerprints) > 0 && len(keys) > 0 { + for _, key := range keys { + found := false + fingerprint := getPublicKeyFingerprint(key) + for _, wantedFingerprint := range tt.wantFingerprints { + if fingerprint == wantedFingerprint { + found = true + } + } + if !found { + t.Errorf("ParseAuthorizedKeys() fingerprint '%s'not in list of wanted fingerprints %s", fingerprint, tt.wantFingerprints) + } + } + } + }) + } +} + +func TestParseAuthorizedKeysFromFixtures(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixture string + fingerprintFile string + wantCount int + wantErr bool + }{ + { + name: "ed25519 key", + fixture: "key_ed25519.pub", + fingerprintFile: "key_ed25519.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "rsa key", + fixture: "key_rsa.pub", + fingerprintFile: "key_rsa.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p256 key", + fixture: "key_ecdsa_p256.pub", + fingerprintFile: "key_ecdsa_p256.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p384 key", + fixture: "key_ecdsa_p384.pub", + fingerprintFile: "key_ecdsa_p384.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p521 key", + fixture: "key_ecdsa_p521.pub", + fingerprintFile: "key_ecdsa_p521.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "all key types combined", + fixture: "keys_all.pub", + wantCount: 5, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.fixture)) + if err != nil { + t.Fatalf("Failed to read fixture file %s: %v", tt.fixture, err) + } + + keys, err := ParseAuthorizedKeys(string(authorizedKeys)) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + + // Read expected fingerprint from file if provided + var expectedFingerprint string + if tt.fingerprintFile != "" { + fingerprintData, err := os.ReadFile(filepath.Join(testDataDir, tt.fingerprintFile)) + if err != nil { + t.Fatalf("Failed to read fingerprint file %s: %v", tt.fingerprintFile, err) + } + expectedFingerprint = strings.TrimSpace(string(fingerprintData)) + } + + // Verify that each key has a valid fingerprint + for i, key := range keys { + fingerprint := getPublicKeyFingerprint(key) + if fingerprint == "" { + t.Errorf("Key %d has empty fingerprint", i) + } + if !strings.HasPrefix(fingerprint, "SHA256:") { + t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) + } + // Validate fingerprint against the one read from file + if expectedFingerprint != "" { + if fingerprint != expectedFingerprint { + t.Errorf("Key %d got fingerprint %s, want %s (from %s)", i, fingerprint, expectedFingerprint, tt.fingerprintFile) + } + } + } + }) + } +} + +func TestParseAuthorizedKeysCombinations(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixtures []string + wantCount int + wantErr bool + }{ + { + name: "ed25519 + rsa", + fixtures: []string{"key_ed25519.pub", "key_rsa.pub"}, + wantCount: 2, + wantErr: false, + }, + { + name: "ed25519 + ecdsa p256", + fixtures: []string{"key_ed25519.pub", "key_ecdsa_p256.pub"}, + wantCount: 2, + wantErr: false, + }, + { + name: "rsa + ecdsa p384 + ecdsa p521", + fixtures: []string{"key_rsa.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, + wantCount: 3, + wantErr: false, + }, + { + name: "all ecdsa variants", + fixtures: []string{"key_ecdsa_p256.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, + wantCount: 3, + wantErr: false, + }, + { + name: "ed25519 + rsa + all ecdsa", + fixtures: []string{"key_ed25519.pub", "key_rsa.pub", "key_ecdsa_p256.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, + wantCount: 5, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var combinedKeys strings.Builder + for _, fixture := range tt.fixtures { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, fixture)) + if err != nil { + t.Fatalf("Failed to read fixture file %s: %v", fixture, err) + } + combinedKeys.Write(authorizedKeys) + combinedKeys.WriteString("\n") + } + + keys, err := ParseAuthorizedKeys(combinedKeys.String()) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + + // Verify that each key has a valid fingerprint + for i, key := range keys { + fingerprint := getPublicKeyFingerprint(key) + if fingerprint == "" { + t.Errorf("Key %d has empty fingerprint", i) + } + if !strings.HasPrefix(fingerprint, "SHA256:") { + t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) + } + } + }) + } +} diff --git a/git/signatures/ssh_signature_test.go b/git/signatures/ssh_signature_test.go index 82c2f1320..80cacb12b 100644 --- a/git/signatures/ssh_signature_test.go +++ b/git/signatures/ssh_signature_test.go @@ -26,422 +26,203 @@ import ( "github.com/fluxcd/pkg/git/signatures" "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" - "github.com/hiddeco/sshsig" ) -func TestParseAuthorizedKeys(t *testing.T) { - tests := []struct { - name string - authorizedKeys string - wantCount int - wantErr bool - wantFingerprints []string - }{ - { - name: "single key", - authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com", - wantCount: 1, - wantErr: false, - wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, - }, - { - name: "key with additional directives", - authorizedKeys: "no-user-rc,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com additional long comment about nothing", - wantCount: 1, - wantErr: false, - wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, - }, - { - name: "multiple keys", - authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test1@example.com -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com`, - wantCount: 2, - wantErr: false, - wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM", "SHA256:oU8IT7UOnJlOTOvr/W1cYf1SkdocFm5F7SAXOwuo8Kc"}, - }, - { - name: "with comments", - authorizedKeys: `# This is a comment -ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com -# Another comment`, - wantCount: 1, - wantErr: false, - wantFingerprints: []string{"SHA256:+vwrYGpHfAAWIzT2x+uV+duJG7ZnSvCbRKwdPApx7JA"}, - }, - { - name: "with empty lines", - authorizedKeys: `ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com +// these tests are in a different package to avoid circular dependencies with gogit.BuildCommitWithRef and gogit.BuildTag -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com`, - wantCount: 2, - wantErr: false, - wantFingerprints: []string{"SHA256:3FcWgX5RsACruglrcBJP/hefUZcYHJGnrk07U6yKin8", "SHA256:TxoYgaeIj5A7Md4rHNfxPdqawooc4NIGjIMbcQ7YKbw"}, - }, - { - name: "empty", - authorizedKeys: "", - wantCount: 0, - wantErr: false, - wantFingerprints: []string{}, - }, - { - name: "invalid key", - authorizedKeys: "invalid-key-data", - wantCount: 0, - wantErr: true, - wantFingerprints: []string{}, - }, - } +func TestVerifySSHSignature(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - keys, err := signatures.ParseAuthorizedKeys(tt.authorizedKeys) - if (err != nil) != tt.wantErr { - t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(keys) != tt.wantCount { - t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) - } - // Validate expected fingerprint if specified - if len(tt.wantFingerprints) > 0 && len(keys) > 0 { - for _, key := range keys { - found := false - fingerprint := signatures.GetPublicKeyFingerprint(key) - for _, wantedFingerprint := range tt.wantFingerprints { - if fingerprint == wantedFingerprint { - found = true - } - } - if !found { - t.Errorf("ParseAuthorizedKeys() fingerprint '%s'not in list of wanted fingerprints %s", fingerprint, tt.wantFingerprints) - } - } - } - }) + pubKeysAll, err := os.ReadFile(filepath.Join(testDataDir, "keys_all.pub")) + if err != nil { + t.Fatalf("Failed to read combined authorized keys: %v", err) } -} - -func TestParseAuthorizedKeysFromFixtures(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - tests := []struct { - name string - fixture string - fingerprintFile string - wantCount int - wantErr bool + // Test cases for each key type using fixtures + keyTypes := []struct { + name string + signedCommitFile string + signedTagFile string + pubKeyFile string + fingerPrintFile string }{ { - name: "ed25519 key", - fixture: "authorized_keys_ed25519", - fingerprintFile: "key_ed25519.pub_fingerprint", - wantCount: 1, - wantErr: false, - }, - { - name: "rsa key", - fixture: "authorized_keys_rsa", - fingerprintFile: "key_rsa.pub_fingerprint", - wantCount: 1, - wantErr: false, + name: "ed25519 valid signature", + signedCommitFile: "commit_ed25519_signed.txt", + signedTagFile: "tag_ed25519_signed.txt", + pubKeyFile: "key_ed25519.pub", + fingerPrintFile: "key_ed25519.pub_fingerprint", }, { - name: "ecdsa p256 key", - fixture: "authorized_keys_ecdsa_p256", - fingerprintFile: "key_ecdsa_p256.pub_fingerprint", - wantCount: 1, - wantErr: false, + name: "rsa valid signature", + signedCommitFile: "commit_rsa_signed.txt", + signedTagFile: "tag_rsa_signed.txt", + pubKeyFile: "key_rsa.pub", + fingerPrintFile: "key_rsa.pub_fingerprint", }, { - name: "ecdsa p384 key", - fixture: "authorized_keys_ecdsa_p384", - fingerprintFile: "key_ecdsa_p384.pub_fingerprint", - wantCount: 1, - wantErr: false, + name: "ecdsa_p256 valid signature", + signedCommitFile: "commit_ecdsa_p256_signed.txt", + signedTagFile: "tag_ecdsa_p256_signed.txt", + pubKeyFile: "key_ecdsa_p256.pub", + fingerPrintFile: "key_ecdsa_p256.pub_fingerprint", }, { - name: "ecdsa p521 key", - fixture: "authorized_keys_ecdsa_p521", - fingerprintFile: "key_ecdsa_p521.pub_fingerprint", - wantCount: 1, - wantErr: false, + name: "ecdsa_p384 valid signature", + signedCommitFile: "commit_ecdsa_p384_signed.txt", + signedTagFile: "tag_ecdsa_p384_signed.txt", + pubKeyFile: "key_ecdsa_p384.pub", + fingerPrintFile: "key_ecdsa_p384.pub_fingerprint", }, { - name: "all key types combined", - fixture: "authorized_keys_all", - wantCount: 5, - wantErr: false, + name: "ecdsa_p521 valid signature", + signedCommitFile: "commit_ecdsa_p521_signed.txt", + signedTagFile: "tag_ecdsa_p521_signed.txt", + pubKeyFile: "key_ecdsa_p521.pub", + fingerPrintFile: "key_ecdsa_p521.pub_fingerprint", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.fixture)) + for _, kt := range keyTypes { + t.Run(kt.name, func(t *testing.T) { + + // Parse the commit from the fixture file + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.signedCommitFile)) if err != nil { - t.Fatalf("Failed to read fixture file %s: %v", tt.fixture, err) + t.Fatalf("Failed to parse commit from fixture: %v", err) } - keys, err := signatures.ParseAuthorizedKeys(string(authorizedKeys)) - if (err != nil) != tt.wantErr { - t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(keys) != tt.wantCount { - t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) } - // Read expected fingerprint from file if provided - var expectedFingerprint string - if tt.fingerprintFile != "" { - fingerprintData, err := os.ReadFile(filepath.Join(testDataDir, tt.fingerprintFile)) - if err != nil { - t.Fatalf("Failed to read fingerprint file %s: %v", tt.fingerprintFile, err) - } - expectedFingerprint = strings.TrimSpace(string(fingerprintData)) + // Parse the commit from the fixture file + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.signedTagFile)) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) } - // Verify that each key has a valid fingerprint - for i, key := range keys { - fingerprint := signatures.GetPublicKeyFingerprint(key) - if fingerprint == "" { - t.Errorf("Key %d has empty fingerprint", i) - } - if !strings.HasPrefix(fingerprint, "SHA256:") { - t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) - } - // Validate fingerprint against the one read from file - if expectedFingerprint != "" { - if fingerprint != expectedFingerprint { - t.Errorf("Key %d got fingerprint %s, want %s (from %s)", i, fingerprint, expectedFingerprint, tt.fingerprintFile) - } - } + // Build a git.Commit using BuildCommitWithRef + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) } - }) - } -} - -func TestParseAuthorizedKeysCombinations(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - tests := []struct { - name string - fixtures []string - wantCount int - wantErr bool - }{ - { - name: "ed25519 + rsa", - fixtures: []string{"authorized_keys_ed25519", "authorized_keys_rsa"}, - wantCount: 2, - wantErr: false, - }, - { - name: "ed25519 + ecdsa p256", - fixtures: []string{"authorized_keys_ed25519", "authorized_keys_ecdsa_p256"}, - wantCount: 2, - wantErr: false, - }, - { - name: "rsa + ecdsa p384 + ecdsa p521", - fixtures: []string{"authorized_keys_rsa", "authorized_keys_ecdsa_p384", "authorized_keys_ecdsa_p521"}, - wantCount: 3, - wantErr: false, - }, - { - name: "all ecdsa variants", - fixtures: []string{"authorized_keys_ecdsa_p256", "authorized_keys_ecdsa_p384", "authorized_keys_ecdsa_p521"}, - wantCount: 3, - wantErr: false, - }, - { - name: "ed25519 + rsa + all ecdsa", - fixtures: []string{"authorized_keys_ed25519", "authorized_keys_rsa", "authorized_keys_ecdsa_p256", "authorized_keys_ecdsa_p384", "authorized_keys_ecdsa_p521"}, - wantCount: 5, - wantErr: false, - }, - } + // Read the authorized keys + authorizedKey, err := os.ReadFile(filepath.Join(testDataDir, kt.pubKeyFile)) + if err != nil { + t.Fatalf("Failed to read authorized keys: %v", err) + } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var combinedKeys strings.Builder - for _, fixture := range tt.fixtures { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, fixture)) - if err != nil { - t.Fatalf("Failed to read fixture file %s: %v", fixture, err) - } - combinedKeys.Write(authorizedKeys) - combinedKeys.WriteString("\n") + expectedFingerprintBytes, err := os.ReadFile(filepath.Join(testDataDir, kt.fingerPrintFile)) + if err != nil { + t.Fatalf("Failed to read fingerprint file %s: %v", kt.fingerPrintFile, err) } + expectedFingerprint := strings.TrimSpace(string(expectedFingerprintBytes)) - keys, err := signatures.ParseAuthorizedKeys(combinedKeys.String()) - if (err != nil) != tt.wantErr { - t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) - return + // Verify the signature using the git.Commit's Signature and Encoded fields + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKey)) + if err != nil { + t.Errorf("Commit signature VerifySSHSignature() error = %v", err) } - if len(keys) != tt.wantCount { - t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + if fingerprint == "" { + t.Errorf("Commit signature VerifySSHSignature() returned empty fingerprint") + } + if fingerprint != expectedFingerprint { + t.Errorf("Commit signature VerifySSHSignature() fingerprint mismatch, got '%s', want '%s'", fingerprint, expectedFingerprint) } - // Verify that each key has a valid fingerprint - for i, key := range keys { - fingerprint := signatures.GetPublicKeyFingerprint(key) - if fingerprint == "" { - t.Errorf("Key %d has empty fingerprint", i) - } - if !strings.HasPrefix(fingerprint, "SHA256:") { - t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) - } + // Verifying the correct fingerprint is returned from a list of public keys + fingerprint, err = signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(pubKeysAll)) + if err != nil { + t.Errorf("Commit signature VerifySSHSignature() error = %v", err) + } + if fingerprint == "" { + t.Errorf("Commit signature VerifySSHSignature() returned empty fingerprint") + } + if fingerprint != expectedFingerprint { + t.Errorf("Commit signature VerifySSHSignature() fingerprint mismatch, got '%s', want '%s'", fingerprint, expectedFingerprint) } - }) - } -} -func TestParseSSHSignature(t *testing.T) { - tests := []struct { - name string - sig string - wantErr bool - }{ - { - name: "valid signature with PEM armor", - sig: `-----BEGIN SSH SIGNATURE----- -U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg9uahUwBLlO2Dvuz0MtIA5/iBcK -JCmB1F6QUeXNtccscAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 -AAAAQFb88f1ZXOK1BByC4QQOthH9bZP0/hMcPl62h4oIuEny6W5xd/oOpDv7dmj9A6DiMS -o6RLdWlvb81l/UyYhGEwE= ------END SSH SIGNATURE-----`, - wantErr: false, - }, - { - name: "valid signature without PEM armor", - sig: "U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg9uahUwBLlO2Dvuz0MtIA5/iBcKJCmB1F6QUeXNtccscAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5AAAAQFb88f1ZXOK1BByC4QQOthH9bZP0/hMcPl62h4oIuEny6W5xd/oOpDv7dmj9A6DiMSo6RLdWlvb81l/UyYhGEwE=", - wantErr: true, // sshsig.Unarmor() requires PEM armor - }, - { - name: "empty signature", - sig: "", - wantErr: true, - }, - { - name: "invalid base64", - sig: "-----BEGIN SSH SIGNATURE-----\ninvalid-base64!!!\n-----END SSH SIGNATURE-----", - wantErr: true, - }, - { - name: "invalid format", - sig: "invalid-signature-format", - wantErr: true, - }, - } + // Verify the signature using the git.Tag's Signature and Encoded fields + fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKey)) + if err != nil { + t.Errorf("Tag signature VerifySSHSignature() error = %v", err) + } + if fingerprint == "" { + t.Errorf("Tag signature VerifySSHSignature() returned empty fingerprint") + } + if fingerprint != expectedFingerprint { + t.Errorf("Tag signature VerifySSHSignature() fingerprint mismatch, got '%s', want '%s'", fingerprint, expectedFingerprint) + } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sig, err := sshsig.Unarmor([]byte(tt.sig)) - if (err != nil) != tt.wantErr { - t.Errorf("sshsig.Unarmor() error = %v, wantErr %v", err, tt.wantErr) - return + // Verifying the correct fingerprint is returned from a list of public keys + fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(pubKeysAll)) + if err != nil { + t.Errorf("Tag signature VerifySSHSignature() error = %v", err) + } + if fingerprint == "" { + t.Errorf("Tag signature VerifySSHSignature() returned empty fingerprint") } - if !tt.wantErr && sig == nil { - t.Errorf("sshsig.Unarmor() returned nil signature") + if fingerprint != expectedFingerprint { + t.Errorf("Tag signature VerifySSHSignature() fingerprint mismatch, got '%s', want '%s'", fingerprint, expectedFingerprint) } + }) } } -func TestGetPublicKeyFingerprint(t *testing.T) { - // Test with a known public key - pubKeyStr := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com" - expectedFingerprint := "SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM" - keys, err := signatures.ParseAuthorizedKeys(pubKeyStr) +func TestSSHSignatureValidationCases(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + key_type := "ed25519" + + pubKey, err := os.ReadFile(filepath.Join(testDataDir, "key_"+key_type+".pub")) if err != nil { - t.Fatalf("Failed to parse test public key: %v", err) - } - if len(keys) == 0 { - t.Fatal("No keys parsed") + t.Fatalf("Failed to read authorized keys: %v", err) } - fingerprint := signatures.GetPublicKeyFingerprint(keys[0]) - if fingerprint == "" { - t.Error("GetPublicKeyFingerprint() returned empty string") - } - if !strings.HasPrefix(fingerprint, expectedFingerprint) { - t.Errorf("GetPublicKeyFingerprint() = %s, want prefix SHA256:", fingerprint) + // Parse the commit from the fixture file + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_"+key_type+"_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse commit from fixture: %v", err) } -} - -func TestVerifySSHSignature(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - // Test cases for each key type using fixtures - keyTypes := []struct { - name string - sigFile string - authFile string - wantErr bool - }{ - {"ed25519 valid signature", "commit_ed25519_signed.txt", "authorized_keys_ed25519", false}, - {"rsa valid signature", "commit_rsa_signed.txt", "authorized_keys_rsa", false}, - {"ecdsa_p256 valid signature", "commit_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, - {"ecdsa_p384 valid signature", "commit_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, - {"ecdsa_p521 valid signature", "commit_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, + // Parse the tag from the fixture file + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_"+key_type+"_signed.txt")) + if err != nil { + t.Fatalf("Failed to parse tag from fixture: %v", err) } - for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) - } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) - } - - // Read the authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } + // Build a git.Commit using BuildCommitWithRef + gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + if err != nil { + t.Fatalf("Failed to build commit: %v", err) + } - // Verify the signature using the git.Commit's Signature and Encoded fields - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKeys)) - if (err != nil) != kt.wantErr { - t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) - return - } - if !kt.wantErr && fingerprint == "" { - t.Errorf("VerifySSHSignature() returned empty fingerprint") - } - if !kt.wantErr { - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) + // Build a git.Tag using BuildTag + gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + if err != nil { + t.Fatalf("Failed to build tag: %v", err) } // Test error cases t.Run("empty signature", func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) + fingerprint, err := signatures.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) + } + if err != nil && err.Error() != "unable to verify payload as the provided signature is empty" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) } - fingerprint, err := signatures.VerifySSHSignature("", gitCommit.Encoded, string(authorizedKeys)) + fingerprint, err = signatures.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") } @@ -451,27 +232,23 @@ func TestVerifySSHSignature(t *testing.T) { if err != nil && err.Error() != "unable to verify payload as the provided signature is empty" { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) } + }) t.Run("empty payload", func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) + fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, []byte{}, string(pubKey)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) + } + if err != nil && err.Error() != "unable to verify payload as the provided payload is empty" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) } - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, []byte{}, string(authorizedKeys)) + fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, []byte{}, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") } @@ -481,21 +258,10 @@ func TestVerifySSHSignature(t *testing.T) { if err != nil && err.Error() != "unable to verify payload as the provided payload is empty" { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) } + }) t.Run("wrong authorized keys", func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) - } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) - } - // Use a different key that won't match wrongKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyM97VxLgOCuB9Eg5cDtTc8ogkdM1xAyJhzODB9cK1 wrong@example.com" @@ -510,21 +276,21 @@ func TestVerifySSHSignature(t *testing.T) { if err != nil && !strings.Contains(err.Error(), "unable to verify payload with any of the given authorized keys") && !strings.Contains(err.Error(), "unable to parse authorized key") { t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) } - }) - t.Run("empty authorized keys", func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) + fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, wrongKey) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) + } + // The error can be either a parsing error or a verification error + if err != nil && !strings.Contains(err.Error(), "unable to verify payload with any of the given authorized keys") && !strings.Contains(err.Error(), "unable to parse authorized key") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) } + }) + t.Run("empty authorized keys", func(t *testing.T) { // Use empty authorized keys emptyAuthKeys := "" @@ -538,29 +304,23 @@ func TestVerifySSHSignature(t *testing.T) { if err != nil && err.Error() != "unable to verify payload with any of the given authorized keys" { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload with any of the given authorized keys'", err) } - }) - t.Run("invalid signature", func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) + fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, emptyAuthKeys) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for empty authorized keys, got nil") } - - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for empty authorized keys: %s", fingerprint) } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) + if err != nil && err.Error() != "unable to verify payload with any of the given authorized keys" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload with any of the given authorized keys'", err) } + }) + t.Run("invalid signature", func(t *testing.T) { invalidSig := "-----BEGIN SSH SIGNATURE-----\n invalid\n -----END SSH SIGNATURE-----" - fingerprint, err := signatures.VerifySSHSignature(invalidSig, gitCommit.Encoded, string(authorizedKeys)) + fingerprint, err := signatures.VerifySSHSignature(invalidSig, gitCommit.Encoded, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") } @@ -570,21 +330,21 @@ func TestVerifySSHSignature(t *testing.T) { if err != nil && !strings.Contains(err.Error(), "unable to unarmor SSH signature") { t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) } - }) - t.Run("non-SSH signature", func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) + fingerprint, err = signatures.VerifySSHSignature(invalidSig, gitTag.Encoded, string(pubKey)) + if err == nil { + t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) + } + if err != nil && !strings.Contains(err.Error(), "unable to unarmor SSH signature") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) } + }) + + t.Run("non-SSH signature", func(t *testing.T) { // Use a PGP signature instead of SSH signature pgpSig := "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----" @@ -598,21 +358,20 @@ func TestVerifySSHSignature(t *testing.T) { if err != nil && err.Error() != "unable to verify SSH signature, detected signature format: openpgp" { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify SSH signature, detected signature format: openpgp'", err) } - }) - t.Run("invalid authorized keys", func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) + fingerprint, err = signatures.VerifySSHSignature(pgpSig, gitTag.Encoded, "") + if err == nil { + t.Errorf("VerifySSHSignature() expected error for non-SSH signature, got nil") } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) + if fingerprint != "" { + t.Errorf("VerifySSHSignature() returned fingerprint for non-SSH signature: %s", fingerprint) + } + if err != nil && err.Error() != "unable to verify SSH signature, detected signature format: openpgp" { + t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify SSH signature, detected signature format: openpgp'", err) } + }) + t.Run("invalid authorized keys", func(t *testing.T) { // Use invalid authorized keys invalidAuthKeys := "invalid-key-data" @@ -626,824 +385,16 @@ func TestVerifySSHSignature(t *testing.T) { if err != nil && !strings.Contains(err.Error(), "unable to parse authorized key") { t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to parse authorized key'", err) } - }) -} - -func TestVerifySSHSignatureAllKeyTypes(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - // Test cases for each key type - keyTypes := []struct { - name string - sigFile string - authFile string - wantErr bool - }{ - {"ed25519", "commit_ed25519_signed.txt", "authorized_keys_ed25519", false}, - {"rsa", "commit_rsa_signed.txt", "authorized_keys_rsa", false}, - {"ecdsa_p256", "commit_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, - {"ecdsa_p384", "commit_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, - {"ecdsa_p521", "commit_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, - } - - for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) - } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) - } - - // Read the authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Verify the signature using the git.Commit's Signature and Encoded fields - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKeys)) - if (err != nil) != kt.wantErr { - t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) - return - } - if !kt.wantErr && fingerprint == "" { - t.Errorf("VerifySSHSignature() returned empty fingerprint") - } - if !kt.wantErr { - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) - } -} - -func TestVerifySSHSignatureCombinedKeys(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - // Read the combined authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_all")) - if err != nil { - t.Fatalf("Failed to read combined authorized keys: %v", err) - } - // Test each key type against the combined authorized keys - keyTypes := []struct { - name string - sigFile string - wantErr bool - }{ - {"ed25519", "commit_ed25519_signed.txt", false}, - {"rsa", "commit_rsa_signed.txt", false}, - {"ecdsa_p256", "commit_ecdsa_p256_signed.txt", false}, - {"ecdsa_p384", "commit_ecdsa_p384_signed.txt", false}, - {"ecdsa_p521", "commit_ecdsa_p521_signed.txt", false}, - } - - for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.sigFile)) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) - } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("Failed to build commit: %v", err) - } - - // Verify the signature with combined authorized keys - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKeys)) - if (err != nil) != kt.wantErr { - t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) - return - } - if !kt.wantErr && fingerprint == "" { - t.Errorf("VerifySSHSignature() returned empty fingerprint") - } - if !kt.wantErr { - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) - } -} - -func TestBuildCommitWithRefFromFixture(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - tests := []struct { - name string - fixture string - wantErr bool - wantSig bool - }{ - { - name: "ed25519 signed commit", - fixture: "commit_ed25519_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "rsa signed commit", - fixture: "commit_rsa_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "ecdsa p256 signed commit", - fixture: "commit_ecdsa_p256_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "ecdsa p384 signed commit", - fixture: "commit_ecdsa_p384_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "ecdsa p521 signed commit", - fixture: "commit_ecdsa_p521_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "unsigned commit", - fixture: "commit_unsigned.txt", - wantErr: false, - wantSig: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) - } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if (err != nil) != tt.wantErr { - t.Errorf("BuildCommitWithRef() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.wantErr { - // Verify the commit was built correctly - if gitCommit == nil { - t.Fatal("BuildCommitWithRef() returned nil commit") - } - - // Check if signature is present as expected - hasSig := gitCommit.Signature != "" - if hasSig != tt.wantSig { - t.Errorf("BuildCommitWithRef() has signature = %v, want %v", hasSig, tt.wantSig) - } - - // Verify the encoded data is present - if len(gitCommit.Encoded) == 0 { - t.Error("BuildCommitWithRef() returned commit with empty Encoded field") - } - - // Verify the reference is set correctly - if gitCommit.Reference != "refs/heads/main" { - t.Errorf("BuildCommitWithRef() reference = %q, want %q", gitCommit.Reference, "refs/heads/main") - } - - // Verify the hash is set - if len(gitCommit.Hash) == 0 { - t.Error("BuildCommitWithRef() returned commit with empty Hash") - } - - // Verify author and committer are set - if gitCommit.Author.Name == "" { - t.Error("BuildCommitWithRef() returned commit with empty Author.Name") - } - if gitCommit.Committer.Name == "" { - t.Error("BuildCommitWithRef() returned commit with empty Committer.Name") - } - - // If the commit has a signature, verify it can be extracted - if tt.wantSig { - // The signature is stored in gitCommit.Signature, not in gitCommit.Encoded - // gitCommit.Encoded contains the encoded commit without the signature - if gitCommit.Signature == "" { - t.Error("BuildCommitWithRef() returned commit with empty Signature field") - } - // Verify the signature contains the expected SSH signature markers - if !strings.Contains(gitCommit.Signature, "-----BEGIN SSH SIGNATURE-----") { - t.Error("BuildCommitWithRef() signature does not contain SSH signature start marker") - } - if !strings.Contains(gitCommit.Signature, "-----END SSH SIGNATURE-----") { - t.Error("BuildCommitWithRef() signature does not contain SSH signature end marker") - } - } - } - }) - } -} - -func TestBuildCommitWithRefAndVerifySSH(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - tests := []struct { - name string - fixture string - authFile string - wantErr bool - }{ - { - name: "ed25519 signed commit", - fixture: "commit_ed25519_signed.txt", - authFile: "authorized_keys_ed25519", - wantErr: false, - }, - { - name: "rsa signed commit", - fixture: "commit_rsa_signed.txt", - authFile: "authorized_keys_rsa", - wantErr: false, - }, - { - name: "ecdsa p256 signed commit", - fixture: "commit_ecdsa_p256_signed.txt", - authFile: "authorized_keys_ecdsa_p256", - wantErr: false, - }, - { - name: "ecdsa p384 signed commit", - fixture: "commit_ecdsa_p384_signed.txt", - authFile: "authorized_keys_ecdsa_p384", - wantErr: false, - }, - { - name: "ecdsa p521 signed commit", - fixture: "commit_ecdsa_p521_signed.txt", - authFile: "authorized_keys_ecdsa_p521", - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, tt.fixture)) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) - } - - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) - if err != nil { - t.Fatalf("BuildCommitWithRef() error = %v", err) - } - - // Read the authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.authFile)) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Verify the SSH signature using the git.Commit's VerifySSH method - fingerprint, err := gitCommit.VerifySSH(string(authorizedKeys)) - if (err != nil) != tt.wantErr { - t.Errorf("git.Commit.VerifySSH() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.wantErr { - if fingerprint == "" { - t.Error("git.Commit.VerifySSH() returned empty fingerprint") - } - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) - } -} - -func TestBuildCommitWithRefWithDifferentRefs(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - // Parse a signed commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) - } - - tests := []struct { - name string - ref plumbing.ReferenceName - wantRef string - }{ - { - name: "branch reference", - ref: plumbing.ReferenceName("refs/heads/main"), - wantRef: "refs/heads/main", - }, - { - name: "tag reference", - ref: plumbing.ReferenceName("refs/tags/v1.0.0"), - wantRef: "refs/tags/v1.0.0", - }, - { - name: "remote branch reference", - ref: plumbing.ReferenceName("refs/remotes/origin/main"), - wantRef: "refs/remotes/origin/main", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Build a git.Commit using BuildCommitWithRef with different references - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, tt.ref) - if err != nil { - t.Fatalf("BuildCommitWithRef() error = %v", err) - } - - // Verify the reference is set correctly - if gitCommit.Reference != tt.wantRef { - t.Errorf("BuildCommitWithRef() reference = %q, want %q", gitCommit.Reference, tt.wantRef) - } - - // Verify other fields are still set correctly - if len(gitCommit.Hash) == 0 { - t.Error("BuildCommitWithRef() returned commit with empty Hash") - } - if gitCommit.Signature == "" { - t.Error("BuildCommitWithRef() returned commit with empty Signature") - } - }) - } -} - -func TestBuildTagFromFixture(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - tests := []struct { - name string - fixture string - wantErr bool - wantSig bool - }{ - { - name: "ed25519 signed tag", - fixture: "tag_ed25519_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "rsa signed tag", - fixture: "tag_rsa_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "ecdsa p256 signed tag", - fixture: "tag_ecdsa_p256_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "ecdsa p384 signed tag", - fixture: "tag_ecdsa_p384_signed.txt", - wantErr: false, - wantSig: true, - }, - { - name: "ecdsa p521 signed tag", - fixture: "tag_ecdsa_p521_signed.txt", - wantErr: false, - wantSig: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if (err != nil) != tt.wantErr { - t.Errorf("BuildTag() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.wantErr { - // Verify the tag was built correctly - if gitTag == nil { - t.Fatal("BuildTag() returned nil tag") - } - - // Check if signature is present as expected - hasSig := gitTag.Signature != "" - if hasSig != tt.wantSig { - t.Errorf("BuildTag() has signature = %v, want %v", hasSig, tt.wantSig) - } - - // Verify the encoded data is present - if len(gitTag.Encoded) == 0 { - t.Error("BuildTag() returned tag with empty Encoded field") - } - - // Verify the name is set correctly - if gitTag.Name == "" { - t.Error("BuildTag() returned tag with empty Name") - } - - // Verify the hash is set - if len(gitTag.Hash) == 0 { - t.Error("BuildTag() returned tag with empty Hash") - } - - // Verify author is set - if gitTag.Author.Name == "" { - t.Error("BuildTag() returned tag with empty Author.Name") - } - - // If the tag has a signature, verify it can be extracted - if tt.wantSig { - if gitTag.Signature == "" { - t.Error("BuildTag() returned tag with empty Signature field") - } - // Verify the signature contains the expected SSH signature markers - if !strings.Contains(gitTag.Signature, "-----BEGIN SSH SIGNATURE-----") { - t.Error("BuildTag() signature does not contain SSH signature start marker") - } - if !strings.Contains(gitTag.Signature, "-----END SSH SIGNATURE-----") { - t.Error("BuildTag() signature does not contain SSH signature end marker") - } - } - } - }) - } -} - -func TestVerifySSHSignatureForTags(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - // Test cases for each key type using fixtures - keyTypes := []struct { - name string - sigFile string - authFile string - wantErr bool - }{ - {"ed25519 valid signature", "tag_ed25519_signed.txt", "authorized_keys_ed25519", false}, - {"rsa valid signature", "tag_rsa_signed.txt", "authorized_keys_rsa", false}, - {"ecdsa_p256 valid signature", "tag_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, - {"ecdsa_p384 valid signature", "tag_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, - {"ecdsa_p521 valid signature", "tag_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, - } - - for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) - } - - // Read the authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Verify the signature using the git.Tag's Signature and Encoded fields - fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKeys)) - if (err != nil) != kt.wantErr { - t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) - return - } - if !kt.wantErr && fingerprint == "" { - t.Errorf("VerifySSHSignature() returned empty fingerprint") - } - if !kt.wantErr { - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) - } - - // Test error cases - t.Run("empty signature", func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) - } - - fingerprint, err := signatures.VerifySSHSignature("", gitTag.Encoded, string(authorizedKeys)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload as the provided signature is empty" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) - } - }) - - t.Run("empty payload", func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) - } - - fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, []byte{}, string(authorizedKeys)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload as the provided payload is empty" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) - } - }) - - t.Run("wrong authorized keys", func(t *testing.T) { - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) - } - - // Use a different key that won't match - wrongKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyM97VxLgOCuB9Eg5cDtTc8ogkdM1xAyJhzODB9cK1 wrong@example.com" - - fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, wrongKey) + fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, invalidAuthKeys) if err == nil { - t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) - } - // The error can be either a parsing error or a verification error - if err != nil && !strings.Contains(err.Error(), "unable to verify payload with any of the given authorized keys") && !strings.Contains(err.Error(), "unable to parse authorized key") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) - } - }) - - t.Run("invalid signature", func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_ed25519")) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_ed25519_signed.txt")) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) - } - - invalidSig := "-----BEGIN SSH SIGNATURE-----\n invalid\n -----END SSH SIGNATURE-----" - - fingerprint, err := signatures.VerifySSHSignature(invalidSig, gitTag.Encoded, string(authorizedKeys)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") + t.Errorf("VerifySSHSignature() expected error for invalid authorized keys, got nil") } if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) + t.Errorf("VerifySSHSignature() returned fingerprint for invalid authorized keys: %s", fingerprint) } - if err != nil && !strings.Contains(err.Error(), "unable to unarmor SSH signature") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) + if err != nil && !strings.Contains(err.Error(), "unable to parse authorized key") { + t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to parse authorized key'", err) } }) } - -func TestVerifySSHSignatureForTagsAllKeyTypes(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - // Test cases for each key type - keyTypes := []struct { - name string - sigFile string - authFile string - wantErr bool - }{ - {"ed25519", "tag_ed25519_signed.txt", "authorized_keys_ed25519", false}, - {"rsa", "tag_rsa_signed.txt", "authorized_keys_rsa", false}, - {"ecdsa_p256", "tag_ecdsa_p256_signed.txt", "authorized_keys_ecdsa_p256", false}, - {"ecdsa_p384", "tag_ecdsa_p384_signed.txt", "authorized_keys_ecdsa_p384", false}, - {"ecdsa_p521", "tag_ecdsa_p521_signed.txt", "authorized_keys_ecdsa_p521", false}, - } - - for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) - } - - // Read the authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, kt.authFile)) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Verify the signature using the git.Tag's Signature and Encoded fields - fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKeys)) - if (err != nil) != kt.wantErr { - t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) - return - } - if !kt.wantErr && fingerprint == "" { - t.Errorf("VerifySSHSignature() returned empty fingerprint") - } - if !kt.wantErr { - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) - } -} - -func TestVerifySSHSignatureForTagsCombinedKeys(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - // Read the combined authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, "authorized_keys_all")) - if err != nil { - t.Fatalf("Failed to read combined authorized keys: %v", err) - } - - // Test each key type against the combined authorized keys - keyTypes := []struct { - name string - sigFile string - wantErr bool - }{ - {"ed25519", "tag_ed25519_signed.txt", false}, - {"rsa", "tag_rsa_signed.txt", false}, - {"ecdsa_p256", "tag_ecdsa_p256_signed.txt", false}, - {"ecdsa_p384", "tag_ecdsa_p384_signed.txt", false}, - {"ecdsa_p521", "tag_ecdsa_p521_signed.txt", false}, - } - - for _, kt := range keyTypes { - t.Run(kt.name, func(t *testing.T) { - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.sigFile)) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) - } - - // Verify the signature with combined authorized keys - fingerprint, err := signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKeys)) - if (err != nil) != kt.wantErr { - t.Errorf("VerifySSHSignature() error = %v, wantErr %v", err, kt.wantErr) - return - } - if !kt.wantErr && fingerprint == "" { - t.Errorf("VerifySSHSignature() returned empty fingerprint") - } - if !kt.wantErr { - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) - } -} - -func TestBuildTagAndVerifySSH(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - tests := []struct { - name string - fixture string - authFile string - wantErr bool - }{ - { - name: "ed25519 signed tag", - fixture: "tag_ed25519_signed.txt", - authFile: "authorized_keys_ed25519", - wantErr: false, - }, - { - name: "rsa signed tag", - fixture: "tag_rsa_signed.txt", - authFile: "authorized_keys_rsa", - wantErr: false, - }, - { - name: "ecdsa p256 signed tag", - fixture: "tag_ecdsa_p256_signed.txt", - authFile: "authorized_keys_ecdsa_p256", - wantErr: false, - }, - { - name: "ecdsa p384 signed tag", - fixture: "tag_ecdsa_p384_signed.txt", - authFile: "authorized_keys_ecdsa_p384", - wantErr: false, - }, - { - name: "ecdsa p521 signed tag", - fixture: "tag_ecdsa_p521_signed.txt", - authFile: "authorized_keys_ecdsa_p521", - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, tt.fixture)) - if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) - } - - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("BuildTag() error = %v", err) - } - - // Read the authorized keys - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.authFile)) - if err != nil { - t.Fatalf("Failed to read authorized keys: %v", err) - } - - // Verify the SSH signature using the git.Tag's VerifySSH method - fingerprint, err := gitTag.VerifySSH(string(authorizedKeys)) - if (err != nil) != tt.wantErr { - t.Errorf("git.Tag.VerifySSH() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.wantErr { - if fingerprint == "" { - t.Error("git.Tag.VerifySSH() returned empty fingerprint") - } - t.Logf("Verified with fingerprint: %s", fingerprint) - } - }) - } -} diff --git a/git/signatures/testdata/ssh_signatures/README.md b/git/signatures/testdata/ssh_signatures/README.md index ab402ffec..1e57a520d 100644 --- a/git/signatures/testdata/ssh_signatures/README.md +++ b/git/signatures/testdata/ssh_signatures/README.md @@ -163,14 +163,7 @@ The script generates the following files: - `key_ecdsa_p384.pub` - ECDSA P-384 public key - `key_ecdsa_p521.pub` - ECDSA P-521 public key - `key_ed25519.pub` - ED25519 public key - -### Authorized Keys Files -- `authorized_keys_rsa` - RSA public key -- `authorized_keys_ecdsa_p256` - ECDSA P-256 public key -- `authorized_keys_ecdsa_p384` - ECDSA P-384 public key -- `authorized_keys_ecdsa_p521` - ECDSA P-521 public key -- `authorized_keys_ed25519` - ED25519 public key -- `authorized_keys_all` - All public keys combined +- `keys_all.pub` - All public keys ### Verified Signers Files - `verified_signers_rsa` - RSA public key with git namespace diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 deleted file mode 100644 index 7364a9a27..000000000 --- a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p256 +++ /dev/null @@ -1 +0,0 @@ -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 deleted file mode 100644 index aabefb80b..000000000 --- a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p384 +++ /dev/null @@ -1 +0,0 @@ -ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 b/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 deleted file mode 100644 index 82d92898f..000000000 --- a/git/signatures/testdata/ssh_signatures/authorized_keys_ecdsa_p521 +++ /dev/null @@ -1 +0,0 @@ -ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 b/git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 deleted file mode 100644 index 8f745c471..000000000 --- a/git/signatures/testdata/ssh_signatures/authorized_keys_ed25519 +++ /dev/null @@ -1 +0,0 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_rsa b/git/signatures/testdata/ssh_signatures/authorized_keys_rsa deleted file mode 100644 index b02a4d38f..000000000 --- a/git/signatures/testdata/ssh_signatures/authorized_keys_rsa +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com diff --git a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh index ab7cba060..b14574bd8 100755 --- a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh +++ b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh @@ -46,18 +46,6 @@ generate_ssh_key() { echo " ✓ key_${key_name}.pub_fingerprint created" } -# Function to create authorized_keys files -create_authorized_keys() { - local key_name=$1 - local output_file="$SCRIPT_DIR/authorized_keys_${key_name}" - - echo "Creating authorized_keys for $key_name..." - - # Copy public key - cp "$TEMP_DIR/${key_name}.pub" "$output_file" - echo " ✓ $output_file created" -} - # Function to create verified signers files with git namespace create_verified_signers() { local key_name=$1 @@ -71,8 +59,8 @@ create_verified_signers() { } # Function to create combined authorized_keys file -create_combined_authorized_keys() { - local output_file="$SCRIPT_DIR/authorized_keys_all" +create_combined_pub_keys() { + local output_file="$SCRIPT_DIR/keys_all.pub" echo "Creating combined authorized_keys..." @@ -218,18 +206,11 @@ main() { generate_ssh_key "ed25519" "" "ed25519" echo "" - echo "Step 2: Create authorized_keys files..." + echo "Step 2: Create pub_keys files..." echo "-----------------------------------------------" - # Individual authorized_keys files - create_authorized_keys "rsa" - create_authorized_keys "ecdsa_p256" - create_authorized_keys "ecdsa_p384" - create_authorized_keys "ecdsa_p521" - create_authorized_keys "ed25519" - - # Combined authorized_keys file - create_combined_authorized_keys + # Combined pub_keys file + create_combined_pub_keys echo "" echo "Step 3: Create verified signers files..." diff --git a/git/signatures/testdata/ssh_signatures/authorized_keys_all b/git/signatures/testdata/ssh_signatures/keys_all.pub similarity index 100% rename from git/signatures/testdata/ssh_signatures/authorized_keys_all rename to git/signatures/testdata/ssh_signatures/keys_all.pub From 04b565e5566824ffc19e807dbb665f63f844e0ed Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 10 Mar 2026 09:53:34 +0100 Subject: [PATCH 08/22] fixes text fixtures public key names Signed-off-by: Ricardo Bartels --- git/git_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/git/git_test.go b/git/git_test.go index d1869b520..a9ce157ee 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -762,18 +762,18 @@ func TestCommit_VerifySSH(t *testing.T) { { name: "valid SSH signature", sigFile: "commit_rsa_signed.txt", - authorizedKeys: "authorized_keys_rsa", + authorizedKeys: "key_rsa.pub", }, { name: "missing signature", sigFile: "commit_unsigned.txt", - authorizedKeys: "authorized_keys_rsa", + authorizedKeys: "key_rsa.pub", wantErr: "unable to verify Git commit SSH signature: unable to verify payload as the provided signature is empty", }, { name: "invalid signature", sigFile: "commit_rsa_signed.txt", - authorizedKeys: "authorized_keys_ed25519", + authorizedKeys: "key_ed25519.pub", wantErr: "unable to verify Git commit SSH signature: unable to verify payload with any of the given authorized keys", }, { @@ -840,18 +840,18 @@ func TestTag_VerifySSH(t *testing.T) { { name: "valid SSH signature", sigFile: "tag_rsa_signed.txt", - authorizedKeys: "authorized_keys_rsa", + authorizedKeys: "key_rsa.pub", }, { name: "missing signature", sigFile: "commit_unsigned.txt", - authorizedKeys: "authorized_keys_rsa", + authorizedKeys: "key_rsa.pub", wantErr: "unable to verify Git tag SSH signature: unable to verify payload as the provided signature is empty", }, { name: "invalid signature", sigFile: "tag_rsa_signed.txt", - authorizedKeys: "authorized_keys_ed25519", + authorizedKeys: "key_ed25519.pub", wantErr: "unable to verify Git tag SSH signature: unable to verify payload with any of the given authorized keys", }, { From 107aaf00d47ff4bcc7edb88fbe728a7e4525061e Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 10 Mar 2026 14:32:15 +0100 Subject: [PATCH 09/22] adds 'SignatureTypeEmpty' check and improves description of 'GetSignatureType' Signed-off-by: Ricardo Bartels --- git/git_test.go | 2 +- git/signatures/signature.go | 14 +++++++++++++- git/signatures/signature_test.go | 2 +- git/signatures/ssh_signature.go | 7 ++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/git/git_test.go b/git/git_test.go index a9ce157ee..ea2347ca8 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -550,7 +550,7 @@ func TestSignatureType(t *testing.T) { name: "unsigned", commit: &Commit{}, tag: &Tag{}, - want: "unknown", + want: "empty", }, { name: "unknown signature type", diff --git a/git/signatures/signature.go b/git/signatures/signature.go index f85d4d03c..5b7ba3ed5 100644 --- a/git/signatures/signature.go +++ b/git/signatures/signature.go @@ -32,6 +32,8 @@ const ( SignatureTypeX509 SignatureType = "x509" // SignatureTypeUnknown represents an unknown signature type. SignatureTypeUnknown SignatureType = "unknown" + // SignatureTypeEmpty represents an empty signature. + SignatureTypeEmpty SignatureType = "empty" ) // IsX509Signature is the prefix used by Git to identify x509 signatures. @@ -72,9 +74,16 @@ func IsX509Signature(signature string) bool { return startsWithStrings(signature, X509SignaturePrefix) } +// IsEmptySignature tests if the given signature string is empty. +// It returns true if the signature string has a length of 0. +func IsEmptySignature(signature string) bool { + return len(signature) == 0 +} + // GetSignatureType returns the type of the signature as a string. // It returns "pgp" for PGP signatures, "ssh" for SSH signatures, -// and "unknown" for unrecognized or empty signatures. +// "x509" for S/MIME signatures, "empty" for an empty signature +// and "unknown" for unrecognized signatures. func GetSignatureType(signature string) string { if IsPGPSignature(signature) { return string(SignatureTypePGP) @@ -85,5 +94,8 @@ func GetSignatureType(signature string) string { if IsX509Signature(signature) { return string(SignatureTypeX509) } + if IsEmptySignature(signature) { + return string(SignatureTypeEmpty) + } return string(SignatureTypeUnknown) } diff --git a/git/signatures/signature_test.go b/git/signatures/signature_test.go index 4350421a4..b079b8784 100644 --- a/git/signatures/signature_test.go +++ b/git/signatures/signature_test.go @@ -217,7 +217,7 @@ func TestGetSignatureType(t *testing.T) { { name: "empty signature", signature: "", - want: string(SignatureTypeUnknown), + want: string(SignatureTypeEmpty), }, { name: "unknown signature", diff --git a/git/signatures/ssh_signature.go b/git/signatures/ssh_signature.go index c3a146cbf..f9c80719a 100644 --- a/git/signatures/ssh_signature.go +++ b/git/signatures/ssh_signature.go @@ -27,6 +27,8 @@ import ( gossh "golang.org/x/crypto/ssh" ) +const SSHSignatureNamespace = "git" + // SSHSignaturePrefix is the prefix used by Git to identify SSH signatures. // https://github.com/git/git/blob/7b2bccb0d58d4f24705bf985de1f4612e4cf06e5/gpg-interface.c#L71 var SSHSignaturePrefix = []string{"-----BEGIN SSH SIGNATURE-----"} @@ -56,7 +58,7 @@ func ParseAuthorizedKeys(authorizedKeys string) ([]gossh.PublicKey, error) { return publicKeys, nil } -// verifySSHSignature verifies the SSH signature against the payload using +// VerifySSHSignature verifies the SSH signature against the payload using // the provided authorized keys. It returns the fingerprint of the key that // successfully verified the signature, or an error. func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...string) (string, error) { @@ -88,8 +90,7 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri // Try to verify with each public key for _, pubKey := range publicKeys { // Verify the signature using sshsig library - // The namespace for Git is "git" - err := sshsig.Verify(bytes.NewReader(payload), sig, pubKey, sig.HashAlgorithm, "git") + err := sshsig.Verify(bytes.NewReader(payload), sig, pubKey, sig.HashAlgorithm, SSHSignatureNamespace) if err == nil { // Signature verified successfully return getPublicKeyFingerprint(pubKey), nil From bfff3ecd27486c2dd70c144f6ae102390a2fd85b Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 19 Mar 2026 12:12:51 +0100 Subject: [PATCH 10/22] Update git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh Co-authored-by: Paulo Gomes Signed-off-by: Ricardo Signed-off-by: Ricardo Bartels --- git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh index 66de6958e..f6d7e740f 100755 --- a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh +++ b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh @@ -244,4 +244,4 @@ main() { find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' } -main \ No newline at end of file +main From 5b46d9c6c44f39470e640ad61c8a1c452e056877 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Thu, 19 Mar 2026 12:30:36 +0100 Subject: [PATCH 11/22] removes insecure DSA keys from signature test Signed-off-by: Ricardo Bartels --- git/signatures/gpg_signature_test.go | 1 - .../testdata/gpg_signatures/README.md | 23 +---- .../gpg_signatures/commit_dsa_2048_signed.txt | 12 --- .../gpg_signatures/generate_gpg_fixtures.sh | 91 +++++++++---------- .../testdata/gpg_signatures/key_dsa_2048.pub | 25 ----- .../gpg_signatures/tag_dsa_2048_signed.txt | 13 --- 6 files changed, 45 insertions(+), 120 deletions(-) delete mode 100644 git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt delete mode 100644 git/signatures/testdata/gpg_signatures/key_dsa_2048.pub delete mode 100644 git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt diff --git a/git/signatures/gpg_signature_test.go b/git/signatures/gpg_signature_test.go index 22ea81575..40c7c6727 100644 --- a/git/signatures/gpg_signature_test.go +++ b/git/signatures/gpg_signature_test.go @@ -208,7 +208,6 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { {"ecdsa_p256 valid signature", "commit_ecdsa_p256_signed.txt", "tag_ecdsa_p256_signed.txt", "key_ecdsa_p256.pub", false}, {"ecdsa_p384 valid signature", "commit_ecdsa_p384_signed.txt", "tag_ecdsa_p384_signed.txt", "key_ecdsa_p384.pub", false}, {"ecdsa_p521 valid signature", "commit_ecdsa_p521_signed.txt", "tag_ecdsa_p521_signed.txt", "key_ecdsa_p521.pub", false}, - {"dsa_2048 valid signature", "commit_dsa_2048_signed.txt", "tag_dsa_2048_signed.txt", "key_dsa_2048.pub", false}, {"brainpool_p256 valid signature", "commit_brainpool_p256_signed.txt", "tag_brainpool_p256_signed.txt", "key_brainpool_p256.pub", false}, {"brainpool_p384 valid signature", "commit_brainpool_p384_signed.txt", "tag_brainpool_p384_signed.txt", "key_brainpool_p384.pub", false}, {"brainpool_p512 valid signature", "commit_brainpool_p512_signed.txt", "tag_brainpool_p512_signed.txt", "key_brainpool_p512.pub", false}, diff --git a/git/signatures/testdata/gpg_signatures/README.md b/git/signatures/testdata/gpg_signatures/README.md index 29fd5fc70..e7e8187ed 100644 --- a/git/signatures/testdata/gpg_signatures/README.md +++ b/git/signatures/testdata/gpg_signatures/README.md @@ -20,7 +20,6 @@ The [`generate_gpg_fixtures.sh`](generate_gpg_fixtures.sh) script automates the 1. **GPG Key Pairs** in supported variants: - RSA (2048 and 4096 bits) - - DSA (2048 bits) - ECC/ECDSA (NIST P-256, P-384, P-521) - Brainpool curves (P-256, P-384, P-512) - EdDSA (Ed25519, Ed448) @@ -81,18 +80,6 @@ Expire-Date: 0 EOF gpg --batch --generate-key batch_rsa_4096.txt -# DSA 2048-bit key -cat > batch_dsa_2048.txt < batch_ecdsa_p256.txt < key_rsa_2048.pub gpg --armor --export test-rsa-4096@example.com > key_rsa_4096.pub -gpg --armor --export test-dsa-2048@example.com > key_dsa_2048.pub gpg --armor --export test-ecdsa-p256@example.com > key_ecdsa_p256.pub gpg --armor --export test-ecdsa-p384@example.com > key_ecdsa_p384.pub gpg --armor --export test-ecdsa-p521@example.com > key_ecdsa_p521.pub @@ -284,7 +270,6 @@ The script generates the following files: ### Public Keys - `key_rsa_2048.pub` - RSA 2048-bit public key - `key_rsa_4096.pub` - RSA 4096-bit public key -- `key_dsa_2048.pub` - DSA 2048-bit public key - `key_ecdsa_p256.pub` - ECDSA P-256 public key - `key_ecdsa_p384.pub` - ECDSA P-384 public key - `key_ecdsa_p521.pub` - ECDSA P-521 public key @@ -297,7 +282,6 @@ The script generates the following files: ### Signed Commits - `commit_rsa_2048_signed.txt` - RSA 2048-bit signed commit - `commit_rsa_4096_signed.txt` - RSA 4096-bit signed commit -- `commit_dsa_2048_signed.txt` - DSA 2048-bit signed commit - `commit_ecdsa_p256_signed.txt` - ECDSA P-256 signed commit - `commit_ecdsa_p384_signed.txt` - ECDSA P-384 signed commit - `commit_ecdsa_p521_signed.txt` - ECDSA P-521 signed commit @@ -310,7 +294,6 @@ The script generates the following files: ### Signed Tags - `tag_rsa_2048_signed.txt` - RSA 2048-bit signed tag - `tag_rsa_4096_signed.txt` - RSA 4096-bit signed tag -- `tag_dsa_2048_signed.txt` - DSA 2048-bit signed tag - `tag_ecdsa_p256_signed.txt` - ECDSA P-256 signed tag - `tag_ecdsa_p384_signed.txt` - ECDSA P-384 signed tag - `tag_ecdsa_p521_signed.txt` - ECDSA P-521 signed tag @@ -330,10 +313,6 @@ The script generates the following files: - **RSA 4096**: Stronger RSA key with 4096-bit modulus - Widely supported, but slower than ECC keys -### DSA (Digital Signature Algorithm) -- **DSA 2048**: Legacy algorithm, 2048-bit key -- Less secure than modern alternatives, included for compatibility testing - ### ECDSA (Elliptic Curve Digital Signature Algorithm) - **P-256**: NIST P-256 curve (secp256r1) - **P-384**: NIST P-384 curve (secp384r1) @@ -385,7 +364,7 @@ If key generation fails, ensure that: ### Script structure The script uses separate functions for different key types: -- `generate_rsa_dsa_key()` - For RSA and DSA keys with key length validation +- `generate_rsa_dsa_key()` - For RSA keys with key length validation - `generate_ecc_key()` - For ECC/ECDSA/EdDSA keys with curve validation - `create_signed_object()` - For creating signed commits and tags - `create_unsigned_commit()` - For creating unsigned test commits diff --git a/git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt b/git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt deleted file mode 100644 index 1132feef8..000000000 --- a/git/signatures/testdata/gpg_signatures/commit_dsa_2048_signed.txt +++ /dev/null @@ -1,12 +0,0 @@ -tree 94e5cb8fdb0551092fe394328dd9de2dbd8394f3 -author Test User 1772188965 +0100 -committer Test User 1772188965 +0100 -gpgsig -----BEGIN PGP SIGNATURE----- - - iHUEABEIAB0WIQQ3p1oEVydAtN6w28QIntqNADkiBwUCaaF1JQAKCRAIntqNADki - B3brAP9bhBteRaxkRDN2rXbAxFdBLACqgqTH10Zv4if3gxZxKQD/ZoAiBYUyWq3C - HKyihQ+PCD2wMv6tyzkC5RI5mumh5Fw= - =C3Wn - -----END PGP SIGNATURE----- - -Test commit signed with dsa_2048 diff --git a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh index f6d7e740f..896881f99 100755 --- a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh +++ b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh @@ -31,52 +31,52 @@ generate_key() { local key_type=$1 local key_param=$2 local key_name=$3 - + echo "Generating $key_type key pair ($key_name)..." - + # Create batch configuration for GPG local batch_file="$TEMP_DIR/batch_${key_name}.txt" cat > "$batch_file" <> "$batch_file" ;; ecdsa|eddsa) echo "Key-Curve: $key_param" >> "$batch_file" ;; esac - + cat >> "$batch_file" <&1 - + # Get the key ID local key_id key_id=$(gpg --list-keys --with-colons "test-${key_name}@example.com" | grep '^fpr' | head -1 | cut -d: -f10) - + echo " Key ID: $key_id" - + # Export public key gpg --armor --export "test-${key_name}@example.com" > "$SCRIPT_DIR/key_${key_name}.pub" echo " ✓ key_${key_name}.pub created" - + # Export secret key (for signing) gpg --armor --export-secret-keys "test-${key_name}@example.com" > "$TEMP_DIR/${key_name}.sec" - + # Store key ID for later use echo "$key_id" > "$TEMP_DIR/${key_name}_id.txt" - + rm -f "$batch_file" echo " ✓ $key_name key pair generated successfully" } @@ -85,41 +85,41 @@ EOF create_signed_object() { local object_type=$1 local key_name=$2 - + echo "Creating signed $object_type for $key_name..." - + # Get key ID local key_id key_id=$(cat "$TEMP_DIR/${key_name}_id.txt") - + # Create temporary Git repository local repo_dir="$TEMP_DIR/repo_${key_name}_${object_type}" mkdir -p "$repo_dir" cd "$repo_dir" - + git init git config user.name "$TEST_USER_NAME" git config user.email "$TEST_USER_EMAIL" git config gpg.program gpg git config user.signingkey "$key_id" - + # Import the secret key for signing gpg --batch --import "$TEMP_DIR/${key_name}.sec" 2>/dev/null - + # Create file and commit echo "Test content for $key_name $object_type" > test.txt git add test.txt git commit -m "Test commit for $object_type" - + if [[ "$object_type" == "commit" ]]; then # Sign the commit (amend) git commit --amend --allow-empty -S -m "Test commit signed with $key_name" - + # Verify the signed commit echo " Verifying signed commit..." git verify-commit HEAD 2>&1 | grep -q "Good signature" echo " ✓ Commit signature verified successfully" - + # Export commit object git cat-file commit HEAD > "$SCRIPT_DIR/commit_${key_name}_signed.txt" cd "$SCRIPT_DIR" @@ -128,12 +128,12 @@ create_signed_object() { elif [[ "$object_type" == "tag" ]]; then # Create and sign tag git tag -a "test-tag-${key_name}" -m "Test tag signed with $key_name" -s - + # Verify the signed tag echo " Verifying signed tag..." git verify-tag "test-tag-${key_name}" 2>&1 | grep -q "Good signature" echo " ✓ Tag signature verified successfully" - + # Export tag object git cat-file tag "test-tag-${key_name}" > "$SCRIPT_DIR/tag_${key_name}_signed.txt" cd "$SCRIPT_DIR" @@ -144,64 +144,61 @@ create_signed_object() { # Function to create unsigned commit create_unsigned_commit() { echo "Creating unsigned commit..." - + # Create temporary Git repository local repo_dir="$TEMP_DIR/repo_unsigned" mkdir -p "$repo_dir" cd "$repo_dir" - + git init git config user.name "$TEST_USER_NAME" git config user.email "$TEST_USER_EMAIL" - + # Create file and commit (without signature) echo "Test content unsigned" > test.txt git add test.txt git commit -m "Test commit unsigned" - + # Export commit object git cat-file commit HEAD > "$SCRIPT_DIR/commit_unsigned.txt" - + cd "$SCRIPT_DIR" echo " ✓ commit_unsigned.txt created" } # Main program main() { - echo "Step 1: Generate RSA/DSA keys..." + echo "Step 1: Generate RSA keys..." echo "-----------------------------------" - + # RSA keys (different key lengths) generate_key "RSA" "2048" "rsa_2048" generate_key "RSA" "4096" "rsa_4096" - - # DSA key (legacy, but still supported) - generate_key "DSA" "2048" "dsa_2048" - + echo "" echo "Step 2: Generate ECC keys..." echo "-----------------------------------" - + # ECDSA keys (different curves) generate_key "ecdsa" "NIST P-256" "ecdsa_p256" generate_key "ecdsa" "NIST P-384" "ecdsa_p384" generate_key "ecdsa" "NIST P-521" "ecdsa_p521" - + # Brainpool curves generate_key "ecdsa" "brainpoolP256r1" "brainpool_p256" generate_key "ecdsa" "brainpoolP384r1" "brainpool_p384" generate_key "ecdsa" "brainpoolP512r1" "brainpool_p512" - + # Ed25519 (modern elliptic curve) generate_key "eddsa" "Ed25519" "ed25519" - + # Ed448 (less common) generate_key "eddsa" "Ed448" "ed448" - + echo "" echo "Step 3: Create signed commits..." echo "----------------------------------------" - + # Get list of successfully generated keys local keys=() key_name="" for key_file in "$TEMP_DIR"/*_id.txt; do @@ -210,32 +207,32 @@ main() { keys+=("$key_name") fi done - + # Signed commits for each key type for key_name in "${keys[@]}"; do create_signed_object "commit" "$key_name" done - + echo "" echo "Step 4: Create signed tags..." echo "-------------------------------------" - + # Signed tags for each key type for key_name in "${keys[@]}"; do create_signed_object "tag" "$key_name" done - + echo "" echo "Step 5: Create unsigned commit..." echo "------------------------------------------" - + create_unsigned_commit - + echo "" echo "=== Cleanup ===" rm -rf "$TEMP_DIR" echo "Temporary directory removed" - + echo "" echo "=== Done! ===" echo "All test fixtures have been successfully created." diff --git a/git/signatures/testdata/gpg_signatures/key_dsa_2048.pub b/git/signatures/testdata/gpg_signatures/key_dsa_2048.pub deleted file mode 100644 index 908d2c05b..000000000 --- a/git/signatures/testdata/gpg_signatures/key_dsa_2048.pub +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQMuBGmhdSIRCACoIRoP5Lvi8g2dE7Nn9/AI6O3SEnCotRNnT10nmELXwqYonn/9 -3LIhiMMqdMPgtIQZLuvoUlZzMG/mufeHZlezhfUqbFOHY09Czcuvm0zkTBwIDq+a -CA729ICuPAgYAq53iPab5WqGO9H/LAX/6yYGLhQ0GHFdpnyxWnO/OtSBlLxqL+97 -oz6lhGSC9JSNxazSr/3qwvaytzOMI8ptDIVlpydv8WTghXBkjrIkXR8vR++2aEhK -voVCS9HSC19kg9B2fybGu4M5foP9ZIL62O+6rvopGSA1tmWctR2oIoi3Bi7x5vzA -f3NyOYZT8F+nnxwotdxzeoYxh/rVld4i321jAQCYLj2IVmgisitLwHKxXVT/aX3O -CAaEI/ESOcBm/arO9Qf9HLfKlc2wVtXL1g0KjaMZVvh9nvqzchBxmTtlLGmU5gIU -r7ZqDQ2pqavmZJ1YRBlGPRLnL8n1NXZMj8OHPRHyUJQ4oph8FFRoBOwspEw+i67j -jl42mc20IhOU28QPmtsmlEHJwdhZsYmCImtWFilHS8ThPewY+Qn2S0L+4nnBkTy4 -1y3ZGRSzQQhH1jOJtBdNBadQcrYppMWgxHNIe0V3s+7FCc89jJRECj608ZrlLYT1 -7KGPZDPqDtR668Br7sP6PjPJD6mnycsrQSNu1rFU3fsuClVlLeT9mWpwQspfQEYa -vmuRh48uuGUQFBanDM5EPTG7c4aB6Gz1k8J/HtXRpwf/Rc8eHZIVGrpc/7CuChGF -fqloBvAz77A3Blr7KaYIViEXj9dcw75Aurtk9lhtUpYe4A66ZdyoZE03xsKmATKX -Ois1YgBaQGEZoOM632pbv3bFrSCjrZnLMnwLIGDhqKnJy7H0mALKL9iILcN7lF0P -WU1YSNgZFU6X70aJvwEmOjeBM5YhGS+e4OPZW/z+b3f/1jE3dGJwz6LsT+M8+xWw -uqN1ZJ+Ijvg8k6HIFx4eXY0zPLElIaWkZExNki/T35jnazb8ZzCeu4/RiJz6YMwd -OAPIZ3I0dZJe5BO32eRbMQFb+OzEcVTNV5Jc/m09b9jEfOvmrHRHoDgGrF6/0S3Y -R7QlVGVzdCBVc2VyIDx0ZXN0LWRzYV8yMDQ4QGV4YW1wbGUuY29tPoiTBBMRCAA7 -FiEEN6daBFcnQLTesNvECJ7ajQA5IgcFAmmhdSICGyMFCwkIBwICIgIGFQoJCAsC -BBYCAwECHgcCF4AACgkQCJ7ajQA5IgcsBAD9F9koK8sIUApNcCFUqCGR9olYimkN -juoedSfOpMV/+j0A+wXU0jUfweGWUv7MPGmh1Sn0oMOBZTIL0LU+x/F3glLl -=lJcF ------END PGP PUBLIC KEY BLOCK----- diff --git a/git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt b/git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt deleted file mode 100644 index 46578ae13..000000000 --- a/git/signatures/testdata/gpg_signatures/tag_dsa_2048_signed.txt +++ /dev/null @@ -1,13 +0,0 @@ -object 8ee3b79d5ad1fa463deea7fc9bfcbca311168d01 -type commit -tag test-tag-dsa_2048 -tagger Test User 1772188968 +0100 - -Test tag signed with dsa_2048 ------BEGIN PGP SIGNATURE----- - -iHUEABEIAB0WIQQ3p1oEVydAtN6w28QIntqNADkiBwUCaaF1KAAKCRAIntqNADki -Byq0AP9rHhQiJKh3rPNYW06C6N9yGnccU8nE5S5EfeH8Gps6SQD/f19dyM5euse9 -vylc3KD1sfdFekiLuW2WpDIw4JbAbMg= -=k9QD ------END PGP SIGNATURE----- From bcac53936373296a62ef83925e33553356e7f8eb Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Mon, 27 Apr 2026 22:32:50 +0200 Subject: [PATCH 12/22] updates git go.mod dependencies Signed-off-by: Ricardo Bartels --- git/go.mod | 7 ------- git/go.sum | 2 -- 2 files changed, 9 deletions(-) diff --git a/git/go.mod b/git/go.mod index 6326fd830..77b9f89e3 100644 --- a/git/go.mod +++ b/git/go.mod @@ -20,13 +20,6 @@ require ( github.com/fluxcd/pkg/version v0.15.0 github.com/go-git/go-billy/v5 v5.9.0 github.com/go-git/go-git/v5 v5.19.1 - github.com/onsi/gomega v1.40.0 - golang.org/x/crypto v0.50.0 - github.com/fluxcd/pkg/gittestserver v0.28.0 - github.com/fluxcd/pkg/ssh v0.25.0 - github.com/fluxcd/pkg/version v0.15.0 - github.com/go-git/go-billy/v5 v5.9.0 - github.com/go-git/go-git/v5 v5.19.0 github.com/hiddeco/sshsig v0.2.0 github.com/onsi/gomega v1.40.0 golang.org/x/crypto v0.50.0 diff --git a/git/go.sum b/git/go.sum index 40ff165ae..4e0ae2093 100644 --- a/git/go.sum +++ b/git/go.sum @@ -42,8 +42,6 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/hiddeco/sshsig v0.2.0 h1:gMWllgKCITXdydVkDL+Zro0PU96QI55LwUwebSwNTSw= github.com/hiddeco/sshsig v0.2.0/go.mod h1:nJc98aGgiH6Yql2doqH4CTBVHexQA40Q+hMMLHP4EqE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= From 21dcb593435e8cab3cd8ae87c8e6bf52aaffa954 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Fri, 8 May 2026 23:34:50 +0200 Subject: [PATCH 13/22] fixes tests for unsigned tags Signed-off-by: Ricardo Bartels --- git/git_test.go | 4 ++-- .../testdata/gpg_signatures/generate_gpg_fixtures.sh | 10 +++++++--- .../testdata/gpg_signatures/tag_unsigned.txt | 6 ++++++ .../testdata/ssh_signatures/generate_ssh_fixtures.sh | 11 ++++++++--- .../testdata/ssh_signatures/tag_unsigned.txt | 6 ++++++ 5 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 git/signatures/testdata/gpg_signatures/tag_unsigned.txt create mode 100644 git/signatures/testdata/ssh_signatures/tag_unsigned.txt diff --git a/git/git_test.go b/git/git_test.go index ea2347ca8..3e286654d 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -677,7 +677,7 @@ func TestTag_VerifyGPG(t *testing.T) { }, { name: "missing signature", - sigFile: "commit_unsigned.txt", + sigFile: "tag_unsigned.txt", keyFile: "key_rsa_2048.pub", wantErr: "unable to verify Git tag: unable to verify payload as the provided signature is empty", }, @@ -844,7 +844,7 @@ func TestTag_VerifySSH(t *testing.T) { }, { name: "missing signature", - sigFile: "commit_unsigned.txt", + sigFile: "tag_unsigned.txt", authorizedKeys: "key_rsa.pub", wantErr: "unable to verify Git tag SSH signature: unable to verify payload as the provided signature is empty", }, diff --git a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh index 896881f99..30514ebeb 100755 --- a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh +++ b/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh @@ -142,7 +142,7 @@ create_signed_object() { } # Function to create unsigned commit -create_unsigned_commit() { +create_unsigned_commit_and_tag() { echo "Creating unsigned commit..." # Create temporary Git repository @@ -162,6 +162,10 @@ create_unsigned_commit() { # Export commit object git cat-file commit HEAD > "$SCRIPT_DIR/commit_unsigned.txt" + # Create and export tag object + git tag -a test-tag -m "Test tag" + git cat-file tag test-tag > "$SCRIPT_DIR/tag_unsigned.txt" + cd "$SCRIPT_DIR" echo " ✓ commit_unsigned.txt created" } @@ -226,7 +230,7 @@ main() { echo "Step 5: Create unsigned commit..." echo "------------------------------------------" - create_unsigned_commit + create_unsigned_commit_and_tag echo "" echo "=== Cleanup ===" @@ -238,7 +242,7 @@ main() { echo "All test fixtures have been successfully created." echo "" echo "Created files:" - find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' + find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' | sort } main diff --git a/git/signatures/testdata/gpg_signatures/tag_unsigned.txt b/git/signatures/testdata/gpg_signatures/tag_unsigned.txt new file mode 100644 index 000000000..fc22314e4 --- /dev/null +++ b/git/signatures/testdata/gpg_signatures/tag_unsigned.txt @@ -0,0 +1,6 @@ +object 4aab80f202c9442c6bb439d6985d70592d30811a +type commit +tag test-tag +tagger Test User 1772188971 +0200 + +Test tag diff --git a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh index b14574bd8..8386b012f 100755 --- a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh +++ b/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh @@ -163,8 +163,9 @@ create_signed_object() { } # Function to create unsigned commit -create_unsigned_commit() { +create_unsigned_commit_and_tag() { local commit_file="$SCRIPT_DIR/commit_unsigned.txt" + local tag_file="$SCRIPT_DIR/tag_unsigned.txt" echo "Creating unsigned commit..." @@ -185,6 +186,10 @@ create_unsigned_commit() { # Export commit object git cat-file commit HEAD > "$commit_file" + # Create and export tag object + git tag -a test-tag -m "Test tag" + git cat-file tag test-tag > "$tag_file" + cd "$SCRIPT_DIR" echo " ✓ $commit_file created" } @@ -252,7 +257,7 @@ main() { echo "Step 6: Create unsigned commit..." echo "------------------------------------------" - create_unsigned_commit + create_unsigned_commit_and_tag echo "" echo "=== Cleanup ===" @@ -264,7 +269,7 @@ main() { echo "All test fixtures have been successfully created." echo "" echo "Created files:" - find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" -o -name "authorized_keys*" -o -name "verified_signers*" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' + find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" -o -name "authorized_keys*" -o -name "verified_signers*" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' | sort } # Run script diff --git a/git/signatures/testdata/ssh_signatures/tag_unsigned.txt b/git/signatures/testdata/ssh_signatures/tag_unsigned.txt new file mode 100644 index 000000000..4e5a55762 --- /dev/null +++ b/git/signatures/testdata/ssh_signatures/tag_unsigned.txt @@ -0,0 +1,6 @@ +object 44e81f94b13509da0a0c9ad89b590c786b383a28 +type commit +tag test-tag +tagger Test User 1772153090 +0200 + +Test tag From a67227a6d23cfc82170e123f9b6e649eb790b861 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Mon, 18 May 2026 23:22:52 +0200 Subject: [PATCH 14/22] renames package to signature and fixes requested changes Signed-off-by: Ricardo Bartels --- git/git.go | 26 +- git/git_test.go | 8 +- .../gpg_signature.go | 6 +- .../gpg_signature_test.go | 16 +- git/{signatures => signature}/signature.go | 31 +- .../signature_test.go | 4 +- .../ssh_signature.go | 15 +- .../ssh_signature_test.go | 317 ++++++++++++++++-- .../testdata/gpg_signatures/README.md | 0 .../commit_brainpool_p256_signed.txt | 0 .../commit_brainpool_p384_signed.txt | 0 .../commit_brainpool_p512_signed.txt | 0 .../commit_ecdsa_p256_signed.txt | 0 .../commit_ecdsa_p384_signed.txt | 0 .../commit_ecdsa_p521_signed.txt | 0 .../gpg_signatures/commit_ed25519_signed.txt | 0 .../gpg_signatures/commit_ed448_signed.txt | 0 .../gpg_signatures/commit_rsa_2048_signed.txt | 0 .../gpg_signatures/commit_rsa_4096_signed.txt | 0 .../gpg_signatures/commit_unsigned.txt | 0 .../gpg_signatures/generate_gpg_fixtures.sh | 0 .../gpg_signatures/key_brainpool_p256.pub | 0 .../gpg_signatures/key_brainpool_p384.pub | 0 .../gpg_signatures/key_brainpool_p512.pub | 0 .../gpg_signatures/key_ecdsa_p256.pub | 0 .../gpg_signatures/key_ecdsa_p384.pub | 0 .../gpg_signatures/key_ecdsa_p521.pub | 0 .../testdata/gpg_signatures/key_ed25519.pub | 0 .../testdata/gpg_signatures/key_ed448.pub | 0 .../testdata/gpg_signatures/key_rsa_2048.pub | 0 .../testdata/gpg_signatures/key_rsa_4096.pub | 0 .../tag_brainpool_p256_signed.txt | 0 .../tag_brainpool_p384_signed.txt | 0 .../tag_brainpool_p512_signed.txt | 0 .../gpg_signatures/tag_ecdsa_p256_signed.txt | 0 .../gpg_signatures/tag_ecdsa_p384_signed.txt | 0 .../gpg_signatures/tag_ecdsa_p521_signed.txt | 0 .../gpg_signatures/tag_ed25519_signed.txt | 0 .../gpg_signatures/tag_ed448_signed.txt | 0 .../gpg_signatures/tag_rsa_2048_signed.txt | 0 .../gpg_signatures/tag_rsa_4096_signed.txt | 0 .../testdata/gpg_signatures/tag_unsigned.txt | 0 .../testdata/ssh_signatures/README.md | 0 .../commit_ecdsa_p256_signed.txt | 0 .../commit_ecdsa_p384_signed.txt | 0 .../commit_ecdsa_p521_signed.txt | 0 .../ssh_signatures/commit_ed25519_signed.txt | 0 .../ssh_signatures/commit_rsa_signed.txt | 0 .../ssh_signatures/commit_unsigned.txt | 0 .../ssh_signatures/generate_ssh_fixtures.sh | 0 .../ssh_signatures/key_ecdsa_p256.pub | 0 .../key_ecdsa_p256.pub_fingerprint | 0 .../ssh_signatures/key_ecdsa_p384.pub | 0 .../key_ecdsa_p384.pub_fingerprint | 0 .../ssh_signatures/key_ecdsa_p521.pub | 0 .../key_ecdsa_p521.pub_fingerprint | 0 .../testdata/ssh_signatures/key_ed25519.pub | 0 .../key_ed25519.pub_fingerprint | 0 .../testdata/ssh_signatures/key_rsa.pub | 0 .../ssh_signatures/key_rsa.pub_fingerprint | 0 .../testdata/ssh_signatures/keys_all.pub | 0 .../ssh_signatures/tag_ecdsa_p256_signed.txt | 0 .../ssh_signatures/tag_ecdsa_p384_signed.txt | 0 .../ssh_signatures/tag_ecdsa_p521_signed.txt | 0 .../ssh_signatures/tag_ed25519_signed.txt | 0 .../ssh_signatures/tag_rsa_signed.txt | 0 .../testdata/ssh_signatures/tag_unsigned.txt | 0 .../ssh_signatures/verified_signers_all | 0 .../verified_signers_ecdsa_p256 | 0 .../verified_signers_ecdsa_p384 | 0 .../verified_signers_ecdsa_p521 | 0 .../ssh_signatures/verified_signers_ed25519 | 0 .../ssh_signatures/verified_signers_rsa | 0 git/signatures/ssh_signature_keys_test.go | 294 ---------------- 74 files changed, 338 insertions(+), 379 deletions(-) rename git/{signatures => signature}/gpg_signature.go (92%) rename git/{signatures => signature}/gpg_signature_test.go (95%) rename git/{signatures => signature}/signature.go (84%) rename git/{signatures => signature}/signature_test.go (98%) rename git/{signatures => signature}/ssh_signature.go (86%) rename git/{signatures => signature}/ssh_signature_test.go (55%) rename git/{signatures => signature}/testdata/gpg_signatures/README.md (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_brainpool_p256_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_brainpool_p384_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_brainpool_p512_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_ed25519_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_ed448_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_rsa_2048_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_rsa_4096_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/commit_unsigned.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/generate_gpg_fixtures.sh (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_brainpool_p256.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_brainpool_p384.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_brainpool_p512.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_ecdsa_p256.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_ecdsa_p384.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_ecdsa_p521.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_ed25519.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_ed448.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_rsa_2048.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/key_rsa_4096.pub (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_brainpool_p256_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_brainpool_p384_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_brainpool_p512_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_ed25519_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_ed448_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_rsa_2048_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_rsa_4096_signed.txt (100%) rename git/{signatures => signature}/testdata/gpg_signatures/tag_unsigned.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/README.md (100%) rename git/{signatures => signature}/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/commit_ed25519_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/commit_rsa_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/commit_unsigned.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/generate_ssh_fixtures.sh (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ecdsa_p256.pub (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ecdsa_p384.pub (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ecdsa_p521.pub (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ed25519.pub (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_ed25519.pub_fingerprint (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_rsa.pub (100%) rename git/{signatures => signature}/testdata/ssh_signatures/key_rsa.pub_fingerprint (100%) rename git/{signatures => signature}/testdata/ssh_signatures/keys_all.pub (100%) rename git/{signatures => signature}/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/tag_ed25519_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/tag_rsa_signed.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/tag_unsigned.txt (100%) rename git/{signatures => signature}/testdata/ssh_signatures/verified_signers_all (100%) rename git/{signatures => signature}/testdata/ssh_signatures/verified_signers_ecdsa_p256 (100%) rename git/{signatures => signature}/testdata/ssh_signatures/verified_signers_ecdsa_p384 (100%) rename git/{signatures => signature}/testdata/ssh_signatures/verified_signers_ecdsa_p521 (100%) rename git/{signatures => signature}/testdata/ssh_signatures/verified_signers_ed25519 (100%) rename git/{signatures => signature}/testdata/ssh_signatures/verified_signers_rsa (100%) delete mode 100644 git/signatures/ssh_signature_keys_test.go diff --git a/git/git.go b/git/git.go index 1d53cd33d..e0cbf35e9 100644 --- a/git/git.go +++ b/git/git.go @@ -22,7 +22,7 @@ import ( "strings" "time" - "github.com/fluxcd/pkg/git/signatures" + "github.com/fluxcd/pkg/git/signature" ) const ( @@ -124,7 +124,7 @@ func (c *Commit) Verify(keyRings ...string) (string, error) { // tag (if present). Users are expected to explicitly verify the referencing // tag's signature using `c.ReferencingTag.Verify()` func (c *Commit) VerifyGPG(keyRings ...string) (string, error) { - fingerprint, err := signatures.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...) + fingerprint, err := signature.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...) if err != nil { return "", fmt.Errorf("unable to verify Git commit: %w", err) } @@ -136,7 +136,7 @@ func (c *Commit) VerifyGPG(keyRings ...string) (string, error) { // It does not verify the signature of the referencing tag (if present). Users are // expected to explicitly verify the referencing tag's signature using `c.ReferencingTag.VerifySSH()` func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) { - fingerprint, err := signatures.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...) + fingerprint, err := signature.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...) if err != nil { return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err) } @@ -179,7 +179,7 @@ func (t *Tag) Verify(keyRings ...string) (string, error) { // It returns the fingerprint of the key the signature was verified // with, or an error. func (t *Tag) VerifyGPG(keyRings ...string) (string, error) { - fingerprint, err := signatures.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...) + fingerprint, err := signature.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...) if err != nil { return "", fmt.Errorf("unable to verify Git tag: %w", err) } @@ -189,7 +189,7 @@ func (t *Tag) VerifyGPG(keyRings ...string) (string, error) { // VerifySSH verifies the SSH signature of the tag with the given authorized keys. // It returns the fingerprint of the key the signature was verified with, or an error. func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) { - fingerprint, err := signatures.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...) + fingerprint, err := signature.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...) if err != nil { return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err) } @@ -245,34 +245,34 @@ func IsSignedTag(t Tag) bool { // IsPGPSigned returns true if the commit has a PGP signature. func (c *Commit) IsPGPSigned() bool { - return signatures.IsPGPSignature(c.Signature) + return signature.IsPGPSignature(c.Signature) } // IsSSHSigned returns true if the commit has an SSH signature. func (c *Commit) IsSSHSigned() bool { - return signatures.IsSSHSignature(c.Signature) + return signature.IsSSHSignature(c.Signature) } // SignatureType returns the type of the commit signature as a string. -// It returns "pgp" for PGP signatures, "ssh" for SSH signatures, +// It returns "openpgp" for PGP signatures, "ssh" for SSH signatures, // and "unknown" for unrecognized or empty signatures. func (c *Commit) SignatureType() string { - return signatures.GetSignatureType(c.Signature) + return signature.GetSignatureType(c.Signature) } // IsPGPSigned returns true if the tag has a PGP signature. func (t *Tag) IsPGPSigned() bool { - return signatures.IsPGPSignature(t.Signature) + return signature.IsPGPSignature(t.Signature) } // IsSSHSigned returns true if the tag has an SSH signature. func (t *Tag) IsSSHSigned() bool { - return signatures.IsSSHSignature(t.Signature) + return signature.IsSSHSignature(t.Signature) } // SignatureType returns the type of the tag signature as a string. -// It returns "pgp" for PGP signatures, "ssh" for SSH signatures, +// It returns "openpgp" for PGP signatures, "ssh" for SSH signatures, // and "unknown" for unrecognized or empty signatures. func (t *Tag) SignatureType() string { - return signatures.GetSignatureType(t.Signature) + return signature.GetSignatureType(t.Signature) } diff --git a/git/git_test.go b/git/git_test.go index 3e286654d..2c7b6ba8c 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -573,7 +573,7 @@ func TestSignatureType(t *testing.T) { } func TestCommit_VerifyGPG(t *testing.T) { - testDataDir := filepath.Join("signatures", "testdata", "gpg_signatures") + testDataDir := filepath.Join("signature", "testdata", "gpg_signatures") tests := []struct { name string @@ -662,7 +662,7 @@ func TestCommit_VerifyGPG(t *testing.T) { } func TestTag_VerifyGPG(t *testing.T) { - testDataDir := filepath.Join("signatures", "testdata", "gpg_signatures") + testDataDir := filepath.Join("signature", "testdata", "gpg_signatures") tests := []struct { name string @@ -751,7 +751,7 @@ func TestTag_VerifyGPG(t *testing.T) { } func TestCommit_VerifySSH(t *testing.T) { - testDataDir := filepath.Join("signatures", "testdata", "ssh_signatures") + testDataDir := filepath.Join("signature", "testdata", "ssh_signatures") tests := []struct { name string @@ -829,7 +829,7 @@ func TestCommit_VerifySSH(t *testing.T) { } func TestTag_VerifySSH(t *testing.T) { - testDataDir := filepath.Join("signatures", "testdata", "ssh_signatures") + testDataDir := filepath.Join("signature", "testdata", "ssh_signatures") tests := []struct { name string diff --git a/git/signatures/gpg_signature.go b/git/signature/gpg_signature.go similarity index 92% rename from git/signatures/gpg_signature.go rename to git/signature/gpg_signature.go index 94c2ae2ac..3d4cea221 100644 --- a/git/signatures/gpg_signature.go +++ b/git/signature/gpg_signature.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signatures +package signature import ( "bytes" @@ -32,7 +32,7 @@ var PGPSignaturePrefix = []string{ } // VerifyPGPSignature verifies the PGP signature against the payload using -// the provided key rings. It returns the fingerprint of the key that +// the provided key rings. It returns the key ID of the key that // successfully verified the signature, or an error. func VerifyPGPSignature(signature string, payload []byte, keyRings ...string) (string, error) { if signature == "" { @@ -53,7 +53,7 @@ func VerifyPGPSignature(signature string, payload []byte, keyRings ...string) (s if err != nil { return "", fmt.Errorf("unable to read armored key ring: %w", err) } - signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewBuffer(payload), bytes.NewBufferString(signature), nil) + signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(payload), strings.NewReader(signature), nil) if err == nil { return signer.PrimaryKey.KeyIdString(), nil } diff --git a/git/signatures/gpg_signature_test.go b/git/signature/gpg_signature_test.go similarity index 95% rename from git/signatures/gpg_signature_test.go rename to git/signature/gpg_signature_test.go index 40c7c6727..56da46564 100644 --- a/git/signatures/gpg_signature_test.go +++ b/git/signature/gpg_signature_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signatures_test +package signature_test import ( "os" @@ -22,7 +22,7 @@ import ( "testing" "github.com/fluxcd/pkg/git/gogit" - "github.com/fluxcd/pkg/git/signatures" + "github.com/fluxcd/pkg/git/signature" "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" . "github.com/onsi/gomega" @@ -177,7 +177,7 @@ func TestVerifyPGPSignature(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - got, err := signatures.VerifyPGPSignature(tt.sig, tt.payload, tt.keyRings...) + got, err := signature.VerifyPGPSignature(tt.sig, tt.payload, tt.keyRings...) if tt.wantErr != "" { g.Expect(err).To(HaveOccurred()) g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) @@ -245,7 +245,7 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) // Verify the signature using the git.Tag's Signature and Encoded fields - fingerprint, err := signatures.VerifyPGPSignature(gitTag.Signature, gitTag.Encoded, string(publicKey)) + fingerprint, err := signature.VerifyPGPSignature(gitTag.Signature, gitTag.Encoded, string(publicKey)) if kt.wantErr { g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) @@ -256,7 +256,7 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { g.Expect(fingerprint).ToNot(BeEmpty()) // Verify the signature using the multi-key keyring - fingerprint, err = signatures.VerifyPGPSignature(gitTag.Signature, gitTag.Encoded, allKeysRing...) + fingerprint, err = signature.VerifyPGPSignature(gitTag.Signature, gitTag.Encoded, allKeysRing...) if kt.wantErr { g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) @@ -286,7 +286,7 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) // Verify the signature using the git.Commit's Signature and Encoded fields - fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) + fingerprint, err := signature.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) if kt.wantErr { g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) @@ -297,7 +297,7 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { g.Expect(fingerprint).ToNot(BeEmpty()) // Verify the signature using the multi-key keyring - fingerprint, err = signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, allKeysRing...) + fingerprint, err = signature.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, allKeysRing...) if kt.wantErr { g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) @@ -327,7 +327,7 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) // Verify the signature - should fail as the commit is unsigned - fingerprint, err := signatures.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) + fingerprint, err := signature.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) }) diff --git a/git/signatures/signature.go b/git/signature/signature.go similarity index 84% rename from git/signatures/signature.go rename to git/signature/signature.go index 5b7ba3ed5..8861dce43 100644 --- a/git/signatures/signature.go +++ b/git/signature/signature.go @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signatures +package signature import ( + "slices" "strings" ) @@ -40,30 +41,20 @@ const ( // https://github.com/git/git/blob/7b2bccb0d58d4f24705bf985de1f4612e4cf06e5/gpg-interface.c#L65 var X509SignaturePrefix = []string{"-----BEGIN SIGNED MESSAGE-----"} -func startsWithStrings(signature string, prefixList []string) bool { - if signature == "" { - return false - } - - for _, prefix := range prefixList { - if strings.HasPrefix(strings.TrimSpace(signature), prefix) { - return true - } - } - - return false -} - // IsPGPSignature tests if the given signature is of type PGP. // It returns true if the signature starts with the PGP signature prefix. func IsPGPSignature(signature string) bool { - return startsWithStrings(signature, PGPSignaturePrefix) + return slices.ContainsFunc(PGPSignaturePrefix, func(prefix string) bool { + return strings.HasPrefix(strings.TrimSpace(signature), prefix) + }) } // IsSSHSignature tests if the given signature is of type SSH. // It returns true if the signature starts with the SSH signature prefix. func IsSSHSignature(signature string) bool { - return startsWithStrings(signature, SSHSignaturePrefix) + return slices.ContainsFunc(SSHSignaturePrefix, func(prefix string) bool { + return strings.HasPrefix(strings.TrimSpace(signature), prefix) + }) } // IsX509Signature tests if the given signature is of type x509. @@ -71,7 +62,9 @@ func IsSSHSignature(signature string) bool { // This is a place holder / compatibility implementation to embed the signature // type into the error message to inform the user about the wrong type of signature func IsX509Signature(signature string) bool { - return startsWithStrings(signature, X509SignaturePrefix) + return slices.ContainsFunc(X509SignaturePrefix, func(prefix string) bool { + return strings.HasPrefix(strings.TrimSpace(signature), prefix) + }) } // IsEmptySignature tests if the given signature string is empty. @@ -81,7 +74,7 @@ func IsEmptySignature(signature string) bool { } // GetSignatureType returns the type of the signature as a string. -// It returns "pgp" for PGP signatures, "ssh" for SSH signatures, +// It returns "openpgp" for PGP signatures, "ssh" for SSH signatures, // "x509" for S/MIME signatures, "empty" for an empty signature // and "unknown" for unrecognized signatures. func GetSignatureType(signature string) string { diff --git a/git/signatures/signature_test.go b/git/signature/signature_test.go similarity index 98% rename from git/signatures/signature_test.go rename to git/signature/signature_test.go index b079b8784..c50f3c02e 100644 --- a/git/signatures/signature_test.go +++ b/git/signature/signature_test.go @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signatures_test +package signature_test import ( "testing" - . "github.com/fluxcd/pkg/git/signatures" + . "github.com/fluxcd/pkg/git/signature" ) func TestIsPGPSignature(t *testing.T) { diff --git a/git/signatures/ssh_signature.go b/git/signature/ssh_signature.go similarity index 86% rename from git/signatures/ssh_signature.go rename to git/signature/ssh_signature.go index f9c80719a..c052a73f1 100644 --- a/git/signatures/ssh_signature.go +++ b/git/signature/ssh_signature.go @@ -14,12 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signatures +package signature import ( "bytes" - "crypto/sha256" - "encoding/base64" "fmt" "strings" @@ -38,7 +36,7 @@ var SSHSignaturePrefix = []string{"-----BEGIN SSH SIGNATURE-----"} func ParseAuthorizedKeys(authorizedKeys string) ([]gossh.PublicKey, error) { var publicKeys []gossh.PublicKey - for _, line := range strings.Split(authorizedKeys, "\n") { + for line := range strings.Lines(authorizedKeys) { line = strings.TrimSpace(line) // Skip empty lines and comments @@ -93,17 +91,10 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri err := sshsig.Verify(bytes.NewReader(payload), sig, pubKey, sig.HashAlgorithm, SSHSignatureNamespace) if err == nil { // Signature verified successfully - return getPublicKeyFingerprint(pubKey), nil + return gossh.FingerprintSHA256(pubKey), nil } } } return "", fmt.Errorf("unable to verify payload with any of the given authorized keys") } - -// getPublicKeyFingerprint returns the SHA256 fingerprint of the public key -// in the format used by SSH (e.g., "SHA256:abc123..."). -func getPublicKeyFingerprint(pubKey gossh.PublicKey) string { - hash := sha256.Sum256(pubKey.Marshal()) - return "SHA256:" + base64.RawStdEncoding.EncodeToString(hash[:]) -} diff --git a/git/signatures/ssh_signature_test.go b/git/signature/ssh_signature_test.go similarity index 55% rename from git/signatures/ssh_signature_test.go rename to git/signature/ssh_signature_test.go index 80cacb12b..fb447abba 100644 --- a/git/signatures/ssh_signature_test.go +++ b/git/signature/ssh_signature_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signatures_test +package signature_test import ( "os" @@ -23,9 +23,10 @@ import ( "testing" "github.com/fluxcd/pkg/git/gogit" - "github.com/fluxcd/pkg/git/signatures" + "github.com/fluxcd/pkg/git/signature" "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" + gossh "golang.org/x/crypto/ssh" ) // these tests are in a different package to avoid circular dependencies with gogit.BuildCommitWithRef and gogit.BuildTag @@ -123,7 +124,7 @@ func TestVerifySSHSignature(t *testing.T) { expectedFingerprint := strings.TrimSpace(string(expectedFingerprintBytes)) // Verify the signature using the git.Commit's Signature and Encoded fields - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKey)) + fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(authorizedKey)) if err != nil { t.Errorf("Commit signature VerifySSHSignature() error = %v", err) } @@ -135,7 +136,7 @@ func TestVerifySSHSignature(t *testing.T) { } // Verifying the correct fingerprint is returned from a list of public keys - fingerprint, err = signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(pubKeysAll)) + fingerprint, err = signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(pubKeysAll)) if err != nil { t.Errorf("Commit signature VerifySSHSignature() error = %v", err) } @@ -147,7 +148,7 @@ func TestVerifySSHSignature(t *testing.T) { } // Verify the signature using the git.Tag's Signature and Encoded fields - fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKey)) + fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(authorizedKey)) if err != nil { t.Errorf("Tag signature VerifySSHSignature() error = %v", err) } @@ -159,7 +160,7 @@ func TestVerifySSHSignature(t *testing.T) { } // Verifying the correct fingerprint is returned from a list of public keys - fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(pubKeysAll)) + fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(pubKeysAll)) if err != nil { t.Errorf("Tag signature VerifySSHSignature() error = %v", err) } @@ -177,21 +178,21 @@ func TestVerifySSHSignature(t *testing.T) { func TestSSHSignatureValidationCases(t *testing.T) { testDataDir := filepath.Join("testdata", "ssh_signatures") - key_type := "ed25519" + keyType := "ed25519" - pubKey, err := os.ReadFile(filepath.Join(testDataDir, "key_"+key_type+".pub")) + pubKey, err := os.ReadFile(filepath.Join(testDataDir, "key_"+keyType+".pub")) if err != nil { t.Fatalf("Failed to read authorized keys: %v", err) } // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_"+key_type+"_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_"+keyType+"_signed.txt")) if err != nil { t.Fatalf("Failed to parse commit from fixture: %v", err) } // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_"+key_type+"_signed.txt")) + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_"+keyType+"_signed.txt")) if err != nil { t.Fatalf("Failed to parse tag from fixture: %v", err) } @@ -211,7 +212,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { // Test error cases t.Run("empty signature", func(t *testing.T) { - fingerprint, err := signatures.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) + fingerprint, err := signature.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") } @@ -222,7 +223,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) } - fingerprint, err = signatures.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) + fingerprint, err = signature.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") } @@ -237,7 +238,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Run("empty payload", func(t *testing.T) { - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, []byte{}, string(pubKey)) + fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, []byte{}, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") } @@ -248,7 +249,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) } - fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, []byte{}, string(pubKey)) + fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, []byte{}, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") } @@ -265,7 +266,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { // Use a different key that won't match wrongKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyM97VxLgOCuB9Eg5cDtTc8ogkdM1xAyJhzODB9cK1 wrong@example.com" - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, wrongKey) + fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, wrongKey) if err == nil { t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") } @@ -277,7 +278,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) } - fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, wrongKey) + fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, wrongKey) if err == nil { t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") } @@ -294,7 +295,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { // Use empty authorized keys emptyAuthKeys := "" - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, emptyAuthKeys) + fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, emptyAuthKeys) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty authorized keys, got nil") } @@ -305,7 +306,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload with any of the given authorized keys'", err) } - fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, emptyAuthKeys) + fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, emptyAuthKeys) if err == nil { t.Errorf("VerifySSHSignature() expected error for empty authorized keys, got nil") } @@ -320,7 +321,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Run("invalid signature", func(t *testing.T) { invalidSig := "-----BEGIN SSH SIGNATURE-----\n invalid\n -----END SSH SIGNATURE-----" - fingerprint, err := signatures.VerifySSHSignature(invalidSig, gitCommit.Encoded, string(pubKey)) + fingerprint, err := signature.VerifySSHSignature(invalidSig, gitCommit.Encoded, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") } @@ -331,7 +332,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) } - fingerprint, err = signatures.VerifySSHSignature(invalidSig, gitTag.Encoded, string(pubKey)) + fingerprint, err = signature.VerifySSHSignature(invalidSig, gitTag.Encoded, string(pubKey)) if err == nil { t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") } @@ -348,7 +349,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { // Use a PGP signature instead of SSH signature pgpSig := "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----" - fingerprint, err := signatures.VerifySSHSignature(pgpSig, gitCommit.Encoded, "") + fingerprint, err := signature.VerifySSHSignature(pgpSig, gitCommit.Encoded, "") if err == nil { t.Errorf("VerifySSHSignature() expected error for non-SSH signature, got nil") } @@ -359,7 +360,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify SSH signature, detected signature format: openpgp'", err) } - fingerprint, err = signatures.VerifySSHSignature(pgpSig, gitTag.Encoded, "") + fingerprint, err = signature.VerifySSHSignature(pgpSig, gitTag.Encoded, "") if err == nil { t.Errorf("VerifySSHSignature() expected error for non-SSH signature, got nil") } @@ -375,7 +376,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { // Use invalid authorized keys invalidAuthKeys := "invalid-key-data" - fingerprint, err := signatures.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, invalidAuthKeys) + fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, invalidAuthKeys) if err == nil { t.Errorf("VerifySSHSignature() expected error for invalid authorized keys, got nil") } @@ -386,7 +387,7 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to parse authorized key'", err) } - fingerprint, err = signatures.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, invalidAuthKeys) + fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, invalidAuthKeys) if err == nil { t.Errorf("VerifySSHSignature() expected error for invalid authorized keys, got nil") } @@ -398,3 +399,271 @@ func TestSSHSignatureValidationCases(t *testing.T) { } }) } + +func TestParseAuthorizedKeysAndPublicFingerprint(t *testing.T) { + tests := []struct { + name string + authorizedKeys string + wantCount int + wantErr bool + wantFingerprints []string + }{ + { + name: "single key", + authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com", + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, + }, + { + name: "key with additional directives", + authorizedKeys: "no-user-rc,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com additional long comment about nothing", + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, + }, + { + name: "multiple keys", + authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test1@example.com +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com`, + wantCount: 2, + wantErr: false, + wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM", "SHA256:oU8IT7UOnJlOTOvr/W1cYf1SkdocFm5F7SAXOwuo8Kc"}, + }, + { + name: "with comments", + authorizedKeys: `# This is a comment +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com +# Another comment`, + wantCount: 1, + wantErr: false, + wantFingerprints: []string{"SHA256:+vwrYGpHfAAWIzT2x+uV+duJG7ZnSvCbRKwdPApx7JA"}, + }, + { + name: "with empty lines", + authorizedKeys: `ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com + +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com`, + wantCount: 2, + wantErr: false, + wantFingerprints: []string{"SHA256:3FcWgX5RsACruglrcBJP/hefUZcYHJGnrk07U6yKin8", "SHA256:TxoYgaeIj5A7Md4rHNfxPdqawooc4NIGjIMbcQ7YKbw"}, + }, + { + name: "empty", + authorizedKeys: "", + wantCount: 0, + wantErr: false, + wantFingerprints: []string{}, + }, + { + name: "invalid key", + authorizedKeys: "invalid-key-data", + wantCount: 0, + wantErr: true, + wantFingerprints: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + keys, err := signature.ParseAuthorizedKeys(tt.authorizedKeys) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + // Validate expected fingerprint if specified + if len(tt.wantFingerprints) > 0 && len(keys) > 0 { + for _, key := range keys { + found := false + fingerprint := gossh.FingerprintSHA256(key) + for _, wantedFingerprint := range tt.wantFingerprints { + if fingerprint == wantedFingerprint { + found = true + } + } + if !found { + t.Errorf("ParseAuthorizedKeys() fingerprint '%s'not in list of wanted fingerprints %s", fingerprint, tt.wantFingerprints) + } + } + } + }) + } +} + +func TestParseAuthorizedKeysFromFixtures(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixture string + fingerprintFile string + wantCount int + wantErr bool + }{ + { + name: "ed25519 key", + fixture: "key_ed25519.pub", + fingerprintFile: "key_ed25519.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "rsa key", + fixture: "key_rsa.pub", + fingerprintFile: "key_rsa.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p256 key", + fixture: "key_ecdsa_p256.pub", + fingerprintFile: "key_ecdsa_p256.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p384 key", + fixture: "key_ecdsa_p384.pub", + fingerprintFile: "key_ecdsa_p384.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "ecdsa p521 key", + fixture: "key_ecdsa_p521.pub", + fingerprintFile: "key_ecdsa_p521.pub_fingerprint", + wantCount: 1, + wantErr: false, + }, + { + name: "all key types combined", + fixture: "keys_all.pub", + wantCount: 5, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.fixture)) + if err != nil { + t.Fatalf("Failed to read fixture file %s: %v", tt.fixture, err) + } + + keys, err := signature.ParseAuthorizedKeys(string(authorizedKeys)) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + + // Read expected fingerprint from file if provided + var expectedFingerprint string + if tt.fingerprintFile != "" { + fingerprintData, err := os.ReadFile(filepath.Join(testDataDir, tt.fingerprintFile)) + if err != nil { + t.Fatalf("Failed to read fingerprint file %s: %v", tt.fingerprintFile, err) + } + expectedFingerprint = strings.TrimSpace(string(fingerprintData)) + } + + // Verify that each key has a valid fingerprint + for i, key := range keys { + fingerprint := gossh.FingerprintSHA256(key) + if fingerprint == "" { + t.Errorf("Key %d has empty fingerprint", i) + } + if !strings.HasPrefix(fingerprint, "SHA256:") { + t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) + } + // Validate fingerprint against the one read from file + if expectedFingerprint != "" { + if fingerprint != expectedFingerprint { + t.Errorf("Key %d got fingerprint %s, want %s (from %s)", i, fingerprint, expectedFingerprint, tt.fingerprintFile) + } + } + } + }) + } +} + +func TestParseAuthorizedKeysCombinations(t *testing.T) { + testDataDir := filepath.Join("testdata", "ssh_signatures") + + tests := []struct { + name string + fixtures []string + wantCount int + wantErr bool + }{ + { + name: "ed25519 + rsa", + fixtures: []string{"key_ed25519.pub", "key_rsa.pub"}, + wantCount: 2, + wantErr: false, + }, + { + name: "ed25519 + ecdsa p256", + fixtures: []string{"key_ed25519.pub", "key_ecdsa_p256.pub"}, + wantCount: 2, + wantErr: false, + }, + { + name: "rsa + ecdsa p384 + ecdsa p521", + fixtures: []string{"key_rsa.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, + wantCount: 3, + wantErr: false, + }, + { + name: "all ecdsa variants", + fixtures: []string{"key_ecdsa_p256.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, + wantCount: 3, + wantErr: false, + }, + { + name: "ed25519 + rsa + all ecdsa", + fixtures: []string{"key_ed25519.pub", "key_rsa.pub", "key_ecdsa_p256.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, + wantCount: 5, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var combinedKeys strings.Builder + for _, fixture := range tt.fixtures { + authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, fixture)) + if err != nil { + t.Fatalf("Failed to read fixture file %s: %v", fixture, err) + } + combinedKeys.Write(authorizedKeys) + combinedKeys.WriteString("\n") + } + + keys, err := signature.ParseAuthorizedKeys(combinedKeys.String()) + if (err != nil) != tt.wantErr { + t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(keys) != tt.wantCount { + t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) + } + + // Verify that each key has a valid fingerprint + for i, key := range keys { + fingerprint := gossh.FingerprintSHA256(key) + if fingerprint == "" { + t.Errorf("Key %d has empty fingerprint", i) + } + if !strings.HasPrefix(fingerprint, "SHA256:") { + t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) + } + } + }) + } +} diff --git a/git/signatures/testdata/gpg_signatures/README.md b/git/signature/testdata/gpg_signatures/README.md similarity index 100% rename from git/signatures/testdata/gpg_signatures/README.md rename to git/signature/testdata/gpg_signatures/README.md diff --git a/git/signatures/testdata/gpg_signatures/commit_brainpool_p256_signed.txt b/git/signature/testdata/gpg_signatures/commit_brainpool_p256_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_brainpool_p256_signed.txt rename to git/signature/testdata/gpg_signatures/commit_brainpool_p256_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_brainpool_p384_signed.txt b/git/signature/testdata/gpg_signatures/commit_brainpool_p384_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_brainpool_p384_signed.txt rename to git/signature/testdata/gpg_signatures/commit_brainpool_p384_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_brainpool_p512_signed.txt b/git/signature/testdata/gpg_signatures/commit_brainpool_p512_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_brainpool_p512_signed.txt rename to git/signature/testdata/gpg_signatures/commit_brainpool_p512_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt b/git/signature/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt rename to git/signature/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt b/git/signature/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt rename to git/signature/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt b/git/signature/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt rename to git/signature/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_ed25519_signed.txt b/git/signature/testdata/gpg_signatures/commit_ed25519_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_ed25519_signed.txt rename to git/signature/testdata/gpg_signatures/commit_ed25519_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_ed448_signed.txt b/git/signature/testdata/gpg_signatures/commit_ed448_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_ed448_signed.txt rename to git/signature/testdata/gpg_signatures/commit_ed448_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_rsa_2048_signed.txt b/git/signature/testdata/gpg_signatures/commit_rsa_2048_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_rsa_2048_signed.txt rename to git/signature/testdata/gpg_signatures/commit_rsa_2048_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_rsa_4096_signed.txt b/git/signature/testdata/gpg_signatures/commit_rsa_4096_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_rsa_4096_signed.txt rename to git/signature/testdata/gpg_signatures/commit_rsa_4096_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/commit_unsigned.txt b/git/signature/testdata/gpg_signatures/commit_unsigned.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/commit_unsigned.txt rename to git/signature/testdata/gpg_signatures/commit_unsigned.txt diff --git a/git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh similarity index 100% rename from git/signatures/testdata/gpg_signatures/generate_gpg_fixtures.sh rename to git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh diff --git a/git/signatures/testdata/gpg_signatures/key_brainpool_p256.pub b/git/signature/testdata/gpg_signatures/key_brainpool_p256.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_brainpool_p256.pub rename to git/signature/testdata/gpg_signatures/key_brainpool_p256.pub diff --git a/git/signatures/testdata/gpg_signatures/key_brainpool_p384.pub b/git/signature/testdata/gpg_signatures/key_brainpool_p384.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_brainpool_p384.pub rename to git/signature/testdata/gpg_signatures/key_brainpool_p384.pub diff --git a/git/signatures/testdata/gpg_signatures/key_brainpool_p512.pub b/git/signature/testdata/gpg_signatures/key_brainpool_p512.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_brainpool_p512.pub rename to git/signature/testdata/gpg_signatures/key_brainpool_p512.pub diff --git a/git/signatures/testdata/gpg_signatures/key_ecdsa_p256.pub b/git/signature/testdata/gpg_signatures/key_ecdsa_p256.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_ecdsa_p256.pub rename to git/signature/testdata/gpg_signatures/key_ecdsa_p256.pub diff --git a/git/signatures/testdata/gpg_signatures/key_ecdsa_p384.pub b/git/signature/testdata/gpg_signatures/key_ecdsa_p384.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_ecdsa_p384.pub rename to git/signature/testdata/gpg_signatures/key_ecdsa_p384.pub diff --git a/git/signatures/testdata/gpg_signatures/key_ecdsa_p521.pub b/git/signature/testdata/gpg_signatures/key_ecdsa_p521.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_ecdsa_p521.pub rename to git/signature/testdata/gpg_signatures/key_ecdsa_p521.pub diff --git a/git/signatures/testdata/gpg_signatures/key_ed25519.pub b/git/signature/testdata/gpg_signatures/key_ed25519.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_ed25519.pub rename to git/signature/testdata/gpg_signatures/key_ed25519.pub diff --git a/git/signatures/testdata/gpg_signatures/key_ed448.pub b/git/signature/testdata/gpg_signatures/key_ed448.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_ed448.pub rename to git/signature/testdata/gpg_signatures/key_ed448.pub diff --git a/git/signatures/testdata/gpg_signatures/key_rsa_2048.pub b/git/signature/testdata/gpg_signatures/key_rsa_2048.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_rsa_2048.pub rename to git/signature/testdata/gpg_signatures/key_rsa_2048.pub diff --git a/git/signatures/testdata/gpg_signatures/key_rsa_4096.pub b/git/signature/testdata/gpg_signatures/key_rsa_4096.pub similarity index 100% rename from git/signatures/testdata/gpg_signatures/key_rsa_4096.pub rename to git/signature/testdata/gpg_signatures/key_rsa_4096.pub diff --git a/git/signatures/testdata/gpg_signatures/tag_brainpool_p256_signed.txt b/git/signature/testdata/gpg_signatures/tag_brainpool_p256_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_brainpool_p256_signed.txt rename to git/signature/testdata/gpg_signatures/tag_brainpool_p256_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_brainpool_p384_signed.txt b/git/signature/testdata/gpg_signatures/tag_brainpool_p384_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_brainpool_p384_signed.txt rename to git/signature/testdata/gpg_signatures/tag_brainpool_p384_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_brainpool_p512_signed.txt b/git/signature/testdata/gpg_signatures/tag_brainpool_p512_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_brainpool_p512_signed.txt rename to git/signature/testdata/gpg_signatures/tag_brainpool_p512_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt b/git/signature/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt rename to git/signature/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt b/git/signature/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt rename to git/signature/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt b/git/signature/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt rename to git/signature/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_ed25519_signed.txt b/git/signature/testdata/gpg_signatures/tag_ed25519_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_ed25519_signed.txt rename to git/signature/testdata/gpg_signatures/tag_ed25519_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_ed448_signed.txt b/git/signature/testdata/gpg_signatures/tag_ed448_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_ed448_signed.txt rename to git/signature/testdata/gpg_signatures/tag_ed448_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_rsa_2048_signed.txt b/git/signature/testdata/gpg_signatures/tag_rsa_2048_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_rsa_2048_signed.txt rename to git/signature/testdata/gpg_signatures/tag_rsa_2048_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_rsa_4096_signed.txt b/git/signature/testdata/gpg_signatures/tag_rsa_4096_signed.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_rsa_4096_signed.txt rename to git/signature/testdata/gpg_signatures/tag_rsa_4096_signed.txt diff --git a/git/signatures/testdata/gpg_signatures/tag_unsigned.txt b/git/signature/testdata/gpg_signatures/tag_unsigned.txt similarity index 100% rename from git/signatures/testdata/gpg_signatures/tag_unsigned.txt rename to git/signature/testdata/gpg_signatures/tag_unsigned.txt diff --git a/git/signatures/testdata/ssh_signatures/README.md b/git/signature/testdata/ssh_signatures/README.md similarity index 100% rename from git/signatures/testdata/ssh_signatures/README.md rename to git/signature/testdata/ssh_signatures/README.md diff --git a/git/signatures/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt b/git/signature/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt rename to git/signature/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt b/git/signature/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt rename to git/signature/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt b/git/signature/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt rename to git/signature/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/commit_ed25519_signed.txt b/git/signature/testdata/ssh_signatures/commit_ed25519_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/commit_ed25519_signed.txt rename to git/signature/testdata/ssh_signatures/commit_ed25519_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/commit_rsa_signed.txt b/git/signature/testdata/ssh_signatures/commit_rsa_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/commit_rsa_signed.txt rename to git/signature/testdata/ssh_signatures/commit_rsa_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/commit_unsigned.txt b/git/signature/testdata/ssh_signatures/commit_unsigned.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/commit_unsigned.txt rename to git/signature/testdata/ssh_signatures/commit_unsigned.txt diff --git a/git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh similarity index 100% rename from git/signatures/testdata/ssh_signatures/generate_ssh_fixtures.sh rename to git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub b/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub rename to git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint rename to git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub b/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub rename to git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint rename to git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub b/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub rename to git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub diff --git a/git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint rename to git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint diff --git a/git/signatures/testdata/ssh_signatures/key_ed25519.pub b/git/signature/testdata/ssh_signatures/key_ed25519.pub similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ed25519.pub rename to git/signature/testdata/ssh_signatures/key_ed25519.pub diff --git a/git/signatures/testdata/ssh_signatures/key_ed25519.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ed25519.pub_fingerprint similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_ed25519.pub_fingerprint rename to git/signature/testdata/ssh_signatures/key_ed25519.pub_fingerprint diff --git a/git/signatures/testdata/ssh_signatures/key_rsa.pub b/git/signature/testdata/ssh_signatures/key_rsa.pub similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_rsa.pub rename to git/signature/testdata/ssh_signatures/key_rsa.pub diff --git a/git/signatures/testdata/ssh_signatures/key_rsa.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_rsa.pub_fingerprint similarity index 100% rename from git/signatures/testdata/ssh_signatures/key_rsa.pub_fingerprint rename to git/signature/testdata/ssh_signatures/key_rsa.pub_fingerprint diff --git a/git/signatures/testdata/ssh_signatures/keys_all.pub b/git/signature/testdata/ssh_signatures/keys_all.pub similarity index 100% rename from git/signatures/testdata/ssh_signatures/keys_all.pub rename to git/signature/testdata/ssh_signatures/keys_all.pub diff --git a/git/signatures/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt b/git/signature/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt rename to git/signature/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt b/git/signature/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt rename to git/signature/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt b/git/signature/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt rename to git/signature/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/tag_ed25519_signed.txt b/git/signature/testdata/ssh_signatures/tag_ed25519_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/tag_ed25519_signed.txt rename to git/signature/testdata/ssh_signatures/tag_ed25519_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/tag_rsa_signed.txt b/git/signature/testdata/ssh_signatures/tag_rsa_signed.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/tag_rsa_signed.txt rename to git/signature/testdata/ssh_signatures/tag_rsa_signed.txt diff --git a/git/signatures/testdata/ssh_signatures/tag_unsigned.txt b/git/signature/testdata/ssh_signatures/tag_unsigned.txt similarity index 100% rename from git/signatures/testdata/ssh_signatures/tag_unsigned.txt rename to git/signature/testdata/ssh_signatures/tag_unsigned.txt diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_all b/git/signature/testdata/ssh_signatures/verified_signers_all similarity index 100% rename from git/signatures/testdata/ssh_signatures/verified_signers_all rename to git/signature/testdata/ssh_signatures/verified_signers_all diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p256 b/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p256 similarity index 100% rename from git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p256 rename to git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p256 diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p384 b/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p384 similarity index 100% rename from git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p384 rename to git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p384 diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p521 b/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p521 similarity index 100% rename from git/signatures/testdata/ssh_signatures/verified_signers_ecdsa_p521 rename to git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p521 diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_ed25519 b/git/signature/testdata/ssh_signatures/verified_signers_ed25519 similarity index 100% rename from git/signatures/testdata/ssh_signatures/verified_signers_ed25519 rename to git/signature/testdata/ssh_signatures/verified_signers_ed25519 diff --git a/git/signatures/testdata/ssh_signatures/verified_signers_rsa b/git/signature/testdata/ssh_signatures/verified_signers_rsa similarity index 100% rename from git/signatures/testdata/ssh_signatures/verified_signers_rsa rename to git/signature/testdata/ssh_signatures/verified_signers_rsa diff --git a/git/signatures/ssh_signature_keys_test.go b/git/signatures/ssh_signature_keys_test.go deleted file mode 100644 index 3761d469a..000000000 --- a/git/signatures/ssh_signature_keys_test.go +++ /dev/null @@ -1,294 +0,0 @@ -/* -Copyright 2026 The Flux authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package signatures - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -// these tests are in the same package to test private getPublicKeyFingerprint function - -func TestParseAuthorizedKeysAndPublicFingerprint(t *testing.T) { - tests := []struct { - name string - authorizedKeys string - wantCount int - wantErr bool - wantFingerprints []string - }{ - { - name: "single key", - authorizedKeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com", - wantCount: 1, - wantErr: false, - wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, - }, - { - name: "key with additional directives", - authorizedKeys: "no-user-rc,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test@example.com additional long comment about nothing", - wantCount: 1, - wantErr: false, - wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM"}, - }, - { - name: "multiple keys", - authorizedKeys: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPbmoVMAS5Ttg77s9DLSAOf4gXCiQpgdRekFHlzbXHLH test1@example.com -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com`, - wantCount: 2, - wantErr: false, - wantFingerprints: []string{"SHA256:CGIPzdGcFuLkjItmqTm5kJNvof4yB662MxZXoxntLYM", "SHA256:oU8IT7UOnJlOTOvr/W1cYf1SkdocFm5F7SAXOwuo8Kc"}, - }, - { - name: "with comments", - authorizedKeys: `# This is a comment -ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com -# Another comment`, - wantCount: 1, - wantErr: false, - wantFingerprints: []string{"SHA256:+vwrYGpHfAAWIzT2x+uV+duJG7ZnSvCbRKwdPApx7JA"}, - }, - { - name: "with empty lines", - authorizedKeys: `ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com - -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com`, - wantCount: 2, - wantErr: false, - wantFingerprints: []string{"SHA256:3FcWgX5RsACruglrcBJP/hefUZcYHJGnrk07U6yKin8", "SHA256:TxoYgaeIj5A7Md4rHNfxPdqawooc4NIGjIMbcQ7YKbw"}, - }, - { - name: "empty", - authorizedKeys: "", - wantCount: 0, - wantErr: false, - wantFingerprints: []string{}, - }, - { - name: "invalid key", - authorizedKeys: "invalid-key-data", - wantCount: 0, - wantErr: true, - wantFingerprints: []string{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - keys, err := ParseAuthorizedKeys(tt.authorizedKeys) - if (err != nil) != tt.wantErr { - t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(keys) != tt.wantCount { - t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) - } - // Validate expected fingerprint if specified - if len(tt.wantFingerprints) > 0 && len(keys) > 0 { - for _, key := range keys { - found := false - fingerprint := getPublicKeyFingerprint(key) - for _, wantedFingerprint := range tt.wantFingerprints { - if fingerprint == wantedFingerprint { - found = true - } - } - if !found { - t.Errorf("ParseAuthorizedKeys() fingerprint '%s'not in list of wanted fingerprints %s", fingerprint, tt.wantFingerprints) - } - } - } - }) - } -} - -func TestParseAuthorizedKeysFromFixtures(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - tests := []struct { - name string - fixture string - fingerprintFile string - wantCount int - wantErr bool - }{ - { - name: "ed25519 key", - fixture: "key_ed25519.pub", - fingerprintFile: "key_ed25519.pub_fingerprint", - wantCount: 1, - wantErr: false, - }, - { - name: "rsa key", - fixture: "key_rsa.pub", - fingerprintFile: "key_rsa.pub_fingerprint", - wantCount: 1, - wantErr: false, - }, - { - name: "ecdsa p256 key", - fixture: "key_ecdsa_p256.pub", - fingerprintFile: "key_ecdsa_p256.pub_fingerprint", - wantCount: 1, - wantErr: false, - }, - { - name: "ecdsa p384 key", - fixture: "key_ecdsa_p384.pub", - fingerprintFile: "key_ecdsa_p384.pub_fingerprint", - wantCount: 1, - wantErr: false, - }, - { - name: "ecdsa p521 key", - fixture: "key_ecdsa_p521.pub", - fingerprintFile: "key_ecdsa_p521.pub_fingerprint", - wantCount: 1, - wantErr: false, - }, - { - name: "all key types combined", - fixture: "keys_all.pub", - wantCount: 5, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, tt.fixture)) - if err != nil { - t.Fatalf("Failed to read fixture file %s: %v", tt.fixture, err) - } - - keys, err := ParseAuthorizedKeys(string(authorizedKeys)) - if (err != nil) != tt.wantErr { - t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(keys) != tt.wantCount { - t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) - } - - // Read expected fingerprint from file if provided - var expectedFingerprint string - if tt.fingerprintFile != "" { - fingerprintData, err := os.ReadFile(filepath.Join(testDataDir, tt.fingerprintFile)) - if err != nil { - t.Fatalf("Failed to read fingerprint file %s: %v", tt.fingerprintFile, err) - } - expectedFingerprint = strings.TrimSpace(string(fingerprintData)) - } - - // Verify that each key has a valid fingerprint - for i, key := range keys { - fingerprint := getPublicKeyFingerprint(key) - if fingerprint == "" { - t.Errorf("Key %d has empty fingerprint", i) - } - if !strings.HasPrefix(fingerprint, "SHA256:") { - t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) - } - // Validate fingerprint against the one read from file - if expectedFingerprint != "" { - if fingerprint != expectedFingerprint { - t.Errorf("Key %d got fingerprint %s, want %s (from %s)", i, fingerprint, expectedFingerprint, tt.fingerprintFile) - } - } - } - }) - } -} - -func TestParseAuthorizedKeysCombinations(t *testing.T) { - testDataDir := filepath.Join("testdata", "ssh_signatures") - - tests := []struct { - name string - fixtures []string - wantCount int - wantErr bool - }{ - { - name: "ed25519 + rsa", - fixtures: []string{"key_ed25519.pub", "key_rsa.pub"}, - wantCount: 2, - wantErr: false, - }, - { - name: "ed25519 + ecdsa p256", - fixtures: []string{"key_ed25519.pub", "key_ecdsa_p256.pub"}, - wantCount: 2, - wantErr: false, - }, - { - name: "rsa + ecdsa p384 + ecdsa p521", - fixtures: []string{"key_rsa.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, - wantCount: 3, - wantErr: false, - }, - { - name: "all ecdsa variants", - fixtures: []string{"key_ecdsa_p256.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, - wantCount: 3, - wantErr: false, - }, - { - name: "ed25519 + rsa + all ecdsa", - fixtures: []string{"key_ed25519.pub", "key_rsa.pub", "key_ecdsa_p256.pub", "key_ecdsa_p384.pub", "key_ecdsa_p521.pub"}, - wantCount: 5, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var combinedKeys strings.Builder - for _, fixture := range tt.fixtures { - authorizedKeys, err := os.ReadFile(filepath.Join(testDataDir, fixture)) - if err != nil { - t.Fatalf("Failed to read fixture file %s: %v", fixture, err) - } - combinedKeys.Write(authorizedKeys) - combinedKeys.WriteString("\n") - } - - keys, err := ParseAuthorizedKeys(combinedKeys.String()) - if (err != nil) != tt.wantErr { - t.Errorf("ParseAuthorizedKeys() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(keys) != tt.wantCount { - t.Errorf("ParseAuthorizedKeys() got %d keys, want %d", len(keys), tt.wantCount) - } - - // Verify that each key has a valid fingerprint - for i, key := range keys { - fingerprint := getPublicKeyFingerprint(key) - if fingerprint == "" { - t.Errorf("Key %d has empty fingerprint", i) - } - if !strings.HasPrefix(fingerprint, "SHA256:") { - t.Errorf("Key %d fingerprint %s does not have SHA256: prefix", i, fingerprint) - } - } - }) - } -} From 9d67599f5f32402b9bc88ba80a086d0cbf53bf46 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Mon, 18 May 2026 23:37:21 +0200 Subject: [PATCH 15/22] switches signarture type from public to private Signed-off-by: Ricardo Bartels --- git/signature/signature.go | 22 +++++++++++----------- git/signature/signature_test.go | 22 ++++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/git/signature/signature.go b/git/signature/signature.go index 8861dce43..1fd833c76 100644 --- a/git/signature/signature.go +++ b/git/signature/signature.go @@ -22,19 +22,19 @@ import ( ) // SignatureType represents the type of a signature. -type SignatureType string +type signatureType string const ( // SignatureTypePGP represents a openPGP signature. - SignatureTypePGP SignatureType = "openpgp" + signatureTypePGP signatureType = "openpgp" // SignatureTypeSSH represents an SSH signature. - SignatureTypeSSH SignatureType = "ssh" + signatureTypeSSH signatureType = "ssh" // SignatureTypeX509 represents an x509 signature. - SignatureTypeX509 SignatureType = "x509" + signatureTypeX509 signatureType = "x509" // SignatureTypeUnknown represents an unknown signature type. - SignatureTypeUnknown SignatureType = "unknown" + signatureTypeUnknown signatureType = "unknown" // SignatureTypeEmpty represents an empty signature. - SignatureTypeEmpty SignatureType = "empty" + signatureTypeEmpty signatureType = "empty" ) // IsX509Signature is the prefix used by Git to identify x509 signatures. @@ -79,16 +79,16 @@ func IsEmptySignature(signature string) bool { // and "unknown" for unrecognized signatures. func GetSignatureType(signature string) string { if IsPGPSignature(signature) { - return string(SignatureTypePGP) + return string(signatureTypePGP) } if IsSSHSignature(signature) { - return string(SignatureTypeSSH) + return string(signatureTypeSSH) } if IsX509Signature(signature) { - return string(SignatureTypeX509) + return string(signatureTypeX509) } if IsEmptySignature(signature) { - return string(SignatureTypeEmpty) + return string(signatureTypeEmpty) } - return string(SignatureTypeUnknown) + return string(signatureTypeUnknown) } diff --git a/git/signature/signature_test.go b/git/signature/signature_test.go index c50f3c02e..7bf82f2b6 100644 --- a/git/signature/signature_test.go +++ b/git/signature/signature_test.go @@ -14,12 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package signature_test +package signature import ( "testing" - - . "github.com/fluxcd/pkg/git/signature" ) func TestIsPGPSignature(t *testing.T) { @@ -187,47 +185,47 @@ func TestGetSignatureType(t *testing.T) { { name: "PGP signature", signature: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", - want: string(SignatureTypePGP), + want: string(signatureTypePGP), }, { name: "PGP signature with leading whitespace", signature: " -----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", - want: string(SignatureTypePGP), + want: string(signatureTypePGP), }, { name: "SSH signature", signature: "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", - want: string(SignatureTypeSSH), + want: string(signatureTypeSSH), }, { name: "SSH signature with leading whitespace", signature: " -----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----", - want: string(SignatureTypeSSH), + want: string(signatureTypeSSH), }, { name: "x509 signature", signature: "-----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", - want: string(SignatureTypeX509), + want: string(signatureTypeX509), }, { name: "x509 signature with leading whitespace", signature: " -----BEGIN SIGNED MESSAGE-----\n-----END SIGNED MESSAGE-----", - want: string(SignatureTypeX509), + want: string(signatureTypeX509), }, { name: "empty signature", signature: "", - want: string(SignatureTypeEmpty), + want: string(signatureTypeEmpty), }, { name: "unknown signature", signature: "-----BEGIN UNKNOWN SIGNATURE-----\n-----END UNKNOWN SIGNATURE-----", - want: string(SignatureTypeUnknown), + want: string(signatureTypeUnknown), }, { name: "whitespace only", signature: " \n\t ", - want: string(SignatureTypeUnknown), + want: string(signatureTypeUnknown), }, } From a9fc7ac5712af3642b11421b21bb2204b933f0ae Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 19 May 2026 00:00:40 +0200 Subject: [PATCH 16/22] moves buildTag and buildCommitWithRef to internal/build package Signed-off-by: Ricardo Bartels --- .gitignore | 2 +- git/gogit/clone.go | 88 ++---------------------- git/internal/build/build.go | 103 ++++++++++++++++++++++++++++ git/signature/gpg_signature_test.go | 14 ++-- git/signature/ssh_signature_test.go | 20 +++--- 5 files changed, 126 insertions(+), 101 deletions(-) create mode 100644 git/internal/build/build.go diff --git a/.gitignore b/.gitignore index 892d69ea2..f0050ac90 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ -build/ +/build/ bin/ testbin/ diff --git a/git/gogit/clone.go b/git/gogit/clone.go index 8b78c3cbb..b59744db0 100644 --- a/git/gogit/clone.go +++ b/git/gogit/clone.go @@ -19,7 +19,6 @@ package gogit import ( "context" "fmt" - "io" "os" "sort" "strings" @@ -29,11 +28,11 @@ import ( extgogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/storage/memory" "github.com/fluxcd/pkg/git" + "github.com/fluxcd/pkg/git/internal/build" "github.com/fluxcd/pkg/git/repository" "github.com/fluxcd/pkg/version" ) @@ -136,7 +135,7 @@ func (g *Client) cloneBranch(ctx context.Context, url, branch string, opts repos } g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return BuildCommitWithRef(cc, nil, ref) + return build.CommitWithRef(cc, nil, ref) } func (g *Client) cloneTag(ctx context.Context, url, tag string, opts repository.CloneConfig) (*git.Commit, error) { @@ -236,7 +235,7 @@ func (g *Client) cloneTag(ctx context.Context, url, tag string, opts repository. g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return BuildCommitWithRef(cc, tagObj, ref) + return build.CommitWithRef(cc, tagObj, ref) } func (g *Client) cloneCommit(ctx context.Context, url, commit string, opts repository.CloneConfig) (*git.Commit, error) { @@ -305,7 +304,7 @@ func (g *Client) cloneCommit(ctx context.Context, url, commit string, opts repos g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return BuildCommitWithRef(cc, nil, cloneOpts.ReferenceName) + return build.CommitWithRef(cc, nil, cloneOpts.ReferenceName) } func (g *Client) cloneSemVer(ctx context.Context, url, semverTag string, opts repository.CloneConfig) (*git.Commit, error) { @@ -439,7 +438,7 @@ func (g *Client) cloneSemVer(ctx context.Context, url, semverTag string, opts re g.repository = repo g.sparseCheckoutDirectories = opts.SparseCheckoutDirectories - return BuildCommitWithRef(cc, tagObj, tagRef.Name()) + return build.CommitWithRef(cc, tagObj, tagRef.Name()) } func (g *Client) cloneRefName(ctx context.Context, url string, refName string, cloneOpts repository.CloneConfig) (*git.Commit, error) { @@ -574,83 +573,6 @@ func filterRefs(refs []*plumbing.Reference, currentRef plumbing.ReferenceName) s return "" } -func buildSignature(s object.Signature) git.Signature { - return git.Signature{ - Name: s.Name, - Email: s.Email, - When: s.When, - } -} - -func BuildTag(t *object.Tag, ref plumbing.ReferenceName) (*git.Tag, error) { - if t == nil { - return &git.Tag{ - Name: ref.Short(), - }, nil - } - - encoded := &plumbing.MemoryObject{} - if err := t.EncodeWithoutSignature(encoded); err != nil { - return nil, fmt.Errorf("unable to encode tag '%s': %w", t.Name, err) - } - reader, err := encoded.Reader() - if err != nil { - return nil, fmt.Errorf("unable to encode tag '%s': %w", t.Name, err) - } - b, err := io.ReadAll(reader) - if err != nil { - return nil, fmt.Errorf("unable to read encoded tag '%s': %w", t.Name, err) - } - - return &git.Tag{ - Hash: []byte(t.Hash.String()), - Name: t.Name, - Author: buildSignature(t.Tagger), - Signature: t.PGPSignature, - Encoded: b, - Message: t.Message, - }, nil -} - -func BuildCommitWithRef(c *object.Commit, t *object.Tag, ref plumbing.ReferenceName) (*git.Commit, error) { - if c == nil { - return nil, fmt.Errorf("unable to construct commit: no object") - } - - // Encode commit components excluding signature into SignedData. - encoded := &plumbing.MemoryObject{} - if err := c.EncodeWithoutSignature(encoded); err != nil { - return nil, fmt.Errorf("unable to encode commit '%s': %w", c.Hash, err) - } - reader, err := encoded.Reader() - if err != nil { - return nil, fmt.Errorf("unable to encode commit '%s': %w", c.Hash, err) - } - b, err := io.ReadAll(reader) - if err != nil { - return nil, fmt.Errorf("unable to read encoded commit '%s': %w", c.Hash, err) - } - cc := &git.Commit{ - Hash: []byte(c.Hash.String()), - Reference: ref.String(), - Author: buildSignature(c.Author), - Committer: buildSignature(c.Committer), - Signature: c.PGPSignature, - Encoded: b, - Message: c.Message, - } - - if ref.IsTag() { - tt, err := BuildTag(t, ref) - if err != nil { - return nil, err - } - cc.ReferencingTag = tt - } - - return cc, nil -} - func isRemoteBranchNotFoundErr(err error, ref string) bool { return strings.Contains(err.Error(), fmt.Sprintf("couldn't find remote ref '%s'", ref)) } diff --git a/git/internal/build/build.go b/git/internal/build/build.go new file mode 100644 index 000000000..773da26cb --- /dev/null +++ b/git/internal/build/build.go @@ -0,0 +1,103 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package build + +import ( + "fmt" + "io" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + + "github.com/fluxcd/pkg/git" +) + +func signature(s object.Signature) git.Signature { + return git.Signature{ + Name: s.Name, + Email: s.Email, + When: s.When, + } +} + +func Tag(t *object.Tag, ref plumbing.ReferenceName) (*git.Tag, error) { + if t == nil { + return &git.Tag{ + Name: ref.Short(), + }, nil + } + + encoded := &plumbing.MemoryObject{} + if err := t.EncodeWithoutSignature(encoded); err != nil { + return nil, fmt.Errorf("unable to encode tag '%s': %w", t.Name, err) + } + reader, err := encoded.Reader() + if err != nil { + return nil, fmt.Errorf("unable to encode tag '%s': %w", t.Name, err) + } + b, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("unable to read encoded tag '%s': %w", t.Name, err) + } + + return &git.Tag{ + Hash: []byte(t.Hash.String()), + Name: t.Name, + Author: signature(t.Tagger), + Signature: t.PGPSignature, + Encoded: b, + Message: t.Message, + }, nil +} + +func CommitWithRef(c *object.Commit, t *object.Tag, ref plumbing.ReferenceName) (*git.Commit, error) { + if c == nil { + return nil, fmt.Errorf("unable to construct commit: no object") + } + + encoded := &plumbing.MemoryObject{} + if err := c.EncodeWithoutSignature(encoded); err != nil { + return nil, fmt.Errorf("unable to encode commit '%s': %w", c.Hash, err) + } + reader, err := encoded.Reader() + if err != nil { + return nil, fmt.Errorf("unable to encode commit '%s': %w", c.Hash, err) + } + b, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("unable to read encoded commit '%s': %w", c.Hash, err) + } + cc := &git.Commit{ + Hash: []byte(c.Hash.String()), + Reference: ref.String(), + Author: signature(c.Author), + Committer: signature(c.Committer), + Signature: c.PGPSignature, + Encoded: b, + Message: c.Message, + } + + if ref.IsTag() { + tt, err := Tag(t, ref) + if err != nil { + return nil, err + } + cc.ReferencingTag = tt + } + + return cc, nil +} diff --git a/git/signature/gpg_signature_test.go b/git/signature/gpg_signature_test.go index 56da46564..5109c152b 100644 --- a/git/signature/gpg_signature_test.go +++ b/git/signature/gpg_signature_test.go @@ -21,7 +21,7 @@ import ( "path/filepath" "testing" - "github.com/fluxcd/pkg/git/gogit" + "github.com/fluxcd/pkg/git/internal/build" "github.com/fluxcd/pkg/git/signature" "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" @@ -236,8 +236,8 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, kt.tagFile)) g.Expect(err).ToNot(HaveOccurred()) - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + // Build a git.Tag using build.Tag + gitTag, err := build.Tag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) g.Expect(err).ToNot(HaveOccurred()) // Read the public key @@ -277,8 +277,8 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, kt.commitFile)) g.Expect(err).ToNot(HaveOccurred()) - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + // Build a git.Commit using build.CommitWithRef + gitCommit, err := build.CommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) g.Expect(err).ToNot(HaveOccurred()) // Read the public key @@ -318,8 +318,8 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) g.Expect(err).ToNot(HaveOccurred()) - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + // Build a git.Commit using build.CommitWithRef + gitCommit, err := build.CommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) g.Expect(err).ToNot(HaveOccurred()) // Read a public key diff --git a/git/signature/ssh_signature_test.go b/git/signature/ssh_signature_test.go index fb447abba..76dac1b1b 100644 --- a/git/signature/ssh_signature_test.go +++ b/git/signature/ssh_signature_test.go @@ -22,14 +22,14 @@ import ( "strings" "testing" - "github.com/fluxcd/pkg/git/gogit" + "github.com/fluxcd/pkg/git/internal/build" "github.com/fluxcd/pkg/git/signature" "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" gossh "golang.org/x/crypto/ssh" ) -// these tests are in a different package to avoid circular dependencies with gogit.BuildCommitWithRef and gogit.BuildTag +// these tests are in a different package to avoid circular dependencies with build.CommitWithRef and build.Tag func TestVerifySSHSignature(t *testing.T) { testDataDir := filepath.Join("testdata", "ssh_signatures") @@ -93,8 +93,8 @@ func TestVerifySSHSignature(t *testing.T) { t.Fatalf("Failed to parse commit from fixture: %v", err) } - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + // Build a git.Commit using build.CommitWithRef + gitCommit, err := build.CommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) if err != nil { t.Fatalf("Failed to build commit: %v", err) } @@ -105,8 +105,8 @@ func TestVerifySSHSignature(t *testing.T) { t.Fatalf("Failed to parse commit from fixture: %v", err) } - // Build a git.Commit using BuildCommitWithRef - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + // Build a git.Commit using build.CommitWithRef + gitTag, err := build.Tag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) if err != nil { t.Fatalf("Failed to build commit: %v", err) } @@ -197,14 +197,14 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Fatalf("Failed to parse tag from fixture: %v", err) } - // Build a git.Commit using BuildCommitWithRef - gitCommit, err := gogit.BuildCommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + // Build a git.Commit using build.CommitWithRef + gitCommit, err := build.CommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) if err != nil { t.Fatalf("Failed to build commit: %v", err) } - // Build a git.Tag using BuildTag - gitTag, err := gogit.BuildTag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + // Build a git.Tag using build.Tag + gitTag, err := build.Tag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) if err != nil { t.Fatalf("Failed to build tag: %v", err) } From 5d4482b8d7ec84432b41ee624c77e653c9b864f1 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 19 May 2026 07:52:56 +0200 Subject: [PATCH 17/22] improves signature validation to test all provided keys/keyrings if malfored key is included Signed-off-by: Ricardo Bartels --- git/signature/gpg_signature.go | 13 +- git/signature/gpg_signature_test.go | 21 ++ git/signature/ssh_signature.go | 12 +- git/signature/ssh_signature_test.go | 302 ++++++++++------------------ 4 files changed, 144 insertions(+), 204 deletions(-) diff --git a/git/signature/gpg_signature.go b/git/signature/gpg_signature.go index 3d4cea221..2f0d09200 100644 --- a/git/signature/gpg_signature.go +++ b/git/signature/gpg_signature.go @@ -47,16 +47,25 @@ func VerifyPGPSignature(signature string, payload []byte, keyRings ...string) (s return "", fmt.Errorf("unable to verify openPGP signature, detected signature format: %s", GetSignatureType(signature)) } + // record reading of armored key error. This error will be returned of no valid key was found. + var readKeyRingError error + for _, r := range keyRings { reader := strings.NewReader(r) keyring, err := openpgp.ReadArmoredKeyRing(reader) - if err != nil { - return "", fmt.Errorf("unable to read armored key ring: %w", err) + if err != nil && readKeyRingError == nil { + readKeyRingError = fmt.Errorf("unable to read armored key ring: %w", err) + continue } signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(payload), strings.NewReader(signature), nil) if err == nil { return signer.PrimaryKey.KeyIdString(), nil } } + + if readKeyRingError != nil { + return "", readKeyRingError + } + return "", fmt.Errorf("unable to verify payload with any of the given key rings") } diff --git a/git/signature/gpg_signature_test.go b/git/signature/gpg_signature_test.go index 5109c152b..b095c677f 100644 --- a/git/signature/gpg_signature_test.go +++ b/git/signature/gpg_signature_test.go @@ -172,6 +172,27 @@ func TestVerifyPGPSignature(t *testing.T) { keyRings: []string{armoredKeyRingFixture}, wantErr: "unable to verify openPGP signature, detected signature format: ssh", }, + { + name: "Malformed key ring followed by valid key ring", + payload: []byte(encodedCommitFixture), + sig: signatureCommitFixture, + keyRings: []string{malformedKeyRingFixture, armoredKeyRingFixture}, + want: keyRingFingerprintFixture, + }, + { + name: "Valid key ring followed by malformed key ring", + payload: []byte(encodedCommitFixture), + sig: signatureCommitFixture, + keyRings: []string{armoredKeyRingFixture, malformedKeyRingFixture}, + want: keyRingFingerprintFixture, + }, + { + name: "Multiple malformed key rings", + payload: []byte(encodedCommitFixture), + sig: signatureCommitFixture, + keyRings: []string{malformedKeyRingFixture, malformedKeyRingFixture}, + wantErr: "unable to read armored key ring: unexpected EOF", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/git/signature/ssh_signature.go b/git/signature/ssh_signature.go index c052a73f1..11696d6b6 100644 --- a/git/signature/ssh_signature.go +++ b/git/signature/ssh_signature.go @@ -78,11 +78,15 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri return "", fmt.Errorf("unable to unarmor SSH signature: %w", err) } + // record reading of authorized keys error. This error will be returned if no valid key was found. + var readAuthorizedKeysError error + // Try to verify with each set of authorized keys for _, keys := range authorizedKeys { publicKeys, err := ParseAuthorizedKeys(keys) - if err != nil { - return "", fmt.Errorf("unable to parse authorized keys: %w", err) + if err != nil && readAuthorizedKeysError == nil { + readAuthorizedKeysError = fmt.Errorf("unable to parse authorized keys: %w", err) + continue } // Try to verify with each public key @@ -96,5 +100,9 @@ func VerifySSHSignature(signature string, payload []byte, authorizedKeys ...stri } } + if readAuthorizedKeysError != nil { + return "", readAuthorizedKeysError + } + return "", fmt.Errorf("unable to verify payload with any of the given authorized keys") } diff --git a/git/signature/ssh_signature_test.go b/git/signature/ssh_signature_test.go index 76dac1b1b..c0bbdfd5e 100644 --- a/git/signature/ssh_signature_test.go +++ b/git/signature/ssh_signature_test.go @@ -26,6 +26,7 @@ import ( "github.com/fluxcd/pkg/git/signature" "github.com/fluxcd/pkg/git/testutils" "github.com/go-git/go-git/v5/plumbing" + . "github.com/onsi/gomega" gossh "golang.org/x/crypto/ssh" ) @@ -185,219 +186,120 @@ func TestSSHSignatureValidationCases(t *testing.T) { t.Fatalf("Failed to read authorized keys: %v", err) } - // Parse the commit from the fixture file - commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_"+keyType+"_signed.txt")) + expectedFingerprintBytes, err := os.ReadFile(filepath.Join(testDataDir, "key_ed25519.pub_fingerprint")) if err != nil { - t.Fatalf("Failed to parse commit from fixture: %v", err) + t.Fatalf("Failed to read fingerprint file: %v", err) } + expectedFingerprint := strings.TrimSpace(string(expectedFingerprintBytes)) - // Parse the tag from the fixture file - tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_"+keyType+"_signed.txt")) + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_"+keyType+"_signed.txt")) if err != nil { - t.Fatalf("Failed to parse tag from fixture: %v", err) + t.Fatalf("Failed to parse commit from fixture: %v", err) } - // Build a git.Commit using build.CommitWithRef gitCommit, err := build.CommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) if err != nil { t.Fatalf("Failed to build commit: %v", err) } - // Build a git.Tag using build.Tag - gitTag, err := build.Tag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) - if err != nil { - t.Fatalf("Failed to build tag: %v", err) + const invalidAuthKeys = "invalid-key-data" + + tests := []struct { + name string + sig string + payload []byte + authorizedKeys []string + want string + wantErr string + }{ + { + name: "Empty signature", + sig: "", + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey)}, + wantErr: "unable to verify payload as the provided signature is empty", + }, + { + name: "Empty payload", + sig: gitCommit.Signature, + payload: []byte{}, + authorizedKeys: []string{string(pubKey)}, + wantErr: "unable to verify payload as the provided payload is empty", + }, + { + name: "Wrong authorized keys", + sig: gitCommit.Signature, + payload: gitCommit.Encoded, + authorizedKeys: []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyM97VxLgOCuB9Eg5cDtTc8ogkdM1xAyJhzODB9cK1 wrong@example.com"}, + wantErr: "unable to parse authorized key", + }, + { + name: "Empty authorized keys", + sig: gitCommit.Signature, + payload: gitCommit.Encoded, + authorizedKeys: []string{""}, + wantErr: "unable to verify payload with any of the given authorized keys", + }, + { + name: "Invalid signature", + sig: "-----BEGIN SSH SIGNATURE-----\n invalid\n -----END SSH SIGNATURE-----", + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey)}, + wantErr: "unable to unarmor SSH signature", + }, + { + name: "Non-SSH signature", + sig: "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----", + payload: gitCommit.Encoded, + authorizedKeys: []string{""}, + wantErr: "unable to verify SSH signature, detected signature format: openpgp", + }, + { + name: "Invalid authorized keys", + sig: gitCommit.Signature, + payload: gitCommit.Encoded, + authorizedKeys: []string{invalidAuthKeys}, + wantErr: "unable to parse authorized key", + }, + { + name: "Invalid keys followed by valid keys", + sig: gitCommit.Signature, + payload: gitCommit.Encoded, + authorizedKeys: []string{invalidAuthKeys, string(pubKey)}, + want: expectedFingerprint, + }, + { + name: "Valid keys followed by invalid keys", + sig: gitCommit.Signature, + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey), invalidAuthKeys}, + want: expectedFingerprint, + }, + { + name: "Multiple invalid authorized keys", + sig: gitCommit.Signature, + payload: gitCommit.Encoded, + authorizedKeys: []string{invalidAuthKeys, invalidAuthKeys}, + wantErr: "unable to parse authorized key", + }, } - // Test error cases - t.Run("empty signature", func(t *testing.T) { - - fingerprint, err := signature.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload as the provided signature is empty" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) - } - - fingerprint, err = signature.VerifySSHSignature("", gitCommit.Encoded, string(pubKey)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty signature, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty signature: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload as the provided signature is empty" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided signature is empty'", err) - } - - }) - - t.Run("empty payload", func(t *testing.T) { - - fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, []byte{}, string(pubKey)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload as the provided payload is empty" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) - } - - fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, []byte{}, string(pubKey)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty payload, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty payload: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload as the provided payload is empty" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload as the provided payload is empty'", err) - } - - }) - - t.Run("wrong authorized keys", func(t *testing.T) { - // Use a different key that won't match - wrongKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyM97VxLgOCuB9Eg5cDtTc8ogkdM1xAyJhzODB9cK1 wrong@example.com" - - fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, wrongKey) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) - } - // The error can be either a parsing error or a verification error - if err != nil && !strings.Contains(err.Error(), "unable to verify payload with any of the given authorized keys") && !strings.Contains(err.Error(), "unable to parse authorized key") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) - } - - fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, wrongKey) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for wrong authorized keys, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for wrong authorized keys: %s", fingerprint) - } - // The error can be either a parsing error or a verification error - if err != nil && !strings.Contains(err.Error(), "unable to verify payload with any of the given authorized keys") && !strings.Contains(err.Error(), "unable to parse authorized key") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to verify payload with any of the given authorized keys' or 'unable to parse authorized key'", err) - } - }) - - t.Run("empty authorized keys", func(t *testing.T) { - // Use empty authorized keys - emptyAuthKeys := "" - - fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, emptyAuthKeys) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty authorized keys, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty authorized keys: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload with any of the given authorized keys" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload with any of the given authorized keys'", err) - } - - fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, emptyAuthKeys) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for empty authorized keys, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for empty authorized keys: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify payload with any of the given authorized keys" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify payload with any of the given authorized keys'", err) - } - }) - - t.Run("invalid signature", func(t *testing.T) { - invalidSig := "-----BEGIN SSH SIGNATURE-----\n invalid\n -----END SSH SIGNATURE-----" - - fingerprint, err := signature.VerifySSHSignature(invalidSig, gitCommit.Encoded, string(pubKey)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) - } - if err != nil && !strings.Contains(err.Error(), "unable to unarmor SSH signature") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) - } - - fingerprint, err = signature.VerifySSHSignature(invalidSig, gitTag.Encoded, string(pubKey)) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for invalid signature, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for invalid signature: %s", fingerprint) - } - if err != nil && !strings.Contains(err.Error(), "unable to unarmor SSH signature") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to unarmor SSH signature'", err) - } - - }) - - t.Run("non-SSH signature", func(t *testing.T) { - // Use a PGP signature instead of SSH signature - pgpSig := "-----BEGIN PGP SIGNATURE-----\n-----END PGP SIGNATURE-----" - - fingerprint, err := signature.VerifySSHSignature(pgpSig, gitCommit.Encoded, "") - if err == nil { - t.Errorf("VerifySSHSignature() expected error for non-SSH signature, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for non-SSH signature: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify SSH signature, detected signature format: openpgp" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify SSH signature, detected signature format: openpgp'", err) - } - - fingerprint, err = signature.VerifySSHSignature(pgpSig, gitTag.Encoded, "") - if err == nil { - t.Errorf("VerifySSHSignature() expected error for non-SSH signature, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for non-SSH signature: %s", fingerprint) - } - if err != nil && err.Error() != "unable to verify SSH signature, detected signature format: openpgp" { - t.Errorf("VerifySSHSignature() error = %v, want 'unable to verify SSH signature, detected signature format: openpgp'", err) - } - }) - - t.Run("invalid authorized keys", func(t *testing.T) { - // Use invalid authorized keys - invalidAuthKeys := "invalid-key-data" - - fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, invalidAuthKeys) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for invalid authorized keys, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for invalid authorized keys: %s", fingerprint) - } - if err != nil && !strings.Contains(err.Error(), "unable to parse authorized key") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to parse authorized key'", err) - } - - fingerprint, err = signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, invalidAuthKeys) - if err == nil { - t.Errorf("VerifySSHSignature() expected error for invalid authorized keys, got nil") - } - if fingerprint != "" { - t.Errorf("VerifySSHSignature() returned fingerprint for invalid authorized keys: %s", fingerprint) - } - if err != nil && !strings.Contains(err.Error(), "unable to parse authorized key") { - t.Errorf("VerifySSHSignature() error = %v, want error containing 'unable to parse authorized key'", err) - } - }) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + got, err := signature.VerifySSHSignature(tt.sig, tt.payload, tt.authorizedKeys...) + if tt.wantErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + g.Expect(got).To(BeEmpty()) + return + } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(Equal(tt.want)) + }) + } } func TestParseAuthorizedKeysAndPublicFingerprint(t *testing.T) { From a5ec92f94db48efb25c1f7f201ccec17e91d6461 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 19 May 2026 17:24:35 +0200 Subject: [PATCH 18/22] removes gnupg ed448 generated key test fixtures and adds mention to README Signed-off-by: Ricardo Bartels --- git/signature/gpg_signature_test.go | 6 ----- .../testdata/gpg_signatures/README.md | 22 ++----------------- .../gpg_signatures/commit_ed448_signed.txt | 13 ----------- .../gpg_signatures/generate_gpg_fixtures.sh | 3 --- .../testdata/gpg_signatures/key_ed448.pub | 11 ---------- .../gpg_signatures/tag_ed448_signed.txt | 14 ------------ 6 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 git/signature/testdata/gpg_signatures/commit_ed448_signed.txt delete mode 100644 git/signature/testdata/gpg_signatures/key_ed448.pub delete mode 100644 git/signature/testdata/gpg_signatures/tag_ed448_signed.txt diff --git a/git/signature/gpg_signature_test.go b/git/signature/gpg_signature_test.go index b095c677f..eab2ad0da 100644 --- a/git/signature/gpg_signature_test.go +++ b/git/signature/gpg_signature_test.go @@ -232,12 +232,6 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { {"brainpool_p256 valid signature", "commit_brainpool_p256_signed.txt", "tag_brainpool_p256_signed.txt", "key_brainpool_p256.pub", false}, {"brainpool_p384 valid signature", "commit_brainpool_p384_signed.txt", "tag_brainpool_p384_signed.txt", "key_brainpool_p384.pub", false}, {"brainpool_p512 valid signature", "commit_brainpool_p512_signed.txt", "tag_brainpool_p512_signed.txt", "key_brainpool_p512.pub", false}, - - // ed448 test fails because the key was created with OpenPGP version 5, - // which is not supported by github.com/ProtonMail/go-crypto (only version 4 is supported). - // The error occurs when trying to read the armored key ring: - // "unable to read armored key ring: openpgp: invalid data: first packet was not a public/private key" - {"ed448 valid signature", "commit_ed448_signed.txt", "tag_ed448_signed.txt", "key_ed448.pub", true}, } var allKeysRing []string diff --git a/git/signature/testdata/gpg_signatures/README.md b/git/signature/testdata/gpg_signatures/README.md index e7e8187ed..369f970c5 100644 --- a/git/signature/testdata/gpg_signatures/README.md +++ b/git/signature/testdata/gpg_signatures/README.md @@ -22,9 +22,9 @@ The [`generate_gpg_fixtures.sh`](generate_gpg_fixtures.sh) script automates the - RSA (2048 and 4096 bits) - ECC/ECDSA (NIST P-256, P-384, P-521) - Brainpool curves (P-256, P-384, P-512) - - EdDSA (Ed25519, Ed448) + - EdDSA (Ed25519) - **Note:** Some key types (like Ed448) require GnuPG 2.3 or higher. The script will report any failures and continue with successfully generated keys. + **Note:** currently it is not possible to use gnupg generate ed448 keys to validate signed commits with backends which implemented RFC9580: https://github.com/ProtonMail/go-crypto/issues/300 2. **Public Keys**: - Individual public key files for each key type @@ -164,18 +164,6 @@ Expire-Date: 0 EOF gpg --batch --generate-key batch_ed25519.txt -# Ed448 key -cat > batch_ed448.txt < key_rsa_2048.pub gpg --armor --export test-rsa-4096@example.com > key_rsa_4096.pub @@ -186,7 +174,6 @@ gpg --armor --export test-brainpool-p256@example.com > key_brainpool_p256.pub gpg --armor --export test-brainpool-p384@example.com > key_brainpool_p384.pub gpg --armor --export test-brainpool-p512@example.com > key_brainpool_p512.pub gpg --armor --export test-ed25519@example.com > key_ed25519.pub -gpg --armor --export test-ed448@example.com > key_ed448.pub ``` #### 2. Create a Test Git Repository @@ -277,7 +264,6 @@ The script generates the following files: - `key_brainpool_p384.pub` - Brainpool P-384 public key - `key_brainpool_p512.pub` - Brainpool P-512 public key - `key_ed25519.pub` - Ed25519 public key -- `key_ed448.pub` - Ed448 public key ### Signed Commits - `commit_rsa_2048_signed.txt` - RSA 2048-bit signed commit @@ -289,7 +275,6 @@ The script generates the following files: - `commit_brainpool_p384_signed.txt` - Brainpool P-384 signed commit - `commit_brainpool_p512_signed.txt` - Brainpool P-512 signed commit - `commit_ed25519_signed.txt` - Ed25519 signed commit -- `commit_ed448_signed.txt` - Ed448 signed commit ### Signed Tags - `tag_rsa_2048_signed.txt` - RSA 2048-bit signed tag @@ -301,7 +286,6 @@ The script generates the following files: - `tag_brainpool_p384_signed.txt` - Brainpool P-384 signed tag - `tag_brainpool_p512_signed.txt` - Brainpool P-512 signed tag - `tag_ed25519_signed.txt` - Ed25519 signed tag -- `tag_ed448_signed.txt` - Ed448 signed tag ### Unsigned Commit - `commit_unsigned.txt` - Unsigned commit for testing negative cases @@ -327,7 +311,6 @@ The script generates the following files: ### EdDSA (Edwards-curve Digital Signature Algorithm) - **Ed25519**: Modern, fast, and secure curve -- **Ed448**: Higher security variant - Recommended for new applications ## Security Note @@ -343,7 +326,6 @@ These test fixtures use generated test keys and should NOT be used in production ## Troubleshooting ### GPG version compatibility -Some key types (like Ed448) require GnuPG 2.3 or higher. If you encounter errors, check your GPG version: ```bash gpg --version diff --git a/git/signature/testdata/gpg_signatures/commit_ed448_signed.txt b/git/signature/testdata/gpg_signatures/commit_ed448_signed.txt deleted file mode 100644 index 6080f3434..000000000 --- a/git/signature/testdata/gpg_signatures/commit_ed448_signed.txt +++ /dev/null @@ -1,13 +0,0 @@ -tree d49a4c033c2a0d7c2d5882461a0e70f61e021959 -author Test User 1772188966 +0100 -committer Test User 1772188966 +0100 -gpgsig -----BEGIN PGP SIGNATURE----- - - iKkFABYKACkiIQWoMJZGKPzTpyuUAJVTaO7/Ty5E4I2nu8xGwMU1m96nfAUCaaF1 - JgAA0yIByPwhpDW6dmJddCS/TsB2z2Wu30Vjd3wGLCp3J6N8FHsVi6jcmbPM2JXF - /uA7DZWryLM1Rgtsbcv9AAHGMTePYjyduBDw/uK7K3kgL0NnLHGHEQ1545mSmk4Z - Q8ltXviJqCQ4Ut549BoZxM8YbIieNmVWtiwA - =cJtf - -----END PGP SIGNATURE----- - -Test commit signed with ed448 diff --git a/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh index 30514ebeb..bf6f8aae7 100755 --- a/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh +++ b/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh @@ -196,9 +196,6 @@ main() { # Ed25519 (modern elliptic curve) generate_key "eddsa" "Ed25519" "ed25519" - # Ed448 (less common) - generate_key "eddsa" "Ed448" "ed448" - echo "" echo "Step 3: Create signed commits..." echo "----------------------------------------" diff --git a/git/signature/testdata/gpg_signatures/key_ed448.pub b/git/signature/testdata/gpg_signatures/key_ed448.pub deleted file mode 100644 index 1a0082821..000000000 --- a/git/signature/testdata/gpg_signatures/key_ed448.pub +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mEkFaaF1IxYAAAA/AytlcQHHVaag9xPUoWmV1JEqTCsKYnFQWm6PiaoTmLlDSIja -hH8gjdxTMzX7K+s9pI3Vxxdx1IdJ5kSumz0AtCJUZXN0IFVzZXIgPHRlc3QtZWQ0 -NDhAZXhhbXBsZS5jb20+iMcFExYKAEciIQWoMJZGKPzTpyuUAJVTaO7/Ty5E4I2n -u8xGwMU1m96nfAUCaaF1IwIbIwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAA -dbgByKYQcf0u88/60iuHN0mrkEQ1DenGhOmizKcrBpxLhHjEk+xQnuvA/tlEJVfZ -4lfWQO/sDJZMV013gAHIleJVkxDqXi+6UlXetODZRu3+kAGunWyzyU1XEjXbRCPh -l4jDOm9PF/GDfeqXWfzChIJZPQt/Uy8A -=VpqO ------END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/tag_ed448_signed.txt b/git/signature/testdata/gpg_signatures/tag_ed448_signed.txt deleted file mode 100644 index 6b27d5dda..000000000 --- a/git/signature/testdata/gpg_signatures/tag_ed448_signed.txt +++ /dev/null @@ -1,14 +0,0 @@ -object 594f42b8ecaad08227805a281ca4b053ff7fdae4 -type commit -tag test-tag-ed448 -tagger Test User 1772188970 +0100 - -Test tag signed with ed448 ------BEGIN PGP SIGNATURE----- - -iKkFABYKACkiIQWoMJZGKPzTpyuUAJVTaO7/Ty5E4I2nu8xGwMU1m96nfAUCaaF1 -KgAAe94Bx0QXkRdhxyHoybrUWYIcs0ZFMhZRQa823NLlMtlPIVjUAieWGSJrVJyD -ZJbDprNIyLFRvEFYoYdEgAHDB+NuVFZQ+wvqwrQ6DryI4Azh6AorZCCxeHQ3dpm/ -o3KzUg0YxLeBptuESwPGyTqnTqublmc4xAMA -=axq3 ------END PGP SIGNATURE----- From 1defd3dfc04ec6a3ace0e41e13ee423f8733d58b Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 19 May 2026 17:30:42 +0200 Subject: [PATCH 19/22] removes verfied signers from ssh signature test fixtures Signed-off-by: Ricardo Bartels --- .../testdata/ssh_signatures/README.md | 20 ++++------------ .../ssh_signatures/generate_ssh_fixtures.sh | 24 ++----------------- .../ssh_signatures/verified_signers_all | 5 ---- .../verified_signers_ecdsa_p256 | 1 - .../verified_signers_ecdsa_p384 | 1 - .../verified_signers_ecdsa_p521 | 1 - .../ssh_signatures/verified_signers_ed25519 | 1 - .../ssh_signatures/verified_signers_rsa | 1 - 8 files changed, 6 insertions(+), 48 deletions(-) delete mode 100644 git/signature/testdata/ssh_signatures/verified_signers_all delete mode 100644 git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p256 delete mode 100644 git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p384 delete mode 100644 git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p521 delete mode 100644 git/signature/testdata/ssh_signatures/verified_signers_ed25519 delete mode 100644 git/signature/testdata/ssh_signatures/verified_signers_rsa diff --git a/git/signature/testdata/ssh_signatures/README.md b/git/signature/testdata/ssh_signatures/README.md index 1e57a520d..7435a145f 100644 --- a/git/signature/testdata/ssh_signatures/README.md +++ b/git/signature/testdata/ssh_signatures/README.md @@ -10,7 +10,7 @@ To generate all test fixtures at once, simply run: ./generate_ssh_fixtures.sh ``` -This script will automatically create all SSH keys, authorized_keys files, verified signers files, signed commits, and signed tags. +This script will automatically create all SSH keys, authorized_keys files, signed commits, and signed tags. ## How to Generate Test Fixtures @@ -27,19 +27,15 @@ The [`generate_ssh_fixtures.sh`](generate_ssh_fixtures.sh) script automates the - Individual files for each key type - Combined file with all keys -3. **Verified Signers Files** (with git namespace): - - Individual files for each key type - - Combined file with all keys - -4. **Signed Git Commits**: +3. **Signed Git Commits**: - One signed commit for each key type - All commits are verified using `git verify-commit` -5. **Signed Git Tags**: +4. **Signed Git Tags**: - One signed tag for each key type - All tags are verified using `git verify-tag` -6. **Unsigned Commit**: +5. **Unsigned Commit**: - One unsigned commit for testing negative cases ### Manual Generation @@ -165,14 +161,6 @@ The script generates the following files: - `key_ed25519.pub` - ED25519 public key - `keys_all.pub` - All public keys -### Verified Signers Files -- `verified_signers_rsa` - RSA public key with git namespace -- `verified_signers_ecdsa_p256` - ECDSA P-256 public key with git namespace -- `verified_signers_ecdsa_p384` - ECDSA P-384 public key with git namespace -- `verified_signers_ecdsa_p521` - ECDSA P-521 public key with git namespace -- `verified_signers_ed25519` - ED25519 public key with git namespace -- `verified_signers_all` - All public keys with git namespace - ### Signed Commits - `commit_rsa_signed.txt` - RSA-signed commit - `commit_ecdsa_p256_signed.txt` - ECDSA P-256 signed commit diff --git a/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh index 8386b012f..36e237428 100755 --- a/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh +++ b/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh @@ -49,7 +49,7 @@ generate_ssh_key() { # Function to create verified signers files with git namespace create_verified_signers() { local key_name=$1 - local output_file="$SCRIPT_DIR/verified_signers_${key_name}" + local output_file="$TEMP_DIR/verified_signers_${key_name}" echo "Creating verified signers file for $key_name..." @@ -76,30 +76,13 @@ create_combined_pub_keys() { echo " ✓ $output_file created" } -# Function to create combined verified signers file -create_combined_verified_signers() { - local output_file="$SCRIPT_DIR/verified_signers_all" - - echo "Creating combined verified signers..." - - # Combine all public keys with git namespace - { - echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/rsa.pub")" - echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ecdsa_p256.pub")" - echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ecdsa_p384.pub")" - echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ecdsa_p521.pub")" - echo "$TEST_USER_EMAIL namespaces=\"git\" $(cat "$TEMP_DIR/ed25519.pub")" - } > "$output_file" - - echo " ✓ $output_file created" -} # Function to create signed Git objects (commits and tags) create_signed_object() { local object_type=$1 local key_name=$2 local key_type=$3 - local verified_signers_file="$SCRIPT_DIR/verified_signers_${key_name}" + local verified_signers_file="$TEMP_DIR/verified_signers_${key_name}" echo "Creating signed $object_type for $key_name..." @@ -228,9 +211,6 @@ main() { create_verified_signers "ecdsa_p521" create_verified_signers "ed25519" - # Combined verified signers file - create_combined_verified_signers - echo "" echo "Step 4: Create signed commits..." echo "----------------------------------------" diff --git a/git/signature/testdata/ssh_signatures/verified_signers_all b/git/signature/testdata/ssh_signatures/verified_signers_all deleted file mode 100644 index 40c5d87a6..000000000 --- a/git/signature/testdata/ssh_signatures/verified_signers_all +++ /dev/null @@ -1,5 +0,0 @@ -sign-user@example.com namespaces="git" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com -sign-user@example.com namespaces="git" ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com -sign-user@example.com namespaces="git" ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com -sign-user@example.com namespaces="git" ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com -sign-user@example.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p256 b/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p256 deleted file mode 100644 index c776e628e..000000000 --- a/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p256 +++ /dev/null @@ -1 +0,0 @@ -sign-user@example.com namespaces="git" ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com diff --git a/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p384 b/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p384 deleted file mode 100644 index ef4a20160..000000000 --- a/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p384 +++ /dev/null @@ -1 +0,0 @@ -sign-user@example.com namespaces="git" ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com diff --git a/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p521 b/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p521 deleted file mode 100644 index 91f0e5571..000000000 --- a/git/signature/testdata/ssh_signatures/verified_signers_ecdsa_p521 +++ /dev/null @@ -1 +0,0 @@ -sign-user@example.com namespaces="git" ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com diff --git a/git/signature/testdata/ssh_signatures/verified_signers_ed25519 b/git/signature/testdata/ssh_signatures/verified_signers_ed25519 deleted file mode 100644 index ce5186928..000000000 --- a/git/signature/testdata/ssh_signatures/verified_signers_ed25519 +++ /dev/null @@ -1 +0,0 @@ -sign-user@example.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com diff --git a/git/signature/testdata/ssh_signatures/verified_signers_rsa b/git/signature/testdata/ssh_signatures/verified_signers_rsa deleted file mode 100644 index 0e845f6f5..000000000 --- a/git/signature/testdata/ssh_signatures/verified_signers_rsa +++ /dev/null @@ -1 +0,0 @@ -sign-user@example.com namespaces="git" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com From f080b82ae8ca851f3524d7f1986ef9aeb88608e9 Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 19 May 2026 18:11:08 +0200 Subject: [PATCH 20/22] updates test fixtures generation scripts and regenerated test fixtures Signed-off-by: Ricardo Bartels --- .../commit_brainpool_p256_signed.txt | 13 +-- .../commit_brainpool_p384_signed.txt | 14 +-- .../commit_brainpool_p512_signed.txt | 15 +-- .../commit_ecdsa_p256_signed.txt | 13 +-- .../commit_ecdsa_p384_signed.txt | 14 +-- .../commit_ecdsa_p521_signed.txt | 15 +-- .../gpg_signatures/commit_ed25519_signed.txt | 13 +-- .../gpg_signatures/commit_rsa_2048_signed.txt | 21 +++-- .../gpg_signatures/commit_rsa_4096_signed.txt | 31 ++++--- .../gpg_signatures/commit_unsigned.txt | 4 +- .../gpg_signatures/generate_gpg_fixtures.sh | 72 ++++++++++++--- .../gpg_signatures/key_brainpool_p256.pub | 15 +-- .../gpg_signatures/key_brainpool_p384.pub | 18 ++-- .../gpg_signatures/key_brainpool_p512.pub | 21 +++-- .../gpg_signatures/key_ecdsa_p256.pub | 15 +-- .../gpg_signatures/key_ecdsa_p384.pub | 17 ++-- .../gpg_signatures/key_ecdsa_p521.pub | 21 +++-- .../testdata/gpg_signatures/key_ed25519.pub | 13 +-- .../testdata/gpg_signatures/key_rsa_2048.pub | 31 ++++--- .../testdata/gpg_signatures/key_rsa_4096.pub | 52 +++++------ .../tag_brainpool_p256_signed.txt | 13 +-- .../tag_brainpool_p384_signed.txt | 14 +-- .../tag_brainpool_p512_signed.txt | 15 +-- .../gpg_signatures/tag_ecdsa_p256_signed.txt | 13 +-- .../gpg_signatures/tag_ecdsa_p384_signed.txt | 14 +-- .../gpg_signatures/tag_ecdsa_p521_signed.txt | 15 +-- .../gpg_signatures/tag_ed25519_signed.txt | 13 +-- .../gpg_signatures/tag_rsa_2048_signed.txt | 21 +++-- .../gpg_signatures/tag_rsa_4096_signed.txt | 31 ++++--- .../testdata/gpg_signatures/tag_unsigned.txt | 4 +- .../commit_ecdsa_p256_signed.txt | 12 +-- .../commit_ecdsa_p384_signed.txt | 14 +-- .../commit_ecdsa_p521_signed.txt | 18 ++-- .../ssh_signatures/commit_ed25519_signed.txt | 12 +-- .../ssh_signatures/commit_rsa_signed.txt | 48 +++++----- .../ssh_signatures/commit_unsigned.txt | 4 +- .../ssh_signatures/generate_ssh_fixtures.sh | 91 ++++++++++++++----- .../ssh_signatures/key_ecdsa_p256.pub | 2 +- .../key_ecdsa_p256.pub_fingerprint | 2 +- .../ssh_signatures/key_ecdsa_p384.pub | 2 +- .../key_ecdsa_p384.pub_fingerprint | 2 +- .../ssh_signatures/key_ecdsa_p521.pub | 2 +- .../key_ecdsa_p521.pub_fingerprint | 2 +- .../testdata/ssh_signatures/key_ed25519.pub | 2 +- .../key_ed25519.pub_fingerprint | 2 +- .../testdata/ssh_signatures/key_rsa.pub | 2 +- .../ssh_signatures/key_rsa.pub_fingerprint | 2 +- .../testdata/ssh_signatures/keys_all.pub | 10 +- .../ssh_signatures/tag_ecdsa_p256_signed.txt | 12 +-- .../ssh_signatures/tag_ecdsa_p384_signed.txt | 14 +-- .../ssh_signatures/tag_ecdsa_p521_signed.txt | 18 ++-- .../ssh_signatures/tag_ed25519_signed.txt | 12 +-- .../ssh_signatures/tag_rsa_signed.txt | 48 +++++----- .../testdata/ssh_signatures/tag_unsigned.txt | 4 +- 54 files changed, 515 insertions(+), 403 deletions(-) diff --git a/git/signature/testdata/gpg_signatures/commit_brainpool_p256_signed.txt b/git/signature/testdata/gpg_signatures/commit_brainpool_p256_signed.txt index 8e2e958c0..d20861042 100644 --- a/git/signature/testdata/gpg_signatures/commit_brainpool_p256_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_brainpool_p256_signed.txt @@ -1,12 +1,13 @@ tree 1673f4226b68c3c29e8d038052698fd10706eb7e -author Test User 1772188964 +0100 -committer Test User 1772188964 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iHUEABMIAB0WIQSHtLFiUpKKegTi4RlOd7ceLHgABgUCaaF1JAAKCRBOd7ceLHgA - BpOTAP9KFSViLeUSJMzw9I2nW/kMJRWIXUE2XE+wuj/A2PTxYgD/ef3PLdiDr0l+ - CzdrXSQRdiNkD6avr8KEyy/Q0vz+03Y= - =IHuS + iJEEABMIADkWIQRlBCAxO+6ynqGKIEdbY0atdy3seQUCagyJ+hsUgAAAAAAEAA5t + YW51MiwyLjUrMS4xMiwwLDMACgkQW2NGrXct7HkyogD+NzBPXbA/WmTV3S3OJw2v + uCJlsozEIdBemRPlud/bdqcA/2VXBWBx/GNDVERCz+CoIgk1r+UYNHPY2sKC4Rof + XZ/b + =iLlU -----END PGP SIGNATURE----- Test commit signed with brainpool_p256 diff --git a/git/signature/testdata/gpg_signatures/commit_brainpool_p384_signed.txt b/git/signature/testdata/gpg_signatures/commit_brainpool_p384_signed.txt index 9feb7d2fa..3c2b2a8ed 100644 --- a/git/signature/testdata/gpg_signatures/commit_brainpool_p384_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_brainpool_p384_signed.txt @@ -1,13 +1,13 @@ tree ff5f115ae071fc5b5984c3cf8a2e14fb86e54596 -author Test User 1772188964 +0100 -committer Test User 1772188964 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iJUEABMJAB0WIQQ/Ad7FJfKxg1LGHLMZ238vxsg1ZQUCaaF1JAAKCRAZ238vxsg1 - ZRMFAX9PF5KWQcYJla4N0RPc/EwrYkmNVH7yJeKUiJA1H6efE99/0tejkP+oNLAr - RUH4HngBf0E/aFFzZD1T/D+mZgpwptGWL+3m41vo92byaUdeEcOfGZWGPzVceAsY - uesfSeUOAA== - =u9GB + iLEEABMJADkWIQTNs8DDx+7VeXH5/sB3IHg7nCO39gUCagyJ+hsUgAAAAAAEAA5t + YW51MiwyLjUrMS4xMiwwLDMACgkQdyB4O5wjt/Z2rgGAheub5m+E+SDusCBJWvEo + kjuBFeZuzB94m8gjMCH3dSxiC7LtSsobbf99tcf4YYH6AX4th44TivpvLO9rh54C + qf0BiV7Exf+3i/rXhwC2eHCmA/SFZvcbpMwtmhgg7kswhpk= + =PNo6 -----END PGP SIGNATURE----- Test commit signed with brainpool_p384 diff --git a/git/signature/testdata/gpg_signatures/commit_brainpool_p512_signed.txt b/git/signature/testdata/gpg_signatures/commit_brainpool_p512_signed.txt index 76c1efc1e..7073c90e4 100644 --- a/git/signature/testdata/gpg_signatures/commit_brainpool_p512_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_brainpool_p512_signed.txt @@ -1,13 +1,14 @@ tree a9ac3b19ae895b654fadecbf65d68b6b904e9015 -author Test User 1772188964 +0100 -committer Test User 1772188964 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iLUEABMKAB0WIQRFqHbkH9cuZyIgGbcl0p71vcaJEQUCaaF1JAAKCRAl0p71vcaJ - EbsGAf0UpYkwRuLxUfV19hj31s8CFTrqe4e8DgKhZxv1cNX/0FUE8n/u15GePsQQ - /I0Omw7bXSKo8wh0VeUD17GjiDOeAf9WBNDV9qQh3Z1Vc01DHQrzp0RKzoeTquxe - ivA0N6jknF9V6smfTbL0I6SLu3dtrA+1dh3CDeQCROdhH3aA7ZaG - =aUDv + iNEEABMKADkWIQTohLjZUdUvESasnEsiUQykOmaHcQUCagyJ+xsUgAAAAAAEAA5t + YW51MiwyLjUrMS4xMiwwLDMACgkQIlEMpDpmh3GorgH+Ipb5VC7CTo1ytrctn0NR + 77xD4xAwp+ut+PwkT2RMoxGqbEkwrW1Y1wGQAKPGnijSfUupISIgzdLJUShKDciz + JgH/US1NCBLnOuMdMdoc3AIqflJ8xu0pdf+0/XWOFGidJ6oiNI3QOxmAYjFvCee0 + TKi0AQTiCNokBpkV+cwe2GHEeg== + =QAlW -----END PGP SIGNATURE----- Test commit signed with brainpool_p512 diff --git a/git/signature/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt b/git/signature/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt index a95b90f90..7b19e0f0b 100644 --- a/git/signature/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_ecdsa_p256_signed.txt @@ -1,12 +1,13 @@ tree 2f0fa5393a2120151c5446eb34b99d1f3713ff12 -author Test User 1772188965 +0100 -committer Test User 1772188965 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iHUEABMIAB0WIQQZYVspROx35dYcOV/9NQRFrxVHiwUCaaF1JQAKCRD9NQRFrxVH - i7V+AQCBE5nzpuGEjw8dTsdQ7o53ec1fN/O8IoRreC98vr2/9AD9E6Yu6b0t+ahp - j90zFJCPdc+cAxk4mVXh4piVbJ8tPvQ= - =dI7Q + iJEEABMIADkWIQSra878nXqQhGlG0J5198+uMUSa1AUCagyJ+xsUgAAAAAAEAA5t + YW51MiwyLjUrMS4xMiwwLDMACgkQdffPrjFEmtQU6gD+IPI8ltvbuafpIpKVLLyY + W9SMNKzfjyqYdUrJ07qpzJMA/3IPL05fc18C0cPrAxZG+Z/aa5ETXKuVSyIpozCB + Ux8o + =N0QF -----END PGP SIGNATURE----- Test commit signed with ecdsa_p256 diff --git a/git/signature/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt b/git/signature/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt index 596f99517..7d95e1f30 100644 --- a/git/signature/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_ecdsa_p384_signed.txt @@ -1,13 +1,13 @@ tree ff58328bd5797f45f6f300c6c39d2cd357b9f3cd -author Test User 1772188965 +0100 -committer Test User 1772188965 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iJUEABMJAB0WIQScyLxivLVGKonynyhueVMDKL7ECgUCaaF1JQAKCRBueVMDKL7E - CvZZAYC3WouUxsPpDyK3rwkhe9/tLEeSq+Z2nIUNTK3CYjw2MbyqKqMav4dZiYun - C78+910BgMF8yGkEhzSVnl5ZtNe6CXP4ZTrtdeo8WsOwvJaiey9YA/HYLLsSW/67 - uhz/ua8xtQ== - =SeMA + iLEEABMJADkWIQQcZZ6vAXQZm7YZRrpR4RR22DRNPgUCagyJ+xsUgAAAAAAEAA5t + YW51MiwyLjUrMS4xMiwwLDMACgkQUeEUdtg0TT7kuQF/TMAeDhAWLFbTSBGV3Jvt + /3QfkM1xJxLYX67kxAV4E6kjIrytC9RGI60YpFhjghcmAYC/wkJ+0qEz35dMhHSs + rN7V40lItKU0Avjd0cjhW8S1h5e6OGQNRQ9UhqmNZ62YAjg= + =6DV/ -----END PGP SIGNATURE----- Test commit signed with ecdsa_p384 diff --git a/git/signature/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt b/git/signature/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt index f0fe269bc..d08b9616b 100644 --- a/git/signature/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_ecdsa_p521_signed.txt @@ -1,13 +1,14 @@ tree 63af4f62a108a6c684181a4488b4bd3a5b51dc8e -author Test User 1772188966 +0100 -committer Test User 1772188966 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iLkEABMKAB0WIQTsiCaQTNePc9nPIlaMg3KiIK8bzwUCaaF1JgAKCRCMg3KiIK8b - z1sOAgkB1oCZKDZ9JVg8VASnxGOr9DBtMuPD3W0afvfjH41UDoSPERuiMvws+AkT - 2NmaqcADWIvTnKWUWmZbVTnypr76mCcCCQFHVhFbQ4BohfHZvEDoMctt07xHVfQg - Hzfjh1JagDgevjnOh1ekzluDamEzPNMCmaRM0gFbtMqamIOAED9U70R7yA== - =oq6z + iNQEABMKADkWIQTKslrsgiOj48BflFB3P0KJyycQKwUCagyJ/BsUgAAAAAAEAA5t + YW51MiwyLjUrMS4xMiwwLDMACgkQdz9CicsnECsOcAIJAV0t6Pyt/4lxZwD9q7Lz + gQG3qhoCyXQM2rS52WhPjfIUR9yZMzavbvr0dPJyRJcAc90qY8mCFUqKt+xycjX7 + YNQPAgiiPlew3hXRt+bLf1yEwIWHSN8Gal/Vxu/Fff8xQya7CRMAxwc10SZtdkkd + 4Lyff1c/HX1n9OpUb/2lRjCBp3ZTng== + =z0F4 -----END PGP SIGNATURE----- Test commit signed with ecdsa_p521 diff --git a/git/signature/testdata/gpg_signatures/commit_ed25519_signed.txt b/git/signature/testdata/gpg_signatures/commit_ed25519_signed.txt index 25d01d2f6..df98a84b1 100644 --- a/git/signature/testdata/gpg_signatures/commit_ed25519_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_ed25519_signed.txt @@ -1,12 +1,13 @@ tree 7c5bd8f246ab8e8c6a5749c3d2f44018aa029fb8 -author Test User 1772188966 +0100 -committer Test User 1772188966 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iHUEABYKAB0WIQRnYqhdVzl5MeAXXPFaeBMjEbdVcgUCaaF1JgAKCRBaeBMjEbdV - cvFBAP9oqFkZXb3J8tGe8wcYoWBCtj1bIEnkOxdWJHqA7KHuiwD/Xe18Vu+IGMSV - xJUkStADGVvF+jlPQshn7C+cak6zWAQ= - =A7mH + iJEEABYKADkWIQRxLmKjJPnrrVipKXm4uLZD9k5L5AUCagyJ/BsUgAAAAAAEAA5t + YW51MiwyLjUrMS4xMiwwLDMACgkQuLi2Q/ZOS+TAiAD/bpWyWxFULhqWJ3zDrPGk + bWl8Cu5MNGW/ovAhBi8zmQ0A/3LoqL701wdC8hbvQUJjh39N9LAOIjxIpO+K6p9C + +bMM + =1Lp5 -----END PGP SIGNATURE----- Test commit signed with ed25519 diff --git a/git/signature/testdata/gpg_signatures/commit_rsa_2048_signed.txt b/git/signature/testdata/gpg_signatures/commit_rsa_2048_signed.txt index d696d92b4..d1205f66d 100644 --- a/git/signature/testdata/gpg_signatures/commit_rsa_2048_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_rsa_2048_signed.txt @@ -1,16 +1,17 @@ tree e3ca2325bfa8013dca224a2f62f0582d70c07b12 -author Test User 1772188967 +0100 -committer Test User 1772188967 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iQEzBAABCAAdFiEEjxo8CPOWlAXK18ap+GK+ySN6ocgFAmmhdScACgkQ+GK+ySN6 - ocgm8wf7BOC9Jxv3QYTz+v9zztniu5phXYIF3Q1v7UuhVIK1uUj0F6OzIsdj7CHm - ryy4pVPHcOPq3Q6bPU7JlTHHfVdk+jzpv/K+SgjAqEdJHiH0FrSnNkXiA7+5jSxP - pJUPcnaeBr7I1jj+RM5uvAlHt7fTjrq6FZYqQuxrK80ICQ+YBz+5CHDm6OCSJGsR - xppNnGd3WkKkRJKInlXvd2eSStX4lffUihpo01JmN6XX9WfY1e3VDWokEpvIzyvJ - 269Kg4EEtmj5FBaAsMjalwF2ZmnfIClwo/zOCrir0QPQCX49F1CBwESTArOtI0/P - tUHIQ9zTWogzEQ0Ob2SyiEnRpEX8Ww== - =CK6f + iQFPBAABCAA5FiEE6CiVtmexoZsKIW5tW10ZTs+Zr8QFAmoMifwbFIAAAAAABAAO + bWFudTIsMi41KzEuMTIsMCwzAAoJEFtdGU7Pma/ELqEH/0/nA3Cdf8zSi/Ut5aOV + gDZx4LJc4O8B/Uxp0lTOuh1OeG22tRxz89moj10e9G2SiwJPlxZ1VAYsociBDmEN + gqJ9qxSY3cJgUk5GpI1mYgESa4QBrxF5vrLIjfqux2f2jJAp4KV3ec720cGmF8zL + KdOzX/RjjgsRwxzCtOEZ8Yf9BWagRdtyghhupyObMai29W2fvNcdA483bMRPuiQ1 + sTLNaH0RhevedFGKj/sSA8adJvWPS27rokyvmIVpbkA10qpC7MHw0tbu1P721vuw + ZzymK0kpjUK3QxIlPhRZ35gQtybcr3pbYcrmUBldTkRnqunLqqkT3+zPQSi7ywfb + CoQ= + =DRTU -----END PGP SIGNATURE----- Test commit signed with rsa_2048 diff --git a/git/signature/testdata/gpg_signatures/commit_rsa_4096_signed.txt b/git/signature/testdata/gpg_signatures/commit_rsa_4096_signed.txt index a983592e2..ce0a55cde 100644 --- a/git/signature/testdata/gpg_signatures/commit_rsa_4096_signed.txt +++ b/git/signature/testdata/gpg_signatures/commit_rsa_4096_signed.txt @@ -1,21 +1,22 @@ tree 596e4c43898dcf2a6aa08cb9c0f3e0bbb8ecc26d -author Test User 1772188967 +0100 -committer Test User 1772188967 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN PGP SIGNATURE----- - iQIzBAABCAAdFiEEXu1Q4zHdzEIbmc8LmBNHwX31dl4FAmmhdScACgkQmBNHwX31 - dl6pzA//RS+ffLDBShXdWCry8pmfIs4/Wkcz0oMlhetcpuErjCjOMI1ZEHao5J5G - +8l7UcMFq1JtpjQ156mFboJ1ZaAPmAAOAoGB+uJ20ncr/TXprOlc5pP6ssSIDsoU - n+zk+bONfIkdMQKdEcrAyOJPVuIFs7OvDY017n2kOTytCsWqxIWLgj/OrZCIyemd - EaumIoHCMkwAdfklWqba0v9OG7fw/knLFg8kvrjTZFmsi8GJcfdrCsqveS/sE3z5 - 2hEsleDavQ1FHTw0zOuN1y7E2CUXbMphQe+OxR6ypk53JQE4f0TsIYGItr9UQn7Y - tY1bYDiyJlTm6v/BRRl5J4qMgnNNsttjrl8cVihacYi1Gq6Mbl/vDYbZBLtWl9/7 - Bx8hPruqeZkix2nmA1lsFXAUDpumSERpjab3GjzzLW2hqIButodToD+3Jais01a/ - +JXsmZRvco3MjoLEKiSsM6BKp/FeWsH72A06/7JJ4i6LjFcJT8t1ljaSmNEZsQm2 - d10mHLQ34+9sgA35IaNFnF56XwZ9mX+NkLM9nTrtbaF/FHlzAd1k1HoNIT2NQ2tH - 5xydmyKJOkUEiaZXUIgsINI8RB5ERSCSJCXHk2G/N4ShT62jKqj3GmywWgKyGCpP - IQOUSxv6TZlZR2r5J1OIGzjZsFEWJyvq2u1vBG71uXnUOExt1k4= - =39uJ + iQJPBAABCAA5FiEE3uXEv0PUIgHzrTdgH/mFHFWyCW4FAmoMifwbFIAAAAAABAAO + bWFudTIsMi41KzEuMTIsMCwzAAoJEB/5hRxVsglu1hUP/RI2L1LGtK8zocDCRbwz + YlO656I8YLNWETVDLcYCJPH0uEDVQIeNbZv+9+4Ehh2F/u6gzANX/zJTTxw9gzTh + q9m8h3e2l8MfOTu/p8RuzGExXEMR+0txFRXcmrUy9tljdTZqSXZSTPTntWC74RXJ + 2IQtMLLEM5VARf/5Oi5htzoYBO+zp6iqmSbcQ2BnzlxbBxmNpfBnOMiSgF1itrX3 + dOK0Uf82iugbWBjhC8v6qXC94xKKXjZHuGwbYFRRNWoH0nEWFsjIlcKUpjSZBGfC + 6FC0tTQuOsnbt9+WJFfi88V24/NKZg6UWFbBoTDX3mG1eIUwT1/k4j9KJaapriSo + L5/zNXso/mnSOuo3H5uYUXIRt2oNsyc/wuxgtapJv33w/5O6Jp956NHpR6QZ/3H6 + tJnGmt55IUcTlT/wLD1cggQoCCDngeiaYach4L2+DQfL5WuMPx871MFkAPXgzmm0 + QZBc6Au12NDRzvG964ieAm/TMoZjX0+FGZT27f5oJZPwiq+CmigeZhT+c4jyhiJD + AeiHkCK1v+/h5GFhbWZlv9lFmQ9VJnkIFmLH+Tm21L375UCgnQ8qCPlUB9nJMxi5 + zgLZA0bhhFHBbTmP8rzkYX0eYU5YE1sA5PHjnN6twfwMIcBe/8A0UhG7x009yHzf + +3HA1c++VHcyBEiGkARQaHOW + =8b6C -----END PGP SIGNATURE----- Test commit signed with rsa_4096 diff --git a/git/signature/testdata/gpg_signatures/commit_unsigned.txt b/git/signature/testdata/gpg_signatures/commit_unsigned.txt index 491a14418..39c5b0a71 100644 --- a/git/signature/testdata/gpg_signatures/commit_unsigned.txt +++ b/git/signature/testdata/gpg_signatures/commit_unsigned.txt @@ -1,5 +1,5 @@ tree 4650a2cda631bc795fc254fe20b598135b265036 -author Test User 1772188971 +0100 -committer Test User 1772188971 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 Test commit unsigned diff --git a/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh b/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh index bf6f8aae7..4914cdf70 100755 --- a/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh +++ b/git/signature/testdata/gpg_signatures/generate_gpg_fixtures.sh @@ -2,16 +2,41 @@ # generate_gpg_fixtures.sh - Script to generate GPG signature test fixtures # Generates GPG keys in all variants and signed Git objects -set -e +set -euo pipefail # Configuration variables TEST_USER_NAME="Test User" TEST_USER_EMAIL="sign-user@example.com" +FIXTURE_DATE="2026-01-01T00:00:00+0000" + +# Isolate Git from user and system configuration for deterministic output +export TZ=UTC +export GIT_AUTHOR_DATE="$FIXTURE_DATE" +export GIT_COMMITTER_DATE="$FIXTURE_DATE" +export GIT_AUTHOR_NAME="$TEST_USER_NAME" +export GIT_AUTHOR_EMAIL="$TEST_USER_EMAIL" +export GIT_COMMITTER_NAME="$TEST_USER_NAME" +export GIT_COMMITTER_EMAIL="$TEST_USER_EMAIL" +export GIT_CONFIG_NOSYSTEM=1 +export GIT_CONFIG_GLOBAL=/dev/null # Directory for temporary files TEMP_DIR=$(mktemp -d) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# define script dependencies +DEPENDENCY=( + gpg + cat + grep + head + cut + awk + git + find + sort +) + echo "=== GPG Signature Test Fixtures Generator ===" echo "Temporary directory: $TEMP_DIR" echo "Output directory: $SCRIPT_DIR" @@ -26,6 +51,33 @@ chmod 700 "$GNUPGHOME" echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" echo "no-tty" >> "$GNUPGHOME/gpg.conf" + +# cleanup on exit +cleanup() { + if [[ -d "${TEMP_DIR}" ]]; then + echo "=== Cleanup ===" + rm -rf "${TEMP_DIR}" + echo "Temporary directory removed" + fi +} + +# check necessary commands +check_dependencies() { + local exit_state=0 + + # check presence of dependencies + for COMMAND in "${DEPENDENCY[@]}"; do + if ! command -v "${COMMAND}" >/dev/null 2>&1; then + echo "command '${COMMAND}' not found, needs to be installed first." + exit_state=1 + fi + done + + if [[ ${exit_state} -ne 0 ]]; then + exit 1 + fi +} + # Function to generate GPG key pair generate_key() { local key_type=$1 @@ -97,9 +149,7 @@ create_signed_object() { mkdir -p "$repo_dir" cd "$repo_dir" - git init - git config user.name "$TEST_USER_NAME" - git config user.email "$TEST_USER_EMAIL" + git init -b main git config gpg.program gpg git config user.signingkey "$key_id" @@ -150,9 +200,7 @@ create_unsigned_commit_and_tag() { mkdir -p "$repo_dir" cd "$repo_dir" - git init - git config user.name "$TEST_USER_NAME" - git config user.email "$TEST_USER_EMAIL" + git init -b main # Create file and commit (without signature) echo "Test content unsigned" > test.txt @@ -172,6 +220,9 @@ create_unsigned_commit_and_tag() { # Main program main() { + + check_dependencies + echo "Step 1: Generate RSA keys..." echo "-----------------------------------" @@ -229,11 +280,6 @@ main() { create_unsigned_commit_and_tag - echo "" - echo "=== Cleanup ===" - rm -rf "$TEMP_DIR" - echo "Temporary directory removed" - echo "" echo "=== Done! ===" echo "All test fixtures have been successfully created." @@ -242,4 +288,6 @@ main() { find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' | sort } +trap cleanup EXIT + main diff --git a/git/signature/testdata/gpg_signatures/key_brainpool_p256.pub b/git/signature/testdata/gpg_signatures/key_brainpool_p256.pub index b08e1ba5c..cc85e3551 100644 --- a/git/signature/testdata/gpg_signatures/key_brainpool_p256.pub +++ b/git/signature/testdata/gpg_signatures/key_brainpool_p256.pub @@ -1,10 +1,11 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mFMEaaF1IxMJKyQDAwIIAQEHAgMEUwknda08hsRC4Npdfcm+1YqDOomET8eB+jJ7 -42mryjwct/lIPxW9lNCcTsu+zw4inUSFie+ppyaUvs2Zn7NcR7QrVGVzdCBVc2Vy -IDx0ZXN0LWJyYWlucG9vbF9wMjU2QGV4YW1wbGUuY29tPoiTBBMTCAA7FiEEh7Sx -YlKSinoE4uEZTne3Hix4AAYFAmmhdSMCGyMFCwkIBwICIgIGFQoJCAsCBBYCAwEC -HgcCF4AACgkQTne3Hix4AAbAdgEApp8sXO9KUkVJBccanhxGOWM1V1u6wMSU4qP9 -maYLTl8A/22K8pAdmUEJNeFPnplgQL8If89hcOulaz9X7IXuX9R9 -=pSV1 +mFMEagyJ+RMJKyQDAwIIAQEHAgMEBDv7xtgM0Kzx/hrjFpjlxxtpbFZzKwVot6Jr +Ih1AowMmTjsFN4PFRjmudBk7mnhpF5XEKfU1hLzyMBFbdenCILQrVGVzdCBVc2Vy +IDx0ZXN0LWJyYWlucG9vbF9wMjU2QGV4YW1wbGUuY29tPoivBBMTCABXFiEEZQQg +MTvusp6hiiBHW2NGrXct7HkFAmoMifkbFIAAAAAABAAObWFudTIsMi41KzEuMTIs +MCwzAhsjBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEFtjRq13Lex5w38B +AJuxG9dsTYS3r+EWw3bOHffETiBWOJBalRW1qqn/IVcmAQChIfOdK3x+GMJH+SAr +y0Le1NA7qRwshq4LLS6MiGNQew== +=agvF -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_brainpool_p384.pub b/git/signature/testdata/gpg_signatures/key_brainpool_p384.pub index 003449460..8fc2a5416 100644 --- a/git/signature/testdata/gpg_signatures/key_brainpool_p384.pub +++ b/git/signature/testdata/gpg_signatures/key_brainpool_p384.pub @@ -1,12 +1,12 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mHMEaaF1IxMJKyQDAwIIAQELAwMEVfXzvz+2tDtNqnttIWwaC2ErDVVrEY3GZZSr -BGnrvj+sy65ZzlrwuvnNTMAS1KbSPweRF90aZVkiyesNHtjIj//JoJETS2UYUJfP -D4vbhcVlhjUwuAIRA9Tv6UqXwdNVtCtUZXN0IFVzZXIgPHRlc3QtYnJhaW5wb29s -X3AzODRAZXhhbXBsZS5jb20+iLMEExMJADsWIQQ/Ad7FJfKxg1LGHLMZ238vxsg1 -ZQUCaaF1IwIbIwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRAZ238vxsg1 -ZZupAX9XXBzWAIUIax+3FzDyiaX52s9I7mReCvOhRUvR14JYMc/f5/CsebPZRw/4 -BFe0taoBfjJqSo0Y+qE/832yB/IuOEsLmSKeXvu8oncwSYQeRoOFBHKmsa+NFh35 -lvl/j9z8ng== -=0Q9w +mHMEagyJ+RMJKyQDAwIIAQELAwMELCn65vIdEylxvlKOE1G8KoJydJX8W+5acDIa +yyp4n8ZKi3rwjvwpV9ODhUh2gyV6LEwtkGxR/E2o4RXp3/T8d7xsiwbL/NvJXRlJ +K+wHE9odh08LK/4Z5pEUqh1BEhRHtCtUZXN0IFVzZXIgPHRlc3QtYnJhaW5wb29s +X3AzODRAZXhhbXBsZS5jb20+iM8EExMJAFcWIQTNs8DDx+7VeXH5/sB3IHg7nCO3 +9gUCagyJ+RsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMCGyMFCwkIBwICIgIG +FQoJCAsCBBYCAwECHgcCF4AACgkQdyB4O5wjt/bdfwF/RJdf5fIuRiuoeyydOT1I +qCzc9Ylo8JYOPSXZR4AzXpC4mh3h+ZafxryXV2b3eA6FAX9wdF1JAbyfUXOPT6SB +TB4oM8KvnFeVXLAvXSG4sGobZedZs3JnrQHoKRcnG1+HXa8= +=gri4 -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_brainpool_p512.pub b/git/signature/testdata/gpg_signatures/key_brainpool_p512.pub index b4916c41a..3fc68d69b 100644 --- a/git/signature/testdata/gpg_signatures/key_brainpool_p512.pub +++ b/git/signature/testdata/gpg_signatures/key_brainpool_p512.pub @@ -1,13 +1,14 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mJMEaaF1IxMJKyQDAwIIAQENBAMEgOA+Jee2aD4ihETrDyd6nIeLNMi5/OoW8ChU -abrNn0A/JtViY0GIwSs8ZZbCWpbktU2cvi81yUOyPuXQNylxAVB+VJTLl2WG6/hm -iJytSnow5mx8jlMrjHralTHgmZ6vGA7113eBaw98uyQaTpW9L7/EnJZmIaWsOc7z -c0CTuNS0K1Rlc3QgVXNlciA8dGVzdC1icmFpbnBvb2xfcDUxMkBleGFtcGxlLmNv -bT6I0wQTEwoAOxYhBEWoduQf1y5nIiAZtyXSnvW9xokRBQJpoXUjAhsjBQsJCAcC -AiICBhUKCQgLAgQWAgMBAh4HAheAAAoJECXSnvW9xokRbhAB/3zbCx9UGG50fbqp -B1kSsRZTJXedRrBVb28l2WCD2M1RnNCEZsQiSbMzMCpjCUomlAHdcekSyIaQUQT2 -bsAnhfEB/j/xcqmLq+uYVlARylj3FdFNRPFMBk31VbmM4MmPGmKEK/Y2wfBA4t1Y -AsElpiiqqjE4h066r0Br0zyGmSH90aI= -=bACz +mJMEagyJ+hMJKyQDAwIIAQENBAMEkWej27PAoUlUqKxnqUg5lEeN9I4o/mtCQxv5 +JWvaeKmqkCCh6x/XPyAeUnW+6ylIOpmd4md0S4nbYump7ygPFDz3fYfxZi44NW8A +6aa4UTF2EVhuFixlMVLp010sBBGsnvZ1bnHAhfsCuWjXzvm1E4ZdE7e9AwADYBoY +wqnXHbu0K1Rlc3QgVXNlciA8dGVzdC1icmFpbnBvb2xfcDUxMkBleGFtcGxlLmNv +bT6I7wQTEwoAVxYhBOiEuNlR1S8RJqycSyJRDKQ6ZodxBQJqDIn6GxSAAAAAAAQA +Dm1hbnUyLDIuNSsxLjEyLDAsMwIbIwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIX +gAAKCRAiUQykOmaHcRAuAf0edXQmsyx3lJpT5TwkIa6dOv8Fpjvr2vF2u7i1mwUi +JREXFCsmEPUbz9QNR1ro2obMv/JdGERQByXqrKi8q0CHAf9ZZJ0Uqsv/NTOXOzxL +4Qbbj6vEZkygfJEHpFjBTwtdsd1Q0Sxf16OZX5AdT8nPFDs1uOS7HsJRMC9kV56L +0i7A +=a1cK -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_ecdsa_p256.pub b/git/signature/testdata/gpg_signatures/key_ecdsa_p256.pub index 3d692bd2d..125c82f8a 100644 --- a/git/signature/testdata/gpg_signatures/key_ecdsa_p256.pub +++ b/git/signature/testdata/gpg_signatures/key_ecdsa_p256.pub @@ -1,10 +1,11 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mFIEaaF1IhMIKoZIzj0DAQcCAwQoQUw24pNLURN7niophK1FO8jxlaS8zIyXHrdk -v57m6jAzbdRsOgZ6q2RQ+mkzGpk+5W+Yv7oWit1On2NI5otNtCdUZXN0IFVzZXIg -PHRlc3QtZWNkc2FfcDI1NkBleGFtcGxlLmNvbT6IkwQTEwgAOxYhBBlhWylE7Hfl -1hw5X/01BEWvFUeLBQJpoXUiAhsjBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA -AAoJEP01BEWvFUeLX50BAO8aO89RwPhvh9AwK9d5p6JrAB1sMQifQa4qWLCxSoCc -AP9RhNEUOygsIPqEKUyZ+yhEcEMQP/5kd7ln52zaVmCIqw== -=SrKK +mFIEagyJ+RMIKoZIzj0DAQcCAwSirbJtjX2fkmtUrg6ppTBNGnM2og6ZV5DSoFwl +8ZGi/jFwbG46q3EERvg17MN/UhD37OWgUHLhqyUKkKoOy6u1tCdUZXN0IFVzZXIg +PHRlc3QtZWNkc2FfcDI1NkBleGFtcGxlLmNvbT6IrwQTEwgAVxYhBKtrzvydepCE +aUbQnnX3z64xRJrUBQJqDIn5GxSAAAAAAAQADm1hbnUyLDIuNSsxLjEyLDAsMwIb +IwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRB198+uMUSa1Dj5AP0dtVFL +fCgq8Rm5aMM2Yd/vBOo2ZVuZukG5iu2uCtVQJwD+JK1aQeX7VZ/I/Y+B10viqjMt +XKqc+bVMwVjrZ9NO6qg= +=vwKL -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_ecdsa_p384.pub b/git/signature/testdata/gpg_signatures/key_ecdsa_p384.pub index bdd907f05..d4390f08e 100644 --- a/git/signature/testdata/gpg_signatures/key_ecdsa_p384.pub +++ b/git/signature/testdata/gpg_signatures/key_ecdsa_p384.pub @@ -1,11 +1,12 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mG8EaaF1IhMFK4EEACIDAwSR0zvO7tXWhXmxDppSEiokEWqRZEy0wuRHJ+7P0o6F -8FDpuip3FkcBFaR47I7dwHIuQhg60pG/OMsuh72ZO0CndiPb4bpVK02ppY7QoE4A -JZNnETMeWEvn7nWdKsLbAvu0J1Rlc3QgVXNlciA8dGVzdC1lY2RzYV9wMzg0QGV4 -YW1wbGUuY29tPoizBBMTCQA7FiEEnMi8Yry1RiqJ8p8obnlTAyi+xAoFAmmhdSIC -GyMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQbnlTAyi+xAprwgGA2MZ2 -fe0jcm780LHpNFn+6skaR9eGKKVXg0gRu5169yLln6DHiXex3h0YNc6RPTveAX9i -cEo2z0sLtILQKIomGZqfqkXLgJPiT8qDZLZkElhM1CkmRWXPGgC96Twwuy/LGig= -=zVPo +mG8EagyJ+RMFK4EEACIDAwTiS6oidH72jJFYn6OK9llncdrmEIbgZz+s0lOPOIAZ +n0Ycc6cBKQH2+UyZwB6BONJPPNTCgncAiApazIMEjjkyJ0tmen0BeckOjeLh567a +vj97aZiZVeIKlcowMVC6nHS0J1Rlc3QgVXNlciA8dGVzdC1lY2RzYV9wMzg0QGV4 +YW1wbGUuY29tPojPBBMTCQBXFiEEHGWerwF0GZu2GUa6UeEUdtg0TT4FAmoMifkb +FIAAAAAABAAObWFudTIsMi41KzEuMTIsMCwzAhsjBQsJCAcCAiICBhUKCQgLAgQW +AgMBAh4HAheAAAoJEFHhFHbYNE0++iwBgI2bwBgFgqvQ88qlo4oWW4yALRLo4I36 +9LHQVC/pugRw+h4hmDW+g0qwFkLF3g1mewGAu9nDtAXQScbYxu/du3d7/AHVpQkn +CsOMB3cJhLhOk/ZrbHmBxEB3Km4qulzuxPaT +=f5RW -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_ecdsa_p521.pub b/git/signature/testdata/gpg_signatures/key_ecdsa_p521.pub index db963c3ca..5f3edfd26 100644 --- a/git/signature/testdata/gpg_signatures/key_ecdsa_p521.pub +++ b/git/signature/testdata/gpg_signatures/key_ecdsa_p521.pub @@ -1,13 +1,14 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mJMEaaF1IxMFK4EEACMEIwQABsj6GXczdoIybwVeCD4H1Bm4/kRA2oSJ8Q0eI8eI -eji8bwafKdEX+oqmW199cfJtwwM9NNe9vvfGvnANmfvhWeEAG9wz7UlhE4VUxgAo -hRYTwnZgBztiXGEjp/flr4y34Lz2IG33arxePBpzza72JyroVcfstYu7jY0KOa5s -NO7tDEO0J1Rlc3QgVXNlciA8dGVzdC1lY2RzYV9wNTIxQGV4YW1wbGUuY29tPojV -BBMTCgA7FiEE7IgmkEzXj3PZzyJWjINyoiCvG88FAmmhdSMCGyMFCwkIBwICIgIG -FQoJCAsCBBYCAwECHgcCF4AACgkQjINyoiCvG8/+7AIHRdZR45qP/DLcLR7BN9Mk -sjoDjUvd2swiVFXO5ZAhxu4/R/URkaSSTDW+a1QJjzSiwdKvVDeVBNNbNU9s2YVF -RFICB3ylAKmuOhs+upo5GqHJpdVgVI7AonTbnD7mlhhlvU5gbtGGO+ftCuZgCdsQ -ERV4BYsGGNM6FB3COlpKH8g+Jx0N -=ISNS +mJMEagyJ+RMFK4EEACMEIwQAPPYI5Mm2Jsdx5Gictjq/dIRTYjPBNBebG4gkkZLO +ZESgIVXhDHAYGonQBCgIX9xZJ5lipz9oZa54kryRewLYonoANRPhOkATQvJr84WX +qdgn4B7c1iRIXF9uNtQMhpBY68pRAojXX6pFkfei5cdUY3VVtAjUKkLn3g+BsR5t +5l+cRcG0J1Rlc3QgVXNlciA8dGVzdC1lY2RzYV9wNTIxQGV4YW1wbGUuY29tPojz +BBMTCgBXFiEEyrJa7IIjo+PAX5RQdz9CicsnECsFAmoMifkbFIAAAAAABAAObWFu +dTIsMi41KzEuMTIsMCwzAhsjBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJ +EHc/QonLJxArE00CCQFO4rZYHnD7ITL/8bd9Uh9ys6aRqW+0hgjyTR1Ks/QRG8uX +bzqyxui6FEuGbVYg3w0oJ3Jdu7LtwUl1w6oOZkbWQQIJAemBhUDcSPsfcFtqEjnN +1d2sdorFHlG67setPAMuIyFfrQ98Go5n0N0i/XEA/UQVHWUrqC+pwiZ9N7RZBLsi ++LPv +=+upk -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_ed25519.pub b/git/signature/testdata/gpg_signatures/key_ed25519.pub index 6ba0bb532..d9a49eccf 100644 --- a/git/signature/testdata/gpg_signatures/key_ed25519.pub +++ b/git/signature/testdata/gpg_signatures/key_ed25519.pub @@ -1,9 +1,10 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mDMEaaF1IxYJKwYBBAHaRw8BAQdARB9dMt7IgHVlZ1LKknKIc18Mp9P0ky1S5oAE -y+Ipvq60JFRlc3QgVXNlciA8dGVzdC1lZDI1NTE5QGV4YW1wbGUuY29tPoiTBBMW -CgA7FiEEZ2KoXVc5eTHgF1zxWngTIxG3VXIFAmmhdSMCGyMFCwkIBwICIgIGFQoJ -CAsCBBYCAwECHgcCF4AACgkQWngTIxG3VXKhNgD8DaeYgQWZUanENgua9f1sveQ5 -ceXJYo5wHKlNN5n0OpYBALLAg5Gg0Z2RzcSU3JKWh+F5KpJx9Xx+xA4GfuIZYgYG -=7VIr +mDMEagyJ+hYJKwYBBAHaRw8BAQdADqQpCJt4Rp3sE87GpTrTRn/VWxyzTHvxW0w3 +HyxUzPi0JFRlc3QgVXNlciA8dGVzdC1lZDI1NTE5QGV4YW1wbGUuY29tPoivBBMW +CgBXFiEEcS5ioyT5661YqSl5uLi2Q/ZOS+QFAmoMifobFIAAAAAABAAObWFudTIs +Mi41KzEuMTIsMCwzAhsjBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJELi4 +tkP2TkvkWCABAJUgD27HKFU/TGCr1060EeA6FKy63dhUz16tueOibgxsAQDaykTr +J9s5fQDoy6Us7dP5UfrwuPxqpAAyTBrkuIJ4CQ== +=8gTZ -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_rsa_2048.pub b/git/signature/testdata/gpg_signatures/key_rsa_2048.pub index 353098510..3d58baff6 100644 --- a/git/signature/testdata/gpg_signatures/key_rsa_2048.pub +++ b/git/signature/testdata/gpg_signatures/key_rsa_2048.pub @@ -1,18 +1,19 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mQENBGmhdR0BCADdMRJ9iHzeJanSzOhTqhONdUlgICL0+0FgOxTXIo7nhf3tCcfb -n9AhSVkiDX5ItDZzjHjeiZ66Frs4O4TP04x5Z8Ayxssx4J6ST/YeXm7vkTquigDs -Qes9uzIKp4aFTuGG9MXzuPtKQeWixebhtS217EUb4rZbSitafmuV/zeIR+4l5+g4 -H2YGsF9m1ElK1EiJuUozBZVjcJYQJ5elWJeWdqHr9oCjeFrnZRMJ/WaFrF0OpFXw -kZVseh50MZ0SZ43JzmlokZqZuMyhY2rq0rTsvD4IH+yV6sS4Gefc0jhijZcRzWpX -QIb/7WrAqPSMOfQeukapw90Ke1sKYEfwLmR5ABEBAAG0JVRlc3QgVXNlciA8dGVz -dC1yc2FfMjA0OEBleGFtcGxlLmNvbT6JAVIEEwEIADwWIQSPGjwI85aUBcrXxqn4 -Yr7JI3qhyAUCaaF1HQMbLwQFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQ -+GK+ySN6ochAmwgAw7MwUF0mXbRQPTQNXp5tgOjBDSloQVoUw4f4Rs1N8XOW6Vvy -CnXwfCX8YHCtAMMs10mELY+iOG3GMdCqvRrImjJyh38JylLf/HQDigzL95tOy3cF -hZ3ZHm8m/H3w/zFDegI2QNMM4dCAdwGwUuxo42CoVMp5PzYtNy8l8WMkVXYLkJfm -wV6rM1rJazCAkY1Fk1FCW/LW8eenPr4rQa36VgmpT4hz+j9mi5mUM5RUdZGLXdPT -uuMcCpm2sfU1Lozx+6AeHng4LHTdQDWazXWLG2Ob1o0coG6zj2iVry04VnGFd/do -mvV/nK4AdBJ4Al/KKT1At/KmP5zVpnpJQdZq8w== -=f9Oy +mQENBGoMifYBCAC5Y/Kwd0lmZMuKBLVzIA6yig2fMnZ63P90ZtfCxAqkqxus7TwC +RgIS8xwLvJLCU7T2fBr2AqK3nRzI7N6zeQf9mehlwzFwr56UTgeZpLGFPePQDORL +3AQbFvdhVYGeQHN+tC5rf1nJoR1ln5SyrGXsozqsRtYeRSxEbGAuZr4AZ5tbR7HV +mR9tYyUxtiXOnjpoMtCVtk5BpJaT6op6G1EENsAH4wQceQ8jbqfY20qrbsPRIIn8 +pesbwkwVSfV446Sa5vzCSsEOeqJV4GKHjQoQXpDURvXEZyYaFZri6RjUWb6eTrQZ +QW62muJP/T5fbN6ckEU9igQv8HAOn+9yo5DRABEBAAG0JVRlc3QgVXNlciA8dGVz +dC1yc2FfMjA0OEBleGFtcGxlLmNvbT6JAW4EEwEIAFgWIQToKJW2Z7Ghmwohbm1b +XRlOz5mvxAUCagyJ9hsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMDGy8EBQsJ +CAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEFtdGU7Pma/EBIkH/A89adLp7eZp +iHSaGuTm8fjtbEokzQg2v2/g2RVH6iSrCJRxZib4KC7o+6JuQY4A2Wtb8prYkRQG +ccmx2o+QfcSCnCFX7EYxgbbmoU38vZk3avrWEXotUlV/KiPoAhe3/9ZIP7xcQvDT +6Q9wW+WY06Yl5s8WL3wXeb9KcLySX9SABc6GeTzGaj/rzU6o+NNis7K9nfRZf1cK +z/xOuiClpWZlcsWTLp0sxLzZe3CBGMyx0gnLo84vO5Gih0jf+rqDtZxZiZ6p3Jf5 ++5mPkywP+9vxzBlPJskXCp634YUokHfQi//PfpHQseA5K8fsa3WIELFgCv0BWY1m +ddpNkEZDUBI= +=yDvj -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/key_rsa_4096.pub b/git/signature/testdata/gpg_signatures/key_rsa_4096.pub index d2356537d..61c4a78a3 100644 --- a/git/signature/testdata/gpg_signatures/key_rsa_4096.pub +++ b/git/signature/testdata/gpg_signatures/key_rsa_4096.pub @@ -1,29 +1,29 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -mQINBGmhdR4BEADDKCJbXPeXZIU4l6NBJaLDL5Po4IGB+3+CbCPk90DFCXrowrBb -BT1SwCU7+bmYKINQprFgG6WqhhQ8rGwkxlTjKlTdKYpHF3I13ONuPYcs1KZiPtXA -6puY9ma5lOH6VSBnK+k+EnuvHBw7NmWtMQbMwSqnPO+PFnG1yaOxRQR641aKw7wI -ciR8hJdlakIy11Z+inWw0RzeK/54837ws05CBDaqRNiO4FQfJ+Q9bBPpYpVR1G7g -4GVtidWpwprJYALmR3ejkn0QAiSGOlR7tin+8x4FXufxaiVwrPcXJCEOXLdLSndV -4purALokhhm0wP3D9fOFU9nqvkhlENLL3pLlPLswRq158RbaMBKgRZ318RRX11yH -qKlO6s4NAoYlhBeEY/gXQ36trALtu3YTB/eZlqoaEFFgXfEoS02W2F0sqYyK/ISG -fvUuxWZWjATNmfNLr2L15aM/GmfpacN8JO2omyKWGJQ3WBcGRdxfBkJ03vOQIFDE -WvJ+XmKpY+XC/N0q16Sz8rIF5LzDxwAMHdG66uSbYHGGlKxbq5YnUw1ZMafRhvcp -epEFRhLHUMGmJrHqfkSKkcDclMFlKG+wm9F/8a8V8zINQ3J1ohaQblT1OkwioSyT -GnIk92sVD28dS9mnoJbEKHEPjcTp2B1VMntHidFE+v4zwb1TPRE5rtFOdQARAQAB -tCVUZXN0IFVzZXIgPHRlc3QtcnNhXzQwOTZAZXhhbXBsZS5jb20+iQJSBBMBCAA8 -FiEEXu1Q4zHdzEIbmc8LmBNHwX31dl4FAmmhdR4DGy8EBQsJCAcCAiICBhUKCQgL -AgQWAgMBAh4HAheAAAoJEJgTR8F99XZe6WYP/2ubM+Mc+cC61MZv755k82xL4t7i -qQWplqjsX4DYXyZmRjqaNp0vKr0A0C11hoosTIS213yoXt0To0grXTP15btu+Dfs -vo8R7oeUDG70UFhArP5vLAwcZRf5+ZV+HKKr4KuxlW2KKbHO5UQtIiv8Lf6NcU5v -K1lDRfQUxhauTb8lOEkt0eFbsobu4GU/M8c2uDDj9Z187Nvm/UrxiB0akrB95iDW -S8ol6+AwHCfrZALbwP1Lsd1hI1RRfT+OUysrK4//K3k4r/8nT0deIulxV1oZezPg -yRXrEHvsDbhV4ZQiSKDx+hwayeKO70ag5Ijl8I4m4Wuz7e5xn9bx2QGZEUsil6ff -oNLnn1p0gXkbKl18+cnla4tQqjRYV50s8FtocZ/ULXU/EOSsuTuvwC45Fd6XUiSx -+awz55iYaYrIQdyir4Ltedt+IvvIDfDZM53r/Xc5H1kixIHYzZ/xse2qremlhuro -fZePkcNQemhdzM5llTpq8AP1TfuT1BtPkrfohoWJGNjqmIm6rcPGFHmWLfvo8I5f -JyRvwz6ljovBywaxXojvrHdGa13ylsIZTUDgDMAG/6noUR80L8JmXz1lTZcbT+zh -5A++/Dg1u1p4TFzqkQmopVXe/ccns5YtBMW3EV85ctsg+dGnSh3jwW2QIAlwgfiA -XZlV0oFIlwpVXcBq -=QDQJ +mQINBGoMifYBEAC0k2xUqcCl3DVYUpqGFQqIkEDVGK/ayXAIPCWuguoo4ZR9qAVp +b3TEN/lCRwlp141bXlAVUN1P9bDNf7M4ib910KFlAbNR6e+Lnu9wkDH+ulSjnU3l +lV+Wkzem/pCddAPx6t9nDXC/CQL1DreIuZ5jFOolfrbVFnXXphkJV2dZODD+bWOW +/gqp7v1P3VR+JqQ4Tm7KVhsMcyK2jfb4pLCss+X188++5Oa9jmGOIywVVREjhhUh +vmhQ0y9xIUFBvUdOo63kFLiUJiekppirlg7dViuheYO1+gKZn6kBN1SUO4L1tJC3 +zGrxN5fkMY5YgxKB7dmjHwEqdAROy42uAw3yIiNj5sPtGNyV92zRYTlW7rKznNXg +fieB8/2sW8LkQeuZaJYAKQSgya3ZOutGKaEA+9iBeY1/lx1Y+VaeNtX8zsKjO+gV +zbWfl0ZZhuZLlGeFPzg4DrjExvyyTDuHKfPi7jhzIcd3is4y7qDwohmdiRs57kL/ +0d1oe6B7aAAA/X43r8AQwE7FjqZV2rp4GW/bf3bjVugM4iK3SWMJVL24Jtt1KlCb +O4XAfeYXxsQWKWSUI7e0XDsMBnBQ44qL6+rLd24jLjbdRpq7FlX4GFrytUJyKQAF +fOcpaidz1nyKV3Td73I3eEGzOsKK2LG75l8enBaQ6j2eUVeeKXJw06VTkQARAQAB +tCVUZXN0IFVzZXIgPHRlc3QtcnNhXzQwOTZAZXhhbXBsZS5jb20+iQJuBBMBCABY +FiEE3uXEv0PUIgHzrTdgH/mFHFWyCW4FAmoMifYbFIAAAAAABAAObWFudTIsMi41 +KzEuMTIsMCwzAxsvBAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRAf+YUc +VbIJbnlAD/0X6Cub3kbkqTmUyYxPU7mF9CiB56Bz9PztJdRu1sB8RyBy5IlTcc5y +Fj4UU34bhp7AHF+GQaqe3Jn9YaaA6Co+Ly24cZAi8lsbQHlUay/YayCe/zWbAehu +S49NmthqOpM9pJDbyZBQOGTGWtSptfLp9s15KlAuBDdxRyjrAoAcRAIJkYu86n8x +/AuAkJ0QECJrkehgtKmjz4o8+4+UophjeV9V5zIvHv+uo5aQV/yE3vH6yVgNsSMZ +AdAhA5zoe79wv7naXjudvKCGugP+lUlWvYX0vmZr/twz+TmEHMhJgNfVgVtviNBv +0b4WJHykpqAwKlBWBMqWVdFB9EN0m7oXM3BnWKmhK6pz8U86WqclyTF7nqCz92OY +NAsF7eRoYGgisbQLyosiyP40Q3c0crR6zLgcD1LhtTJ3cV6y5ksw6wpbeTEPrlmj +dd6ryvGwyMKSET/CPxSeKYf46M2qYvxKn4BNUIeUSstql6Y3bcLYYgx2wj/Y4Uq/ +st8/QzD4nkneXUiqphiOazq5USR0LZHFBUiHlKI7UbGUHsvUQtEmlrGoq6BmI9FS +GhoBUr3gbxWPMtVbFOMYrCmVYRfX/KITKtl0S29yWGXpDWnDF0u4sIIFopWObD8E +b4GwOO35FtrGuKRjFqlpDYbtxihkOdKQNgT/KucTQOc9EV03QpSABQ== +=b6dx -----END PGP PUBLIC KEY BLOCK----- diff --git a/git/signature/testdata/gpg_signatures/tag_brainpool_p256_signed.txt b/git/signature/testdata/gpg_signatures/tag_brainpool_p256_signed.txt index f5429018a..67e641db7 100644 --- a/git/signature/testdata/gpg_signatures/tag_brainpool_p256_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_brainpool_p256_signed.txt @@ -1,13 +1,14 @@ -object 9b70151dee2f47896dc875733450d2b81d22b5bd +object 060daaa406f6124f553a000db3e5b0a7f65005f5 type commit tag test-tag-brainpool_p256 -tagger Test User 1772188967 +0100 +tagger Test User 1767225600 +0000 Test tag signed with brainpool_p256 -----BEGIN PGP SIGNATURE----- -iHUEABMIAB0WIQSHtLFiUpKKegTi4RlOd7ceLHgABgUCaaF1JwAKCRBOd7ceLHgA -BqiIAQCT5NXXc2q8B5zF9qZMcuRxbV9sXzZnZcerDddzIyw3JAD/TQKfIbKZNGdv -lYE+mLhclLxPs6fzlFnr/PUUP+W28q8= -=VWLG +iJEEABMIADkWIQRlBCAxO+6ynqGKIEdbY0atdy3seQUCagyJ/RsUgAAAAAAEAA5t +YW51MiwyLjUrMS4xMiwwLDMACgkQW2NGrXct7HkA6AD+O/esPf4KJss/Civ2/VzZ +KmepNCursKGEzhDlSI2xCb0A/j2J/Y6l7J2+gWMkTOTPgMhFMb2omVABnzOgjQdm +E8FI +=gHvh -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_brainpool_p384_signed.txt b/git/signature/testdata/gpg_signatures/tag_brainpool_p384_signed.txt index 90d96b407..e1af5f38b 100644 --- a/git/signature/testdata/gpg_signatures/tag_brainpool_p384_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_brainpool_p384_signed.txt @@ -1,14 +1,14 @@ -object 68211368f80d9087df5e0d9ec5e9f0f01d0f9251 +object c8bf2fedd064e636aee6747ea95092449db07e03 type commit tag test-tag-brainpool_p384 -tagger Test User 1772188968 +0100 +tagger Test User 1767225600 +0000 Test tag signed with brainpool_p384 -----BEGIN PGP SIGNATURE----- -iJUEABMJAB0WIQQ/Ad7FJfKxg1LGHLMZ238vxsg1ZQUCaaF1KAAKCRAZ238vxsg1 -ZUxdAX9Ymfjm35gtB0+cEXryF+10W2EBt8xYtw11BSfhwZ43qiHzw6GeNgGqGWf+ -Q+6aq/wBf0/1JmwcKWR6kko5TXcvU6SIjxg8JJxEzjFvUNKuhAu29QmK+bv+oW2I -kqg3pbWh9A== -=Yavx +iLEEABMJADkWIQTNs8DDx+7VeXH5/sB3IHg7nCO39gUCagyJ/RsUgAAAAAAEAA5t +YW51MiwyLjUrMS4xMiwwLDMACgkQdyB4O5wjt/YVlgF/XCVC7XZeLzfCse1G/HkO +GxPLeH/dGysWShFQB8GcdBw6LMrZvimdkhp+OLNJx5SLAYCDRQBNrrUS6yrAeyzx +C2uJJ3hF/Dao3ZUsFojeLhsMV//0N3jB8wvk/lJFOeLY8Qw= +=1X+b -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_brainpool_p512_signed.txt b/git/signature/testdata/gpg_signatures/tag_brainpool_p512_signed.txt index 60815c75a..d9465a1e8 100644 --- a/git/signature/testdata/gpg_signatures/tag_brainpool_p512_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_brainpool_p512_signed.txt @@ -1,14 +1,15 @@ -object b3156f0627e50dfa726e48dbf8e94adc6bdebf03 +object 684a723a5eb868b452ac576da351f38f9cd8a49b type commit tag test-tag-brainpool_p512 -tagger Test User 1772188968 +0100 +tagger Test User 1767225600 +0000 Test tag signed with brainpool_p512 -----BEGIN PGP SIGNATURE----- -iLUEABMKAB0WIQRFqHbkH9cuZyIgGbcl0p71vcaJEQUCaaF1KAAKCRAl0p71vcaJ -ESzuAfkBoKFp7ZeomqTWBgHSkMRgzSup5vhlit8+RcH9b4pEy+kXCq8OjWEh45S6 -ACSbOwUGXPOb3azuUqDEaNu/RDEPAf0aJQv16PdYHKayxyV64UNn+dZvoTbmOVtr -cAOWxHe2rfix9yob9Rt497/hCUWFjxy3LLeIIsSEAARLXrmSokTE -=ZE3W +iNEEABMKADkWIQTohLjZUdUvESasnEsiUQykOmaHcQUCagyJ/RsUgAAAAAAEAA5t +YW51MiwyLjUrMS4xMiwwLDMACgkQIlEMpDpmh3E53wH/cyvuxAcYIk79TI7/3VNd +bsHyOEoj7sVSO/8DpzbOf9GJIACa8dzLxmbVaK//x5KOYFK6y4JOb8FyA31JKbWH +MAH+KFIdHIil6XUTRFQCmVLR+pB7i5mBLhW5SXcekFiFX95PG7tnVFGp971l+ugO +QulvbRjXMhYldg3R8Q4E5lZL6Q== +=f770 -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt b/git/signature/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt index c21ef5bff..b0f46a93d 100644 --- a/git/signature/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_ecdsa_p256_signed.txt @@ -1,13 +1,14 @@ -object 9b386e46cb3c08a84860225689ebb0696874a288 +object 22d258d46f5bc50420db6a5e4c8d60f35a78fa8d type commit tag test-tag-ecdsa_p256 -tagger Test User 1772188969 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ecdsa_p256 -----BEGIN PGP SIGNATURE----- -iHUEABMIAB0WIQQZYVspROx35dYcOV/9NQRFrxVHiwUCaaF1KQAKCRD9NQRFrxVH -i55VAP97X6IxOp3ZxAvdof4h8weHE66FzmqdseCsvUeWHatRWgEAgt7H/Eg2kQUH -PRHHy4l+joi9tAAg9KClfvq/lA+VcxI= -=dPQQ +iJEEABMIADkWIQSra878nXqQhGlG0J5198+uMUSa1AUCagyJ/hsUgAAAAAAEAA5t +YW51MiwyLjUrMS4xMiwwLDMACgkQdffPrjFEmtQp7QD5ARj4NkkgkNvNPYYj+f1s +y1eiGEhv78qT9hAMVL8OHsUBAN8/NVpUkxZaZ///tJybwJW54TF//wse7ADe0kgG +eIoe +=N1G6 -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt b/git/signature/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt index 0340f25c6..49629c870 100644 --- a/git/signature/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_ecdsa_p384_signed.txt @@ -1,14 +1,14 @@ -object c87dc41b03d89ae26c1ebcc5ae34b816e915d76c +object 35f5804b751475a1856b7efc13ccba249bdcd32d type commit tag test-tag-ecdsa_p384 -tagger Test User 1772188969 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ecdsa_p384 -----BEGIN PGP SIGNATURE----- -iJUEABMJAB0WIQScyLxivLVGKonynyhueVMDKL7ECgUCaaF1KQAKCRBueVMDKL7E -ClbhAYCKIE4pMka3pHBjX4XmSvsq0El0DctONYNZgE15uRyIF/P+Oeonm3t9tF51 -XAkMS98BgMO27cmy6TMl1cnYBW34yrBmpLeHpctSk5pkxSddfhKAxj1aOLJHp6eu -/nFMr2HSow== -=l/X+ +iLEEABMJADkWIQQcZZ6vAXQZm7YZRrpR4RR22DRNPgUCagyJ/hsUgAAAAAAEAA5t +YW51MiwyLjUrMS4xMiwwLDMACgkQUeEUdtg0TT7axQGAoDxgow0h/roUSeBuvsj4 +2iEzJX7jAviKSBCGHVXqm/V/fp/keO+gioGwZZe3dEWTAX9HdFCOB7Zn/Q73Xg/p +0poDDj5UtSd0Z2BaPuQHCQY489qLfJukDLsPY1Mr3VUKZzs= +=Ay2z -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt b/git/signature/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt index c43adb5ce..89dc91e03 100644 --- a/git/signature/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_ecdsa_p521_signed.txt @@ -1,14 +1,15 @@ -object 08dadb22fa537a9efa99d565ff01fc5d5854e802 +object 18011cecb9ae833a29b75526e801e8c0b7951c3f type commit tag test-tag-ecdsa_p521 -tagger Test User 1772188969 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ecdsa_p521 -----BEGIN PGP SIGNATURE----- -iLkEABMKAB0WIQTsiCaQTNePc9nPIlaMg3KiIK8bzwUCaaF1KQAKCRCMg3KiIK8b -z+HcAgkB8d27ZgMvPQ0ueTNeVnUtxJwu1zyXfVnoC9/cdeAU+D5yE/nEugwysds+ -/9aKjsLMV5v7gxTa6lg1dvGN2CdGEf4CCQEgnjuQkSgfaLmRmpsKPbGJoUDA1RJT -0zrv56m//eCOHFYJtcKFy95mNn5+9IiBWXrY3Ilz48jaQSg9CntzaITmCA== -=w0Ur +iNQEABMKADkWIQTKslrsgiOj48BflFB3P0KJyycQKwUCagyJ/hsUgAAAAAAEAA5t +YW51MiwyLjUrMS4xMiwwLDMACgkQdz9CicsnECtH5AIIgfzJtOJnaAFV0kYZIQ8l +wiyWXZx5l1VL5TbYfa53dKWrMf9KUQhmDa4TNzXSLzDJBnEI63idJCrNnhB2ajSY +5ugCCQFX8d2MPDrkqbuTgikUA2L4nyPriCT8cn1a3gaUlSCme1YL65tNpsYTPYwk +SFUiXk2/W++2+Du0/kx791BWX9I8eg== +=C+FE -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_ed25519_signed.txt b/git/signature/testdata/gpg_signatures/tag_ed25519_signed.txt index 3ab00c63f..657b002df 100644 --- a/git/signature/testdata/gpg_signatures/tag_ed25519_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_ed25519_signed.txt @@ -1,13 +1,14 @@ -object 35e58b202d6ba7a15f33b4e893b6da021c7132b7 +object b01ca16de562c561f62427eb0a30cb775d1c1dab type commit tag test-tag-ed25519 -tagger Test User 1772188970 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ed25519 -----BEGIN PGP SIGNATURE----- -iHUEABYKAB0WIQRnYqhdVzl5MeAXXPFaeBMjEbdVcgUCaaF1KgAKCRBaeBMjEbdV -cgc0AQDdONxRMTofNPtHP+BDEWsGFcDdyBGb9xxp5D5Xa3rYyQD/VLvlPmxl3jk5 -JUczWsHgXxcLWXP6e/N42Mf6ddU4lwg= -=Dt+S +iJEEABYKADkWIQRxLmKjJPnrrVipKXm4uLZD9k5L5AUCagyJ/hsUgAAAAAAEAA5t +YW51MiwyLjUrMS4xMiwwLDMACgkQuLi2Q/ZOS+SDJQEAqqdgow+zK3rCLSmkd1ms +GFgpHWgOorNAD0by5LI5Hj4BAJev0pBf61utMzpZT6B3/1sYqgqh9uOdifFAn8h+ +q14J +=Hps8 -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_rsa_2048_signed.txt b/git/signature/testdata/gpg_signatures/tag_rsa_2048_signed.txt index b2967d822..05232707c 100644 --- a/git/signature/testdata/gpg_signatures/tag_rsa_2048_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_rsa_2048_signed.txt @@ -1,17 +1,18 @@ -object 2aa79d2bc04180cb05948619bcd3edb60703c214 +object adb09c411509ca74c29235aca9336e64b52bd29d type commit tag test-tag-rsa_2048 -tagger Test User 1772188970 +0100 +tagger Test User 1767225600 +0000 Test tag signed with rsa_2048 -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEjxo8CPOWlAXK18ap+GK+ySN6ocgFAmmhdSoACgkQ+GK+ySN6 -ocib9gf/ewYsCH6QEx6L3MAT5sJFlN2USLRCSeLTE9/l6Bm/h/DITK2xlkbADQOC -3Ct4IXjrXaWMJ+G2vTdvmdxDAvNkga/RpbkEPapedwVoYMRqVWgC4pF6+aZH6EF2 -omd7p7+er/HCmRfe+5NFwUOSsYAxt0yB2lZC5Mq3Vz99KLi0daUoHY+ymkzFE1kk -Hdu94PG/g4YLHFY7PP7EtOq3NH0HrCxombcU+n8rkqjquwH7rJk5ZYMSI5HcDD2l -qB6R0zRGDpwH9IiMmSpNNWpcRKqUmORLGCaeJdfhh++ZaEJdF7AkBQkGy4WV/A2k -Te0lLC3zVqsMB/9T3nzbklyWpweZsA== -=xjd+ +iQFPBAABCAA5FiEE6CiVtmexoZsKIW5tW10ZTs+Zr8QFAmoMif8bFIAAAAAABAAO +bWFudTIsMi41KzEuMTIsMCwzAAoJEFtdGU7Pma/E6hoIAJeEOuA12XMoC2kTWwpH +LGguh068/mlABcs+b9Ck+gp490SvocX9kC4/0ew1JSo4w/YO1vGJNgmR+snkWFQr +wPi+IU5DjLXWPG09Yl2pCfYItUAAlGxddoqZy9WuqdP3KNU1Ntihv/F8ceKFhXJ8 +RHAylpQ5oUSvwnnwdeZAIRDFuj7gQC9RjPAApzIw/bfIVxlNDeNdNxkQeCd+pWw6 +4LdUTdCk4DnGciwXEjXOkFMWm1P/k4q3nxYa53cXdzTA2w+vx5MSQpqcgnn+bR+4 +ynfK1lkucN6sINgytspASBaZCkUAqbG4zbsnVkC/GH8hp6xBOsfFUyRlZ2O1U+TT +kqc= +=dpy5 -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_rsa_4096_signed.txt b/git/signature/testdata/gpg_signatures/tag_rsa_4096_signed.txt index 4c5ae5000..2b8261e06 100644 --- a/git/signature/testdata/gpg_signatures/tag_rsa_4096_signed.txt +++ b/git/signature/testdata/gpg_signatures/tag_rsa_4096_signed.txt @@ -1,22 +1,23 @@ -object f75808dabeb766be8c47519fdea37ae4a0a6a613 +object 4d0c7c2e471d3320c4d415c390ae6bf3778720b0 type commit tag test-tag-rsa_4096 -tagger Test User 1772188971 +0100 +tagger Test User 1767225600 +0000 Test tag signed with rsa_4096 -----BEGIN PGP SIGNATURE----- -iQIzBAABCAAdFiEEXu1Q4zHdzEIbmc8LmBNHwX31dl4FAmmhdSsACgkQmBNHwX31 -dl4gqxAAi/EifC0soQ6F5tj2EkKn4j9w7I5B505X2c2KUKWPUtGAMevwbeFFNLgn -S+kx/cl0xjkrrfv8mEWts9OPr2YRqMOojVKa5kBfqfSaZVEXJpic8Ocs2FhYbic+ -h7FQggtMNagkMKtqSw6qbXg9E3ZnZ/9iaF+EEHGNLdp6OSJEtpulidyOB1zPS5A3 -K7D1Y1Q+Z47v+x2ljwlAGjabZzkokwSIDScM1PyHCwoRmGeolzGjgyZFg/ROg8he -HpmxnDqS3uIzfjqFvusfYOO8aMJh9cir2KSsqzyc+basbciwwm/ChwXg93rpE7kc -sQWaWCBRCq4Z3VHL19Grl+BeqoSl2aeSgJn1hG2pYEDxbFe3ci6l8frgppcUXlhL -rMo5NaAZainHMge0lin3aZBenqH0GUzbaf4VtwzKVpnwWF/TGLcjNemnRn0Slfui -9w4tYQTiv6zNTwNBUG7YXgWs4jMgvLor5bbsTcZX6Zm3zvKDOGWPHX9UlQGFFBpB -W8zifKGES0KykcpJsGximwamoc5tjnuBSIUiFJVnGOT3uSONQRsSjX+CLiLrym/1 -k9V1OH92mW/1R8uW8ZjndOCmjNwKsLzU9hBg6MVaV+9gIbc37OTGMohLyEAn4mbk -8MuhIkSW8FsDedJCBhxbjMdCBV97cgffyHFu9FirchSAjbfQBZA= -=xzA8 +iQJPBAABCAA5FiEE3uXEv0PUIgHzrTdgH/mFHFWyCW4FAmoMif8bFIAAAAAABAAO +bWFudTIsMi41KzEuMTIsMCwzAAoJEB/5hRxVsgluKQkP/0Nd3f2XpkEQyfHXGpVY +4w+vc/WkAE5x5vDuCLHlxWZD1XCQGMahHvzKhR5xE7CE9MLXSAsWOK/FpvGG3Sld +PxMHN1KHayEdRnQutZ7loBGNaYqTXwnr8XReZRfiAK06RAeBGoxppif4hR0eeIU6 ++5/dCafoXjm1eYn8di+bqQDGkWImS7dliIBKpj0iamzw5IpWWHfw3BQ9tTcwR/fz +w6bat/NDUoCrfFNCHJYeiZHZRR8Lx9NaJYwIgWeL4AdDNzWeKMSMpmuWNMg9AcGG +mzWlIITRJuSyHg/BjyGAIrAv5wPriNFo84TX4K19a1wh8t8agL/czT8pLY1WG845 +kKgF6gPmGAlMboSTQrUBhF1K1GdtcHaIun6F5FAcEaULfkBrwB6//Lyb0JpWh7+C +tb7Ab+n2neNjUi8YrABzAMv3eUnsvD/VaRYiQ2djKnZlgZcMiGmPWyZDXTQ+jnKL +HUEnZ3NuAckogKagtkPCyYXFuGb5Y0lOS4wuTV5TFD9ns0VTNnO8jAc1JrU5GayE +SMFfXQrNoVhWc0xW4WH8sRvWp9hNaLshFwOAnySdf40RFy4aEIDstpx9IXhrO8kb +1CxpQYZy/tlVL/yoMcHCYxMlRsZPv3pp2w6YYuPkwb9tpVBE3YKuNsuzMHXt7xSj +3bRjjd9ILAw7KaytvmF9+uyp +=JzFg -----END PGP SIGNATURE----- diff --git a/git/signature/testdata/gpg_signatures/tag_unsigned.txt b/git/signature/testdata/gpg_signatures/tag_unsigned.txt index fc22314e4..ec80cabd7 100644 --- a/git/signature/testdata/gpg_signatures/tag_unsigned.txt +++ b/git/signature/testdata/gpg_signatures/tag_unsigned.txt @@ -1,6 +1,6 @@ -object 4aab80f202c9442c6bb439d6985d70592d30811a +object f85d47148d57e658056d9859377ec47ff17d0e98 type commit tag test-tag -tagger Test User 1772188971 +0200 +tagger Test User 1767225600 +0000 Test tag diff --git a/git/signature/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt b/git/signature/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt index 12ed29b51..372f1518b 100644 --- a/git/signature/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt +++ b/git/signature/testdata/ssh_signatures/commit_ecdsa_p256_signed.txt @@ -1,12 +1,12 @@ tree 2f0fa5393a2120151c5446eb34b99d1f3713ff12 -author Test User 1772153087 +0100 -committer Test User 1772153087 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE - EEvteyl/kGZEPuKkajhI0J+2PN66evLXOeZTvxGFxU5jAs0JHkxWbbY31zVphpwjEeaL9P - GQ1N1B0QHx13iZ8DhAAAAANnaXQAAAAAAAAABnNoYTUxMgAAAGUAAAATZWNkc2Etc2hhMi - 1uaXN0cDI1NgAAAEoAAAAhAPQhsSXLRif71JKQ1QN9z79VfPHTOeKKAhpplCh5VY5/AAAA - IQDZBEQLxlx8YuKNFC3c2pZ6oS0Ry8MkkkpgZio9gsDl3w== + EE+VmrN/R3AA+VBW+s/32EXhHSFZrQMO42zR4BTGQau9KJWpSuqMs4s1yjl2JsMUJgabtU + GrokE3v2OPHptca7QAAAAANnaXQAAAAAAAAABnNoYTUxMgAAAGQAAAATZWNkc2Etc2hhMi + 1uaXN0cDI1NgAAAEkAAAAhAPeodf77MdlZNmkYbLeIDTwiQwdx/mS2NTuapK0RAKv7AAAA + IBtzmPK1xUu7BALEfqRfhScJ1glf+xcvziyxZ0NaWY9E -----END SSH SIGNATURE----- Test commit signed with ecdsa_p256 diff --git a/git/signature/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt b/git/signature/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt index 860fd0f26..aa09e20e1 100644 --- a/git/signature/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt +++ b/git/signature/testdata/ssh_signatures/commit_ecdsa_p384_signed.txt @@ -1,13 +1,13 @@ tree ff58328bd5797f45f6f300c6c39d2cd357b9f3cd -author Test User 1772153088 +0100 -committer Test User 1772153088 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAG - EEpD1Slvc9rtvk1ZujObbQ+qkVzlZkIIIGVf354UQsCMp0HN7YRtNMq/H1iyQonw9YsTwP - 3DbSyMOK83B9SOiJkaBslBwkpwo+u2i85g+/QkqmjJnQ+4umr2SNJFNGdKETAAAAA2dpdA - AAAAAAAAAGc2hhNTEyAAAAhQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAagAAADEA1fuA - 0MeTI0m7DUxVP/EIRljC3Y6L9ElAU7Sqv5HXcOKVCxPYnZYuOrWgbnk+IhD4AAAAMQC+qA - zQUSgM0KFWFRPoxWUYo2gODfyizXdJqWIazjri9IlFZE/1eDZH8M32Ron3UII= + EEpeCgMWsTpFYqb4OTBJ7L8W8k597NKpUtDBLHTfzb68owpNlRHsmH06BMS4VtvytNITuw + KGUUEPhU0ya4O8wLPuIZrUQYDiFhOTAYDAA0qb/fjESo1TN79X1PhTrfVU9yAAAAA2dpdA + AAAAAAAAAGc2hhNTEyAAAAhQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAagAAADEA2xJu + CgHho+KGRdfVJBSfr1oSGlcKizC16MsuhA1hscqSFVfeErbsZ59j0Id3Qg64AAAAMQC2SO + +pJcX7Hlyg7dg2KpfBaFSxUWrbVfC/Mdy8vSSROxf8weJKw1gP/s9SCmNzxLs= -----END SSH SIGNATURE----- Test commit signed with ecdsa_p384 diff --git a/git/signature/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt b/git/signature/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt index fcc8de7ef..4420a4db7 100644 --- a/git/signature/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt +++ b/git/signature/testdata/ssh_signatures/commit_ecdsa_p521_signed.txt @@ -1,15 +1,15 @@ tree 63af4f62a108a6c684181a4488b4bd3a5b51dc8e -author Test User 1772153088 +0100 -committer Test User 1772153088 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAKwAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAI - UEAZJj44ARus1InhAPo2AkglBXySaOqL4GF94AC2ES/R4KrUIAOsKoq3SmjEJqFg0JMwuU - y+pbvEHDrAMHSRXT/gJPAFf0dF+0VSlplqc+1+8w2E9P8IMytOw1LOD8ffYe79+68vDI9D - QnNFeB/6qKrc5nirRWMRFTsvXdQOjPgWAckh5VAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAAA - pgAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAAiwAAAEIAqSn31cfI4XZEhgnOPL5BJ42jbD - G9nC/F0n94PJPLL1Y2aq9uFT69diEuTTYYFEzuJkk0CZdTCCDSi7Lbg2l3g4IAAABBDYLv - jKD5wuPhyt1tvLaTPNBIElMbkOULaLgespZHEbrgEh0KYNQXphnTgyF3lnuMBiPGqgDUW7 - 7TkSxoBDsI4D0= + UEAHvvwuT/Fa+NvAjwAG/1dWxWflOpz6m6IQ0lALlGwFu5LsK/DGezWNeiWdkfmcA/58Yw + Fov/0Qc3QIB9h5GpPGRUAZR5H028WhpUU5WV82dYFZDKVFovDie80eqWtJ1/v8Cim5QchX + KJI3zvEwi+LNzg9ghTi0rUdMTbf2ZHeGPIWZjUAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAAA + pQAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAAigAAAEEROu/iLh3XrEXwBNbdLGVShUkipC + adRTF8PUh5lWKiT3yjZPPTH7RvJo5FRAzN/GDFlMqDcRFxoG2GeOIEHut7zAAAAEEvqaFh + 8WylK+yvxjFx6qIE2atS+oT2Fbd6rfSJkeNIJrO5bO2ik6SL1KZ+NAJvzpspJEi9zhMVLH + heD4jqP7n49w== -----END SSH SIGNATURE----- Test commit signed with ecdsa_p521 diff --git a/git/signature/testdata/ssh_signatures/commit_ed25519_signed.txt b/git/signature/testdata/ssh_signatures/commit_ed25519_signed.txt index 4f6da87f3..a5d830a58 100644 --- a/git/signature/testdata/ssh_signatures/commit_ed25519_signed.txt +++ b/git/signature/testdata/ssh_signatures/commit_ed25519_signed.txt @@ -1,11 +1,11 @@ tree 7c5bd8f246ab8e8c6a5749c3d2f44018aa029fb8 -author Test User 1772153088 +0100 -committer Test User 1772153088 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN SSH SIGNATURE----- - U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgHRfgHA3PrVXK+vIj9qrm9Rz19k - rWdqNjpYJJ3HOkstYAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 - AAAAQFOFBI8DavCfBEiobPbMvmFO5gcAzy1BLwKfo4djvxbhDYi74cg7Bejqqcv7NakDNL - rKJYnzrfnNIIk6GDmC7QY= + U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgrYSEhPKV/65kzG2JLYU+586anT + AORbbZ0UW9qzon28EAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 + AAAAQD72vwusTGiRbH8ZtPm53vl065Ocv6Sp6VbHq4mkONAM0mzDLrD7BmAgWkjtmL2JpK + msqgFJcKs6Z3E1zH86fQ0= -----END SSH SIGNATURE----- Test commit signed with ed25519 diff --git a/git/signature/testdata/ssh_signatures/commit_rsa_signed.txt b/git/signature/testdata/ssh_signatures/commit_rsa_signed.txt index ca23e48e5..873881da8 100644 --- a/git/signature/testdata/ssh_signatures/commit_rsa_signed.txt +++ b/git/signature/testdata/ssh_signatures/commit_rsa_signed.txt @@ -1,29 +1,29 @@ tree 1207106d0fef65cd05d7a8428fc871886a36fa78 -author Test User 1772153087 +0100 -committer Test User 1772153087 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 gpgsig -----BEGIN SSH SIGNATURE----- - U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAKmt6I77xewHjFcY2bC47j - xrtY+CFvmKEIk0/JBmmdo9+rq+E1VZKCrwAiMGYk4lijgdnKIeqlLg4FzzqCWTqoy/xgdG - 3hVFUE/4OM8sMiw5Hv7YcGU48cybyVMOL6Iw8cEPGoXLuZIMHj6/ufvTT7j29iFaNkml6y - ecTomiK3FJWGaqnvmN41E1To8PTTP6AxRy+K/xQ6z8CmDULDl/7hP3I6eOU4doUf8G629n - S3ZUXRyzby18K4sCi6aOKd7kabq0JFCVk6hqk0nO+dhP7zp/88RT/iQNh/fBPtL42dQN8K - wikNWU5c++OdD8O52GoSede99yH54EIjuu0kEcgY8oV2YLhxRE5rRQMZHqj8nEu3HhlNuQ - amroxXB2tvuon46JvVTzFWKZYV9quSbt15VdYPfAHlZrwqj9r9a5h8TeBhGFvyJc9h3vUW - wTLgXjUkIxkNwioyJBF7d7aC9j7Pax/TaQc4V5YmasBj0UWM8vzlUPQOD76OsTGQYWNwFP - D2BQbk48anCpD1Yc7wTwM/Nr6Lkn/C7gM4PIusvG5cc95JhSNy+HmWfw/vSov4ivCfWaCf - Y+QhlbRcI8G3ojKWDnm3mx/LLTc/QqZvhTdOumkx6KDsuv0sNTgOiHsWPqgtgMYVRX4XnZ - JK4+zlJ+tZ3oCmLP5U+OwCHlu088k2XNAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAACFAAAAA - xyc2Etc2hhMi01MTIAAAIAnpVTEIaHs0ngRHSOk3oxEBHmZd/A0uMCznRjHNHDgHW8qa09 - qvII0n1RQI8Q0Wi8XvZsQqxXJ9/8nzfsrss1qDg8w4UnggnBYVnH/mgUIjw0tWxdoAv5Ga - BLfMOu+6gOp7YaqFYHe4RwtR/M2nCXbtnsEVrzLWKSBUaRI+TZHzExLJ4o6NpgJLMRhwpp - d8sGT6LuH/P08psOu9jCASksODcbWerAx+LfLcDIXje+WLzqu4Mn/HqZncMyf28bXJHcoq - X2ZWPHjZuRbcr9EeLdkHCDyD1kb7wAzR2Mpma9W99ZtpIXkugDSlbNQOyDGqB/b7t+I6Er - Sm/FL+1m3+pBnOxORpaxSkqFMlbWou4SNmYjSVU0XltxTpTV27svt0Lapmu3CpAptp3kx+ - 0Gd1y4QWyc2f38NPpConekGFKS/4O16zyGtFAUY5p4UCa/YUmC/H5QDskgv/MtZ/N+3RAr - EAlPOpTKM7876puPPd9tyEj9Tax5uNT7C039gyER/+B/eGWGcK08bq/YLfdgbmi51hrehd - DK3Z5wDfvIXci2rO0A/MB/HC1c75urX95uiHQV9pglQ+8zkrYeL/fD9+COaxqPJru+hdT0 - qlUJGIil/VBUTvu9PbsyyZA8UvPpFJRyGreNByyBxfhu33o2jx08OB9AgoctJ1tEgWJrty - fY/nQ= + U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAM/nZRSKToZ1F5uTTRUDNo + MM7VWH75aoyE+RphS7k29LHe3qVGobBXQIzlU8TM9nkjw1MairofggcPymTi041VBVSr1g + gmCz0Cq9ONgVtppJ+IiqUs3Gi3gLJzwgSeyRIP1dmhm02NsueusVx04Q5EZptHFtYxRev0 + 7FokYi2kY7voYIIY3sWnLFqvbqwztaDDLobQjzbGWYrKtm4J+vMlAzA24cSzYWQOKy7kzk + JdTltsE3188inIPMNjwMsF+aDxMg0plkuUl7bB/abItAkAHEmihcvI3wVN4+7gx89Aeixb + 61o4zs1qES2d7hFk6F0wbQDVYl+su6S7fD6ldkPnfQhc+HrfADH5vPg8yszfyJH5Tc51XY + LB+TQvRyazB/Y7GFnsTEjz3GoVJ1L7cobNa2XIItSeLw2pBTlKKmxOhBXAQ4W1y0Exgb8/ + J9jVCM9iG5ervrRKwgdQERC8wsouELGQ0X/202ZHJuWE7oHWE6feNl+b2sIdBzbNku8eI9 + xYkADzcY9liB6jsMCwYSov37l8I1ZTRMBqRNGS9iUftMFrHFOCVKMZhtcdOVih6JRUoCRJ + 98BWNl06Ee6+f3KsCtEKLZK0Zd6e54dny3NHAZ+LpcUSR6OShvSlFpVM2bLcKgrgcUZuSE + kAu4feUowiTuTO0MA5IscYuVAOWkig5bAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAACFAAAAA + xyc2Etc2hhMi01MTIAAAIAuF/iEiesuFacqW/3f/6ejF9b/HvvMa8mlZ/xiDYFH/bE1PSg + Pfs+ntjfGQ/YZ3bk5w6jQ1yx/A1OySH8eJCVb14UcKnJj1/NuNmUHdffIGTCuiT8wpSnuE + KVHPrtrRYZQ69QUcjnfpYsn4uG32UZgIzJSgczaB91mTOYkSIrHKRTS5vNq462DdAqXjc3 + 30PF8wCPNKWYuxSISHMuzZtJchIK9EZ278Ac/kj8AsXQmEJ2kHdFtriPYNLjDKq3WTdge+ + uSrGpGBIeuHu/CS7fOEqkSAoyjIPqe7R/U2DxTLZWUBxKRp/ozUqYW+fui5AWXXAzWcWsp + /Fs+JHbPQZh5P79lUZYwxZQIawy8hRmOb088rVhxXlNLF+u5Rc2bSgtj1WgWu5AML8IWwu + w8Uvlprit/Fb3CeNp55wTxu8j/u3lQGUh7dySsdRXExCDUCFQDiOV4pJA4AUxNdJ6urZDP + 4K0vJd0J8YFPoC0V1ugXlaB4Ya+qrsZzjBJFIK2oHBwDkkOTcVynRGqcbMu2PBMWJbGaVE + jTc5P8+zktNhacUa1fwwWlFi9Lcs/k8N7D4nNFiCJn1qjvDik/aOTPP3ek5sYZcinmssPj + TVGDVuHo8fXQmH+y1ppQbkkJ2KwoWnV0A8Zpd75f2WhBCS+R7bXnRi78AT/KPQi5rkV5+5 + vrUOY= -----END SSH SIGNATURE----- Test commit signed with rsa diff --git a/git/signature/testdata/ssh_signatures/commit_unsigned.txt b/git/signature/testdata/ssh_signatures/commit_unsigned.txt index 84dc42228..39c5b0a71 100644 --- a/git/signature/testdata/ssh_signatures/commit_unsigned.txt +++ b/git/signature/testdata/ssh_signatures/commit_unsigned.txt @@ -1,5 +1,5 @@ tree 4650a2cda631bc795fc254fe20b598135b265036 -author Test User 1772153090 +0100 -committer Test User 1772153090 +0100 +author Test User 1767225600 +0000 +committer Test User 1767225600 +0000 Test commit unsigned diff --git a/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh index 36e237428..ab963a27a 100755 --- a/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh +++ b/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh @@ -2,21 +2,69 @@ # generate_fixtures.sh - Script to generate SSH signature test fixtures # Generates SSH keys in all variants and signed Git objects -set -e +set -euo pipefail # Configuration variables TEST_USER_NAME="Test User" TEST_USER_EMAIL="sign-user@example.com" +FIXTURE_DATE="2026-01-01T00:00:00+0000" + +# Isolate Git from user and system configuration for deterministic output +export TZ=UTC +export GIT_AUTHOR_DATE="$FIXTURE_DATE" +export GIT_COMMITTER_DATE="$FIXTURE_DATE" +export GIT_AUTHOR_NAME="$TEST_USER_NAME" +export GIT_AUTHOR_EMAIL="$TEST_USER_EMAIL" +export GIT_COMMITTER_NAME="$TEST_USER_NAME" +export GIT_COMMITTER_EMAIL="$TEST_USER_EMAIL" +export GIT_CONFIG_NOSYSTEM=1 +export GIT_CONFIG_GLOBAL=/dev/null # Directory for temporary files TEMP_DIR=$(mktemp -d) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# define script dependencies +DEPENDENCY=( + ssh-keygen + cat + awk + git + find + sort +) + echo "=== SSH Signature Test Fixtures Generator ===" echo "Temporary directory: $TEMP_DIR" echo "Output directory: $SCRIPT_DIR" echo "" +# cleanup on exit +cleanup() { + if [[ -d "${TEMP_DIR}" ]]; then + echo "=== Cleanup ===" + rm -rf "${TEMP_DIR}" + echo "Temporary directory removed" + fi +} + +# check necessary commands +check_dependencies() { + local exit_state=0 + + # check presence of dependencies + for COMMAND in "${DEPENDENCY[@]}"; do + if ! command -v "${COMMAND}" >/dev/null 2>&1; then + echo "command '${COMMAND}' not found, needs to be installed first." + exit_state=1 + fi + done + + if [[ ${exit_state} -ne 0 ]]; then + exit 1 + fi +} + # Function to generate SSH keys generate_ssh_key() { local key_type=$1 @@ -81,7 +129,6 @@ create_combined_pub_keys() { create_signed_object() { local object_type=$1 local key_name=$2 - local key_type=$3 local verified_signers_file="$TEMP_DIR/verified_signers_${key_name}" echo "Creating signed $object_type for $key_name..." @@ -91,9 +138,7 @@ create_signed_object() { mkdir -p "$repo_dir" cd "$repo_dir" - git init - git config user.name "$TEST_USER_NAME" - git config user.email "$TEST_USER_EMAIL" + git init -b main git config gpg.format ssh git config user.signingkey "$TEMP_DIR/${key_name}.pub" git config gpg.ssh.allowedSignersFile "$verified_signers_file" @@ -157,9 +202,7 @@ create_unsigned_commit_and_tag() { mkdir -p "$repo_dir" cd "$repo_dir" - git init - git config user.name "$TEST_USER_NAME" - git config user.email "$TEST_USER_EMAIL" + git init -b main # Create file and commit (without signature) echo "Test content unsigned" > test.txt @@ -179,6 +222,9 @@ create_unsigned_commit_and_tag() { # Main program main() { + + check_dependencies + echo "Step 1: Generate SSH keys..." echo "-----------------------------------" @@ -216,22 +262,22 @@ main() { echo "----------------------------------------" # Signed commits for each key type - create_signed_object "commit" "rsa" "rsa" - create_signed_object "commit" "ecdsa_p256" "ecdsa" - create_signed_object "commit" "ecdsa_p384" "ecdsa" - create_signed_object "commit" "ecdsa_p521" "ecdsa" - create_signed_object "commit" "ed25519" "ed25519" + create_signed_object "commit" "rsa" + create_signed_object "commit" "ecdsa_p256" + create_signed_object "commit" "ecdsa_p384" + create_signed_object "commit" "ecdsa_p521" + create_signed_object "commit" "ed25519" echo "" echo "Step 5: Create signed tags..." echo "-------------------------------------" # Signed tags for each key type - create_signed_object "tag" "rsa" "rsa" - create_signed_object "tag" "ecdsa_p256" "ecdsa" - create_signed_object "tag" "ecdsa_p384" "ecdsa" - create_signed_object "tag" "ecdsa_p521" "ecdsa" - create_signed_object "tag" "ed25519" "ed25519" + create_signed_object "tag" "rsa" + create_signed_object "tag" "ecdsa_p256" + create_signed_object "tag" "ecdsa_p384" + create_signed_object "tag" "ecdsa_p521" + create_signed_object "tag" "ed25519" echo "" echo "Step 6: Create unsigned commit..." @@ -239,11 +285,6 @@ main() { create_unsigned_commit_and_tag - echo "" - echo "=== Cleanup ===" - rm -rf "$TEMP_DIR" - echo "Temporary directory removed" - echo "" echo "=== Done! ===" echo "All test fixtures have been successfully created." @@ -252,5 +293,7 @@ main() { find "$SCRIPT_DIR" -maxdepth 1 \( -name "*.txt" -o -name "key_*.pub" -o -name "authorized_keys*" -o -name "verified_signers*" \) -exec ls -lh {} \; 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' | sort } +trap cleanup EXIT + # Run script -main "$@" \ No newline at end of file +main diff --git a/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub b/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub index 7364a9a27..6b72ec718 100644 --- a/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub +++ b/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub @@ -1 +1 @@ -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPlZqzf0dwAPlQVvrP99hF4R0hWa0DDuNs0eAUxkGrvSiVqUrqjLOLNco5dibDFCYGm7VBq6JBN79jjx6bXGu0A= test-ecdsa_p256@example.com diff --git a/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint index f62198c01..e318d1da1 100644 --- a/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint +++ b/git/signature/testdata/ssh_signatures/key_ecdsa_p256.pub_fingerprint @@ -1 +1 @@ -SHA256:oU8IT7UOnJlOTOvr/W1cYf1SkdocFm5F7SAXOwuo8Kc +SHA256:KmuONXzOKczqEizPDlnkithlWIcGBeFZFxfbyIWNPuI diff --git a/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub b/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub index aabefb80b..b893f2ca9 100644 --- a/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub +++ b/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub @@ -1 +1 @@ -ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKXgoDFrE6RWKm+DkwSey/FvJOfezSqVLQwSx0382+vKMKTZUR7Jh9OgTEuFbb8rTSE7sChlFBD4VNMmuDvMCz7iGa1EGA4hYTkwGAwANKm/34xEqNUze/V9T4U631VPcg== test-ecdsa_p384@example.com diff --git a/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint index ee5243a33..f337ad857 100644 --- a/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint +++ b/git/signature/testdata/ssh_signatures/key_ecdsa_p384.pub_fingerprint @@ -1 +1 @@ -SHA256:+vwrYGpHfAAWIzT2x+uV+duJG7ZnSvCbRKwdPApx7JA +SHA256:sUEVeU46mIrOIYG9o6xrcIDB/0PM0ZcKDrVTIgtpdW0 diff --git a/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub b/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub index 82d92898f..705008d4e 100644 --- a/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub +++ b/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub @@ -1 +1 @@ -ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAB778Lk/xWvjbwI8ABv9XVsVn5Tqc+puiENJQC5RsBbuS7Cvwxns1jXolnZH5nAP+fGMBaL/9EHN0CAfYeRqTxkVAGUeR9NvFoaVFOVlfNnWBWQylRaLw4nvNHqlrSdf7/AopuUHIVyiSN87xMIvizc4PYIU4tK1HTE239mR3hjyFmY1A== test-ecdsa_p521@example.com diff --git a/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint index 34f471eca..74c3ee8a9 100644 --- a/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint +++ b/git/signature/testdata/ssh_signatures/key_ecdsa_p521.pub_fingerprint @@ -1 +1 @@ -SHA256:3FcWgX5RsACruglrcBJP/hefUZcYHJGnrk07U6yKin8 +SHA256:sAL1j3Z4OVuLuUl+beESBkqaTfsAu4qNJe60wHAw0Bc diff --git a/git/signature/testdata/ssh_signatures/key_ed25519.pub b/git/signature/testdata/ssh_signatures/key_ed25519.pub index 8f745c471..d2d083218 100644 --- a/git/signature/testdata/ssh_signatures/key_ed25519.pub +++ b/git/signature/testdata/ssh_signatures/key_ed25519.pub @@ -1 +1 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2EhITylf+uZMxtiS2FPufOmp0wDkW22dFFvas6J9vB test-ed25519@example.com diff --git a/git/signature/testdata/ssh_signatures/key_ed25519.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_ed25519.pub_fingerprint index 1ccdda317..676565164 100644 --- a/git/signature/testdata/ssh_signatures/key_ed25519.pub_fingerprint +++ b/git/signature/testdata/ssh_signatures/key_ed25519.pub_fingerprint @@ -1 +1 @@ -SHA256:eNi885YLo10DYWUdJOAs+CeXcDLX7X+Aqg2PprKFE3A +SHA256:SDB4adE/BP2VLwX9Pdf7aFUwW9JNdzoPSsHjd/wZIw4 diff --git a/git/signature/testdata/ssh_signatures/key_rsa.pub b/git/signature/testdata/ssh_signatures/key_rsa.pub index b02a4d38f..a5f229c19 100644 --- a/git/signature/testdata/ssh_signatures/key_rsa.pub +++ b/git/signature/testdata/ssh_signatures/key_rsa.pub @@ -1 +1 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDP52UUik6GdRebk00VAzaDDO1Vh++WqMhPkaYUu5NvSx3t6lRqGwV0CM5VPEzPZ5I8NTGoq6H4IHD8pk4tONVQVUq9YIJgs9AqvTjYFbaaSfiIqlLNxot4Cyc8IEnskSD9XZoZtNjbLnrrFcdOEORGabRxbWMUXr9OxaJGItpGO76GCCGN7Fpyxar26sM7Wgwy6G0I82xlmKyrZuCfrzJQMwNuHEs2FkDisu5M5CXU5bbBN9fPIpyDzDY8DLBfmg8TINKZZLlJe2wf2myLQJABxJooXLyN8FTePu4MfPQHosW+taOM7NahEtne4RZOhdMG0A1WJfrLuku3w+pXZD530IXPh63wAx+bz4PMrM38iR+U3OdV2Cwfk0L0cmswf2OxhZ7ExI89xqFSdS+3KGzWtlyCLUni8NqQU5SipsToQVwEOFtctBMYG/PyfY1QjPYhuXq760SsIHUBEQvMLKLhCxkNF/9tNmRyblhO6B1hOn3jZfm9rCHQc2zZLvHiPcWJAA83GPZYgeo7DAsGEqL9+5fCNWU0TAakTRkvYlH7TBaxxTglSjGYbXHTlYoeiUVKAkSffAVjZdOhHuvn9yrArRCi2StGXenueHZ8tzRwGfi6XFEkejkob0pRaVTNmy3CoK4HFGbkhJALuH3lKMIk7kztDAOSLHGLlQDlpIoOWw== test-rsa@example.com diff --git a/git/signature/testdata/ssh_signatures/key_rsa.pub_fingerprint b/git/signature/testdata/ssh_signatures/key_rsa.pub_fingerprint index 060a2c804..4ce5c9079 100644 --- a/git/signature/testdata/ssh_signatures/key_rsa.pub_fingerprint +++ b/git/signature/testdata/ssh_signatures/key_rsa.pub_fingerprint @@ -1 +1 @@ -SHA256:TxoYgaeIj5A7Md4rHNfxPdqawooc4NIGjIMbcQ7YKbw +SHA256:ruOMGhsHMnFnPXNt2DmM3XHHHQAJWib+sokHBG6tWdA diff --git a/git/signature/testdata/ssh_signatures/keys_all.pub b/git/signature/testdata/ssh_signatures/keys_all.pub index 2a587db72..e08b73097 100644 --- a/git/signature/testdata/ssh_signatures/keys_all.pub +++ b/git/signature/testdata/ssh_signatures/keys_all.pub @@ -1,5 +1,5 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpreiO+8XsB4xXGNmwuO48a7WPghb5ihCJNPyQZpnaPfq6vhNVWSgq8AIjBmJOJYo4HZyiHqpS4OBc86glk6qMv8YHRt4VRVBP+DjPLDIsOR7+2HBlOPHMm8lTDi+iMPHBDxqFy7mSDB4+v7n700+49vYhWjZJpesnnE6JoitxSVhmqp75jeNRNU6PD00z+gMUcviv8UOs/Apg1Cw5f+4T9yOnjlOHaFH/ButvZ0t2VF0cs28tfCuLAoumjine5Gm6tCRQlZOoapNJzvnYT+86f/PEU/4kDYf3wT7S+NnUDfCsIpDVlOXPvjnQ/DudhqEnnXvfch+eBCI7rtJBHIGPKFdmC4cUROa0UDGR6o/JxLtx4ZTbkGpq6MVwdrb7qJ+Oib1U8xVimWFfarkm7deVXWD3wB5Wa8Ko/a/WuYfE3gYRhb8iXPYd71FsEy4F41JCMZDcIqMiQRe3e2gvY+z2sf02kHOFeWJmrAY9FFjPL85VD0Dg++jrExkGFjcBTw9gUG5OPGpwqQ9WHO8E8DPza+i5J/wu4DODyLrLxuXHPeSYUjcvh5ln8P70qL+Irwn1mgn2PkIZW0XCPBt6Iylg55t5sfyy03P0Kmb4U3TrppMeig7Lr9LDU4Doh7Fj6oLYDGFUV+F52SSuPs5SfrWd6Apiz+VPjsAh5btPPJNlzQ== test-rsa@example.com -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL7Xspf5BmRD7ipGo4SNCftjzeunry1znmU78RhcVOYwLNCR5MVm22N9c1aYacIxHmi/TxkNTdQdEB8dd4mfA4Q= test-ecdsa_p256@example.com -ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKQ9Upb3Pa7b5NWbozm20PqpFc5WZCCCBlX9+eFELAjKdBze2EbTTKvx9YskKJ8PWLE8D9w20sjDivNwfUjoiZGgbJQcJKcKPrtovOYPv0JKpoyZ0PuLpq9kjSRTRnShEw== test-ecdsa_p384@example.com -ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGSY+OAEbrNSJ4QD6NgJIJQV8kmjqi+BhfeAAthEv0eCq1CADrCqKt0poxCahYNCTMLlMvqW7xBw6wDB0kV0/4CTwBX9HRftFUpaZanPtfvMNhPT/CDMrTsNSzg/H32Hu/fuvLwyPQ0JzRXgf+qiq3OZ4q0VjERU7L13UDoz4FgHJIeVQ== test-ecdsa_p521@example.com -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0X4BwNz61VyvryI/aq5vUc9fZK1najY6WCSdxzpLLW test-ed25519@example.com +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDP52UUik6GdRebk00VAzaDDO1Vh++WqMhPkaYUu5NvSx3t6lRqGwV0CM5VPEzPZ5I8NTGoq6H4IHD8pk4tONVQVUq9YIJgs9AqvTjYFbaaSfiIqlLNxot4Cyc8IEnskSD9XZoZtNjbLnrrFcdOEORGabRxbWMUXr9OxaJGItpGO76GCCGN7Fpyxar26sM7Wgwy6G0I82xlmKyrZuCfrzJQMwNuHEs2FkDisu5M5CXU5bbBN9fPIpyDzDY8DLBfmg8TINKZZLlJe2wf2myLQJABxJooXLyN8FTePu4MfPQHosW+taOM7NahEtne4RZOhdMG0A1WJfrLuku3w+pXZD530IXPh63wAx+bz4PMrM38iR+U3OdV2Cwfk0L0cmswf2OxhZ7ExI89xqFSdS+3KGzWtlyCLUni8NqQU5SipsToQVwEOFtctBMYG/PyfY1QjPYhuXq760SsIHUBEQvMLKLhCxkNF/9tNmRyblhO6B1hOn3jZfm9rCHQc2zZLvHiPcWJAA83GPZYgeo7DAsGEqL9+5fCNWU0TAakTRkvYlH7TBaxxTglSjGYbXHTlYoeiUVKAkSffAVjZdOhHuvn9yrArRCi2StGXenueHZ8tzRwGfi6XFEkejkob0pRaVTNmy3CoK4HFGbkhJALuH3lKMIk7kztDAOSLHGLlQDlpIoOWw== test-rsa@example.com +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPlZqzf0dwAPlQVvrP99hF4R0hWa0DDuNs0eAUxkGrvSiVqUrqjLOLNco5dibDFCYGm7VBq6JBN79jjx6bXGu0A= test-ecdsa_p256@example.com +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBKXgoDFrE6RWKm+DkwSey/FvJOfezSqVLQwSx0382+vKMKTZUR7Jh9OgTEuFbb8rTSE7sChlFBD4VNMmuDvMCz7iGa1EGA4hYTkwGAwANKm/34xEqNUze/V9T4U631VPcg== test-ecdsa_p384@example.com +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAB778Lk/xWvjbwI8ABv9XVsVn5Tqc+puiENJQC5RsBbuS7Cvwxns1jXolnZH5nAP+fGMBaL/9EHN0CAfYeRqTxkVAGUeR9NvFoaVFOVlfNnWBWQylRaLw4nvNHqlrSdf7/AopuUHIVyiSN87xMIvizc4PYIU4tK1HTE239mR3hjyFmY1A== test-ecdsa_p521@example.com +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2EhITylf+uZMxtiS2FPufOmp0wDkW22dFFvas6J9vB test-ed25519@example.com diff --git a/git/signature/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt b/git/signature/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt index a20933c0b..5a1008f34 100644 --- a/git/signature/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt +++ b/git/signature/testdata/ssh_signatures/tag_ecdsa_p256_signed.txt @@ -1,13 +1,13 @@ -object a9ce559c0acfc9268bdd854dec51d77ead112ab5 +object 22d258d46f5bc50420db6a5e4c8d60f35a78fa8d type commit tag test-tag-ecdsa_p256 -tagger Test User 1772153089 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ecdsa_p256 -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAE -EEvteyl/kGZEPuKkajhI0J+2PN66evLXOeZTvxGFxU5jAs0JHkxWbbY31zVphpwjEeaL9P -GQ1N1B0QHx13iZ8DhAAAAANnaXQAAAAAAAAABnNoYTUxMgAAAGUAAAATZWNkc2Etc2hhMi -1uaXN0cDI1NgAAAEoAAAAhAOQrMY08WBF4tTiUz3vq48VoKjvjOR9y75YzhMShbmGEAAAA -IQCF2ZvBxS6o/sZuRRw6HrFNryg2PU4ambnsRlC2cqOgfA== +EE+VmrN/R3AA+VBW+s/32EXhHSFZrQMO42zR4BTGQau9KJWpSuqMs4s1yjl2JsMUJgabtU +GrokE3v2OPHptca7QAAAAANnaXQAAAAAAAAABnNoYTUxMgAAAGMAAAATZWNkc2Etc2hhMi +1uaXN0cDI1NgAAAEgAAAAgCxZSTGktMcJiqH9WpFa00o5hcxF8nSBfNIPlgF9Jy4AAAAAg +Z1z3tz/8gqR34z6y7GaNbDNM1Nl7r9bvEE3jFrAZ6OQ= -----END SSH SIGNATURE----- diff --git a/git/signature/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt b/git/signature/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt index 002180388..0cf1c1dfb 100644 --- a/git/signature/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt +++ b/git/signature/testdata/ssh_signatures/tag_ecdsa_p384_signed.txt @@ -1,14 +1,14 @@ -object 095e9cde03a267af2c9ef62cf4868b126994714a +object 35f5804b751475a1856b7efc13ccba249bdcd32d type commit tag test-tag-ecdsa_p384 -tagger Test User 1772153089 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ecdsa_p384 -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAG -EEpD1Slvc9rtvk1ZujObbQ+qkVzlZkIIIGVf354UQsCMp0HN7YRtNMq/H1iyQonw9YsTwP -3DbSyMOK83B9SOiJkaBslBwkpwo+u2i85g+/QkqmjJnQ+4umr2SNJFNGdKETAAAAA2dpdA -AAAAAAAAAGc2hhNTEyAAAAgwAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAaAAAADA35l3E -HiF5ajYffjQRjxx37o8DG0eZIwDGtM2suBElqRKPrv2lNXUAZIFOt60X7EgAAAAwSE8BAK -DzSrdmwWwGIdsURzNrb0ziNQG5TJUI6oexNNGqP+JvZeGSJpSsS/PtRJyq +EEpeCgMWsTpFYqb4OTBJ7L8W8k597NKpUtDBLHTfzb68owpNlRHsmH06BMS4VtvytNITuw +KGUUEPhU0ya4O8wLPuIZrUQYDiFhOTAYDAA0qb/fjESo1TN79X1PhTrfVU9yAAAAA2dpdA +AAAAAAAAAGc2hhNTEyAAAAhQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAagAAADEAtnQb +F4WKv4eNjOlVh+nRn6emQwVp1VeXuGVp2wepXCINVnytAG76n/b5ZJ6gNSIcAAAAMQDkHJ +VlowOSJVMPP4jraIYZ2jNuxO5ABgATi5EHR0tTIFyeuxFx5z8hPfMB4YoDtJk= -----END SSH SIGNATURE----- diff --git a/git/signature/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt b/git/signature/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt index 48690d844..a38277f13 100644 --- a/git/signature/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt +++ b/git/signature/testdata/ssh_signatures/tag_ecdsa_p521_signed.txt @@ -1,16 +1,16 @@ -object f98d104240f097f9912d3dd654710a4ea9710a0d +object 18011cecb9ae833a29b75526e801e8c0b7951c3f type commit tag test-tag-ecdsa_p521 -tagger Test User 1772153090 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ecdsa_p521 -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAAKwAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAI -UEAZJj44ARus1InhAPo2AkglBXySaOqL4GF94AC2ES/R4KrUIAOsKoq3SmjEJqFg0JMwuU -y+pbvEHDrAMHSRXT/gJPAFf0dF+0VSlplqc+1+8w2E9P8IMytOw1LOD8ffYe79+68vDI9D -QnNFeB/6qKrc5nirRWMRFTsvXdQOjPgWAckh5VAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAAA -pwAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAAjAAAAEIBZnxQJm8pVQbZLRVgFzBa6mKgyo -Ndyi4pEsccUjrIVxkHV+choqQaLBv0hiLNx9pj7a4ZXCNxxTO0XO4LY5OMP40AAABCAROG -/LBErKEWKIFHOMYwPdaCEPUtimfYwAH6rBUhAFJdeDwm9WHoU2XcXO2Ca6+LCNQGTRBZSu -UxOfXY4xBKbaf2 +UEAHvvwuT/Fa+NvAjwAG/1dWxWflOpz6m6IQ0lALlGwFu5LsK/DGezWNeiWdkfmcA/58Yw +Fov/0Qc3QIB9h5GpPGRUAZR5H028WhpUU5WV82dYFZDKVFovDie80eqWtJ1/v8Cim5QchX +KJI3zvEwi+LNzg9ghTi0rUdMTbf2ZHeGPIWZjUAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAAA +pwAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAAjAAAAEIAupc/dSHO3kpXDI8XQYNX9ovZas +4uedvAnvXmtote/H1WurUtUh57q125yLeh1fxBAhs7mpO/OZiTBjBsdM5K9OcAAABCAQzQ +1xw+lmMhLsFMrzZ1y3EgXI74968Fbchj9APgQIzZG6cyfSZzE+UIkgDPk8j6x1BaMbwWYw +EuzjWGZa2mO8QC -----END SSH SIGNATURE----- diff --git a/git/signature/testdata/ssh_signatures/tag_ed25519_signed.txt b/git/signature/testdata/ssh_signatures/tag_ed25519_signed.txt index 4b811e555..ea7e50bac 100644 --- a/git/signature/testdata/ssh_signatures/tag_ed25519_signed.txt +++ b/git/signature/testdata/ssh_signatures/tag_ed25519_signed.txt @@ -1,12 +1,12 @@ -object 04285f60c0dcb310174dccae49f08475981aba2c +object b01ca16de562c561f62427eb0a30cb775d1c1dab type commit tag test-tag-ed25519 -tagger Test User 1772153090 +0100 +tagger Test User 1767225600 +0000 Test tag signed with ed25519 -----BEGIN SSH SIGNATURE----- -U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgHRfgHA3PrVXK+vIj9qrm9Rz19k -rWdqNjpYJJ3HOkstYAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 -AAAAQH9s7JZFHLzLGztuessbqdQwofdi/4WLeBnaRXdxy0g5WTLOUxENnJtLYcdKKowJBs -xS/FE43Cfu3YGmXAsSWwk= +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgrYSEhPKV/65kzG2JLYU+586anT +AORbbZ0UW9qzon28EAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQOZe8xDqF1oktx3h9p3EQKSx13zFVHQ2YcpF/HfeuQusKA1tvJY+T+ykPH4+zbva93 +KXi2P5xm89dQni0kTeAAY= -----END SSH SIGNATURE----- diff --git a/git/signature/testdata/ssh_signatures/tag_rsa_signed.txt b/git/signature/testdata/ssh_signatures/tag_rsa_signed.txt index 3882a4fa4..d4f8fde7b 100644 --- a/git/signature/testdata/ssh_signatures/tag_rsa_signed.txt +++ b/git/signature/testdata/ssh_signatures/tag_rsa_signed.txt @@ -1,30 +1,30 @@ -object 76abbeedee42f812d7fa2cdf0545f9cc13ae0463 +object d3a06c9ce3496cc156cdb256efafd82c45ac96d9 type commit tag test-tag-rsa -tagger Test User 1772153089 +0100 +tagger Test User 1767225600 +0000 Test tag signed with rsa -----BEGIN SSH SIGNATURE----- -U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAKmt6I77xewHjFcY2bC47j -xrtY+CFvmKEIk0/JBmmdo9+rq+E1VZKCrwAiMGYk4lijgdnKIeqlLg4FzzqCWTqoy/xgdG -3hVFUE/4OM8sMiw5Hv7YcGU48cybyVMOL6Iw8cEPGoXLuZIMHj6/ufvTT7j29iFaNkml6y -ecTomiK3FJWGaqnvmN41E1To8PTTP6AxRy+K/xQ6z8CmDULDl/7hP3I6eOU4doUf8G629n -S3ZUXRyzby18K4sCi6aOKd7kabq0JFCVk6hqk0nO+dhP7zp/88RT/iQNh/fBPtL42dQN8K -wikNWU5c++OdD8O52GoSede99yH54EIjuu0kEcgY8oV2YLhxRE5rRQMZHqj8nEu3HhlNuQ -amroxXB2tvuon46JvVTzFWKZYV9quSbt15VdYPfAHlZrwqj9r9a5h8TeBhGFvyJc9h3vUW -wTLgXjUkIxkNwioyJBF7d7aC9j7Pax/TaQc4V5YmasBj0UWM8vzlUPQOD76OsTGQYWNwFP -D2BQbk48anCpD1Yc7wTwM/Nr6Lkn/C7gM4PIusvG5cc95JhSNy+HmWfw/vSov4ivCfWaCf -Y+QhlbRcI8G3ojKWDnm3mx/LLTc/QqZvhTdOumkx6KDsuv0sNTgOiHsWPqgtgMYVRX4XnZ -JK4+zlJ+tZ3oCmLP5U+OwCHlu088k2XNAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAACFAAAAA -xyc2Etc2hhMi01MTIAAAIAUtTbcgmerQKpLoDxALJWACnkNDtKFagkZFMmFUo8vpAp5Zyz -jbC9uo6Oql/JwVNoAI+pm4/gxRDAKRGU1abki5Ge998m/FSkKi6ka0E4qwuNZkd9dOHAqt -kKeFhwIp0xiWCDF8s3iPpraaJbEfuGkGsAyAVNgfR0W8hu0wOOj8uwHuVZj7LeNLS3/jEu -bHwWhmzWCT0IPhFdkegDJMJ4XXgjxfsgGCXUahUfNZgOCXBfEBQkhHNoTq55+8DVqZ47hK -nRGjAZTVTnIxZhJqvaCHErse5A2jBJs2QfzmAIJhNAlDKmeHdWGDUADGxk5U7gD5IK5j/A -lBWp/ruXVqc7gwRKwQc7muu0Kzwa0yw8pBGi+8Y089a0M8Ti0cci57koXD8tPBLgz66710 -zLe9xkAZFvwxurHcgf01POvlCf6KGamCTNRsncnaUKfTvZVSOXHPeurNVlEpsDPZAsJ6wI -hFc0Y/RvLbTMlCxA6/brvr+peYSKmnCXJO+SXgZkN0QoKrq27RvPwB/2j1sNGgKpftfxUK -ymhPPKlzXGgrSDkBLhcaqGI1+5J3qjN0qLRGjwpgvkuM2JFLUaFVk1w8EvU19yMjmYkddn -AdZEB0xiAu1vZEEw9jhaTWW2R1qQ3ftf1+D8iXm+t1I3HlrSOBcVGzDi0x66a62K0bmXXy -AIRCY= +U1NIU0lHAAAAAQAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAM/nZRSKToZ1F5uTTRUDNo +MM7VWH75aoyE+RphS7k29LHe3qVGobBXQIzlU8TM9nkjw1MairofggcPymTi041VBVSr1g +gmCz0Cq9ONgVtppJ+IiqUs3Gi3gLJzwgSeyRIP1dmhm02NsueusVx04Q5EZptHFtYxRev0 +7FokYi2kY7voYIIY3sWnLFqvbqwztaDDLobQjzbGWYrKtm4J+vMlAzA24cSzYWQOKy7kzk +JdTltsE3188inIPMNjwMsF+aDxMg0plkuUl7bB/abItAkAHEmihcvI3wVN4+7gx89Aeixb +61o4zs1qES2d7hFk6F0wbQDVYl+su6S7fD6ldkPnfQhc+HrfADH5vPg8yszfyJH5Tc51XY +LB+TQvRyazB/Y7GFnsTEjz3GoVJ1L7cobNa2XIItSeLw2pBTlKKmxOhBXAQ4W1y0Exgb8/ +J9jVCM9iG5ervrRKwgdQERC8wsouELGQ0X/202ZHJuWE7oHWE6feNl+b2sIdBzbNku8eI9 +xYkADzcY9liB6jsMCwYSov37l8I1ZTRMBqRNGS9iUftMFrHFOCVKMZhtcdOVih6JRUoCRJ +98BWNl06Ee6+f3KsCtEKLZK0Zd6e54dny3NHAZ+LpcUSR6OShvSlFpVM2bLcKgrgcUZuSE +kAu4feUowiTuTO0MA5IscYuVAOWkig5bAAAAA2dpdAAAAAAAAAAGc2hhNTEyAAACFAAAAA +xyc2Etc2hhMi01MTIAAAIAAgRV8G/ngPQBftA2zeWyTC9hntqtHkKlIcc5958ti0T/XnY/ +yex2Jt3Ex1BzIoaPUQPIKutYVLgU2K9ZR4tg9NZamey6TCU/KJvojyTi9R9niGtTNmGKMN +tB9qlb459TmAD25RrB3kN07BeJ4V6fgdXReoiRKviQLJLSGSGPfG4MEoA/TViKwBH7tdsT +U5MWBwazejuOMqojD0yoKRVyFbcH7I34Pk3MfOGTxoXz6902RonYDZddAqGCg4knAp0Nfe +EcA07SlXy2B33yF0YW+wH5G+iSuXJ3WctIoZBn0qqYdltDJIug8jBosFtQTnq2jnu+02HN +ufCDPL7UJfQFdEQFCfGY614G+WJS3/clf6cD5qfvb0B9lRbdr4UDrln/JTztFYxrknYbKz +43xV9p2muj1ym707h0BmOc+fWlWxd1GvnYnev2S5MiFdPh3Q5SKT9ejCL7UhnAvhSq75/9 +AE1t5nV89dHl2Asa6D3wq0U4e7GA8fMZK1cjWVVH6JA3PiyhcUZA3sxD2AQlenMgVLqqfW +fyj/p6jihKGfV5zVSoyMp9sL/riBnmzXPhyVRWPgm2vrKs5BTEqSSZEM5PxUTSBMgf1q88 +53fniVIk1lLn6plydVi0Tbwut4wd70w/9pwct2HXeceFVge0NuTaq1JdRzrg8G5/ms4d/I +sBdYQ= -----END SSH SIGNATURE----- diff --git a/git/signature/testdata/ssh_signatures/tag_unsigned.txt b/git/signature/testdata/ssh_signatures/tag_unsigned.txt index 4e5a55762..ec80cabd7 100644 --- a/git/signature/testdata/ssh_signatures/tag_unsigned.txt +++ b/git/signature/testdata/ssh_signatures/tag_unsigned.txt @@ -1,6 +1,6 @@ -object 44e81f94b13509da0a0c9ad89b590c786b383a28 +object f85d47148d57e658056d9859377ec47ff17d0e98 type commit tag test-tag -tagger Test User 1772153090 +0200 +tagger Test User 1767225600 +0000 Test tag From 18724a3feb963db09a350c5a11761fda3525ba8c Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Tue, 19 May 2026 23:34:12 +0200 Subject: [PATCH 21/22] fixes text fixtures creation description and adds test for unsigned commits and tags Signed-off-by: Ricardo Bartels --- git/signature/gpg_signature_test.go | 21 ++++++++--- git/signature/ssh_signature_test.go | 35 ++++++++++++++++++ .../testdata/gpg_signatures/README.md | 36 ++++++++----------- .../gpg_signatures/generate_gpg_fixtures.sh | 8 ++--- .../testdata/ssh_signatures/README.md | 25 ++++++++----- .../ssh_signatures/generate_ssh_fixtures.sh | 8 ++--- 6 files changed, 92 insertions(+), 41 deletions(-) diff --git a/git/signature/gpg_signature_test.go b/git/signature/gpg_signature_test.go index eab2ad0da..0c6508560 100644 --- a/git/signature/gpg_signature_test.go +++ b/git/signature/gpg_signature_test.go @@ -329,21 +329,34 @@ func TestVerifyPGPSignatureForCommitsAndTags(t *testing.T) { t.Run("unsigned commit", func(t *testing.T) { g := NewWithT(t) - // Parse the unsigned commit from the fixture file commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) g.Expect(err).ToNot(HaveOccurred()) - // Build a git.Commit using build.CommitWithRef gitCommit, err := build.CommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) g.Expect(err).ToNot(HaveOccurred()) - // Read a public key publicKey, err := os.ReadFile(filepath.Join(testDataDir, "key_rsa_2048.pub")) g.Expect(err).ToNot(HaveOccurred()) - // Verify the signature - should fail as the commit is unsigned fingerprint, err := signature.VerifyPGPSignature(gitCommit.Signature, gitCommit.Encoded, string(publicKey)) g.Expect(err).To(HaveOccurred()) g.Expect(fingerprint).To(BeEmpty()) }) + + t.Run("unsigned tag", func(t *testing.T) { + g := NewWithT(t) + + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_unsigned.txt")) + g.Expect(err).ToNot(HaveOccurred()) + + gitTag, err := build.Tag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + g.Expect(err).ToNot(HaveOccurred()) + + publicKey, err := os.ReadFile(filepath.Join(testDataDir, "key_rsa_2048.pub")) + g.Expect(err).ToNot(HaveOccurred()) + + fingerprint, err := signature.VerifyPGPSignature(gitTag.Signature, gitTag.Encoded, string(publicKey)) + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + }) } diff --git a/git/signature/ssh_signature_test.go b/git/signature/ssh_signature_test.go index c0bbdfd5e..888460f83 100644 --- a/git/signature/ssh_signature_test.go +++ b/git/signature/ssh_signature_test.go @@ -174,6 +174,41 @@ func TestVerifySSHSignature(t *testing.T) { }) } + + // Test error cases + t.Run("unsigned commit", func(t *testing.T) { + g := NewWithT(t) + + commitObj, err := testutils.ParseCommitFromFixture(filepath.Join(testDataDir, "commit_unsigned.txt")) + g.Expect(err).ToNot(HaveOccurred()) + + gitCommit, err := build.CommitWithRef(commitObj, nil, plumbing.ReferenceName("refs/heads/main")) + g.Expect(err).ToNot(HaveOccurred()) + + pubKey, err := os.ReadFile(filepath.Join(testDataDir, "key_ed25519.pub")) + g.Expect(err).ToNot(HaveOccurred()) + + fingerprint, err := signature.VerifySSHSignature(gitCommit.Signature, gitCommit.Encoded, string(pubKey)) + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + }) + + t.Run("unsigned tag", func(t *testing.T) { + g := NewWithT(t) + + tagObj, err := testutils.ParseTagFromFixture(filepath.Join(testDataDir, "tag_unsigned.txt")) + g.Expect(err).ToNot(HaveOccurred()) + + gitTag, err := build.Tag(tagObj, plumbing.ReferenceName("refs/tags/test-tag")) + g.Expect(err).ToNot(HaveOccurred()) + + pubKey, err := os.ReadFile(filepath.Join(testDataDir, "key_ed25519.pub")) + g.Expect(err).ToNot(HaveOccurred()) + + fingerprint, err := signature.VerifySSHSignature(gitTag.Signature, gitTag.Encoded, string(pubKey)) + g.Expect(err).To(HaveOccurred()) + g.Expect(fingerprint).To(BeEmpty()) + }) } func TestSSHSignatureValidationCases(t *testing.T) { diff --git a/git/signature/testdata/gpg_signatures/README.md b/git/signature/testdata/gpg_signatures/README.md index 369f970c5..1b18c5d7c 100644 --- a/git/signature/testdata/gpg_signatures/README.md +++ b/git/signature/testdata/gpg_signatures/README.md @@ -37,8 +37,8 @@ The [`generate_gpg_fixtures.sh`](generate_gpg_fixtures.sh) script automates the - One signed tag for each key type - All tags are verified using `git verify-tag` -5. **Unsigned Commit**: - - One unsigned commit for testing negative cases +5. **Unsigned Objects**: + - One unsigned commit and one unsigned tag for testing negative cases ### Manual Generation @@ -83,7 +83,7 @@ gpg --batch --generate-key batch_rsa_4096.txt # ECDSA P-256 key cat > batch_ecdsa_p256.txt < batch_ecdsa_p384.txt < batch_ecdsa_p521.txt < batch_brainpool_p256.txt < batch_brainpool_p384.txt < batch_brainpool_p512.txt < batch_ed25519.txt < "$SCRIPT_DIR/tag_unsigned.txt" cd "$SCRIPT_DIR" - echo " ✓ commit_unsigned.txt created" + echo " ✓ commit_unsigned.txt and tag_unsigned.txt created" } # Main program @@ -275,7 +275,7 @@ main() { done echo "" - echo "Step 5: Create unsigned commit..." + echo "Step 5: Create unsigned commit and tag..." echo "------------------------------------------" create_unsigned_commit_and_tag diff --git a/git/signature/testdata/ssh_signatures/README.md b/git/signature/testdata/ssh_signatures/README.md index 7435a145f..a902eb198 100644 --- a/git/signature/testdata/ssh_signatures/README.md +++ b/git/signature/testdata/ssh_signatures/README.md @@ -10,7 +10,7 @@ To generate all test fixtures at once, simply run: ./generate_ssh_fixtures.sh ``` -This script will automatically create all SSH keys, authorized_keys files, signed commits, and signed tags. +This script will automatically create all SSH keys, public key files, fingerprint files, signed commits, and signed tags. ## How to Generate Test Fixtures @@ -23,9 +23,10 @@ The [`generate_ssh_fixtures.sh`](generate_ssh_fixtures.sh) script automates the - ECDSA (p256, p384, p521) - ED25519 -2. **Authorized Keys Files**: - - Individual files for each key type - - Combined file with all keys +2. **Public Keys**: + - Individual public key files for each key type + - Fingerprint files for each public key + - Combined file with all public keys 3. **Signed Git Commits**: - One signed commit for each key type @@ -35,8 +36,8 @@ The [`generate_ssh_fixtures.sh`](generate_ssh_fixtures.sh) script automates the - One signed tag for each key type - All tags are verified using `git verify-tag` -5. **Unsigned Commit**: - - One unsigned commit for testing negative cases +5. **Unsigned Objects**: + - One unsigned commit and one unsigned tag for testing negative cases ### Manual Generation @@ -159,7 +160,14 @@ The script generates the following files: - `key_ecdsa_p384.pub` - ECDSA P-384 public key - `key_ecdsa_p521.pub` - ECDSA P-521 public key - `key_ed25519.pub` - ED25519 public key -- `keys_all.pub` - All public keys +- `keys_all.pub` - All public keys combined + +### Public Key Fingerprints +- `key_rsa.pub_fingerprint` - SHA256 fingerprint of RSA public key +- `key_ecdsa_p256.pub_fingerprint` - SHA256 fingerprint of ECDSA P-256 public key +- `key_ecdsa_p384.pub_fingerprint` - SHA256 fingerprint of ECDSA P-384 public key +- `key_ecdsa_p521.pub_fingerprint` - SHA256 fingerprint of ECDSA P-521 public key +- `key_ed25519.pub_fingerprint` - SHA256 fingerprint of ED25519 public key ### Signed Commits - `commit_rsa_signed.txt` - RSA-signed commit @@ -175,8 +183,9 @@ The script generates the following files: - `tag_ecdsa_p521_signed.txt` - ECDSA P-521 signed tag - `tag_ed25519_signed.txt` - ED25519 signed tag -### Unsigned Commit +### Unsigned Objects - `commit_unsigned.txt` - Unsigned commit for testing negative cases +- `tag_unsigned.txt` - Unsigned tag for testing negative cases ## Security Note diff --git a/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh b/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh index ab963a27a..bcbc8bcc1 100755 --- a/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh +++ b/git/signature/testdata/ssh_signatures/generate_ssh_fixtures.sh @@ -190,12 +190,12 @@ create_signed_object() { fi } -# Function to create unsigned commit +# Function to create unsigned commit and tag create_unsigned_commit_and_tag() { local commit_file="$SCRIPT_DIR/commit_unsigned.txt" local tag_file="$SCRIPT_DIR/tag_unsigned.txt" - echo "Creating unsigned commit..." + echo "Creating unsigned commit and tag..." # Create temporary Git repository local repo_dir="$TEMP_DIR/repo_unsigned" @@ -217,7 +217,7 @@ create_unsigned_commit_and_tag() { git cat-file tag test-tag > "$tag_file" cd "$SCRIPT_DIR" - echo " ✓ $commit_file created" + echo " ✓ $commit_file and $tag_file created" } # Main program @@ -280,7 +280,7 @@ main() { create_signed_object "tag" "ed25519" echo "" - echo "Step 6: Create unsigned commit..." + echo "Step 6: Create unsigned commit and tag..." echo "------------------------------------------" create_unsigned_commit_and_tag From 8ef0280f4dcf4624241ebd21b80605080ef2a0ef Mon Sep 17 00:00:00 2001 From: Ricardo Bartels Date: Wed, 20 May 2026 00:04:31 +0200 Subject: [PATCH 22/22] adds more test for malfromed signatures Signed-off-by: Ricardo Bartels --- git/signature/gpg_signature_test.go | 49 +++++++++++++++++++++++++++++ git/signature/signature_test.go | 30 ++++++++++++++++++ git/signature/ssh_signature_test.go | 35 +++++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/git/signature/gpg_signature_test.go b/git/signature/gpg_signature_test.go index 0c6508560..28571fe27 100644 --- a/git/signature/gpg_signature_test.go +++ b/git/signature/gpg_signature_test.go @@ -193,6 +193,55 @@ func TestVerifyPGPSignature(t *testing.T) { keyRings: []string{malformedKeyRingFixture, malformedKeyRingFixture}, wantErr: "unable to read armored key ring: unexpected EOF", }, + { + name: "Missing END PGP SIGNATURE marker", + payload: []byte(encodedCommitFixture), + sig: "-----BEGIN PGP SIGNATURE-----\n\niHUEABEIAB0WIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCYV//1AAKCRAyma6w5Ahb", + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload with any of the given key rings", + }, + { + name: "Missing END PGP MESSAGE marker", + payload: []byte(encodedCommitFixture), + sig: "-----BEGIN PGP MESSAGE-----\n\niHUEABEIAB0WIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCYV//1AAKCRAyma6w5Ahb", + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload with any of the given key rings", + }, + { + name: "Corrupted base64 body", + payload: []byte(encodedCommitFixture), + sig: "-----BEGIN PGP SIGNATURE-----\n\n!!!!!!\n-----END PGP SIGNATURE-----", + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload with any of the given key rings", + }, + { + name: "Empty body between markers", + payload: []byte(encodedCommitFixture), + sig: "-----BEGIN PGP SIGNATURE-----\n\n-----END PGP SIGNATURE-----", + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload with any of the given key rings", + }, + { + name: "Truncated signature mid-base64", + payload: []byte(encodedCommitFixture), + sig: "-----BEGIN PGP SIGNATURE-----\n\niHUEABEIAB0WIQQHgExUr4FrLdKzpNYy", + keyRings: []string{armoredKeyRingFixture}, + wantErr: "unable to verify payload with any of the given key rings", + }, + { + name: "Mismatched BEGIN/END markers", + payload: []byte(encodedCommitFixture), + sig: "-----BEGIN PGP SIGNATURE-----\n\niHUEABEIAB0WIQQHgExUr4FrLdKzpNYyma6w5AhbrwUCYV//1AAKCRAyma6w5Ahb\nr7nJAQCQU4zEJu04/Q0ac/UaL6htjhq/wTDNMeUM+aWG/LcBogEAqFUea1oR2BJQ\nJCJmEtERFh39zNWSazQmxPAFhEE0kbc=\n=+Wlj\n-----END PGP MESSAGE-----", + keyRings: []string{armoredKeyRingFixture}, + want: keyRingFingerprintFixture, + }, + { + name: "Extra data after END marker", + payload: []byte(encodedCommitFixture), + sig: signatureCommitFixture + "\ngarbage-data-after-end", + keyRings: []string{armoredKeyRingFixture}, + want: keyRingFingerprintFixture, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/git/signature/signature_test.go b/git/signature/signature_test.go index 7bf82f2b6..a2223bd77 100644 --- a/git/signature/signature_test.go +++ b/git/signature/signature_test.go @@ -66,6 +66,16 @@ func TestIsPGPSignature(t *testing.T) { signature: " \n\t ", want: false, }, + { + name: "PGP SIGNATURE without END marker", + signature: "-----BEGIN PGP SIGNATURE-----", + want: true, + }, + { + name: "PGP MESSAGE without END marker", + signature: "-----BEGIN PGP MESSAGE-----", + want: true, + }, } for _, tt := range tests { @@ -113,6 +123,11 @@ func TestIsSSHSignature(t *testing.T) { signature: " \n\t ", want: false, }, + { + name: "SSH signature without END marker", + signature: "-----BEGIN SSH SIGNATURE-----", + want: true, + }, } for _, tt := range tests { @@ -227,6 +242,21 @@ func TestGetSignatureType(t *testing.T) { signature: " \n\t ", want: string(signatureTypeUnknown), }, + { + name: "PGP SIGNATURE without END marker", + signature: "-----BEGIN PGP SIGNATURE-----", + want: string(signatureTypePGP), + }, + { + name: "PGP MESSAGE without END marker", + signature: "-----BEGIN PGP MESSAGE-----", + want: string(signatureTypePGP), + }, + { + name: "SSH signature without END marker", + signature: "-----BEGIN SSH SIGNATURE-----", + want: string(signatureTypeSSH), + }, } for _, tt := range tests { diff --git a/git/signature/ssh_signature_test.go b/git/signature/ssh_signature_test.go index 888460f83..b61a19e0d 100644 --- a/git/signature/ssh_signature_test.go +++ b/git/signature/ssh_signature_test.go @@ -317,6 +317,41 @@ func TestSSHSignatureValidationCases(t *testing.T) { authorizedKeys: []string{invalidAuthKeys, invalidAuthKeys}, wantErr: "unable to parse authorized key", }, + { + name: "Missing END SSH SIGNATURE marker", + sig: "-----BEGIN SSH SIGNATURE-----\nAAAA", + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey)}, + wantErr: "unable to unarmor SSH signature", + }, + { + name: "Empty body between markers", + sig: "-----BEGIN SSH SIGNATURE-----\n\n-----END SSH SIGNATURE-----", + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey)}, + wantErr: "unable to unarmor SSH signature", + }, + { + name: "Truncated signature mid-base64", + sig: "-----BEGIN SSH SIGNATURE-----\nU1NIU0lHAAAAAQAAABYAAAhlZDI1NTE5AAAAIM", + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey)}, + wantErr: "unable to unarmor SSH signature", + }, + { + name: "Corrupted base64 body", + sig: "-----BEGIN SSH SIGNATURE-----\n!!!!!!\n-----END SSH SIGNATURE-----", + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey)}, + wantErr: "unable to unarmor SSH signature", + }, + { + name: "Extra data after END marker", + sig: gitCommit.Signature + "\ngarbage-data-after-end", + payload: gitCommit.Encoded, + authorizedKeys: []string{string(pubKey)}, + want: expectedFingerprint, + }, } for _, tt := range tests {