+6
-45
appview/commitverify/verify.go
+6
-45
appview/commitverify/verify.go
···
3
import (
4
"log"
5
6
-
"github.com/go-git/go-git/v5/plumbing/object"
7
"tangled.org/core/appview/db"
8
"tangled.org/core/appview/models"
9
"tangled.org/core/crypto"
···
35
return ""
36
}
37
38
-
func GetVerifiedObjectCommits(e db.Execer, emailToDid map[string]string, commits []*object.Commit) (VerifiedCommits, error) {
39
-
ndCommits := []types.NiceDiff{}
40
-
for _, commit := range commits {
41
-
ndCommits = append(ndCommits, ObjectCommitToNiceDiff(commit))
42
-
}
43
-
return GetVerifiedCommits(e, emailToDid, ndCommits)
44
-
}
45
-
46
-
func GetVerifiedCommits(e db.Execer, emailToDid map[string]string, ndCommits []types.NiceDiff) (VerifiedCommits, error) {
47
vcs := VerifiedCommits{}
48
49
didPubkeyCache := make(map[string][]models.PublicKey)
50
51
for _, commit := range ndCommits {
52
-
c := commit.Commit
53
-
54
-
committerEmail := c.Committer.Email
55
if did, exists := emailToDid[committerEmail]; exists {
56
// check if we've already fetched public keys for this did
57
pubKeys, ok := didPubkeyCache[did]
···
67
}
68
69
// try to verify with any associated pubkeys
70
for _, pk := range pubKeys {
71
-
if _, ok := crypto.VerifyCommitSignature(pk.Key, commit); ok {
72
73
fp, err := crypto.SSHFingerprint(pk.Key)
74
if err != nil {
75
log.Println("error computing ssh fingerprint:", err)
76
}
77
78
-
vc := verifiedCommit{fingerprint: fp, hash: c.This}
79
vcs[vc] = struct{}{}
80
break
81
}
···
86
87
return vcs, nil
88
}
89
-
90
-
// ObjectCommitToNiceDiff is a compatibility function to convert a
91
-
// commit object into a NiceDiff structure.
92
-
func ObjectCommitToNiceDiff(c *object.Commit) types.NiceDiff {
93
-
var niceDiff types.NiceDiff
94
-
95
-
// set commit information
96
-
niceDiff.Commit.Message = c.Message
97
-
niceDiff.Commit.Author = c.Author
98
-
niceDiff.Commit.This = c.Hash.String()
99
-
niceDiff.Commit.Committer = c.Committer
100
-
niceDiff.Commit.Tree = c.TreeHash.String()
101
-
niceDiff.Commit.PGPSignature = c.PGPSignature
102
-
103
-
changeId, ok := c.ExtraHeaders["change-id"]
104
-
if ok {
105
-
niceDiff.Commit.ChangedId = string(changeId)
106
-
}
107
-
108
-
// set parent hash if available
109
-
if len(c.ParentHashes) > 0 {
110
-
niceDiff.Commit.Parent = c.ParentHashes[0].String()
111
-
}
112
-
113
-
// XXX: Stats and Diff fields are typically populated
114
-
// after fetching the actual diff information, which isn't
115
-
// directly available in the commit object itself.
116
-
117
-
return niceDiff
118
-
}
···
3
import (
4
"log"
5
6
"tangled.org/core/appview/db"
7
"tangled.org/core/appview/models"
8
"tangled.org/core/crypto"
···
34
return ""
35
}
36
37
+
func GetVerifiedCommits(e db.Execer, emailToDid map[string]string, ndCommits []types.Commit) (VerifiedCommits, error) {
38
vcs := VerifiedCommits{}
39
40
didPubkeyCache := make(map[string][]models.PublicKey)
41
42
for _, commit := range ndCommits {
43
+
committerEmail := commit.Committer.Email
44
if did, exists := emailToDid[committerEmail]; exists {
45
// check if we've already fetched public keys for this did
46
pubKeys, ok := didPubkeyCache[did]
···
56
}
57
58
// try to verify with any associated pubkeys
59
+
payload := commit.Payload()
60
+
signature := commit.PGPSignature
61
for _, pk := range pubKeys {
62
+
if _, ok := crypto.VerifySignature([]byte(pk.Key), []byte(signature), []byte(payload)); ok {
63
64
fp, err := crypto.SSHFingerprint(pk.Key)
65
if err != nil {
66
log.Println("error computing ssh fingerprint:", err)
67
}
68
69
+
vc := verifiedCommit{fingerprint: fp, hash: commit.This}
70
vcs[vc] = struct{}{}
71
break
72
}
···
77
78
return vcs, nil
79
}
+1
-2
appview/pages/pages.go
+1
-2
appview/pages/pages.go
···
31
"github.com/bluesky-social/indigo/atproto/identity"
32
"github.com/bluesky-social/indigo/atproto/syntax"
33
"github.com/go-git/go-git/v5/plumbing"
34
-
"github.com/go-git/go-git/v5/plumbing/object"
35
)
36
37
//go:embed templates/* static legal
···
649
RepoInfo repoinfo.RepoInfo
650
Active string
651
TagMap map[string][]string
652
-
CommitsTrunc []*object.Commit
653
TagsTrunc []*types.TagReference
654
BranchesTrunc []types.Branch
655
// ForkInfo *types.ForkInfo
···
31
"github.com/bluesky-social/indigo/atproto/identity"
32
"github.com/bluesky-social/indigo/atproto/syntax"
33
"github.com/go-git/go-git/v5/plumbing"
34
)
35
36
//go:embed templates/* static legal
···
648
RepoInfo repoinfo.RepoInfo
649
Active string
650
TagMap map[string][]string
651
+
CommitsTrunc []types.Commit
652
TagsTrunc []*types.TagReference
653
BranchesTrunc []types.Branch
654
// ForkInfo *types.ForkInfo
+1
-1
appview/pages/templates/repo/commit.html
+1
-1
appview/pages/templates/repo/commit.html
···
35
{{ end }}
36
37
<span class="px-1 select-none before:content-['\00B7']"></span>
38
-
{{ template "repo/fragments/time" $commit.Author.When }}
39
<span class="px-1 select-none before:content-['\00B7']"></span>
40
41
<a href="/{{ $repo }}/commit/{{ $commit.This }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ slice $commit.This 0 8 }}</a>
···
35
{{ end }}
36
37
<span class="px-1 select-none before:content-['\00B7']"></span>
38
+
{{ template "repo/fragments/time" $commit.Committer.When }}
39
<span class="px-1 select-none before:content-['\00B7']"></span>
40
41
<a href="/{{ $repo }}/commit/{{ $commit.This }}" class="no-underline hover:underline text-gray-500 dark:text-gray-300">{{ slice $commit.This 0 8 }}</a>
+1
-1
appview/repo/index.go
+1
-1
appview/repo/index.go
+2
-2
appview/repo/log.go
+2
-2
appview/repo/log.go
···
116
l.Error("failed to fetch email to did mapping", "err", err)
117
}
118
119
-
vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, xrpcResp.Commits)
120
if err != nil {
121
l.Error("failed to GetVerifiedObjectCommits", "err", err)
122
}
···
192
l.Error("failed to get email to did mapping", "err", err)
193
}
194
195
-
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.NiceDiff{*result.Diff})
196
if err != nil {
197
l.Error("failed to GetVerifiedCommits", "err", err)
198
}
···
116
l.Error("failed to fetch email to did mapping", "err", err)
117
}
118
119
+
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, xrpcResp.Commits)
120
if err != nil {
121
l.Error("failed to GetVerifiedObjectCommits", "err", err)
122
}
···
192
l.Error("failed to get email to did mapping", "err", err)
193
}
194
195
+
vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.Commit{result.Diff.Commit})
196
if err != nil {
197
l.Error("failed to GetVerifiedCommits", "err", err)
198
}
+1
-3
appview/repo/repo_util.go
+1
-3
appview/repo/repo_util.go
···
8
"tangled.org/core/appview/db"
9
"tangled.org/core/appview/models"
10
"tangled.org/core/types"
11
-
12
-
"github.com/go-git/go-git/v5/plumbing/object"
13
)
14
15
func sortFiles(files []types.NiceTree) {
···
42
})
43
}
44
45
-
func uniqueEmails(commits []*object.Commit) []string {
46
emails := make(map[string]struct{})
47
for _, commit := range commits {
48
if commit.Author.Email != "" {
···
8
"tangled.org/core/appview/db"
9
"tangled.org/core/appview/models"
10
"tangled.org/core/types"
11
)
12
13
func sortFiles(files []types.NiceTree) {
···
40
})
41
}
42
43
+
func uniqueEmails(commits []types.Commit) []string {
44
emails := make(map[string]struct{})
45
for _, commit := range commits {
46
if commit.Author.Email != "" {
+1
-34
crypto/verify.go
+1
-34
crypto/verify.go
···
5
"crypto/sha256"
6
"encoding/base64"
7
"fmt"
8
-
"strings"
9
10
"github.com/hiddeco/sshsig"
11
"golang.org/x/crypto/ssh"
12
-
"tangled.org/core/types"
13
)
14
15
func VerifySignature(pubKey, signature, payload []byte) (error, bool) {
···
28
// multiple algorithms but sha-512 is most secure, and git's ssh signing defaults
29
// to sha-512 for all key types anyway.
30
err = sshsig.Verify(buf, sig, pub, sshsig.HashSHA512, "git")
31
-
return err, err == nil
32
-
}
33
34
-
// VerifyCommitSignature reconstructs the payload used to sign a commit. This is
35
-
// essentially the git cat-file output but without the gpgsig header.
36
-
//
37
-
// Caveats: signature verification will fail on commits with more than one parent,
38
-
// i.e. merge commits, because types.NiceDiff doesn't carry more than one Parent field
39
-
// and we are unable to reconstruct the payload correctly.
40
-
//
41
-
// Ideally this should directly operate on an *object.Commit.
42
-
func VerifyCommitSignature(pubKey string, commit types.NiceDiff) (error, bool) {
43
-
signature := commit.Commit.PGPSignature
44
-
45
-
author := bytes.NewBuffer([]byte{})
46
-
committer := bytes.NewBuffer([]byte{})
47
-
commit.Commit.Author.Encode(author)
48
-
commit.Commit.Committer.Encode(committer)
49
-
50
-
payload := strings.Builder{}
51
-
52
-
fmt.Fprintf(&payload, "tree %s\n", commit.Commit.Tree)
53
-
if commit.Commit.Parent != "" {
54
-
fmt.Fprintf(&payload, "parent %s\n", commit.Commit.Parent)
55
-
}
56
-
fmt.Fprintf(&payload, "author %s\n", author.String())
57
-
fmt.Fprintf(&payload, "committer %s\n", committer.String())
58
-
if commit.Commit.ChangedId != "" {
59
-
fmt.Fprintf(&payload, "change-id %s\n", commit.Commit.ChangedId)
60
-
}
61
-
fmt.Fprintf(&payload, "\n%s", commit.Commit.Message)
62
-
63
-
return VerifySignature([]byte(pubKey), []byte(signature), []byte(payload.String()))
64
}
65
66
// SSHFingerprint computes the fingerprint of the supplied ssh pubkey.
···
5
"crypto/sha256"
6
"encoding/base64"
7
"fmt"
8
9
"github.com/hiddeco/sshsig"
10
"golang.org/x/crypto/ssh"
11
)
12
13
func VerifySignature(pubKey, signature, payload []byte) (error, bool) {
···
26
// multiple algorithms but sha-512 is most secure, and git's ssh signing defaults
27
// to sha-512 for all key types anyway.
28
err = sshsig.Verify(buf, sig, pub, sshsig.HashSHA512, "git")
29
30
+
return err, err == nil
31
}
32
33
// SSHFingerprint computes the fingerprint of the supplied ssh pubkey.
+1
-17
knotserver/git/diff.go
+1
-17
knotserver/git/diff.go
···
77
nd.Diff = append(nd.Diff, ndiff)
78
}
79
80
-
nd.Stat.FilesChanged = len(diffs)
81
-
nd.Commit.This = c.Hash.String()
82
-
nd.Commit.PGPSignature = c.PGPSignature
83
-
nd.Commit.Committer = c.Committer
84
-
nd.Commit.Tree = c.TreeHash.String()
85
-
86
-
if parent.Hash.IsZero() {
87
-
nd.Commit.Parent = ""
88
-
} else {
89
-
nd.Commit.Parent = parent.Hash.String()
90
-
}
91
-
nd.Commit.Author = c.Author
92
-
nd.Commit.Message = c.Message
93
-
94
-
if v, ok := c.ExtraHeaders["change-id"]; ok {
95
-
nd.Commit.ChangedId = string(v)
96
-
}
97
98
return &nd, nil
99
}
+6
-1
knotserver/xrpc/repo_log.go
+6
-1
knotserver/xrpc/repo_log.go
···
62
return
63
}
64
65
+
tcommits := make([]types.Commit, len(commits))
66
+
for i, c := range commits {
67
+
tcommits[i].FromGoGitCommit(c)
68
+
}
69
+
70
// Create response using existing types.RepoLogResponse
71
response := types.RepoLogResponse{
72
+
Commits: tcommits,
73
Ref: ref,
74
Page: (offset / limit) + 1,
75
PerPage: limit,
-1
patchutil/patchutil.go
-1
patchutil/patchutil.go
+2
-12
types/diff.go
+2
-12
types/diff.go
···
2
3
import (
4
"github.com/bluekeyes/go-gitdiff/gitdiff"
5
-
"github.com/go-git/go-git/v5/plumbing/object"
6
)
7
8
type DiffOpts struct {
···
43
44
// A nicer git diff representation.
45
type NiceDiff struct {
46
-
Commit struct {
47
-
Message string `json:"message"`
48
-
Author object.Signature `json:"author"`
49
-
This string `json:"this"`
50
-
Parent string `json:"parent"`
51
-
PGPSignature string `json:"pgp_signature"`
52
-
Committer object.Signature `json:"committer"`
53
-
Tree string `json:"tree"`
54
-
ChangedId string `json:"change_id"`
55
-
} `json:"commit"`
56
-
Stat struct {
57
FilesChanged int `json:"files_changed"`
58
Insertions int `json:"insertions"`
59
Deletions int `json:"deletions"`
···
2
3
import (
4
"github.com/bluekeyes/go-gitdiff/gitdiff"
5
)
6
7
type DiffOpts struct {
···
42
43
// A nicer git diff representation.
44
type NiceDiff struct {
45
+
Commit Commit `json:"commit"`
46
+
Stat struct {
47
FilesChanged int `json:"files_changed"`
48
Insertions int `json:"insertions"`
49
Deletions int `json:"deletions"`
+17
-17
types/repo.go
+17
-17
types/repo.go
···
8
)
9
10
type RepoIndexResponse struct {
11
-
IsEmpty bool `json:"is_empty"`
12
-
Ref string `json:"ref,omitempty"`
13
-
Readme string `json:"readme,omitempty"`
14
-
ReadmeFileName string `json:"readme_file_name,omitempty"`
15
-
Commits []*object.Commit `json:"commits,omitempty"`
16
-
Description string `json:"description,omitempty"`
17
-
Files []NiceTree `json:"files,omitempty"`
18
-
Branches []Branch `json:"branches,omitempty"`
19
-
Tags []*TagReference `json:"tags,omitempty"`
20
-
TotalCommits int `json:"total_commits,omitempty"`
21
}
22
23
type RepoLogResponse struct {
24
-
Commits []*object.Commit `json:"commits,omitempty"`
25
-
Ref string `json:"ref,omitempty"`
26
-
Description string `json:"description,omitempty"`
27
-
Log bool `json:"log,omitempty"`
28
-
Total int `json:"total,omitempty"`
29
-
Page int `json:"page,omitempty"`
30
-
PerPage int `json:"per_page,omitempty"`
31
}
32
33
type RepoCommitResponse struct {
···
8
)
9
10
type RepoIndexResponse struct {
11
+
IsEmpty bool `json:"is_empty"`
12
+
Ref string `json:"ref,omitempty"`
13
+
Readme string `json:"readme,omitempty"`
14
+
ReadmeFileName string `json:"readme_file_name,omitempty"`
15
+
Commits []Commit `json:"commits,omitempty"`
16
+
Description string `json:"description,omitempty"`
17
+
Files []NiceTree `json:"files,omitempty"`
18
+
Branches []Branch `json:"branches,omitempty"`
19
+
Tags []*TagReference `json:"tags,omitempty"`
20
+
TotalCommits int `json:"total_commits,omitempty"`
21
}
22
23
type RepoLogResponse struct {
24
+
Commits []Commit `json:"commits,omitempty"`
25
+
Ref string `json:"ref,omitempty"`
26
+
Description string `json:"description,omitempty"`
27
+
Log bool `json:"log,omitempty"`
28
+
Total int `json:"total,omitempty"`
29
+
Page int `json:"page,omitempty"`
30
+
PerPage int `json:"per_page,omitempty"`
31
}
32
33
type RepoCommitResponse struct {