mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 13:50:49 +01:00
moved S/MIME initialization into msg and the pointer to the data structure, also adjusted tests
This commit is contained in:
parent
07d9654ce7
commit
158c1b0458
5 changed files with 82 additions and 102 deletions
17
client.go
17
client.go
|
@ -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()
|
||||||
|
|
|
@ -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
20
msg.go
|
@ -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)
|
||||||
|
|
32
msg_test.go
32
msg_test.go
|
@ -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
41
util_test.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue