#11: implement MTA-STS, TLS-RPT & Autoconfig HTTP services

This commit is contained in:
Lucas Rayan committed 2026-07-25 10:18:03 -03:00
1 parent c0042ef207
commit 314eb67dda
20 files changed
+1177 -31

No files matched your search

@@ -0,0 +1,103 @@
package entity
import (
"bytes"
"compress/gzip"
"encoding/xml"
"fmt"
"io"
"time"
)
type DmarcRecord struct {
SourceIP string
Count int
Disposition string
DKIMResult string
SPFResult string
HeaderFrom string
}
type DmarcReport struct {
OrgName string
ReportID string
Domain string
DateRangeBegin time.Time
DateRangeEnd time.Time
Records []DmarcRecord
}
type dmarcXmlReport struct {
ReportMetadata struct {
OrgName string `xml:"org_name"`
ReportID string `xml:"report_id"`
DateRange struct {
Begin int64 `xml:"begin"`
End int64 `xml:"end"`
} `xml:"date_range"`
} `xml:"report_metadata"`
PolicyPublished struct {
Domain string `xml:"domain"`
} `xml:"policy_published"`
Record []struct {
Row struct {
SourceIP string `xml:"source_ip"`
Count int `xml:"count"`
PolicyEvaluated struct {
Disposition string `xml:"disposition"`
DKIM string `xml:"dkim"`
SPF string `xml:"spf"`
} `xml:"policy_evaluated"`
} `xml:"row"`
Identifiers struct {
HeaderFrom string `xml:"header_from"`
} `xml:"identifiers"`
} `xml:"record"`
}
// ParseDmarcXmlReport parses raw XML or gzipped XML DMARC aggregate report.
func ParseDmarcXmlReport(payload []byte) (*DmarcReport, error) {
data := payload
if isGzip(payload) {
gz, err := gzip.NewReader(bytes.NewReader(payload))
if err != nil {
return nil, fmt.Errorf("gzip reader: %w", err)
}
defer gz.Close()
decompressed, err := io.ReadAll(gz)
if err != nil {
return nil, fmt.Errorf("decompress gzip: %w", err)
}
data = decompressed
}
var parsed dmarcXmlReport
if err := xml.Unmarshal(data, &parsed); err != nil {
return nil, fmt.Errorf("unmarshal dmarc xml: %w", err)
}
report := &DmarcReport{
OrgName: parsed.ReportMetadata.OrgName,
ReportID: parsed.ReportMetadata.ReportID,
Domain: parsed.PolicyPublished.Domain,
DateRangeBegin: time.Unix(parsed.ReportMetadata.DateRange.Begin, 0),
DateRangeEnd: time.Unix(parsed.ReportMetadata.DateRange.End, 0),
}
for _, r := range parsed.Record {
report.Records = append(report.Records, DmarcRecord{
SourceIP: r.Row.SourceIP,
Count: r.Row.Count,
Disposition: r.Row.PolicyEvaluated.Disposition,
DKIMResult: r.Row.PolicyEvaluated.DKIM,
SPFResult: r.Row.PolicyEvaluated.SPF,
HeaderFrom: r.Identifiers.HeaderFrom,
})
}
return report, nil
}
func isGzip(payload []byte) bool {
return len(payload) >= 2 && payload[0] == 0x1f && payload[1] == 0x8b
}
@@ -0,0 +1,96 @@
package entity
import (
"bytes"
"compress/gzip"
"testing"
)
func TestParseDmarcXmlReport_ValidXML(t *testing.T) {
xmlData := `<?xml version="1.0"?>
<feedback>
<report_metadata>
<org_name>google.com</org_name>
<report_id>123456</report_id>
<date_range>
<begin>1609459200</begin>
<end>1609545600</end>
</date_range>
</report_metadata>
<policy_published>
<domain>example.test</domain>
</policy_published>
<record>
<row>
<source_ip>198.51.100.1</source_ip>
<count>10</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.test</header_from>
</identifiers>
</record>
</feedback>`
report, err := ParseDmarcXmlReport([]byte(xmlData))
if err != nil {
t.Fatalf("ParseDmarcXmlReport: %v", err)
}
if report.OrgName != "google.com" || report.Domain != "example.test" {
t.Errorf("unexpected report metadata: %+v", report)
}
if len(report.Records) != 1 || report.Records[0].Count != 10 {
t.Errorf("unexpected records: %+v", report.Records)
}
// Test Gzip handling
var gzBuf bytes.Buffer
gw := gzip.NewWriter(&gzBuf)
gw.Write([]byte(xmlData))
gw.Close()
gzReport, err := ParseDmarcXmlReport(gzBuf.Bytes())
if err != nil || gzReport.OrgName != "google.com" {
t.Fatalf("ParseDmarcXmlReport gzip: %v", err)
}
}
func TestParseTlsRptReport_ValidJSON(t *testing.T) {
jsonData := `{
"organization-name": "Google LLC",
"report-id": "2026-07-25-001",
"date-range": {
"start-datetime": "2026-07-25T00:00:00Z",
"end-datetime": "2026-07-25T23:59:59Z"
},
"policies": [
{
"policy": {
"policy-type": "sts",
"domain": "example.test"
},
"summary": {
"total-successful-session-count": 100,
"total-failure-session-count": 0
}
}
]
}`
report, err := ParseTlsRptReport([]byte(jsonData))
if err != nil {
t.Fatalf("ParseTlsRptReport: %v", err)
}
if report.OrganizationName != "Google LLC" || report.Domain != "example.test" {
t.Errorf("unexpected tlsrpt metadata: %+v", report)
}
if len(report.Policies) != 1 || report.Policies[0].SuccessCount != 100 {
t.Errorf("unexpected policies: %+v", report.Policies)
}
}
@@ -0,0 +1,92 @@
package entity
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"time"
)
type TlsRptPolicy struct {
PolicyType string
SuccessCount int
FailureCount int
}
type TlsRptReport struct {
OrganizationName string
ReportID string
Domain string
DateRangeBegin time.Time
DateRangeEnd time.Time
Policies []TlsRptPolicy
}
type tlsRptJsonReport struct {
OrganizationName string `json:"organization-name"`
ReportID string `json:"report-id"`
DateRange struct {
StartDatetime string `json:"start-datetime"`
EndDatetime string `json:"end-datetime"`
} `json:"date-range"`
Policies []struct {
Policy struct {
PolicyType string `json:"policy-type"`
Domain string `json:"domain"`
} `json:"policy"`
Summary struct {
TotalSuccessfulSessionCount int `json:"total-successful-session-count"`
TotalFailureSessionCount int `json:"total-failure-session-count"`
} `json:"summary"`
} `json:"policies"`
}
// ParseTlsRptReport parses JSON or gzipped JSON TLS-RPT report.
func ParseTlsRptReport(payload []byte) (*TlsRptReport, error) {
data := payload
if isGzip(payload) {
gz, err := gzip.NewReader(bytes.NewReader(payload))
if err != nil {
return nil, fmt.Errorf("gzip reader: %w", err)
}
defer gz.Close()
decompressed, err := io.ReadAll(gz)
if err != nil {
return nil, fmt.Errorf("decompress gzip: %w", err)
}
data = decompressed
}
var parsed tlsRptJsonReport
if err := json.Unmarshal(data, &parsed); err != nil {
return nil, fmt.Errorf("unmarshal tls-rpt json: %w", err)
}
beginTime, _ := time.Parse(time.RFC3339, parsed.DateRange.StartDatetime)
endTime, _ := time.Parse(time.RFC3339, parsed.DateRange.EndDatetime)
domain := ""
if len(parsed.Policies) > 0 {
domain = parsed.Policies[0].Policy.Domain
}
report := &TlsRptReport{
OrganizationName: parsed.OrganizationName,
ReportID: parsed.ReportID,
Domain: domain,
DateRangeBegin: beginTime,
DateRangeEnd: endTime,
}
for _, p := range parsed.Policies {
report.Policies = append(report.Policies, TlsRptPolicy{
PolicyType: p.Policy.PolicyType,
SuccessCount: p.Summary.TotalSuccessfulSessionCount,
FailureCount: p.Summary.TotalFailureSessionCount,
})
}
return report, nil
}