implementation of S/MIME singing without tests of type smime

This commit is contained in:
theexiile1305 2024-09-18 14:23:26 +02:00
parent e0a59dba6d
commit 94942ed383
No known key found for this signature in database
GPG key ID: A1BDDE98F2BF6E40
5 changed files with 114 additions and 6 deletions

2
go.mod
View file

@ -5,3 +5,5 @@
module github.com/wneessen/go-mail module github.com/wneessen/go-mail
go 1.16 go 1.16
require go.mozilla.org/pkcs7 v0.9.0

2
go.sum
View file

@ -0,0 +1,2 @@
go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=
go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=

41
msg.go
View file

@ -7,6 +7,8 @@ package mail
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rsa"
"crypto/x509"
"embed" "embed"
"errors" "errors"
"fmt" "fmt"
@ -981,10 +983,47 @@ func (m *Msg) applyMiddlewares(msg *Msg) *Msg {
return msg return msg
} }
// signMessage sign the Msg with S/MIME
func (m *Msg) signMessage(msg *Msg) (*Msg, error) {
currentPart := m.GetParts()[0]
currentPart.SetEncoding(EncodingUSASCII)
currentPart.SetContentType(TypeTextPlain)
content, err := currentPart.GetContent()
if err != nil {
return nil, errors.New("failed to extract content from part")
}
signedContent, err := m.sMime.Sign(content)
if err != nil {
return nil, errors.New("failed to sign message")
}
signedPart := msg.newPart(
typeSMimeSigned,
WithPartEncoding(EncodingB64),
WithContentDisposition(DispositionSMime),
)
signedPart.SetContent(*signedContent)
msg.parts = append(msg.parts, signedPart)
return msg, nil
}
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface // WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
func (m *Msg) WriteTo(writer io.Writer) (int64, error) { func (m *Msg) WriteTo(writer io.Writer) (int64, error) {
mw := &msgWriter{writer: writer, charset: m.charset, encoder: m.encoder} mw := &msgWriter{writer: writer, charset: m.charset, encoder: m.encoder}
mw.writeMsg(m.applyMiddlewares(m)) msg := m.applyMiddlewares(m)
if m.sMime != nil {
signedMsg, err := m.signMessage(msg)
if err != nil {
return 0, err
}
msg = signedMsg
}
mw.writeMsg(msg)
return mw.bytesWritten, mw.err return mw.bytesWritten, mw.err
} }

View file

@ -88,6 +88,10 @@ func (mw *msgWriter) writeMsg(msg *Msg) {
} }
} }
if msg.hasSMime() {
mw.startMP(MIMESMime, msg.boundary)
mw.writeString(DoubleNewLine)
}
if msg.hasMixed() { if msg.hasMixed() {
mw.startMP(MIMEMixed, msg.boundary) mw.startMP(MIMEMixed, msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
@ -96,7 +100,7 @@ func (mw *msgWriter) writeMsg(msg *Msg) {
mw.startMP(MIMERelated, msg.boundary) mw.startMP(MIMERelated, msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if msg.hasAlt() { if msg.hasAlt() && !msg.hasSMime() {
mw.startMP(MIMEAlternative, msg.boundary) mw.startMP(MIMEAlternative, msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
@ -265,6 +269,9 @@ func (mw *msgWriter) writePart(part *Part, charset Charset) {
if part.description != "" { if part.description != "" {
mimeHeader.Add(string(HeaderContentDescription), part.description) mimeHeader.Add(string(HeaderContentDescription), part.description)
} }
if part.disposition != "" {
mimeHeader.Add(string(HeaderContentDisposition), part.disposition.String())
}
mimeHeader.Add(string(HeaderContentType), contentType) mimeHeader.Add(string(HeaderContentType), contentType)
mimeHeader.Add(string(HeaderContentTransferEnc), contentTransferEnc) mimeHeader.Add(string(HeaderContentTransferEnc), contentTransferEnc)
mw.newPart(mimeHeader) mw.newPart(mimeHeader)

66
sime.go
View file

@ -3,10 +3,68 @@ package mail
import ( import (
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"errors"
"go.mozilla.org/pkcs7"
) )
// SMimeAuthConfig represents the authentication type for s/mime crypto key material var (
type SMimeAuthConfig struct { // ErrInvalidPrivateKey should be used if private key is invalid
Certificate *x509.Certificate ErrInvalidPrivateKey = errors.New("invalid private key")
PrivateKey *rsa.PrivateKey
// ErrInvalidCertificate should be used if certificate is invalid
ErrInvalidCertificate = errors.New("invalid certificate")
// ErrCouldNotInitialize should be used if the signed data could not initialize
ErrCouldNotInitialize = errors.New("could not initialize signed data")
// ErrCouldNotAddSigner should be used if the signer could not be added
ErrCouldNotAddSigner = errors.New("could not add signer message")
// ErrCouldNotFinishSigning should be used if the signing could not be finished
ErrCouldNotFinishSigning = errors.New("could not finish signing")
)
// SMime is used to sign messages with S/MIME
type SMime struct {
privateKey *rsa.PrivateKey
certificate *x509.Certificate
}
// NewSMime construct a new instance of SMime with a provided *rsa.PrivateKey
func NewSMime(privateKey *rsa.PrivateKey, certificate *x509.Certificate) (*SMime, error) {
if privateKey == nil {
return nil, ErrInvalidPrivateKey
}
if certificate == nil {
return nil, ErrInvalidCertificate
}
return &SMime{
privateKey: privateKey,
certificate: certificate,
}, nil
}
// Sign the content with the given privateKey of the method NewSMime
func (sm *SMime) Sign(content []byte) (*string, error) {
toBeSigned, err := pkcs7.NewSignedData(content)
toBeSigned.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256)
if err != nil {
return nil, ErrCouldNotInitialize
}
if err = toBeSigned.AddSigner(sm.certificate, sm.privateKey, pkcs7.SignerInfoConfig{}); err != nil {
return nil, ErrCouldNotAddSigner
}
signed, err := toBeSigned.Finish()
if err != nil {
return nil, ErrCouldNotFinishSigning
}
signedData := string(signed)
return &signedData, nil
} }