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}