verifier.go RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3// Package sigverify checks commit signatures against the trusted keys
4// (ADR 0030). It is pure Go: no gpg or ssh-keygen binary is run. SSH
5// signatures (SSHSIG, gpg.format=ssh) and OpenPGP detached signatures are
6// verified; X.509 signatures are recognized and reported as unsupported.
7//
8// Every verifier is a stateless value, safe for concurrent use, and never
9// panics on hostile input: a malformed signature is a bad_signature status.
10package sigverify
11
12import (
13 "bytes"
14
15 "bnema.dev/ermite/internal/domain"
16 "bnema.dev/ermite/internal/ports"
17)
18
19// maxSignatureBytes bounds the signature a verifier parses; a larger one is
20// reported as bad_signature without being read.
21const maxSignatureBytes = 64 << 10
22
23// Verifier dispatches a signature to the verifier of its format.
24type Verifier struct{}
25
26var _ ports.SignatureVerifier = Verifier{}
27
28// Verify implements ports.SignatureVerifier. An x509 signature is
29// unsupported; an unrecognized armor is a bad signature without format.
30func (Verifier) Verify(payload, signature []byte, committerEmail string, signedAt int64, keys []domain.TrustedKey) domain.CommitVerification {
31 switch domain.SignatureFormatOf(bytes.TrimLeft(signature, " \t\r\n")) {
32 case domain.FormatSSH:
33 return SSH{}.Verify(payload, signature, committerEmail, signedAt, keys)
34 case domain.FormatGPG:
35 return GPG{}.Verify(payload, signature, committerEmail, signedAt, keys)
36 case domain.FormatX509:
37 return domain.CommitVerification{Format: domain.FormatX509, Status: domain.StatusUnsupported}
38 }
39 return domain.CommitVerification{Format: domain.FormatNone, Status: domain.StatusBadSignature}
40}
41
42// trusted completes the verification of a signature whose cryptography is
43// valid and whose key is trusted: verified when the key may sign for the
44// committer email, email_mismatch otherwise.
45//
46//hexcheck:ignore suspicious-business-logic-in-adapter the email rule is domain.TrustedKey.TrustsEmail; this only maps it to the port's status
47func trusted(v domain.CommitVerification, key domain.TrustedKey, committerEmail string) domain.CommitVerification {
48 v.KeyID, v.KeyTitle = key.ID, key.Title
49 if key.TrustsEmail(committerEmail) {
50 v.Status = domain.StatusVerified
51 } else {
52 v.Status = domain.StatusEmailMismatch
53 }
54 return v
55}