GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package user
6
7import (
8 "maps"
9 "slices"
10
11 "github.com/42wim/sshsig"
12 "github.com/go-git/go-git/v5/plumbing"
13 "github.com/go-git/go-git/v5/plumbing/object"
14)
15
16const (
17 namespace = "git"
18)
19
20func IsValidSignCommit(groups []Group, com *object.Commit) (bool, string, error) {
21 if com.PGPSignature == "" {
22 return false, "", nil
23 }
24 keys := findCryptoKeysByEmail(groups, com.Committer.Email)
25 if len(keys) == 0 {
26 return false, "", nil
27 }
28 encoded := &plumbing.MemoryObject{}
29 com.EncodeWithoutSignature(encoded)
30 r, err := encoded.Reader()
31 if err != nil {
32 return false, "", err
33 }
34 defer r.Close()
35 for _, key := range keys {
36 if err := sshsig.Verify(r, []byte(com.PGPSignature), []byte(key), namespace); err == nil {
37 return true, key, nil
38 } else if _, err := com.Verify(key); err == nil { // TODO not optimal because verify encodeWithoutSignature for each
39 return true, key, nil
40 }
41 }
42 return false, "", nil
43}
44
45func findCryptoKeysByEmail(groups []Group, email string) []string {
46 keys := make(map[string]any, 0)
47 for _, group := range groups {
48 for _, u := range group.Users {
49 if slices.Contains(u.Emails, email) {
50 for _, k := range u.Ssh {
51 keys[k] = 1
52 }
53 }
54 }
55 }
56 return slices.Collect(maps.Keys(keys))
57}