Add SMTP authentication auto-discovery

Implemented a mechanism to automatically discover and select the strongest supported SMTP authentication type. This feature simplifies the authentication process for users and enhances security by prioritizing stronger mechanisms based on server capabilities. Corresponding tests and documentation have been updated.
This commit is contained in:
Winni Neessen 2024-11-16 14:29:34 +01:00
parent a4fa95f7ca
commit ac9117dc50
Signed by: wneessen
GPG key ID: 385AC9889632126E
3 changed files with 60 additions and 1 deletions

20
auth.go
View file

@ -136,6 +136,21 @@ const (
//
// https://datatracker.ietf.org/doc/html/rfc7677
SMTPAuthSCRAMSHA256PLUS SMTPAuthType = "SCRAM-SHA-256-PLUS"
// SMTPAuthAutoDiscover is a mechanism that dynamically discovers all authentication mechanisms
// supported by the SMTP server and selects the strongest available one.
//
// This type simplifies authentication by automatically negotiating the most secure mechanism
// offered by the server, based on a predefined security ranking. For instance, mechanisms like
// SCRAM-SHA-256(-PLUS) or XOAUTH2 are prioritized over weaker mechanisms such as CRAM-MD5 or PLAIN.
//
// The negotiation process ensures that mechanisms requiring additional capabilities (e.g.,
// SCRAM-SHA-X-PLUS with TLS channel binding) are only selected when the necessary prerequisites
// are in place, such as an active TLS-secured connection.
//
// By automating mechanism selection, SMTPAuthAutoDiscover minimizes configuration effort while
// maximizing security and compatibility with a wide range of SMTP servers.
SMTPAuthAutoDiscover SMTPAuthType = "AUTODISCOVER"
)
// SMTP Auth related static errors
@ -170,6 +185,11 @@ var (
// ErrSCRAMSHA256PLUSAuthNotSupported is returned when the server does not support the "SCRAM-SHA-256-PLUS" SMTP
// authentication type.
ErrSCRAMSHA256PLUSAuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-256-PLUS")
// ErrNoSupportedAuthDiscovered is returned when the SMTP Auth AutoDiscover process fails to identify
// any supported authentication mechanisms offered by the server.
ErrNoSupportedAuthDiscovered = errors.New("SMTP Auth autodiscover was not able to detect a supported " +
"authentication mechanism")
)
// UnmarshalString satisfies the fig.StringUnmarshaler interface for the SMTPAuthType type

View file

@ -1100,7 +1100,16 @@ func (c *Client) auth() error {
return fmt.Errorf("server does not support SMTP AUTH")
}
switch c.smtpAuthType {
authType := c.smtpAuthType
if c.smtpAuthType == SMTPAuthAutoDiscover {
discoveredType, err := c.authTypeAutoDiscover(smtpAuthType)
if err != nil {
return err
}
authType = discoveredType
}
switch authType {
case SMTPAuthPlain:
if !strings.Contains(smtpAuthType, string(SMTPAuthPlain)) {
return ErrPlainAuthNotSupported
@ -1172,6 +1181,31 @@ func (c *Client) auth() error {
return nil
}
func (c *Client) authTypeAutoDiscover(supported string) (SMTPAuthType, error) {
preferList := []SMTPAuthType{SMTPAuthSCRAMSHA256PLUS, SMTPAuthSCRAMSHA256, SMTPAuthSCRAMSHA1PLUS, SMTPAuthSCRAMSHA1,
SMTPAuthXOAUTH2, SMTPAuthCramMD5, SMTPAuthPlain, SMTPAuthLogin}
if !c.isEncrypted {
preferList = []SMTPAuthType{SMTPAuthSCRAMSHA256, SMTPAuthSCRAMSHA1, SMTPAuthXOAUTH2, SMTPAuthCramMD5}
}
mechs := strings.Split(supported, " ")
for _, item := range preferList {
if sliceContains(mechs, string(item)) {
return item, nil
}
}
return "", ErrNoSupportedAuthDiscovered
}
func sliceContains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// sendSingleMsg sends out a single message and returns an error if the transmission or
// delivery fails. It is invoked by the public Send methods.
//

View file

@ -2304,6 +2304,11 @@ func TestClient_auth(t *testing.T) {
name string
authType SMTPAuthType
}{
{"LOGIN via AUTODISCOVER", SMTPAuthAutoDiscover},
{"PLAIN via AUTODISCOVER", SMTPAuthAutoDiscover},
{"SCRAM-SHA-1 via AUTODISCOVER", SMTPAuthAutoDiscover},
{"SCRAM-SHA-256 via AUTODISCOVER", SMTPAuthAutoDiscover},
{"XOAUTH2 via AUTODISCOVER", SMTPAuthAutoDiscover},
{"CRAM-MD5", SMTPAuthCramMD5},
{"LOGIN", SMTPAuthLogin},
{"LOGIN-NOENC", SMTPAuthLoginNoEnc},