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 }