From faffc025cb8f1d37051ba28ce99a82e7d0f1b7f4 Mon Sep 17 00:00:00 2001 From: theexiile1305 Date: Sun, 27 Oct 2024 09:42:13 +0100 Subject: [PATCH] feat: s/mime singing supports quoted printable encoding, also added tests --- msg.go | 7 +++- smime.go | 29 +++++++++++++- smime_test.go | 108 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 131 insertions(+), 13 deletions(-) diff --git a/msg.go b/msg.go index 7f5bbd9..758373b 100644 --- a/msg.go +++ b/msg.go @@ -2184,8 +2184,11 @@ func (m *Msg) signMessage(msg *Msg) (*Msg, error) { // createSignaturePart creates an additional part that be used for storing the S/MIME signature of the given body func (m *Msg) createSignaturePart(encoding Encoding, contentType ContentType, charSet Charset, body []byte) (*Part, error) { - message := m.sMime.prepareMessage(encoding, contentType, charSet, body) - signedMessage, err := m.sMime.signMessage(message) + message, err := m.sMime.prepareMessage(encoding, contentType, charSet, body) + if err != nil { + return nil, err + } + signedMessage, err := m.sMime.signMessage(*message) if err != nil { return nil, err } diff --git a/smime.go b/smime.go index da09713..6a898ce 100644 --- a/smime.go +++ b/smime.go @@ -13,6 +13,7 @@ import ( "encoding/pem" "errors" "fmt" + "mime/quotedprintable" "strings" ) @@ -119,8 +120,32 @@ func (sm *SMime) signMessage(message string) (*string, error) { } // createMessage prepares the message that will be used for the sign method later -func (sm *SMime) prepareMessage(encoding Encoding, contentType ContentType, charset Charset, body []byte) string { - return fmt.Sprintf("Content-Transfer-Encoding: %v\r\nContent-Type: %v; charset=%v\r\n\r\n%v", encoding, contentType, charset, string(body)) +func (sm *SMime) prepareMessage(encoding Encoding, contentType ContentType, charset Charset, body []byte) (*string, error) { + encodedMessage, err := sm.encodeMessage(encoding, string(body)) + if err != nil { + return nil, err + } + preparedMessage := fmt.Sprintf("Content-Transfer-Encoding: %v\r\nContent-Type: %v; charset=%v\r\n\r\n%v", encoding, contentType, charset, *encodedMessage) + return &preparedMessage, nil +} + +// encodeMessage encodes the message with the given encoding +func (sm *SMime) encodeMessage(encoding Encoding, message string) (*string, error) { + if encoding != EncodingQP { + return &message, nil + } + + buffer := bytes.Buffer{} + writer := quotedprintable.NewWriter(&buffer) + if _, err := writer.Write([]byte(message)); err != nil { + return nil, err + } + if err := writer.Close(); err != nil { + return nil, err + } + encodedMessage := buffer.String() + + return &encodedMessage, nil } // encodeToPEM uses the method pem.Encode from the standard library but cuts the typical PEM preamble diff --git a/smime_test.go b/smime_test.go index d6fae44..cb41666 100644 --- a/smime_test.go +++ b/smime_test.go @@ -121,19 +121,109 @@ func TestPrepareMessage(t *testing.T) { contentType := TypeTextPlain charset := CharsetUTF8 body := []byte("This is the body!") - result := sMime.prepareMessage(encoding, contentType, charset, body) + result, err := sMime.prepareMessage(encoding, contentType, charset, body) + if err != nil { + t.Errorf("Error preparing message: %s", err) + } - if !strings.Contains(result, encoding.String()) { - t.Errorf("createMessage() did not return the correct encoding") + if !strings.Contains(*result, encoding.String()) { + t.Errorf("prepareMessage() did not return the correct encoding") } - if !strings.Contains(result, contentType.String()) { - t.Errorf("createMessage() did not return the correct contentType") + if !strings.Contains(*result, contentType.String()) { + t.Errorf("prepareMessage() did not return the correct contentType") } - if !strings.Contains(result, string(body)) { - t.Errorf("createMessage() did not return the correct body") + if !strings.Contains(*result, string(body)) { + t.Errorf("prepareMessage() did not return the correct body") } - if result != fmt.Sprintf("Content-Transfer-Encoding: %s\r\nContent-Type: %s; charset=%s\r\n\r\n%s", encoding, contentType, charset, string(body)) { - t.Errorf("createMessage() did not sucessfully create the message") + if *result != fmt.Sprintf("Content-Transfer-Encoding: %s\r\nContent-Type: %s; charset=%s\r\n\r\n%s", encoding, contentType, charset, string(body)) { + t.Errorf("prepareMessage() did not sucessfully create the message") + } +} + +// TestPrepareMessage_QuotedPrintable tests the prepareMessage method with quoted printable encoding +func TestPrepareMessage_QuotedPrintable(t *testing.T) { + privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial() + if err != nil { + t.Errorf("Error getting dummy crypto material: %s", err) + } + + sMime, err := newSMimeWithRSA(privateKey, certificate, intermediateCertificate) + if err != nil { + t.Errorf("Error creating new SMime from keyPair: %s", err) + } + + body := "This is the body with special chars like äöü ÄÖÜ ß!" + quotedPrintableBody := "This is the body with special chars like =C3=A4=C3=B6=C3=BC =C3=84=C3=96=C3=\r\n=9C =C3=9F!" + encoding := EncodingQP + contentType := TypeTextPlain + charset := CharsetUTF8 + result, err := sMime.prepareMessage(encoding, contentType, charset, []byte(body)) + if err != nil { + t.Errorf("Error preparing message: %s", err) + } + + if !strings.Contains(*result, encoding.String()) { + t.Errorf("prepareMessage() did not return the correct encoding") + } + if !strings.Contains(*result, contentType.String()) { + t.Errorf("prepareMessage() did not return the correct contentType") + } + if !strings.Contains(*result, quotedPrintableBody) { + t.Errorf("prepareMessage() did not return the correct body") + } + if *result != fmt.Sprintf("Content-Transfer-Encoding: %s\r\nContent-Type: %s; charset=%s\r\n\r\n%s", encoding, contentType, charset, quotedPrintableBody) { + t.Errorf("prepareMessage() did not sucessfully create the message") + } +} + +// TestEncodeMessage tests the TestEncodeMessage method without any encoding +func TestEncodeMessage(t *testing.T) { + body := "This is the body with special chars like äöü ÄÖÜ ß!" + encoding := EncodingUSASCII + + privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial() + if err != nil { + t.Errorf("Error getting dummy crypto material: %s", err) + } + + sMime, err := newSMimeWithRSA(privateKey, certificate, intermediateCertificate) + if err != nil { + t.Errorf("Error creating new SMime from keyPair: %s", err) + } + + result, err := sMime.encodeMessage(encoding, body) + if err != nil { + t.Errorf("Error preparing message: %s", err) + } + + if *result != body { + t.Errorf("encodeMessage() did not return the correct encoded message: %s", *result) + } +} + +// TestEncodeMessage_QuotedPrintable tests the TestEncodeMessage method with quoted printable body +func TestEncodeMessage_QuotedPrintable(t *testing.T) { + body := "This is the body with special chars like äöü ÄÖÜ ß!" + quotedPrintableBody := "This is the body with special chars like =C3=A4=C3=B6=C3=BC =C3=84=C3=96=C3=\r\n=9C =C3=9F!" + encoding := EncodingQP + + privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial() + if err != nil { + t.Errorf("Error getting dummy crypto material: %s", err) + } + + sMime, err := newSMimeWithRSA(privateKey, certificate, intermediateCertificate) + if err != nil { + t.Errorf("Error creating new SMime from keyPair: %s", err) + } + + result, err := sMime.encodeMessage(encoding, body) + if err != nil { + t.Errorf("Error preparing message: %s", err) + } + + if *result != quotedPrintableBody { + t.Errorf("encodeMessage() did not return the correct encoded message: %s", *result) } }