1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6 "bnema.dev/ermite/internal/domain"
7)
8
9// SignatureBadge is the pill of a signed commit. Label is the verdict, Proto
10// the protocol ("SSH", "GPG", "X.509", empty when unknown), Class the style
11// (sig-ok, sig-warn, sig-bad, sig-none) and Title the tooltip that says why.
12// Reason says why without naming the key, for a page that shows the key next
13// to it.
14type SignatureBadge struct {
15 Label, Proto, Class, Title, Reason string
16}
17
18// signatureProto is the display name of a signature format.
19func signatureProto(f domain.SignatureFormat) string {
20 switch f {
21 case domain.FormatSSH:
22 return "SSH"
23 case domain.FormatGPG:
24 return "GPG"
25 case domain.FormatX509:
26 return "X.509"
27 }
28 return ""
29}
30
31// signatureBadge is the badge of a verification, nil when there is nothing to
32// show: the commit was not checked, or (unless long, the commit page) it is
33// not signed.
34func signatureBadge(v domain.CommitVerification, long bool) *SignatureBadge {
35 switch v.Status {
36 case domain.StatusUnchecked:
37 return nil
38 case domain.StatusUnsigned:
39 if !long {
40 return nil
41 }
42 return &SignatureBadge{Label: "unsigned", Class: "sig-none", Title: "This commit is not signed", Reason: "Not signed"}
43 }
44 b := &SignatureBadge{Proto: signatureProto(v.Format)}
45 key := keyPhrase(v)
46 switch v.Status {
47 case domain.StatusVerified:
48 b.Label, b.Class = "Verified", "sig-ok"
49 b.Title = "Signed with " + key + " — verified for the committer email"
50 b.Reason = "Verified for the committer email"
51 case domain.StatusUnknownKey:
52 b.Label, b.Class = "Unverified", "sig-warn"
53 b.Title = unknownKeyTitle(v)
54 b.Reason = "The key is unknown"
55 case domain.StatusEmailMismatch:
56 b.Label, b.Class = "Unverified", "sig-warn"
57 b.Title = "Signed with " + key + " — this key is not trusted for the committer email"
58 b.Reason = "This key is not trusted for the committer email"
59 case domain.StatusExpired:
60 b.Label, b.Class = "Unverified", "sig-warn"
61 b.Title = "Signed with " + key + " — the key or the signature expired"
62 b.Reason = "The key or the signature expired"
63 case domain.StatusRevoked:
64 b.Label, b.Class = "Unverified", "sig-warn"
65 b.Title = "Signed with " + key + " — the key is revoked"
66 b.Reason = "The key is revoked"
67 case domain.StatusBadSignature:
68 b.Label, b.Class = "Bad signature", "sig-bad"
69 b.Reason = "The signature does not match the commit"
70 case domain.StatusUnsupported:
71 b.Label, b.Class = "Unverified", "sig-warn"
72 if proto := signatureProto(v.Format); proto != "" {
73 b.Reason = proto + " signatures are not checked"
74 } else {
75 b.Reason = "This kind of signature is not checked"
76 }
77 default:
78 return nil
79 }
80 if b.Title == "" {
81 b.Title = b.Reason
82 }
83 return b
84}
85
86// keyPhrase names the signing key for a tooltip: "SSH key SHA256:… (title)".
87func keyPhrase(v domain.CommitVerification) string {
88 s := "a key"
89 if p := signatureProto(v.Format); p != "" {
90 s = p + " key"
91 }
92 if v.Fingerprint != "" {
93 s += " " + v.Fingerprint
94 }
95 if v.KeyTitle != "" {
96 s += " (" + visibleText(v.KeyTitle) + ")"
97 }
98 return s
99}
100
101// unknownKeyTitle is the tooltip of a signature by a key that is not trusted.
102func unknownKeyTitle(v domain.CommitVerification) string {
103 s := "Signed with an unknown "
104 if p := signatureProto(v.Format); p != "" {
105 s += p + " "
106 }
107 s += "key"
108 if v.Fingerprint != "" {
109 s += " " + v.Fingerprint
110 }
111 return s
112}