mirror of
https://github.com/vee1e/gittuf.git
synced 2026-09-03 19:27:07 +00:00
349 lines
10 KiB
Go
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
|
|
}
|