diff --git a/msg.go b/msg.go index 455ba3b..60f758f 100644 --- a/msg.go +++ b/msg.go @@ -7,7 +7,8 @@ package mail import ( "bytes" "context" - "crypto/tls" + "crypto/rsa" + "crypto/x509" "embed" "errors" "fmt" @@ -338,8 +339,8 @@ func WithNoDefaultUserAgent() MsgOption { } // SignWithSMime configures the Msg to be signed with S/MIME -func (m *Msg) SignWithSMime(keyPair *tls.Certificate) error { - sMime, err := newSMime(keyPair) +func (m *Msg) SignWithSMime(privateKey *rsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) error { + sMime, err := newSMime(privateKey, certificate, intermediateCertificate) if err != nil { return err } diff --git a/msg_test.go b/msg_test.go index ffd1357..7842cda 100644 --- a/msg_test.go +++ b/msg_test.go @@ -1909,15 +1909,15 @@ func TestMsg_hasAlt(t *testing.T) { // TestMsg_hasAlt tests the hasAlt() method of the Msg with active S/MIME func TestMsg_hasAltWithSMime(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("failed to load dummy certificate. Cause: %v", err) + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) } m := NewMsg() m.SetBodyString(TypeTextPlain, "Plain") m.AddAlternativeString(TypeTextHTML, "HTML") - if err := m.SignWithSMime(keyPair); err != nil { - t.Errorf("set of certificate was not successful") + if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil { + t.Errorf("failed to init smime configuration") } if m.hasAlt() { t.Errorf("mail has alternative parts and S/MIME is active, but hasAlt() returned true") @@ -1926,13 +1926,13 @@ func TestMsg_hasAltWithSMime(t *testing.T) { // TestMsg_hasSMime tests the hasSMime() method of the Msg func TestMsg_hasSMime(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("failed to load dummy certificate. Cause: %v", err) + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) } m := NewMsg() - if err := m.SignWithSMime(keyPair); err != nil { - t.Errorf("set of certificate was not successful") + if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil { + t.Errorf("failed to init smime configuration") } m.SetBodyString(TypeTextPlain, "Plain") if !m.hasSMime() { @@ -2009,16 +2009,16 @@ func TestMsg_WriteToSkipMiddleware(t *testing.T) { // TestMsg_WriteToWithSMIME tests the WriteTo() method of the Msg func TestMsg_WriteToWithSMIME(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("failed to load dummy certificate. Cause: %v", err) + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) } m := NewMsg() m.Subject("This is a test") m.SetBodyString(TypeTextPlain, "Plain") - if err := m.SignWithSMime(keyPair); err != nil { - t.Errorf("set of certificate was not successful") + if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil { + t.Errorf("failed to init smime configuration") } wbuf := bytes.Buffer{} @@ -3345,12 +3345,12 @@ func TestNewMsgWithNoDefaultUserAgent(t *testing.T) { // TestSignWithSMime_ValidKeyPair tests WithSMimeSinging with given key pair func TestSignWithSMime_ValidKeyPair(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("failed to load dummy certificate. Cause: %v", err) + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) } m := NewMsg() - if err := m.SignWithSMime(keyPair); err != nil { + if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil { t.Errorf("failed to set sMime. Cause: %v", err) } if m.sMime.privateKey == nil { @@ -3361,13 +3361,41 @@ func TestSignWithSMime_ValidKeyPair(t *testing.T) { } } -// TestSignWithSMime_InvalidKeyPair tests WithSMimeSinging with given invalid key pair -func TestSignWithSMime_InvalidKeyPair(t *testing.T) { +// TestSignWithSMime_InvalidPrivateKey tests WithSMimeSinging with given invalid private key +func TestSignWithSMime_InvalidPrivateKey(t *testing.T) { m := NewMsg() - err := m.SignWithSMime(nil) - if !errors.Is(err, ErrInvalidKeyPair) { - t.Errorf("failed to check sMimeAuthConfig values correctly: %s", err) + err := m.SignWithSMime(nil, nil, nil) + if !errors.Is(err, ErrInvalidPrivateKey) { + t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err) + } +} + +// TestSignWithSMime_InvalidCertificate tests WithSMimeSinging with given invalid certificate +func TestSignWithSMime_InvalidCertificate(t *testing.T) { + privateKey, _, _, err := getDummyCryptoMaterial() + if err != nil { + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) + } + m := NewMsg() + + err = m.SignWithSMime(privateKey, nil, nil) + if !errors.Is(err, ErrInvalidCertificate) { + t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err) + } +} + +// TestSignWithSMime_InvalidIntermediateCertificate tests WithSMimeSinging with given invalid intermediate certificate +func TestSignWithSMime_InvalidIntermediateCertificate(t *testing.T) { + privateKey, certificate, _, err := getDummyCryptoMaterial() + if err != nil { + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) + } + m := NewMsg() + + err = m.SignWithSMime(privateKey, certificate, nil) + if !errors.Is(err, ErrInvalidIntermediateCertificate) { + t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err) } } @@ -3401,13 +3429,13 @@ func FuzzMsg_From(f *testing.F) { // TestMsg_createSignaturePart tests the Msg.createSignaturePart method func TestMsg_createSignaturePart(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("failed to load dummy certificate. Cause: %v", err) + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) } m := NewMsg() - if err := m.SignWithSMime(keyPair); err != nil { - t.Errorf("set of certificate was not successful") + if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil { + t.Errorf("failed to init smime configuration") } body := []byte("This is the body") part, err := m.createSignaturePart(EncodingQP, TypeTextPlain, CharsetUTF7, body) @@ -3431,15 +3459,15 @@ func TestMsg_createSignaturePart(t *testing.T) { // TestMsg_signMessage tests the Msg.signMessage method func TestMsg_signMessage(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("failed to load dummy certificate. Cause: %v", err) + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) } body := []byte("This is the body") m := NewMsg() m.SetBodyString(TypeTextPlain, string(body)) - if err := m.SignWithSMime(keyPair); err != nil { - t.Errorf("set of certificate was not successful") + if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil { + t.Errorf("failed to init smime configuration") } msg, err := m.signMessage(m) if err != nil { diff --git a/msgwriter_test.go b/msgwriter_test.go index 15e751f..6e36c4b 100644 --- a/msgwriter_test.go +++ b/msgwriter_test.go @@ -157,14 +157,14 @@ func TestMsgWriter_writeMsg_PGP(t *testing.T) { // TestMsgWriter_writeMsg_SMime tests the writeMsg method of the msgWriter with S/MIME types set func TestMsgWriter_writeMsg_SMime(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("failed to load dummy certificate. Cause: %v", err) + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) } m := NewMsg() - if err := m.SignWithSMime(keyPair); err != nil { - t.Errorf("set of certificate was not successful") + if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil { + t.Errorf("failed to init smime configuration") } _ = m.From(`"Toni Tester" `) _ = m.To(`"Toni Receiver" `) diff --git a/smime.go b/smime.go index ae6e4f2..37fb536 100644 --- a/smime.go +++ b/smime.go @@ -3,7 +3,6 @@ package mail import ( "bytes" "crypto/rsa" - "crypto/tls" "crypto/x509" "encoding/pem" "errors" @@ -13,11 +12,14 @@ import ( ) var ( - // ErrInvalidKeyPair should be used if key pair is invalid - ErrInvalidKeyPair = errors.New("invalid key pair") + // ErrInvalidPrivateKey should be used if private key is invalid + ErrInvalidPrivateKey = errors.New("invalid private key") - // ErrInvalidParentCertificates should be used if one of the parent certificates is invalid - ErrInvalidParentCertificates = errors.New("invalid parent certificates") + // ErrInvalidCertificate should be used if the certificate is invalid + ErrInvalidCertificate = errors.New("invalid certificate") + + // ErrInvalidIntermediateCertificate should be used if the intermediate certificate is invalid + ErrInvalidIntermediateCertificate = errors.New("invalid intermediate certificate") // ErrCouldNotInitialize should be used if the signed data could not initialize ErrCouldNotInitialize = errors.New("could not initialize signed data") @@ -34,30 +36,32 @@ var ( // SMime is used to sign messages with S/MIME type SMime struct { - privateKey *rsa.PrivateKey - parentCertificates []*x509.Certificate - certificate *x509.Certificate + privateKey *rsa.PrivateKey + certificate *x509.Certificate + intermediateCertificate *x509.Certificate } -// NewSMime construct a new instance of SMime with a provided *tls.Certificate -func newSMime(keyPair *tls.Certificate) (*SMime, error) { - if keyPair == nil { - return nil, ErrInvalidKeyPair +// NewSMime construct a new instance of SMime with provided parameters +// privateKey as *rsa.PrivateKey +// certificate as *x509.Certificate +// intermediateCertificate as *x509.Certificate +func newSMime(privateKey *rsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) (*SMime, error) { + if privateKey == nil { + return nil, ErrInvalidPrivateKey } - parentCertificates := make([]*x509.Certificate, 0) - for _, cert := range keyPair.Certificate[1:] { - c, err := x509.ParseCertificate(cert) - if err != nil { - return nil, ErrInvalidParentCertificates - } - parentCertificates = append(parentCertificates, c) + if certificate == nil { + return nil, ErrInvalidCertificate + } + + if intermediateCertificate == nil { + return nil, ErrInvalidIntermediateCertificate } return &SMime{ - privateKey: keyPair.PrivateKey.(*rsa.PrivateKey), - certificate: keyPair.Leaf, - parentCertificates: parentCertificates, + privateKey: privateKey, + certificate: certificate, + intermediateCertificate: intermediateCertificate, }, nil } @@ -72,7 +76,7 @@ func (sm *SMime) signMessage(message string) (*string, error) { return nil, ErrCouldNotInitialize } - if err = signedData.AddSignerChain(sm.certificate, sm.privateKey, sm.parentCertificates, pkcs7.SignerInfoConfig{}); err != nil { + if err = signedData.AddSignerChain(sm.certificate, sm.privateKey, []*x509.Certificate{sm.intermediateCertificate}, pkcs7.SignerInfoConfig{}); err != nil { return nil, ErrCouldNotAddSigner } diff --git a/smime_test.go b/smime_test.go index cfd8518..bf4e2d0 100644 --- a/smime_test.go +++ b/smime_test.go @@ -10,32 +10,35 @@ import ( // TestNewSMime tests the newSMime method func TestNewSMime(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("Error getting dummy certificate: %s", err) + t.Errorf("Error getting dummy crypto material: %s", err) } - sMime, err := newSMime(keyPair) + sMime, err := newSMime(privateKey, certificate, intermediateCertificate) if err != nil { t.Errorf("Error creating new SMime from keyPair: %s", err) } - if sMime.privateKey != keyPair.PrivateKey { + if sMime.privateKey != privateKey { t.Errorf("NewSMime() did not return the same private key") } - if sMime.certificate != keyPair.Leaf { - t.Errorf("NewSMime() did not return the same leaf certificate") + if sMime.certificate != certificate { + t.Errorf("NewSMime() did not return the same certificate") + } + if sMime.intermediateCertificate != intermediateCertificate { + t.Errorf("NewSMime() did not return the same intermedidate certificate") } } // TestSign tests the sign method func TestSign(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("Error getting dummy certificate: %s", err) + t.Errorf("Error getting dummy crypto material: %s", err) } - sMime, err := newSMime(keyPair) + sMime, err := newSMime(privateKey, certificate, intermediateCertificate) if err != nil { t.Errorf("Error creating new SMime from keyPair: %s", err) } @@ -53,12 +56,12 @@ func TestSign(t *testing.T) { // TestPrepareMessage tests the createMessage method func TestPrepareMessage(t *testing.T) { - keyPair, err := getDummyCertificate() + privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial() if err != nil { - t.Errorf("Error getting dummy certificate: %s", err) + t.Errorf("Error getting dummy crypto material: %s", err) } - sMime, err := newSMime(keyPair) + sMime, err := newSMime(privateKey, certificate, intermediateCertificate) if err != nil { t.Errorf("Error creating new SMime from keyPair: %s", err) } diff --git a/util_test.go b/util_test.go index 7c54b7d..33615d8 100644 --- a/util_test.go +++ b/util_test.go @@ -1,6 +1,7 @@ package mail import ( + "crypto/rsa" "crypto/tls" "crypto/x509" ) @@ -10,14 +11,24 @@ const ( keyFilePath = "dummy-child-key.pem" ) -// getDummyCertificate loads a certificate and a private key form local disk for testing purposes -func getDummyCertificate() (*tls.Certificate, error) { +// getDummyCryptoMaterial loads a certificate and a private key form local disk for testing purposes +func getDummyCryptoMaterial() (*rsa.PrivateKey, *x509.Certificate, *x509.Certificate, error) { keyPair, err := tls.LoadX509KeyPair(certFilePath, keyFilePath) if err != nil { - return nil, err + return nil, nil, nil, err } - keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) + privateKey := keyPair.PrivateKey.(*rsa.PrivateKey) - return &keyPair, nil + certificate, err := x509.ParseCertificate(keyPair.Certificate[0]) + if err != nil { + return nil, nil, nil, err + } + + intermediateCertificate, err := x509.ParseCertificate(keyPair.Certificate[1]) + if err != nil { + return nil, nil, nil, err + } + + return privateKey, certificate, intermediateCertificate, nil }