1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
package identity
import (
"crypto/ed25519"
"encoding/base64"
"errors"
"fmt"
"io"
"net/mail"
"strings"
"aegis/internal/cryptokit"
)
type VerificationResult struct {
VFaceValid bool
IdentityHashValid bool
FaceValid bool
SignaturePresent bool
SignatureValid bool
PublicKey string
IdentityHash string
PNGHash string
Error error
}
func VerifyArticle(article string) VerificationResult {
message, err := mail.ReadMessage(strings.NewReader(article))
if err != nil {
return VerificationResult{Error: fmt.Errorf("parse article: %w", err)}
}
from, err := mail.ParseAddress(message.Header.Get("From"))
if err != nil {
return VerificationResult{Error: fmt.Errorf("parse From: %w", err)}
}
publicKey := strings.TrimSpace(message.Header.Get("X-Ed25519-Pub"))
result := VerificationResult{PublicKey: publicKey, IdentityHash: strings.TrimSpace(message.Header.Get("Identity-Hash")), PNGHash: strings.TrimSpace(message.Header.Get("X-VFace-PNG-SHA256"))}
profile, profileErr := GenerateVFace(from.Name, from.Address, publicKey)
if profileErr != nil {
result.Error = profileErr
return result
}
result.IdentityHashValid = strings.EqualFold(strings.TrimPrefix(result.IdentityHash, "sha256:"), profile.IdentityHash)
result.FaceValid = strings.TrimSpace(message.Header.Get("Face")) == profile.FaceBase64
result.VFaceValid = result.IdentityHashValid && result.FaceValid
if result.PNGHash != "" && !strings.EqualFold(strings.TrimPrefix(result.PNGHash, "sha256:"), profile.PNGHash) {
result.VFaceValid = false
}
signature := strings.TrimSpace(message.Header.Get("X-Ed25519-Sig"))
if signature == "" {
value := strings.TrimSpace(message.Header.Get("X-Aegis-Signature"))
if parts := strings.SplitN(value, ";", 2); len(parts) == 2 && strings.EqualFold(strings.TrimSpace(parts[0]), "ed25519") {
signature = strings.TrimSpace(parts[1])
}
}
if signature == "" {
return result
}
result.SignaturePresent = true
signatureBytes, decodeErr := base64.StdEncoding.DecodeString(signature)
if decodeErr != nil || len(signatureBytes) != ed25519.SignatureSize {
result.Error = errors.New("invalid Ed25519 signature encoding")
return result
}
body, readErr := io.ReadAll(io.LimitReader(message.Body, 16<<20))
if readErr != nil {
result.Error = fmt.Errorf("read article body: %w", readErr)
return result
}
key, keyErr := decodePublicKey(publicKey)
if keyErr != nil {
result.Error = keyErr
return result
}
result.SignatureValid = ed25519.Verify(key, body, signatureBytes)
if !result.SignatureValid {
result.Error = errors.New("Ed25519 signature verification failed")
}
return result
}
func decodePublicKey(value string) (ed25519.PublicKey, error) {
canonicalText, err := cryptokit.CanonicalEd25519PublicKey(value)
if err != nil {
return nil, err
}
canonical, err := base64.StdEncoding.DecodeString(canonicalText)
if err != nil {
return nil, fmt.Errorf("decode Ed25519 public key: %w", err)
}
return ed25519.PublicKey(canonical), nil
}
|