From 81e4a3cb2c5e61a62981611415f56250d39bcf45 Mon Sep 17 00:00:00 2001 From: Kay Lukas Date: Wed, 5 Sep 2018 14:56:06 +0200 Subject: [PATCH] Bugfixes --- mime.go | 297 +++++------------------------------------ mime_test.go | 267 +++++++++++++++++++++++++++++++++++- signature_collector.go | 161 ++++++++++++++++++++++ 3 files changed, 456 insertions(+), 269 deletions(-) create mode 100644 signature_collector.go diff --git a/mime.go b/mime.go index a75c30a..f622e2f 100644 --- a/mime.go +++ b/mime.go @@ -1,191 +1,32 @@ package pmcrypto import ( + "proton/pmmime" "net/mail" "strings" + "golang.org/x/crypto/openpgp/packet" "net/textproto" "io/ioutil" "bytes" - "proton/pmmime" - "io" - log "github.com/Sirupsen/logrus" - "mime" - "mime/multipart" - "bufio" - "fmt" - "golang.org/x/crypto/openpgp/packet" "golang.org/x/crypto/openpgp" ) - - -func DecodePart(partReader io.Reader, header textproto.MIMEHeader) (decodedPart io.Reader) { - decodedPart = pmmime.DecodeContentEncoding(partReader, header.Get("Content-Transfer-Encoding")) - if decodedPart == nil { - log.Warnf("Unsupported Content-Transfer-Encoding '%v'", header.Get("Content-Transfer-Encoding")) - decodedPart = partReader - } - return -} - // ======================== Attachments Collector ============== // Collect contents of all attachment parts and return // them as a string -type SignatureCollector struct { - config packet.Config - keyring openpgp.KeyRing - target pmmime.VisitAcceptor - signature string - verified int -} - -func NewSignatureCollector(config packet.Config, targetAccepter pmmime.VisitAcceptor) *SignatureCollector { - return &SignatureCollector{ - target: targetAccepter, - config: config, - - } -} - -func getRawMimePart(rawdata io.Reader, boundary string) (io.Reader, io.Reader) { - b, _ := ioutil.ReadAll(rawdata) - tee := bytes.NewReader(b) - - reader := bufio.NewReader(bytes.NewReader(b)) - byteBoundary := []byte(boundary) - bodyBuffer := &bytes.Buffer{} - for { - line, _, err := reader.ReadLine() - if err != nil { - return tee, bytes.NewReader(bodyBuffer.Bytes()) - } - if bytes.HasPrefix(line, byteBoundary) { - break - } - } - for { - line, _, err := reader.ReadLine() - if err != nil { - return tee, bytes.NewReader(bodyBuffer.Bytes()) - } - if bytes.HasPrefix(line, byteBoundary) { - break - } - bodyBuffer.Write(line) - } - ioutil.ReadAll(reader) - return tee, bytes.NewReader(bodyBuffer.Bytes()) -} - -func getMultipartParts(r io.Reader, params map[string]string) (parts []io.Reader, headers []textproto.MIMEHeader, err error) { - mr := multipart.NewReader(r, params["boundary"]) - parts = []io.Reader{} - headers = []textproto.MIMEHeader{} - var p *multipart.Part - for { - p, err = mr.NextPart() - if err == io.EOF { - err = nil - break - } - if err != nil { - return - } - b, _ := ioutil.ReadAll(p) - buffer := bytes.NewBuffer(b) - - parts = append(parts, buffer) - headers = append(headers, p.Header) - } - return -} - -func verifyMime(body io.Reader, bodyHeader textproto.MIMEHeader, signature io.Reader) (err error) { - rawData, err := ioutil.ReadAll(body) - if err != nil { - return err - } - - decodedBodyStream := pmmime.DecodeContentEncoding(bytes.NewReader(rawData), bodyHeader.Get("Content-Transfer-Encoding")) - -} - -func (sc *SignatureCollector) Accept(part io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { - parentMediaType, params, _ := mime.ParseMediaType(header.Get("Content-Type")) - if parentMediaType == "multipart/signed" { - newPart, rawBody := getRawMimePart(part, params["boundary"]) - var multiparts []io.Reader - var multipartHeaders []textproto.MIMEHeader - if multiparts, multipartHeaders, err = getMultipartParts(newPart, params); err != nil { - return - } else { - hasPlainChild := false - for _, header := range multipartHeaders { - mediaType, _, _ := mime.ParseMediaType(header.Get("Content-Type")) - if mediaType == "text/plain" { - hasPlainChild = true - } - } - if len(multiparts) != 2 { - sc.verified = notSigned - // Invalid multipart/signed format just pass along - ioutil.ReadAll(rawBody) - for i, p := range multiparts { - if err = sc.target.Accept(p, multipartHeaders[i], hasPlainChild, true, true); err != nil { - return - } - } - return - } - - // actual multipart/signed format - err = sc.target.Accept(multiparts[0], multipartHeaders[0], hasPlainChild, true, true) - if err != nil { - return - } - partData, _ := ioutil.ReadAll(multiparts[1]) - decodedPart := pmmime.DecodeContentEncoding(bytes.NewReader(partData), multipartHeaders[1].Get("Content-Transfer-Encoding")) - buffer, err := ioutil.ReadAll(decodedPart) - if err != nil { - return err - } - buffer, err = pmmime.DecodeCharset(buffer, params) - if err != nil { - return err - } - sc.signature = string(buffer) - - _, err = openpgp.CheckArmoredDetachedSignature(sc.keyring, rawBody, bytes.NewReader(buffer), &sc.config) - - if err != nil { - sc.verified = failed - } else { - sc.verified = ok - } - return nil - } - return - } - sc.target.Accept(part, header, hasPlainSibling, isFirst, isLast) - return nil -} - - -func (ac SignatureCollector) GetSignature() string { - return ac.signature -} - -func (openpgp OpenPGP) ParseMIME(mimeBody string, verifierKey []byte) (body *pmmime.BodyCollector, atts, attHeaders []string, err error) { +func (o OpenPGP) parseMIME(mimeBody string, verifierKey []byte) (*pmmime.BodyCollector, int, []string, []string, error) { + privKey := bytes.NewReader(verifierKey) + privKeyEntries, err := openpgp.ReadKeyRing(privKey) mm, err := mail.ReadMessage(strings.NewReader(mimeBody)) if err != nil { - return + return nil, 0, nil, nil, err } - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: openpgp.getTimeGenerator() } + config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: o.getTimeGenerator()} h := textproto.MIMEHeader(mm.Header) mmBodyData, err := ioutil.ReadAll(mm.Body) @@ -194,93 +35,15 @@ func (openpgp OpenPGP) ParseMIME(mimeBody string, verifierKey []byte) (body *pmm bodyCollector := pmmime.NewBodyCollector(printAccepter) attachmentsCollector := pmmime.NewAttachmentsCollector(bodyCollector) mimeVisitor := pmmime.NewMimeVisitor(attachmentsCollector) - signatureCollector := NewSignatureCollector(config, mimeVisitor) + signatureCollector := NewSignatureCollector(mimeVisitor, privKeyEntries, config) err = pmmime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector) - body = bodyCollector - atts = attachmentsCollector.GetAttachments() - attHeaders = attachmentsCollector.GetAttHeaders() + verified := signatureCollector.verified + body := bodyCollector + atts := attachmentsCollector.GetAttachments() + attHeaders := attachmentsCollector.GetAttHeaders() - return -} - -/* - -func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) { - var issuerKeyId uint64 - var hashFunc crypto.Hash - var sigType packet.SignatureType - var keys []Key - var p packet.Packet - - packets := packet.NewReader(signature) - for { - p, err = packets.Next() - if err == io.EOF { - return nil, errors.ErrUnknownIssuer - } - if err != nil { - return nil, err - } - - switch sig := p.(type) { - case *packet.Signature: - if sig.IssuerKeyId == nil { - return nil, errors.StructuralError("signature doesn't have an issuer") - } - issuerKeyId = *sig.IssuerKeyId - hashFunc = sig.Hash - sigType = sig.SigType - case *packet.SignatureV3: - issuerKeyId = sig.IssuerKeyId - hashFunc = sig.Hash - sigType = sig.SigType - default: - return nil, errors.StructuralError("non signature packet found") - } - - keys = keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign) - if len(keys) > 0 { - break - } - } - - if len(keys) == 0 { - panic("unreachable") - } - - h, wrappedHash, err := hashForSignature(hashFunc, sigType) - if err != nil { - return nil, err - } - - if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF { - return nil, err - } - - for _, key := range keys { - switch sig := p.(type) { - case *packet.Signature: - err = key.PublicKey.VerifySignature(h, sig) - if err == nil && sig.KeyExpired(config.Now()) { - err = errors.ErrSignatureExpired - } - case *packet.SignatureV3: - err = key.PublicKey.VerifySignatureV3(h, sig) - default: - panic("unreachable") - } - - if err == errors.ErrSignatureExpired { - return key.Entity, err - } - - if err == nil { - return key.Entity, nil - } - } - - return nil, err + return body, verified, atts, attHeaders, nil } // define call back interface @@ -289,21 +52,22 @@ type MIMECallbacks interface { onAttachment(headers string, data []byte) // Encrypted headers can be an attachment and thus be placed at the end of the mime structure onEncryptedHeaders(headers string) -} -func (o *OpenPGP) DecryptMessageVerifyBinKeyPrivbinkeys(encryptedText string, veriferKey []byte, privateKeys []byte, passphrase string, verifyTime int64) (*DecryptSignedVerify, error) { - return o.decryptMessageVerifyAllBin(encryptedText, veriferKey, privateKeys, passphrase, verifyTime) + onVerified(verified int) + onError(err error) } -func (o *OpenPGP) decryptMIMEMessage(encryptedText string, verifierKey string, privateKeys []byte, - passphrase string, callbacks MIMECallbacks, verifyTime int64) (verifier int, err error) { - decsignverify, err := o.DecryptMessageVerifyPrivbinkeys(encryptedText, verifierKey, privateKeys, passphrase, verifyTime) - if (err != nil) { - return 0, err +func (o *OpenPGP) decryptMIMEMessage(encryptedText string, verifierKey []byte, privateKeys []byte, + passphrase string, callbacks MIMECallbacks, verifyTime int64) { + decsignverify, err := o.decryptMessageVerifyAllBin(encryptedText, verifierKey, privateKeys, passphrase, verifyTime) + if err != nil { + callbacks.onError(err) + return } - body, attachments, attachmentHeaders, err := parseMIME(decsignverify.Plaintext) - if (err != nil) { - return 0, err + body, verified, attachments, attachmentHeaders, err := o.parseMIME(decsignverify.Plaintext, verifierKey) + if err != nil { + callbacks.onError(err) + return } bodyContent, bodyMimeType := body.GetBody() callbacks.onBody(bodyContent, bodyMimeType) @@ -311,8 +75,9 @@ func (o *OpenPGP) decryptMIMEMessage(encryptedText string, verifierKey string, p callbacks.onAttachment(attachmentHeaders[i], []byte(attachments[i])) } callbacks.onEncryptedHeaders("") - - // Todo verify the signature included in the attachment - - return verifier, nil -}*/ \ No newline at end of file + if decsignverify.Verify == notSigned { + callbacks.onVerified(verified) + } else { + callbacks.onVerified(decsignverify.Verify) + } +} \ No newline at end of file diff --git a/mime_test.go b/mime_test.go index 672b158..a700f08 100644 --- a/mime_test.go +++ b/mime_test.go @@ -3,7 +3,268 @@ package pmcrypto import ( "testing" "fmt" - ) + "io/ioutil" +) + +const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +OQTmvM6R +=DvFv +-----END PGP PUBLIC KEY BLOCK-----` +const testMessage = `-----BEGIN PGP MESSAGE----- +Version: ProtonMail +Comment: https://protonmail.com + +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 +LloBMV1YNGsW45M+oQ== +=BRHT +-----END PGP MESSAGE-----` +const privatekeypassword = "test" +const privatekey = ` +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: OpenPGP.js v3.1.2 +Comment: https://openpgpjs.org + +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 +=xWqS +-----END PGP PRIVATE KEY BLOCK----- +` + + +// define call back interface +type Callbacks struct { +} + +func (t *Callbacks) onBody(body string, mimetype string) { + fmt.Println(body) +} +func (t Callbacks) onAttachment(headers string, data []byte) { + fmt.Println(headers) +} +func (t Callbacks) onEncryptedHeaders(headers string) { + fmt.Println(headers) +} +func (t Callbacks) onVerified(verified int) { + fmt.Println(verified) +} +func (t Callbacks) onError(err error) { + fmt.Println(err) +} + +func TestDecrypt(t *testing.T) { + callbacks := Callbacks{} + o := OpenPGP{} + block, _ := unArmor(publicKey) + publicKeyUnarmored, _ := ioutil.ReadAll(block.Body) + block, _ = unArmor(privatekey) + privateKeyUnarmored, _ := ioutil.ReadAll(block.Body) + o.decryptMIMEMessage(testMessage, publicKeyUnarmored, privateKeyUnarmored, privatekeypassword, + &callbacks, o.GetTime()) +} func TestParse(t *testing.T) { testMessage := @@ -56,7 +317,6 @@ X3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+PGJyPjwvZGl2PjwvZGl2PjwvZGl2Pg== -----------------------3ca028eaeffb3ca0fb0dd6461f639c2b-- -----------------------f0e64db835d0f5c3674df52a164b06bb Content-Type: image/png; filename="Screenshot from 2018-02-06 17-13-21.png"; name="Screenshot from 2018-02-06 17-13-21.png" -Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="Screenshot from 2018-02-06 17-13-21.png"; name="Screenshot from 2018-02-06 17-13-21.png" Content-ID: <46098e9b@protonmail.com> @@ -128,7 +388,8 @@ RIzX2CG47PuGl/uvImFW/Iw= -----------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0-- ` - body, atts, attHeaders, err := ParseMIME(testMessage) + o := OpenPGP{} + body, _, atts, attHeaders, err := o.parseMIME(testMessage, nil) if err != nil { t.Error(err) } diff --git a/signature_collector.go b/signature_collector.go new file mode 100644 index 0000000..cf0ffcd --- /dev/null +++ b/signature_collector.go @@ -0,0 +1,161 @@ +package pmcrypto + +import ( + "io" + "net/textproto" + "proton/pmmime" + "golang.org/x/crypto/openpgp/packet" + "golang.org/x/crypto/openpgp" + "io/ioutil" + "bytes" + "bufio" + "mime/multipart" + "mime" + ) + +type SignatureCollector struct { + config *packet.Config + keyring openpgp.KeyRing + target pmmime.VisitAcceptor + signature string + verified int +} + +func NewSignatureCollector(targetAccepter pmmime.VisitAcceptor, keyring openpgp.KeyRing, config *packet.Config) *SignatureCollector { + return &SignatureCollector{ + target: targetAccepter, + config: config, + keyring: keyring, + } +} + +func getRawMimePart(rawdata io.Reader, boundary string) (io.Reader, io.Reader) { + b, _ := ioutil.ReadAll(rawdata) + tee := bytes.NewReader(b) + + reader := bufio.NewReader(bytes.NewReader(b)) + byteBoundary := []byte(boundary) + bodyBuffer := &bytes.Buffer{} + for { + line, _, err := reader.ReadLine() + if err != nil { + return tee, bytes.NewReader(bodyBuffer.Bytes()) + } + if bytes.HasPrefix(line, byteBoundary) { + break + } + } + for { + line, isPrefix, err := reader.ReadLine() + if err != nil { + return tee, bytes.NewReader(bodyBuffer.Bytes()) + } + if bytes.HasPrefix(line, byteBoundary) { + break + } + bodyBuffer.Write(line) + if !isPrefix { + reader.UnreadByte() + reader.UnreadByte() + token, _ := reader.ReadByte() + if token == '\r' { + bodyBuffer.WriteByte(token) + } + bodyBuffer.WriteByte(token) + } + } + ioutil.ReadAll(reader) + data := bodyBuffer.Bytes() + return tee, bytes.NewReader(data[0:len(data) - 1]) +} + +func getMultipartParts(r io.Reader, params map[string]string) (parts []io.Reader, headers []textproto.MIMEHeader, err error) { + mr := multipart.NewReader(r, params["boundary"]) + parts = []io.Reader{} + headers = []textproto.MIMEHeader{} + var p *multipart.Part + for { + p, err = mr.NextPart() + if err == io.EOF { + err = nil + break + } + if err != nil { + return + } + b, _ := ioutil.ReadAll(p) + buffer := bytes.NewBuffer(b) + + parts = append(parts, buffer) + headers = append(headers, p.Header) + } + return +} + +func (sc *SignatureCollector) Accept(part io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { + parentMediaType, params, _ := mime.ParseMediaType(header.Get("Content-Type")) + if parentMediaType == "multipart/signed" { + newPart, rawBody := getRawMimePart(part, "--" + params["boundary"]) + var multiparts []io.Reader + var multipartHeaders []textproto.MIMEHeader + if multiparts, multipartHeaders, err = getMultipartParts(newPart, params); err != nil { + return + } else { + hasPlainChild := false + for _, header := range multipartHeaders { + mediaType, _, _ := mime.ParseMediaType(header.Get("Content-Type")) + if mediaType == "text/plain" { + hasPlainChild = true + } + } + if len(multiparts) != 2 { + sc.verified = notSigned + // Invalid multipart/signed format just pass along + ioutil.ReadAll(rawBody) + for i, p := range multiparts { + if err = sc.target.Accept(p, multipartHeaders[i], hasPlainChild, true, true); err != nil { + return + } + } + return + } + + // actual multipart/signed format + err = sc.target.Accept(multiparts[0], multipartHeaders[0], hasPlainChild, true, true) + if err != nil { + return + } + partData, _ := ioutil.ReadAll(multiparts[1]) + decodedPart := pmmime.DecodeContentEncoding(bytes.NewReader(partData), multipartHeaders[1].Get("Content-Transfer-Encoding")) + buffer, err := ioutil.ReadAll(decodedPart) + if err != nil { + return err + } + buffer, err = pmmime.DecodeCharset(buffer, params) + if err != nil { + return err + } + sc.signature = string(buffer) + + if sc.keyring != nil { + _, err = openpgp.CheckArmoredDetachedSignature(sc.keyring, rawBody, bytes.NewReader(buffer), sc.config) + + if err != nil { + sc.verified = failed + } else { + sc.verified = ok + } + } else { + sc.verified = noVerifier + } + return nil + } + return + } + sc.target.Accept(part, header, hasPlainSibling, isFirst, isLast) + return nil +} + +func (ac SignatureCollector) GetSignature() string { + return ac.signature +}