moved S/MIME initialization into msg and the pointer to the data structure, also adjusted tests

This commit is contained in:
theexiile1305 2024-09-18 14:13:24 +02:00
parent 07d9654ce7
commit 158c1b0458
No known key found for this signature in database
GPG key ID: A1BDDE98F2BF6E40
5 changed files with 82 additions and 102 deletions

View file

@ -127,9 +127,6 @@ type Client struct {
// smtpAuthType represents the authentication type for SMTP AUTH // smtpAuthType represents the authentication type for SMTP AUTH
smtpAuthType SMTPAuthType smtpAuthType SMTPAuthType
// SMimeAuthConfig represents the authentication type for s/mime crypto key material
sMimeAuthConfig *SMimeAuthConfig
// smtpClient is the smtp.Client that is set up when using the Dial*() methods // smtpClient is the smtp.Client that is set up when using the Dial*() methods
smtpClient *smtp.Client smtpClient *smtp.Client
@ -171,9 +168,6 @@ var (
// ErrInvalidTLSConfig should be used if an empty tls.Config is provided // ErrInvalidTLSConfig should be used if an empty tls.Config is provided
ErrInvalidTLSConfig = errors.New("invalid TLS config") ErrInvalidTLSConfig = errors.New("invalid TLS config")
// ErrInvalidSMimeAuthConfig should be used if the values in the struct SMimeAuthConfig are empty
ErrInvalidSMimeAuthConfig = errors.New("invalid S/MIME authentication config")
// ErrNoHostname should be used if a Client has no hostname set // ErrNoHostname should be used if a Client has no hostname set
ErrNoHostname = errors.New("hostname for client cannot be empty") ErrNoHostname = errors.New("hostname for client cannot be empty")
@ -465,17 +459,6 @@ func WithDialContextFunc(dialCtxFunc DialContextFunc) Option {
} }
} }
// WithSMimeConfig tells the client to use the provided SMIMEAuth for s/mime crypto key material authentication
func WithSMimeConfig(sMimeAuthConfig *SMimeAuthConfig) Option {
return func(c *Client) error {
if sMimeAuthConfig.Certificate == nil && sMimeAuthConfig.PrivateKey == nil {
return ErrInvalidSMimeAuthConfig
}
c.sMimeAuthConfig = sMimeAuthConfig
return nil
}
}
// TLSPolicy returns the currently set TLSPolicy as string // TLSPolicy returns the currently set TLSPolicy as string
func (c *Client) TLSPolicy() string { func (c *Client) TLSPolicy() string {
return c.tlspolicy.String() return c.tlspolicy.String()

View file

@ -6,15 +6,10 @@ package mail
import ( import (
"context" "context"
"crypto/rand"
"crypto/rsa"
"crypto/tls" "crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/big"
"net" "net"
"os" "os"
"strconv" "strconv"
@ -122,7 +117,6 @@ func TestNewClientWithOptions(t *testing.T) {
{"WithDialContextFunc()", WithDialContextFunc(func(ctx context.Context, network, address string) (net.Conn, error) { {"WithDialContextFunc()", WithDialContextFunc(func(ctx context.Context, network, address string) (net.Conn, error) {
return nil, nil return nil, nil
}), false}, }), false},
{"WithSMimeConfig()", WithSMimeConfig(&SMimeAuthConfig{}), true},
{ {
"WithDSNRcptNotifyType() NEVER combination", "WithDSNRcptNotifyType() NEVER combination",
WithDSNRcptNotifyType(DSNRcptNotifySuccess, DSNRcptNotifyNever), true, WithDSNRcptNotifyType(DSNRcptNotifySuccess, DSNRcptNotifyNever), true,
@ -761,43 +755,6 @@ func TestClient_DialWithContextInvalidAuth(t *testing.T) {
} }
} }
// TestWithSMime tests the WithSMime method with invalid SMimeAuthConfig for the Client object
func TestWithSMime_InvalidConfig(t *testing.T) {
_, err := NewClient(DefaultHost, WithSMimeConfig(&SMimeAuthConfig{}))
if !errors.Is(err, ErrInvalidSMimeAuthConfig) {
t.Errorf("failed to check sMimeAuthConfig values correctly: %s", err)
}
}
// TestWithSMime tests the WithSMime method with valid SMimeAuthConfig that is loaded from dummy certificate for the Client object
func TestWithSMime_ValidConfig(t *testing.T) {
privateKey, err := getDummyPrivateKey()
if err != nil {
t.Errorf("failed to load dummy private key: %s", err)
}
certificate, err := getDummyCertificate(privateKey)
if err != nil {
t.Errorf("failed to load dummy certificate: %s", err)
}
sMimeAuthConfig := &SMimeAuthConfig{PrivateKey: privateKey, Certificate: certificate}
c, err := NewClient(DefaultHost, WithSMimeConfig(sMimeAuthConfig))
if err != nil {
t.Errorf("failed to create new client: %s", err)
}
if c.sMimeAuthConfig != sMimeAuthConfig {
t.Errorf("failed to set smeAuthConfig. Expected %v, got: %v", sMimeAuthConfig, c.sMimeAuthConfig)
}
if c.sMimeAuthConfig.PrivateKey != sMimeAuthConfig.PrivateKey {
t.Errorf("failed to set smeAuthConfig.PrivateKey Expected %v, got: %v", sMimeAuthConfig, c.sMimeAuthConfig)
}
if c.sMimeAuthConfig.Certificate != sMimeAuthConfig.Certificate {
t.Errorf("failed to set smeAuthConfig.Certificate Expected %v, got: %v", sMimeAuthConfig, c.sMimeAuthConfig)
}
}
// TestClient_checkConn tests the checkConn method with intentional breaking for the Client object // TestClient_checkConn tests the checkConn method with intentional breaking for the Client object
func TestClient_checkConn(t *testing.T) { func TestClient_checkConn(t *testing.T) {
c, err := getTestConnection(true) c, err := getTestConnection(true)
@ -1526,37 +1483,6 @@ func getFakeDialFunc(conn net.Conn) DialContextFunc {
} }
} }
func getDummyPrivateKey() (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return privateKey, nil
}
func getDummyCertificate(privateKey *rsa.PrivateKey) (*x509.Certificate, error) {
template := &x509.Certificate{
SerialNumber: big.NewInt(1234),
Subject: pkix.Name{Organization: []string{"My Organization"}},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil, err
}
return cert, nil
}
type faker struct { type faker struct {
io.ReadWriter io.ReadWriter
} }

20
msg.go
View file

@ -121,8 +121,8 @@ type Msg struct {
// noDefaultUserAgent indicates whether the default User Agent will be excluded for the Msg when it's sent. // noDefaultUserAgent indicates whether the default User Agent will be excluded for the Msg when it's sent.
noDefaultUserAgent bool noDefaultUserAgent bool
// sMimeSinging indicates whether the message should be singed with S/MIME when it's sent. // SMime represents a middleware used to sign messages with S/MIME
sMimeSinging bool sMime *SMime
} }
// SendmailPath is the default system path to the sendmail binary // SendmailPath is the default system path to the sendmail binary
@ -205,11 +205,14 @@ func WithNoDefaultUserAgent() MsgOption {
} }
} }
// WithSMimeSinging configures the Msg to be S/MIME singed sent. // SignWithSMime configures the Msg to be signed with S/MIME
func WithSMimeSinging() MsgOption { func (m *Msg) SignWithSMime(privateKey *rsa.PrivateKey, certificate *x509.Certificate) error {
return func(m *Msg) { sMime, err := NewSMime(privateKey, certificate)
m.sMimeSinging = true if err != nil {
return err
} }
m.sMime = sMime
return nil
} }
// SetCharset sets the encoding charset of the Msg // SetCharset sets the encoding charset of the Msg
@ -1176,6 +1179,11 @@ func (m *Msg) hasMixed() bool {
return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1) return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1)
} }
// hasSMime returns true if the Msg has should be signed with S/MIME
func (m *Msg) hasSMime() bool {
return m.sMime != nil
}
// hasRelated returns true if the Msg has related parts // hasRelated returns true if the Msg has related parts
func (m *Msg) hasRelated() bool { func (m *Msg) hasRelated() bool {
return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.embeds) > 0) || len(m.embeds) > 1) return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.embeds) > 0) || len(m.embeds) > 1)

View file

@ -3233,11 +3233,33 @@ func TestNewMsgWithNoDefaultUserAgent(t *testing.T) {
} }
} }
// TestWithSMimeSinging tests WithSMimeSinging // TestWithSMimeSinging_ValidPrivateKey tests WithSMimeSinging with given privateKey
func TestWithSMimeSinging(t *testing.T) { func TestWithSMimeSinging_ValidPrivateKey(t *testing.T) {
m := NewMsg(WithSMimeSinging()) privateKey, err := getDummyPrivateKey()
if m.sMimeSinging != true { if err != nil {
t.Errorf("WithSMimeSinging() failed. Expected: %t, got: %t", true, false) t.Errorf("failed to load dummy private key: %s", err)
}
certificate, err := getDummyCertificate(privateKey)
if err != nil {
t.Errorf("failed to load dummy certificate: %s", err)
}
m := NewMsg()
if err := m.SignWithSMime(privateKey, certificate); err != nil {
t.Errorf("failed to set sMime. Cause: %v", err)
}
if m.sMime.privateKey != privateKey {
t.Errorf("WithSMimeSinging. Expected %v, got: %v", privateKey, m.sMime.privateKey)
}
}
// TestWithSMimeSinging_InvalidPrivateKey tests WithSMimeSinging with given invalid privateKey
func TestWithSMimeSinging_InvalidPrivateKey(t *testing.T) {
m := NewMsg()
err := m.SignWithSMime(nil, nil)
if !errors.Is(err, ErrInvalidPrivateKey) {
t.Errorf("failed to check sMimeAuthConfig values correctly: %s", err)
} }
} }

41
util_test.go Normal file
View file

@ -0,0 +1,41 @@
package mail
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"time"
)
func getDummyPrivateKey() (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return privateKey, nil
}
func getDummyCertificate(privateKey *rsa.PrivateKey) (*x509.Certificate, error) {
template := &x509.Certificate{
SerialNumber: big.NewInt(1234),
Subject: pkix.Name{Organization: []string{"My Organization"}},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil, err
}
return cert, nil
}