gittuf/internal/gitinterface/commit_test.go
Aditya Sirish 03159de27f
*: Centralize test artifacts
Signed-off-by: Aditya Sirish <aditya@saky.in>
2024-01-15 00:23:47 -05:00

349 lines
10 KiB
Go

// SPDX-License-Identifier: Apache-2.0
package gitinterface
import (
"bytes"
"context"
"strings"
"testing"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/gittuf/gittuf/internal/signerverifier"
"github.com/gittuf/gittuf/internal/signerverifier/gpg"
artifacts "github.com/gittuf/gittuf/internal/testartifacts"
sslibsv "github.com/gittuf/gittuf/internal/third_party/go-securesystemslib/signerverifier"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/hiddeco/sshsig"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/ssh"
)
var (
rsaSSHPublicKeyBytes = artifacts.SSHRSAPublic
rsaSSHPrivateKeyBytes = artifacts.SSHRSAPrivate
ecdsaSSHPublicKeyBytes = artifacts.SSHECDSAPublic
ecdsaSSHPrivateKeyBytes = artifacts.SSHECDSAPrivate
gpgPublicKey = artifacts.GPGKey1Public
gpgPrivateKey = artifacts.GPGKey1Private
)
func TestCreateCommitObject(t *testing.T) {
t.Run("zero commit and zero parent", func(t *testing.T) {
commit := CreateCommitObject(testGitConfig, plumbing.ZeroHash, []plumbing.Hash{plumbing.ZeroHash}, "Test commit", testClock)
enc := memory.NewStorage().NewEncodedObject()
if err := commit.Encode(enc); err != nil {
t.Error(err)
}
assert.Equal(t, "22ddfd55fb5fba7b37b50b068d1527a1b0f9f561", enc.Hash().String())
})
t.Run("zero commit and single non-zero parent", func(t *testing.T) {
pHashes := []plumbing.Hash{EmptyTree()}
commit := CreateCommitObject(testGitConfig, plumbing.ZeroHash, pHashes, "Test commit", testClock)
enc := memory.NewStorage().NewEncodedObject()
if err := commit.Encode(enc); err != nil {
t.Error(err)
}
for parentHashInd := range commit.ParentHashes {
assert.Equal(t, pHashes[parentHashInd], commit.ParentHashes[parentHashInd])
}
})
t.Run("zero commit and multiple parents", func(t *testing.T) {
pHashes := []plumbing.Hash{EmptyTree(), EmptyTree()}
commit := CreateCommitObject(testGitConfig, plumbing.ZeroHash, pHashes, "Test commit", testClock)
enc := memory.NewStorage().NewEncodedObject()
if err := commit.Encode(enc); err != nil {
t.Error(err)
}
for parentHashInd := range commit.ParentHashes {
assert.Equal(t, pHashes[parentHashInd], commit.ParentHashes[parentHashInd])
}
})
}
func TestVerifyCommitSignature(t *testing.T) {
gpgSignedCommit := createTestSignedCommit(t)
// FIXME: fix gitsign testing
gitsignSignedCommit := &object.Commit{
Hash: plumbing.NewHash("d6b230478965e25477263aa65f1ca6d23d0c0d97"),
Author: object.Signature{
Name: "Aditya Sirish",
Email: "aditya@saky.in",
When: time.Date(2023, time.August, 1, 15, 44, 23, 0, time.FixedZone("", -4*3600)),
},
Committer: object.Signature{
Name: "Aditya Sirish",
Email: "aditya@saky.in",
When: time.Date(2023, time.August, 1, 15, 44, 23, 0, time.FixedZone("", -4*3600)),
},
PGPSignature: `-----BEGIN SIGNED MESSAGE-----
MIIEMAYJKoZIhvcNAQcCoIIEITCCBB0CAQExDTALBglghkgBZQMEAgEwCwYJKoZI
hvcNAQcBoIIC0DCCAswwggJToAMCAQICFHIJCrBVHxoHlGos++k1xJxcElGaMAoG
CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln
c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIzMDgwMTE5NDQzMVoXDTIzMDgwMTE5NTQz
MVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD8d752TJfGtANVYoiJJn+o6
JPKj5NwEZs1AcVRT2qElikVun5t+bQ07iDFa/Xiun5ytZrEK2YJVgqdntLd6hSOj
ggFyMIIBbjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD
VR0OBBYEFAuYzgyBA01YSSN1v0fYenGo7+PcMB8GA1UdIwQYMBaAFN/T6c9WJBGW
+ajY6ShVosYuGGQ/MBwGA1UdEQEB/wQSMBCBDmFkaXR5YUBzYWt5LmluMCwGCisG
AQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDAuBgorBgEE
AYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCBigYKKwYB
BAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6O
AAABibKhcJgAAAQDAEcwRQIgcWuz6NhFgdL0fNni6j0SOQnAgFpPEaN8jDH70mbD
uPMCIQCX8koEnIX4c9crMT1hfoBBf1Z/CHJ6HLLHpQwWfEUMIzAKBggqhkjOPQQD
AwNnADBkAjBozIBaBtEu7JUyYLH7Ly698E0o8DdIOmqcUMUYWNC6zyJVdrL5gAla
mQSxfObSQasCMHQuw8youTjmFJXT7pNOYX4DW25knt+6P+W/m6zwcRRe3dMjmUAB
gdBJb32+XXJMRDGCASYwggEiAgEBME8wNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2
MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUCFHIJCrBVHxoHlGos++k1
xJxcElGaMAsGCWCGSAFlAwQCAaBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw
HAYJKoZIhvcNAQkFMQ8XDTIzMDgwMTE5NDQzMlowLwYJKoZIhvcNAQkEMSIEIBe6
VHcVlkO8jRm/fbUipwxwxNaI7UFDAL38Jl8eUj/5MAoGCCqGSM49BAMCBEgwRgIh
AIYiRbnVeWjjgX2XwljDryzQN5RhUQaVH/AcUj+tbvWxAiEAhm9l3BU58tQsgyJW
oYBpMWLgg6AUzpxx9mITZ2EKr4c=
-----END SIGNED MESSAGE-----
`,
Message: "Test commit\n",
TreeHash: plumbing.NewHash("4b825dc642cb6eb9a060e54bf8d69288fbee4904"),
}
sshCommits := createTestSSHSignedCommits(t)
gpgKey, err := gpg.LoadGPGKeyFromBytes(gpgPublicKey)
if err != nil {
t.Fatal(err)
}
fulcioKey := &sslibsv.SSLibKey{
KeyType: signerverifier.FulcioKeyType,
Scheme: "fulcio",
KeyVal: sslibsv.KeyVal{
Identity: "aditya@saky.in",
Issuer: "https://github.com/login/oauth",
},
}
rsaKey, err := sslibsv.LoadKey(rsaSSHPublicKeyBytes)
if err != nil {
t.Fatal(err)
}
ecdsaKey, err := sslibsv.LoadKey(ecdsaSSHPublicKeyBytes)
if err != nil {
t.Fatal(err)
}
t.Run("gpg signed commit", func(t *testing.T) {
err = VerifyCommitSignature(context.Background(), gpgSignedCommit, gpgKey)
assert.Nil(t, err)
})
// FIXME: fix gitsign testing
// t.Run("gitsign signed commit", func(t *testing.T) {
// err := VerifyCommitSignature(context.Background(), gitsignSignedCommit, fulcioKey)
// assert.Nil(t, err)
// })
t.Run("use gpg signed commit with gitsign key", func(t *testing.T) {
err := VerifyCommitSignature(context.Background(), gpgSignedCommit, fulcioKey)
assert.ErrorIs(t, err, ErrIncorrectVerificationKey)
})
t.Run("use gitsign signed commit with gpg key", func(t *testing.T) {
err := VerifyCommitSignature(context.Background(), gitsignSignedCommit, gpgKey)
assert.ErrorIs(t, err, ErrIncorrectVerificationKey)
})
t.Run("use ssh signed commits with corresponding keys", func(t *testing.T) {
err := VerifyCommitSignature(context.Background(), sshCommits[0], rsaKey)
assert.Nil(t, err)
err = VerifyCommitSignature(context.Background(), sshCommits[1], ecdsaKey)
assert.Nil(t, err)
})
t.Run("use ssh signed commits with wrong keys", func(t *testing.T) {
err := VerifyCommitSignature(context.Background(), sshCommits[0], ecdsaKey)
assert.ErrorIs(t, err, ErrIncorrectVerificationKey)
err = VerifyCommitSignature(context.Background(), sshCommits[1], rsaKey)
assert.ErrorIs(t, err, ErrIncorrectVerificationKey)
})
}
func TestKnowsCommit(t *testing.T) {
repo, err := git.Init(memory.NewStorage(), memfs.New())
if err != nil {
t.Fatal(err)
}
refName := "refs/heads/main"
if err := repo.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(refName), plumbing.ZeroHash)); err != nil {
t.Fatal(err)
}
emptyTreeHash, err := WriteTree(repo, nil)
if err != nil {
t.Fatal(err)
}
if _, err := Commit(repo, emptyTreeHash, refName, "First commit", false); err != nil {
t.Fatal(err)
}
ref, err := repo.Reference(plumbing.ReferenceName(refName), true)
if err != nil {
t.Fatal(err)
}
firstCommitID := ref.Hash()
firstCommit, err := GetCommit(repo, firstCommitID)
if err != nil {
t.Fatal(err)
}
if _, err := Commit(repo, emptyTreeHash, refName, "Second commit", false); err != nil {
t.Fatal(err)
}
ref, err = repo.Reference(plumbing.ReferenceName(refName), true)
if err != nil {
t.Fatal(err)
}
secondCommitID := ref.Hash()
secondCommit, err := GetCommit(repo, secondCommitID)
if err != nil {
t.Fatal(err)
}
t.Run("check if second commit knows first", func(t *testing.T) {
knows, err := KnowsCommit(repo, secondCommitID, firstCommit)
assert.Nil(t, err)
assert.True(t, knows)
})
t.Run("check that first commit does not know second", func(t *testing.T) {
knows, err := KnowsCommit(repo, firstCommitID, secondCommit)
assert.Nil(t, err)
assert.False(t, knows)
})
t.Run("check that both commits know themselves", func(t *testing.T) {
knows, err := KnowsCommit(repo, firstCommitID, firstCommit)
assert.Nil(t, err)
assert.True(t, knows)
knows, err = KnowsCommit(repo, secondCommitID, secondCommit)
assert.Nil(t, err)
assert.True(t, knows)
})
t.Run("check that an unknown commit can't know a known commit", func(t *testing.T) {
knows, err := KnowsCommit(repo, plumbing.ZeroHash, firstCommit)
assert.ErrorIs(t, err, plumbing.ErrObjectNotFound)
assert.False(t, knows)
})
}
func createTestSignedCommit(t *testing.T) *object.Commit {
t.Helper()
repo, err := git.Init(memory.NewStorage(), memfs.New())
if err != nil {
t.Fatal(err)
}
testCommit := &object.Commit{
Author: object.Signature{
Name: testName,
Email: testEmail,
When: testClock.Now(),
},
Committer: object.Signature{
Name: testName,
Email: testEmail,
When: testClock.Now(),
},
Message: "Test commit",
TreeHash: plumbing.ZeroHash,
}
commitEncoded := repo.Storer.NewEncodedObject()
if err := testCommit.EncodeWithoutSignature(commitEncoded); err != nil {
t.Fatal(err)
}
r, err := commitEncoded.Reader()
if err != nil {
t.Fatal(err)
}
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(gpgPrivateKey))
if err != nil {
t.Fatal(err)
}
sig := new(strings.Builder)
if err := openpgp.ArmoredDetachSign(sig, keyring[0], r, nil); err != nil {
t.Fatal(err)
}
testCommit.PGPSignature = sig.String()
return testCommit
}
func createTestSSHSignedCommits(t *testing.T) []*object.Commit {
t.Helper()
testCommits := []*object.Commit{}
signingKeys := [][]byte{rsaSSHPrivateKeyBytes, ecdsaSSHPrivateKeyBytes}
for _, keyBytes := range signingKeys {
testCommit := &object.Commit{
Author: object.Signature{
Name: testName,
Email: testEmail,
When: testClock.Now(),
},
Committer: object.Signature{
Name: testName,
Email: testEmail,
When: testClock.Now(),
},
Message: "Test commit",
TreeHash: EmptyTree(),
}
commitBytes, err := getCommitBytesWithoutSignature(testCommit)
if err != nil {
t.Fatal(err)
}
signer, err := ssh.ParsePrivateKey(keyBytes)
if err != nil {
t.Fatal(err)
}
sshSig, err := sshsig.Sign(bytes.NewReader(commitBytes), signer, sshsig.HashSHA512, namespaceSSHSignature)
if err != nil {
t.Fatal(err)
}
sigBytes := sshsig.Armor(sshSig)
testCommit.PGPSignature = string(sigBytes)
testCommits = append(testCommits, testCommit)
}
return testCommits
}