tls: surface transport TLS status in email path and authentication
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Parse TLS details (version, cipher, bits, cert verification) from the Postfix Received header parenthetical and expose them per hop, rendered as a per-hop badge in the Email Path card. Add an x-tls Authentication-Results result: parse it when present, and otherwise synthesize it from the inbound hop's TLS info. A negative result (unencrypted inbound connection) applies a -10 authentication score penalty and is shown in the Authentication card. Enable the TLS handler in authentication_milter. Closes: #40
This commit is contained in:
parent
8e7e56851b
commit
d53c1b1e00
11 changed files with 593 additions and 0 deletions
|
|
@ -26,6 +26,7 @@ import (
|
|||
"net"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -693,5 +694,50 @@ func (h *HeaderAnalyzer) parseReceivedHeader(receivedValue string) *model.Receiv
|
|||
}
|
||||
}
|
||||
|
||||
// Extract TLS details from the Received header parentheticals
|
||||
// (e.g. "(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) ...)")
|
||||
hop.Tls = parseReceivedTLS(normalized)
|
||||
|
||||
return hop
|
||||
}
|
||||
|
||||
// parseReceivedTLS extracts TLS connection details from a normalized Received header value.
|
||||
// Returns nil when the hop was not encrypted (no TLS version/cipher found).
|
||||
func parseReceivedTLS(normalized string) *model.TLSInfo {
|
||||
tls := &model.TLSInfo{}
|
||||
found := false
|
||||
|
||||
// TLS protocol version, e.g. "using TLSv1.3"
|
||||
if matches := regexp.MustCompile(`(?i)using\s+(TLSv[0-9.]+|SSLv[0-9.]+)`).FindStringSubmatch(normalized); len(matches) > 1 {
|
||||
tls.Version = &matches[1]
|
||||
found = true
|
||||
}
|
||||
|
||||
// Cipher suite, e.g. "with cipher TLS_AES_256_GCM_SHA384"
|
||||
if matches := regexp.MustCompile(`(?i)with cipher\s+([A-Za-z0-9_-]+)`).FindStringSubmatch(normalized); len(matches) > 1 {
|
||||
tls.Cipher = &matches[1]
|
||||
found = true
|
||||
}
|
||||
|
||||
// Cipher strength, e.g. "(256/256 bits)"
|
||||
if matches := regexp.MustCompile(`\((\d+)/\d+ bits\)`).FindStringSubmatch(normalized); len(matches) > 1 {
|
||||
if bits, err := strconv.Atoi(matches[1]); err == nil {
|
||||
tls.Bits = &bits
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificate verification status. Postfix emits "(verified OK)" when the peer
|
||||
// certificate was trusted, "(not verified)" otherwise. "No client certificate
|
||||
// requested" leaves the field unset (trust is simply not applicable).
|
||||
if regexp.MustCompile(`(?i)verified OK`).MatchString(normalized) {
|
||||
tls.Verified = utils.PtrTo(true)
|
||||
} else if regexp.MustCompile(`(?i)not verified`).MatchString(normalized) {
|
||||
tls.Verified = utils.PtrTo(false)
|
||||
}
|
||||
|
||||
return tls
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue