passforios-gopenpgp/crypto/mime.go
M. Thiercelin a2fd1c6a3b
Sanitize non utf8 strings before returning them to iOS apps
In swift, strings must be strictly utf8, and when golang
returns a string with non utf8 characters, it gets translated to
an empty string for utf8.
To avoid this situation, we sanitize strings before returning them.
This behavior is only enabled when building with the "ios" build tag.
2022-11-03 12:31:05 +01:00

118 lines
3.6 KiB
Go

package crypto
import (
"bytes"
"io/ioutil"
"net/mail"
"net/textproto"
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
gomime "github.com/ProtonMail/go-mime"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
)
// MIMECallbacks defines callback methods to process a MIME message.
type MIMECallbacks interface {
OnBody(body string, mimetype string)
OnAttachment(headers string, data []byte)
// Encrypted headers can be in an attachment and thus be placed at the end of the mime structure.
OnEncryptedHeaders(headers string)
OnVerified(verified int)
OnError(err error)
}
// DecryptMIMEMessage decrypts a MIME message.
func (keyRing *KeyRing) DecryptMIMEMessage(
message *PGPMessage, verifyKey *KeyRing, callbacks MIMECallbacks, verifyTime int64,
) {
decryptedMessage, err := keyRing.Decrypt(message, verifyKey, verifyTime)
embeddedSigError, err := separateSigError(err)
if err != nil {
callbacks.OnError(err)
return
}
body, attachments, attachmentHeaders, err := parseMIME(string(decryptedMessage.GetBinary()), verifyKey)
mimeSigError, err := separateSigError(err)
if err != nil {
callbacks.OnError(err)
return
}
// We only consider the signature to be failed if both embedded and mime verification failed
if embeddedSigError != nil && mimeSigError != nil {
callbacks.OnError(embeddedSigError)
callbacks.OnError(mimeSigError)
callbacks.OnVerified(prioritizeSignatureErrors(embeddedSigError, mimeSigError))
} else if verifyKey != nil {
callbacks.OnVerified(constants.SIGNATURE_OK)
}
bodyContent, bodyMimeType := body.GetBody()
bodyContentSanitized := sanitizeString(bodyContent)
callbacks.OnBody(bodyContentSanitized, bodyMimeType)
for i := 0; i < len(attachments); i++ {
callbacks.OnAttachment(attachmentHeaders[i], []byte(attachments[i]))
}
callbacks.OnEncryptedHeaders("")
}
// ----- INTERNAL FUNCTIONS -----
func prioritizeSignatureErrors(signatureErrs ...*SignatureVerificationError) (maxError int) {
// select error with the highest value, if any
// FAILED > NO VERIFIER > NOT SIGNED > SIGNATURE OK
maxError = constants.SIGNATURE_OK
for _, err := range signatureErrs {
if err.Status > maxError {
maxError = err.Status
}
}
return
}
func separateSigError(err error) (*SignatureVerificationError, error) {
sigErr := &SignatureVerificationError{}
if errors.As(err, sigErr) {
return sigErr, nil
}
return nil, err
}
func parseMIME(
mimeBody string, verifierKey *KeyRing,
) (*gomime.BodyCollector, []string, []string, error) {
mm, err := mail.ReadMessage(strings.NewReader(mimeBody))
if err != nil {
return nil, nil, nil, errors.Wrap(err, "gopenpgp: error in reading message")
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()}
h := textproto.MIMEHeader(mm.Header)
mmBodyData, err := ioutil.ReadAll(mm.Body)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "gopenpgp: error in reading message body data")
}
printAccepter := gomime.NewMIMEPrinter()
bodyCollector := gomime.NewBodyCollector(printAccepter)
attachmentsCollector := gomime.NewAttachmentsCollector(bodyCollector)
mimeVisitor := gomime.NewMimeVisitor(attachmentsCollector)
var verifierEntities openpgp.KeyRing
if verifierKey != nil {
verifierEntities = verifierKey.entities
}
signatureCollector := newSignatureCollector(mimeVisitor, verifierEntities, config)
err = gomime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector)
if err == nil && verifierKey != nil {
err = signatureCollector.verified
}
return bodyCollector,
attachmentsCollector.GetAttachments(),
attachmentsCollector.GetAttHeaders(),
err
}