tlsrpt

package
v0.0.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2024 License: MIT Imports: 23 Imported by: 1

Documentation

Overview

Package tlsrpt implements SMTP TLS Reporting, RFC 8460.

TLSRPT allows a domain to publish a policy requesting feedback of TLS connectivity to its SMTP servers. Reports can be sent to an address defined in the TLSRPT DNS record. These reports can be parsed by tlsrpt.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNoRecord        = errors.New("tlsrpt: no tlsrpt dns txt record")
	ErrMultipleRecords = errors.New("tlsrpt: multiple tlsrpt records") // Must be treated as if domain does not implement TLSRPT.
	ErrDNS             = errors.New("tlsrpt: temporary error")
	ErrRecordSyntax    = errors.New("tlsrpt: record syntax error")
)
View Source
var ErrNoReport = errors.New("no tlsrpt report found")

Functions

This section is empty.

Types

type Extension

type Extension struct {
	Key   string
	Value string
}

Extension is an additional key/value pair for a TLSRPT record.

type FailureDetails

type FailureDetails struct {
	ResultType            ResultType
	SendingMTAIP          string
	ReceivingMXHostname   string
	ReceivingMXHelo       string
	ReceivingIP           string
	FailedSessionCount    int64
	AdditionalInformation string
	FailureReasonCode     string
}

func Details added in v0.0.8

func Details(t ResultType, r string) FailureDetails

Details is a convenience function to compose a FailureDetails.

func (FailureDetails) Convert added in v0.0.9

func (v FailureDetails) Convert() FailureDetailsJSON

type FailureDetailsJSON added in v0.0.9

type FailureDetailsJSON struct {
	ResultType            ResultType `json:"result-type"`
	SendingMTAIP          string     `json:"sending-mta-ip"`
	ReceivingMXHostname   string     `json:"receiving-mx-hostname"`
	ReceivingMXHelo       string     `json:"receiving-mx-helo,omitempty"`
	ReceivingIP           string     `json:"receiving-ip"`
	FailedSessionCount    int64      `json:"failed-session-count"`
	AdditionalInformation string     `json:"additional-information"`
	FailureReasonCode     string     `json:"failure-reason-code"`
}

func (FailureDetailsJSON) Convert added in v0.0.9

func (v FailureDetailsJSON) Convert() FailureDetails

type PolicyType added in v0.0.8

type PolicyType string

PolicyType indicates the policy success/failure results are for.

const (
	// For DANE, against a mail host (not recipient domain).
	TLSA PolicyType = "tlsa"

	// For MTA-STS, against a recipient domain (not a mail host).
	STS PolicyType = "sts"

	// Recipient domain did not have MTA-STS policy, or mail host (TSLA base domain)
	// did not have DANE TLSA records.
	NoPolicyFound PolicyType = "no-policy-found"
)

type RUA added in v0.0.8

type RUA string

RUA is a reporting address with scheme and special characters ",", "!" and ";" not encoded.

func (RUA) String added in v0.0.8

func (rua RUA) String() string

String returns the RUA with special characters encoded, for inclusion in a TLSRPT record.

func (RUA) URI added in v0.0.8

func (rua RUA) URI() (*url.URL, error)

URI parses a RUA as URI, with either a mailto or https scheme.

type Record

type Record struct {
	Version string // "TLSRPTv1", for "v=".

	// Aggregate reporting URI, for "rua=". "rua=" can occur multiple times, each can
	// be a list.
	RUAs [][]RUA

	Extensions []Extension
}

Record is a parsed TLSRPT record, to be served under "_smtp._tls.<domain>".

Example:

v=TLSRPTv1; rua=mailto:tlsrpt@mox.example;

func Lookup

func Lookup(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, domain dns.Domain) (rrecord *Record, rtxt string, rerr error)

Lookup looks up a TLSRPT DNS TXT record for domain at "_smtp._tls.<domain>" and parses it.

Example
package main

import (
	"context"
	"log"
	"log/slog"

	"github.com/mjl-/mox/dns"
	"github.com/mjl-/mox/tlsrpt"
)

func main() {
	ctx := context.Background()
	resolver := dns.StrictResolver{}
	domain, err := dns.ParseDomain("domain.example")
	if err != nil {
		log.Fatalf("parsing domain: %v", err)
	}

	// Lookup TLSRPT record in DNS, and parse it.
	record, txt, err := tlsrpt.Lookup(ctx, slog.Default(), resolver, domain)
	if err != nil {
		log.Fatalf("looking up tlsrpt record: %v", err)
	}

	log.Printf("TLSRPT record: %s", txt)
	log.Printf("Parsed: %v", record)
}
Output:

func ParseRecord

func ParseRecord(txt string) (record *Record, istlsrpt bool, err error)

ParseRecord parses a TLSRPT record.

func (Record) String

func (r Record) String() string

String returns a string or use as a TLSRPT DNS TXT record.

type Report

type Report struct {
	OrganizationName string
	DateRange        TLSRPTDateRange
	ContactInfo      string
	ReportID         string
	Policies         []Result
}

Report is a TLSRPT report.

func (*Report) Add added in v0.0.8

func (r *Report) Add(policy ResultPolicy, success, failure int64, fds ...FailureDetails)

Add is a convenience function for merging making a Result and merging it into the report.

func (Report) Convert added in v0.0.9

func (v Report) Convert() ReportJSON

func (*Report) Merge added in v0.0.8

func (r *Report) Merge(results ...Result)

Merge combines the counts and failure details of results into the report. Policies are merged if identical and added otherwise. Same for failure details within a result.

type ReportJSON added in v0.0.9

type ReportJSON struct {
	OrganizationName string              `json:"organization-name"`
	DateRange        TLSRPTDateRangeJSON `json:"date-range"`
	ContactInfo      string              `json:"contact-info"` // Email address.
	ReportID         string              `json:"report-id"`
	Policies         []ResultJSON        `json:"policies"`
}

ReportJSON is a TLS report with field names as used in the specification. These field names are inconvenient to use in JavaScript, so after parsing a ReportJSON is turned into a Report.

func Parse

func Parse(r io.Reader) (*ReportJSON, error)

Parse parses a Report. The maximum size is 20MB.

func ParseMessage

func ParseMessage(elog *slog.Logger, r io.ReaderAt) (*ReportJSON, error)

ParseMessage parses a Report from a mail message. The maximum size of the message is 15MB, the maximum size of the decompressed report is 20MB.

Example
package main

import (
	"log"
	"log/slog"
	"strings"

	"github.com/mjl-/mox/tlsrpt"
)

func main() {
	// Message, as received over SMTP.
	msg := `From: <tlsrpt@mail.sender.example.com>
To: <mts-sts-tlsrpt@example.net>
Subject: Report Domain: example.net
Report-ID: <735ff.e317+bf22029@example.net>
TLS-Report-Domain: example.net
TLS-Report-Submitter: mail.sender.example.com
MIME-Version: 1.0
Content-Type: application/tlsrpt+gzip
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
        filename="mail.sender.example!example.com!1013662812!1013749130.json.gz"

H4sIAPZreGUAA51UbW/aMBD+DL/Cyr5NdeokJIVI0za1aPswdRWgiXWqImMbai2JI9tBMMR/n52Y
lwkx2KQocXx3vud57s6bbscTcoFL/gtrLkpY4oJ5KfDuRVHhcg2n3o1xoVgzKHG5sLZNt9PxlMZS
Q7uveRsRoiCBqAdRMEEobZ5nG9zxWEnPeYZRGg/M8+x1O1ubiYhSY6IhL+fC+iqtoGSVkJqXiw/E
oVr5bIWLKmcNutYOObUBMUriXnhHYBjRCPbuCIazhCE46CUMI9YnvVkbVYmcE86UCfphUFpWbnPt
SO7/oV5XzKFpKB0sSksDzJ1h95dMKiNkCsaT8TJw3h2vEJSlQDNleRx2Vyl46xeY5/6O2vqYWuuE
VxlemOh+0kPIa3Zf/kRBhTmjtAjPHWNSwVeh9BHSs4nbDPa9bYI9VRcFlkeyaKFxDlVNCFNqXpul
+dr2IaIubY44CpObY9+5SVVLduIYoego0c6LMm1Wag924yBLpupc78tBmGmLOSe2O9mq4pLRvWrK
dJ2RGhYaQ161bYeClM76KZ4RarozCNP0UCDJCOPLJqJVajcJxSq4VCELm9ETbgFCjUNL7hyJZpJ0
rmAptJG0sr38bzyiK3mEl3gcYneZIh/5QRD5cXKJrEG188CUcnuZmLLbMZZFc7XYA1+1rlR6e9tO
rPJP5tlZMhsH3gNOwTvgJhoQAEEYARq9AWOn2aPQ451iwLtC7CXOOW1vOtdrfxE6GPT9OPBNGf0k
vEak/jVVgDNMftbVf/ZUdGy3oyIZVo2dNudPYzTIvmXD0Sh7Gn2dfs+ePk4+Z1+Gj5/MZzi9Hw4f
hg9Oqa4bc7N46W67vwF2Eq+hDAYAAA==
`
	msg = strings.ReplaceAll(msg, "\n", "\r\n")

	// Parse the email message, and the TLSRPT report within.
	reportJSON, err := tlsrpt.ParseMessage(slog.Default(), strings.NewReader(msg))
	if err != nil {
		log.Fatalf("parsing tlsrpt report in message: %v", err)
	}

	log.Printf("report: %#v", reportJSON)
}
Output:

func (ReportJSON) Convert added in v0.0.9

func (v ReportJSON) Convert() Report

type Result

type Result struct {
	Policy         ResultPolicy
	Summary        Summary
	FailureDetails []FailureDetails
}

func MakeResult added in v0.0.8

func MakeResult(policyType PolicyType, domain dns.Domain, fds ...FailureDetails) Result

func (*Result) Add added in v0.0.8

func (r *Result) Add(success, failure int64, fds ...FailureDetails)

Add increases the success/failure counts of a result, and adds any failure details.

func (Result) Convert added in v0.0.9

func (r Result) Convert() ResultJSON

type ResultJSON added in v0.0.9

type ResultJSON struct {
	Policy         ResultPolicyJSON     `json:"policy"`
	Summary        SummaryJSON          `json:"summary"`
	FailureDetails []FailureDetailsJSON `json:"failure-details"`
}

func (ResultJSON) Convert added in v0.0.9

func (r ResultJSON) Convert() Result

type ResultPolicy

type ResultPolicy struct {
	Type   PolicyType
	String []string
	Domain string // ASCII/A-labels, ../rfc/8460:704
	MXHost []string
}

func TLSAPolicy added in v0.0.8

func TLSAPolicy(records []adns.TLSA, tlsaBaseDomain dns.Domain) ResultPolicy

TLSAPolicy returns a policy for DANE.

type ResultPolicyJSON added in v0.0.9

type ResultPolicyJSON struct {
	Type   PolicyType `json:"policy-type"`
	String []string   `json:"policy-string"`
	Domain string     `json:"policy-domain"`
	MXHost []string   `json:"mx-host"` // Example in RFC has errata, it originally was a single string. ../rfc/8460-eid6241 ../rfc/8460:1779
}

type ResultType

type ResultType string

ResultType represents a TLS error.

const (
	ResultSTARTTLSNotSupported    ResultType = "starttls-not-supported"
	ResultCertificateHostMismatch ResultType = "certificate-host-mismatch"
	ResultCertificateExpired      ResultType = "certificate-expired"
	ResultTLSAInvalid             ResultType = "tlsa-invalid"
	ResultDNSSECInvalid           ResultType = "dnssec-invalid"
	ResultDANERequired            ResultType = "dane-required"
	ResultCertificateNotTrusted   ResultType = "certificate-not-trusted"
	ResultSTSPolicyInvalid        ResultType = "sts-policy-invalid"
	ResultSTSWebPKIInvalid        ResultType = "sts-webpki-invalid"
	ResultValidationFailure       ResultType = "validation-failure" // Other error.
	ResultSTSPolicyFetch          ResultType = "sts-policy-fetch-error"
)

func TLSFailureDetails added in v0.0.8

func TLSFailureDetails(err error) (ResultType, string)

TLSFailureDetails turns errors encountered during TLS handshakes into a result type and failure reason code for use with FailureDetails.

Errors from crypto/tls, including local and remote alerts, from crypto/x509, and generic i/o and timeout errors are recognized.

type Summary

type Summary struct {
	TotalSuccessfulSessionCount int64
	TotalFailureSessionCount    int64
}

type SummaryJSON added in v0.0.9

type SummaryJSON struct {
	TotalSuccessfulSessionCount int64 `json:"total-successful-session-count"`
	TotalFailureSessionCount    int64 `json:"total-failure-session-count"`
}

type TLSRPTDateRange

type TLSRPTDateRange struct {
	Start time.Time
	End   time.Time
}

note: with TLSRPT prefix to prevent clash in sherpadoc types.

func (TLSRPTDateRange) Convert added in v0.0.9

type TLSRPTDateRangeJSON added in v0.0.9

type TLSRPTDateRangeJSON struct {
	Start time.Time `json:"start-datetime"`
	End   time.Time `json:"end-datetime"`
}

func (TLSRPTDateRangeJSON) Convert added in v0.0.9

func (*TLSRPTDateRangeJSON) UnmarshalJSON added in v0.0.9

func (dr *TLSRPTDateRangeJSON) UnmarshalJSON(buf []byte) error

UnmarshalJSON is defined on the date range, not the individual time.Time fields because it is easier to keep the unmodified time.Time fields stored in the database.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL