Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'internal/git/catfile/parse_commit_test.go')
-rw-r--r--internal/git/catfile/parse_commit_test.go531
1 files changed, 531 insertions, 0 deletions
diff --git a/internal/git/catfile/parse_commit_test.go b/internal/git/catfile/parse_commit_test.go
new file mode 100644
index 000000000..2e80dc38f
--- /dev/null
+++ b/internal/git/catfile/parse_commit_test.go
@@ -0,0 +1,531 @@
+package catfile
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git/gittest"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
+ "google.golang.org/protobuf/types/known/timestamppb"
+)
+
+const (
+ pgpSignature = `-----BEGIN PGP SIGNATURE-----
+Version: ObjectivePGP
+Comment: https://www.objectivepgp.com
+Charset: UTF-8
+
+wsFcBAABCgAGBQJecon1AAoJEDYMjTn1G2THmSsP/At/jskLdF0i7p0nKf4JLjeeqRJ4k2IUg87U
+ZwV6mbLo5XFm8Sq7CJBAGAhlOZE4BAwKALuawmgs5XMEZwK2z6AIgosGTVpmxDTTI11bXt4XIOdz
+qF7c/gUrJOZzjFXOqDsd5UuPRupwznC5eKlLbfImR+NYxKryo8JGdF5t52ph4kChcQsKlSkXuYNI
++9UgbaMclEjb0OLm+mcP9QxW+Cs9JS2Jb4Jh6XONWW1nDN3ZTDDskguIqqF47UxIgSImrmpMcEj9
+YSNU0oMoHM4+1DoXp1t99EGPoAMvO+a5g8gd1jouCIrI6KOX+GeG/TFFM0mQwg/d/N9LR049m8ed
+vgqg/lMiWUxQGL2IPpYPcgiUEqfn7ete+NMzQV5zstxF/q7Yj2BhM2L7FPHxKaoy/w5Q/DcAO4wN
+5gxVmIvbCDk5JOx8I+boIS8ZxSvIlJ5IWaPrcjg5Mc40it+WHvMqxVnCzH0c6KcXaJ2SibVb59HR
+pdRhEXXw/hRN65l/xwyM8sklQalAGu755gNJZ4k9ApBVUssZyiu+te2+bDirAcmK8/x1jvMQY6bn
+DFxBE7bMHDp24IFPaVID84Ryt3vSSBEkrUGm7OkyDESTpHCr4sfD5o3LCUCIibTqv/CAhe59mhbB
+2AXL7X+EzylKy6C1N5KUUiMTW94AuF6f8FqBoxnf
+=U6zM
+-----END PGP SIGNATURE-----`
+
+ sshSignature = `-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgtc+Qk8jhMwVZk/jFEFCM16LNQb
+30q5kK30bbetfjyTMAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+AAAAQADE1oOMKxqQu86XUQbhCoWx8GnnYHQ/i3mHdA0zPycIlDv8N6BRVDS6b0ja2Avj+s
+uNvjRqSEGQJ4q6vhKOnQw=
+-----END SSH SIGNATURE-----`
+)
+
+func TestParseCommits(t *testing.T) {
+ t.Parallel()
+
+ type setupData struct {
+ content string
+ oid git.ObjectID
+ expectedCommit *Commit
+ expectedErr error
+ }
+
+ // Valid-but-interesting commits should be test at the FindCommit level.
+ // Invalid objects (that Git would complain about during fsck) can be
+ // tested here.
+ //
+ // Once a repository contains a pathological object it can be hard to get
+ // rid of it. Because of this I think it's nicer to ignore such objects
+ // than to throw hard errors.
+ for _, tc := range []struct {
+ desc string
+ setup func(t *testing.T) setupData
+ }{
+ {
+ desc: "empty commit object",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{Id: gittest.DefaultObjectHash.EmptyTreeOID.String()},
+ SignatureData: SignatureData{Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "no email",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Jane Doe",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{Name: []byte("Jane Doe")},
+ },
+ SignatureData: SignatureData{Payload: []byte("author Jane Doe"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "normal author",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Au Thor <au.thor@example.com> 1625121079 +0000",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Au Thor"),
+ Email: []byte("au.thor@example.com"),
+ Date: timestamppb.New(time.Unix(1625121079, 0)),
+ Timezone: []byte("+0000"),
+ },
+ },
+ SignatureData: SignatureData{Payload: []byte("author Au Thor <au.thor@example.com> 1625121079 +0000"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "author with missing mail",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Au Thor <> 1625121079 +0000",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Au Thor"),
+ Date: timestamppb.New(time.Unix(1625121079, 0)),
+ Timezone: []byte("+0000"),
+ },
+ },
+ SignatureData: SignatureData{Payload: []byte("author Au Thor <> 1625121079 +0000"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "author with missing date",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Au Thor <au.thor@example.com>",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Au Thor"),
+ Email: []byte("au.thor@example.com"),
+ },
+ },
+ SignatureData: SignatureData{Payload: []byte("author Au Thor <au.thor@example.com>"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "unmatched <",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Jane Doe <janedoe@example.com",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{Name: []byte("Jane Doe")},
+ },
+ SignatureData: SignatureData{Payload: []byte("author Jane Doe <janedoe@example.com"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "unmatched >",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Jane Doe janedoe@example.com>",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{Name: []byte("Jane Doe janedoe@example.com>")},
+ },
+ SignatureData: SignatureData{Payload: []byte("author Jane Doe janedoe@example.com>"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "date too high",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Jane Doe <janedoe@example.com> 9007199254740993 +0200",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Jane Doe"),
+ Email: []byte("janedoe@example.com"),
+ Date: &timestamppb.Timestamp{Seconds: 9223371974719179007},
+ Timezone: []byte("+0200"),
+ },
+ },
+ SignatureData: SignatureData{Payload: []byte("author Jane Doe <janedoe@example.com> 9007199254740993 +0200"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "date negative",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "author Jane Doe <janedoe@example.com> -1 +0200",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Jane Doe"),
+ Email: []byte("janedoe@example.com"),
+ Date: &timestamppb.Timestamp{Seconds: 9223371974719179007},
+ Timezone: []byte("+0200"),
+ },
+ },
+ SignatureData: SignatureData{Payload: []byte("author Jane Doe <janedoe@example.com> -1 +0200"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "huge",
+ setup: func(_ *testing.T) setupData {
+ repeat := strings.Repeat("A", 100000)
+ content := "author " + repeat
+
+ return setupData{
+ content: content,
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte(repeat),
+ },
+ },
+ SignatureData: SignatureData{Payload: []byte(content), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "has encoding",
+ setup: func(_ *testing.T) setupData {
+ return setupData{
+ content: "encoding Windows-1251",
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Encoding: "Windows-1251",
+ },
+ SignatureData: SignatureData{Payload: []byte("encoding Windows-1251"), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "PGP signed commit",
+ setup: func(_ *testing.T) setupData {
+ commitData, signedCommitData := createSignedCommitData(gpgSignaturePrefix, pgpSignature, "random commit message")
+
+ return setupData{
+ content: signedCommitData,
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ Committer: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ SignatureType: gitalypb.SignatureType_PGP,
+ TreeId: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Subject: []byte("random commit message"),
+ Body: []byte("random commit message\n"),
+ BodySize: 22,
+ },
+ SignatureData: SignatureData{Payload: []byte(commitData), Signatures: [][]byte{[]byte(pgpSignature)}},
+ },
+ }
+ },
+ },
+ {
+ desc: "PGP SHA256-signed signed commit",
+ setup: func(_ *testing.T) setupData {
+ commitData, signedCommitData := createSignedCommitData(gpgSignaturePrefixSha256, pgpSignature, "random commit message")
+
+ return setupData{
+ content: signedCommitData,
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ Committer: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ SignatureType: gitalypb.SignatureType_PGP,
+ TreeId: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Subject: []byte("random commit message"),
+ Body: []byte("random commit message\n"),
+ BodySize: 22,
+ },
+ SignatureData: SignatureData{Payload: []byte(commitData), Signatures: [][]byte{[]byte(pgpSignature)}},
+ },
+ }
+ },
+ },
+ {
+ desc: "SSH signed commit",
+ setup: func(_ *testing.T) setupData {
+ commitData, signedCommitData := createSignedCommitData(gpgSignaturePrefix, sshSignature, "random commit message")
+
+ return setupData{
+ content: signedCommitData,
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ Committer: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ SignatureType: gitalypb.SignatureType_SSH,
+ TreeId: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Subject: []byte("random commit message"),
+ Body: []byte("random commit message\n"),
+ BodySize: 22,
+ },
+ SignatureData: SignatureData{Payload: []byte(commitData), Signatures: [][]byte{[]byte(sshSignature)}},
+ },
+ }
+ },
+ },
+ {
+ desc: "garbage signed commit",
+ setup: func(_ *testing.T) setupData {
+ _, signedCommitData := createSignedCommitData("gpgsig-garbage", sshSignature, "garbage-signed commit message")
+
+ return setupData{
+ content: signedCommitData,
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ Committer: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ TreeId: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Subject: []byte("garbage-signed commit message"),
+ Body: []byte("garbage-signed commit message\n"),
+ BodySize: 30,
+ },
+ SignatureData: SignatureData{Payload: []byte(signedCommitData), Signatures: [][]byte{}},
+ },
+ }
+ },
+ },
+ {
+ desc: "commits with multiple signatures",
+ setup: func(_ *testing.T) setupData {
+ commitData, signedCommitData := createSignedCommitData("gpgsig", pgpSignature, "commit message")
+
+ signatureLines := strings.Split(sshSignature, "\n")
+ for i, signatureLine := range signatureLines {
+ signatureLines[i] = " " + signatureLine
+ }
+
+ signedCommitData = strings.Replace(signedCommitData, "\n\n", fmt.Sprintf("\n%s%s\n\n", "gpgsig-sha256", strings.Join(signatureLines, "\n")), 1)
+
+ return setupData{
+ content: signedCommitData,
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ Committer: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ SignatureType: gitalypb.SignatureType_PGP,
+ TreeId: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Subject: []byte("commit message"),
+ Body: []byte("commit message\n"),
+ BodySize: 15,
+ },
+ SignatureData: SignatureData{Payload: []byte(commitData), Signatures: [][]byte{[]byte(pgpSignature), []byte(sshSignature)}},
+ },
+ }
+ },
+ },
+ {
+ desc: "PGP signed commit with headers after the signature",
+ setup: func(_ *testing.T) setupData {
+ commitMessage := "random commit message"
+ commitData, signedCommitData := createSignedCommitData(gpgSignaturePrefix, pgpSignature, "random commit message")
+
+ // Headers after the signature shouldn't be ignored and should be
+ // part of the commit data. If ignored, malicious users can draft a
+ // commit similar to a previously signed commit but with additional
+ // headers and the new commit would still show as verified since we
+ // won't add the additional headers to commit data.
+ postSignatureHeader := fmt.Sprintf("parent %s", gittest.DefaultObjectHash.EmptyTreeOID)
+ modifyCommitData := func(data string) string {
+ return strings.Replace(
+ data,
+ fmt.Sprintf("\n%s", commitMessage),
+ fmt.Sprintf("%s\n\n%s", postSignatureHeader, commitMessage),
+ 1,
+ )
+ }
+
+ // We expect that these post signature headers are added to the commit data,
+ // that way any modified commit will fail verification.
+ signedCommitData = modifyCommitData(signedCommitData)
+ commitData = modifyCommitData(commitData)
+
+ return setupData{
+ content: signedCommitData,
+ oid: gittest.DefaultObjectHash.EmptyTreeOID,
+ expectedCommit: &Commit{
+ GitCommit: &gitalypb.GitCommit{
+ Id: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Author: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ Committer: &gitalypb.CommitAuthor{
+ Name: []byte("Bug Fixer"),
+ Email: []byte("bugfixer@email.com"),
+ Date: &timestamppb.Timestamp{Seconds: 1584564725},
+ Timezone: []byte("+0100"),
+ },
+ SignatureType: gitalypb.SignatureType_PGP,
+ TreeId: gittest.DefaultObjectHash.EmptyTreeOID.String(),
+ Subject: []byte("random commit message"),
+ Body: []byte("random commit message\n"),
+ BodySize: 22,
+ },
+ SignatureData: SignatureData{Payload: []byte(commitData), Signatures: [][]byte{[]byte(pgpSignature)}},
+ },
+ }
+ },
+ },
+ } {
+ tc := tc
+
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ setup := tc.setup(t)
+
+ commit, err := NewParser().ParseCommit(newStaticObject(setup.content, "commit", setup.oid))
+ require.Equal(t, setup.expectedErr, err)
+ testhelper.ProtoEqual(t, setup.expectedCommit.GitCommit, commit.GitCommit)
+ require.Equal(t, setup.expectedCommit.SignatureData.Payload, commit.SignatureData.Payload)
+ require.Equal(t, setup.expectedCommit.SignatureData.Signatures, commit.SignatureData.Signatures)
+ })
+ }
+}
+
+func createSignedCommitData(signatureField, signature, commitMessage string) (string, string) {
+ commitData := fmt.Sprintf(`tree %s
+author Bug Fixer <bugfixer@email.com> 1584564725 +0100
+committer Bug Fixer <bugfixer@email.com> 1584564725 +0100
+
+%s
+`, gittest.DefaultObjectHash.EmptyTreeOID, commitMessage)
+
+ // Each line of the signature needs to start with a space so that Git recognizes it as a continuation of the
+ // field.
+ signatureLines := strings.Split(signature, "\n")
+ for i, signatureLine := range signatureLines {
+ signatureLines[i] = " " + signatureLine
+ }
+
+ signedCommitData := strings.Replace(commitData, "\n\n", fmt.Sprintf("\n%s%s\n\n", signatureField, strings.Join(signatureLines, "\n")), 1)
+
+ return commitData, signedCommitData
+}