mirror of
https://github.com/wneessen/go-mail.git
synced 2024-12-23 02:50:39 +01:00
Attribute sMimeSinging was added in msg that indicates whether the message should be singed with S/MIME when it's sent. Also, sMimeAuthConfig was introduced in client so that required privateKey and certificate can be used for S/MIME signing.
This commit is contained in:
parent
168f924a9e
commit
07d9654ce7
5 changed files with 121 additions and 1 deletions
17
client.go
17
client.go
|
@ -127,6 +127,9 @@ type Client struct {
|
|||
// smtpAuthType represents the authentication type for SMTP AUTH
|
||||
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 *smtp.Client
|
||||
|
||||
|
@ -168,6 +171,9 @@ var (
|
|||
// ErrInvalidTLSConfig should be used if an empty tls.Config is provided
|
||||
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 = errors.New("hostname for client cannot be empty")
|
||||
|
||||
|
@ -459,6 +465,17 @@ 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
|
||||
func (c *Client) TLSPolicy() string {
|
||||
return c.tlspolicy.String()
|
||||
|
|
|
@ -6,10 +6,15 @@ package mail
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
@ -117,7 +122,7 @@ func TestNewClientWithOptions(t *testing.T) {
|
|||
{"WithDialContextFunc()", WithDialContextFunc(func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return nil, nil
|
||||
}), false},
|
||||
|
||||
{"WithSMimeConfig()", WithSMimeConfig(&SMimeAuthConfig{}), true},
|
||||
{
|
||||
"WithDSNRcptNotifyType() NEVER combination",
|
||||
WithDSNRcptNotifyType(DSNRcptNotifySuccess, DSNRcptNotifyNever), true,
|
||||
|
@ -756,6 +761,43 @@ 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
|
||||
func TestClient_checkConn(t *testing.T) {
|
||||
c, err := getTestConnection(true)
|
||||
|
@ -1484,6 +1526,37 @@ 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 {
|
||||
io.ReadWriter
|
||||
}
|
||||
|
|
10
msg.go
10
msg.go
|
@ -120,6 +120,9 @@ type Msg struct {
|
|||
|
||||
// noDefaultUserAgent indicates whether the default User Agent will be excluded for the Msg when it's sent.
|
||||
noDefaultUserAgent bool
|
||||
|
||||
// sMimeSinging indicates whether the message should be singed with S/MIME when it's sent.
|
||||
sMimeSinging bool
|
||||
}
|
||||
|
||||
// SendmailPath is the default system path to the sendmail binary
|
||||
|
@ -202,6 +205,13 @@ func WithNoDefaultUserAgent() MsgOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithSMimeSinging configures the Msg to be S/MIME singed sent.
|
||||
func WithSMimeSinging() MsgOption {
|
||||
return func(m *Msg) {
|
||||
m.sMimeSinging = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetCharset sets the encoding charset of the Msg
|
||||
func (m *Msg) SetCharset(c Charset) {
|
||||
m.charset = c
|
||||
|
|
|
@ -3233,6 +3233,14 @@ func TestNewMsgWithNoDefaultUserAgent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestWithSMimeSinging tests WithSMimeSinging
|
||||
func TestWithSMimeSinging(t *testing.T) {
|
||||
m := NewMsg(WithSMimeSinging())
|
||||
if m.sMimeSinging != true {
|
||||
t.Errorf("WithSMimeSinging() failed. Expected: %t, got: %t", true, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzzing tests
|
||||
func FuzzMsg_Subject(f *testing.F) {
|
||||
f.Add("Testsubject")
|
||||
|
|
12
sime.go
Normal file
12
sime.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
// SMimeAuthConfig represents the authentication type for s/mime crypto key material
|
||||
type SMimeAuthConfig struct {
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
Loading…
Reference in a new issue