mox/mox-/dkimsign.go

66 lines
2 KiB
Go
Raw Permalink Normal View History

package mox
import (
"bytes"
"context"
"fmt"
"strings"
"time"
"github.com/mjl-/mox/config"
"github.com/mjl-/mox/dkim"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/smtp"
)
// DKIMSelectors returns the selectors to use for signing.
func DKIMSelectors(dkimConf config.DKIM) []dkim.Selector {
var l []dkim.Selector
for _, sign := range dkimConf.Sign {
sel := dkimConf.Selectors[sign]
s := dkim.Selector{
Hash: sel.HashEffective,
HeaderRelaxed: sel.Canonicalization.HeaderRelaxed,
BodyRelaxed: sel.Canonicalization.BodyRelaxed,
Headers: sel.HeadersEffective,
SealHeaders: !sel.DontSealHeaders,
Expiration: time.Duration(sel.ExpirationSeconds) * time.Second,
PrivateKey: sel.Key,
Domain: sel.Domain,
}
l = append(l, s)
}
return l
}
// DKIMSign looks up the domain for "from", and uses its DKIM configuration to
// generate DKIM-Signature headers, for inclusion in a message. The
// DKIM-Signatur headers, are returned. If no domain was found an empty string and
// nil error is returned.
func DKIMSign(ctx context.Context, log mlog.Log, from smtp.Path, smtputf8 bool, data []byte) (string, error) {
// Add DKIM signature for domain, even if higher up than the full mail hostname.
// This helps with an assumed (because default) relaxed DKIM policy. If the DMARC
// policy happens to be strict, the signature won't help, but won't hurt either.
fd := from.IPDomain.Domain
var zerodom dns.Domain
for fd != zerodom {
confDom, ok := Conf.Domain(fd)
if !ok {
var nfd dns.Domain
_, nfd.ASCII, _ = strings.Cut(fd.ASCII, ".")
_, nfd.Unicode, _ = strings.Cut(fd.Unicode, ".")
fd = nfd
continue
}
selectors := DKIMSelectors(confDom.DKIM)
dkimHeaders, err := dkim.Sign(ctx, log.Logger, from.Localpart, fd, selectors, smtputf8, bytes.NewReader(data))
if err != nil {
return "", fmt.Errorf("dkim sign for domain %s: %v", fd, err)
}
return dkimHeaders, nil
}
return "", nil
}