From dccb3ba50bb7542506271550e375d6603d757ccd Mon Sep 17 00:00:00 2001 From: Kay Lukas Date: Tue, 4 Sep 2018 20:27:29 +0200 Subject: [PATCH] Implement first version of signature collector --- glide.lock | 10 ++-- glide.yaml | 3 +- mime.go | 141 +++++++++++++++++++++++++++++++++++++++++------ mime_test.go | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+), 22 deletions(-) create mode 100644 mime_test.go diff --git a/glide.lock b/glide.lock index fea7de2..56311c1 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 834f29378b80e2f7486b79420679fb1d854c16f2989574090ba80d322a7b71c5 -updated: 2018-09-02T14:56:08.452174+02:00 +hash: 4251e23b87e24d3580705ee49d6354cbd956d2ecc254bd9cb2037d5fd6f34d48 +updated: 2018-09-04T20:05:07.075614+02:00 imports: - name: github.com/Sirupsen/logrus version: 78fa2915c1fa231f62e0438da493688c21ca678e @@ -31,7 +31,7 @@ imports: - rsa - ssh/terminal - name: golang.org/x/sys - version: fa5fdf94c78965f1aa8423f0cc50b8b8d728b05a + version: 2b024373dcd9800f0cae693839fac6ede8d64a8c subpackages: - unix - windows @@ -43,7 +43,7 @@ imports: - encoding/internal - encoding/internal/identifier - transform -- name: mimeparser - version: f4de8a5a52ecd93189c785c7d87259ac637e1d7c +- name: proton/pmmime + version: f64ddc9969090ae55c92712def8289587816dc11 repo: git@gitlab.protontech.ch:ProtonMail/go-pm-mime.git testImports: [] diff --git a/glide.yaml b/glide.yaml index 95ef39c..1a3e42a 100644 --- a/glide.yaml +++ b/glide.yaml @@ -3,5 +3,6 @@ import: - package: golang.org/x/crypto version: v1.0.0 repo: https://github.com/ProtonMail/crypto.git -- package: mimeparser +- package: proton/pmmime repo: git@gitlab.protontech.ch:ProtonMail/go-pm-mime.git + version: feat/mimevisitor diff --git a/mime.go b/mime.go index be8e5d9..2b61b5e 100644 --- a/mime.go +++ b/mime.go @@ -6,10 +6,117 @@ import ( "net/textproto" "io/ioutil" "bytes" - "mimeparser" + "proton/pmmime" + "io" + log "github.com/Sirupsen/logrus" + "mime" + "mime/multipart" ) -func parseMIME(mimeBody string) (body *mimeparser.BodyCollector, atts, attHeaders []string, err error) { + + +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 { + target pmmime.VisitAcceptor + signature string +} + +func NewSignatureCollector(targetAccepter pmmime.VisitAcceptor) *SignatureCollector { + return &SignatureCollector{ + target: targetAccepter, + } +} + +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" { + var multiparts []io.Reader + var multipartHeaders []textproto.MIMEHeader + if multiparts, multipartHeaders, err = getMultipartParts(part, 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 { + // Invalid multipart/signed format just pass along + 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), header.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) + return err + } + return + } + sc.target.Accept(part, header, hasPlainSibling, isFirst, isLast) + return nil +} + + +func (ac SignatureCollector) GetSignature() string { + return ac.signature +} + + +func ParseMIME(mimeBody string) (body *pmmime.BodyCollector, atts, attHeaders []string, err error) { mm, err := mail.ReadMessage(strings.NewReader(mimeBody)) if err != nil { @@ -19,10 +126,12 @@ func parseMIME(mimeBody string) (body *mimeparser.BodyCollector, atts, attHeader h := textproto.MIMEHeader(mm.Header) mmBodyData, err := ioutil.ReadAll(mm.Body) - printAccepter := mimeparser.NewMIMEPrinter() - bodyCollector := mimeparser.NewBodyCollector(printAccepter) - attachmentsCollector := mimeparser.NewAttachmentsCollector(bodyCollector) - err = mimeparser.VisitAll(bytes.NewReader(mmBodyData), h, attachmentsCollector) + printAccepter := pmmime.NewMIMEPrinter() + bodyCollector := pmmime.NewBodyCollector(printAccepter) + attachmentsCollector := pmmime.NewAttachmentsCollector(bodyCollector) + mimeVisitor := pmmime.NewMimeVisitor(attachmentsCollector) + signatureCollector := NewSignatureCollector(mimeVisitor) + err = pmmime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector) body = bodyCollector atts = attachmentsCollector.GetAttachments() @@ -31,7 +140,7 @@ func parseMIME(mimeBody string) (body *mimeparser.BodyCollector, atts, attHeader return } - +/* // define call back interface type MIMECallbacks interface { @@ -42,16 +151,16 @@ type MIMECallbacks interface { } func (o *OpenPGP) decryptMIMEMessage(encryptedText string, verifierKey string, privateKeys []byte, - passphrase string, callbacks MIMECallbacks, verifyTime int64) (verifier int, err error) { - decsignverify, error := o.DecryptMessageVerifyPrivbinkeys(encryptedText, verifierKey, privateKeys, passphrase, verifyTime) - if (error != nil) { - return 0, error + 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 } - body, attachments, attachmentHeaders, error := parseMIME(decsignverify.Plaintext - if (error != nil) { - return 0, error - }) + body, attachments, attachmentHeaders, err := parseMIME(decsignverify.Plaintext) + if (err != nil) { + return 0, err + } bodyContent, bodyMimeType := body.GetBody() callbacks.onBody(bodyContent, bodyMimeType) for i := 0; i < len(attachments); i++ { @@ -62,4 +171,4 @@ func (o *OpenPGP) decryptMIMEMessage(encryptedText string, verifierKey string, p // Todo verify the signature included in the attachment return verifier, nil -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/mime_test.go b/mime_test.go new file mode 100644 index 0000000..672b158 --- /dev/null +++ b/mime_test.go @@ -0,0 +1,151 @@ +package pmcrypto + +import ( + "testing" + "fmt" + ) + +func TestParse(t *testing.T) { + testMessage := + `Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg=pgp-sha256; boundary="---------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0"; charset=UTF-8 +X-Spam-Status: No, score=1.2 required=7.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_ENVFROM_END_DIGIT,FREEMAIL_FROM, FREEMAIL_REPLYTO_END_DIGIT,HTML_IMAGE_ONLY_08,HTML_MESSAGE autolearn=no autolearn_force=no version=3.4.0 +X-Spam-Level: * +X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on mail.protonmail.ch + +-----------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0 +Content-Type: multipart/mixed; boundary="---------------------f0e64db835d0f5c3674df52a164b06bb" + +-----------------------f0e64db835d0f5c3674df52a164b06bb +Content-Type: multipart/alternative; boundary="---------------------3ca028eaeffb3ca0fb0dd6461f639c2b" + +-----------------------3ca028eaeffb3ca0fb0dd6461f639c2b +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain;charset=utf-8 + + + + +[Screenshot from 2018-02-06 17-13-21.png] + +--=C2=A0 +Julien Palard +https://mdk.fr + + +-----------------------3ca028eaeffb3ca0fb0dd6461f639c2b +Content-Type: multipart/related; boundary="---------------------ce717c368c2d3981c954ad7c46cd7bf2" + +-----------------------ce717c368c2d3981c954ad7c46cd7bf2 +Content-Type: text/html;charset=utf-8 +Content-Transfer-Encoding: base64 + +PGRpdj48ZGl2Pjxicj48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2IGNsYXNzPSJwcm90b25tYWls +X3NpZ25hdHVyZV9ibG9jayI+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2st +dXNlciI+PGRpdj48aW1nIHNyYz0iY2lkOjQ2MDk4ZTliQHByb3Rvbm1haWwuY29tIiBjbGFzcz0i +cHJvdG9uLWVtYmVkZGVkIiBhbHQ9IlNjcmVlbnNob3QgZnJvbSAyMDE4LTAyLTA2IDE3LTEzLTIx +LnBuZyI+PGJyPjwvZGl2PjxkaXY+LS0mbmJzcDs8YnI+PC9kaXY+PGRpdj5KdWxpZW4gUGFsYXJk +PGJyPjwvZGl2PjxkaXY+PGNvZGUgc3R5bGU9ImZvbnQtZmFtaWx5OidTRk1vbm8tUmVndWxhcics +IENvbnNvbGFzLCAnTGliZXJhdGlvbiBNb25vJywgTWVubG8sIENvdXJpZXIsIG1vbm9zcGFjZTtm +b250LXNpemU6MTEuODk5OTk5NjE4NTMwMjczcHg7cGFkZGluZzowcHg7bWFyZ2luOjBweDtiYWNr +Z3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O3doaXRlLXNwYWNlOnByZTtib3JkZXI6MHB4O2Rpc3Bs +YXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7bGluZS1oZWlnaHQ6aW5oZXJpdDsiPjxhIGhyZWY9 +Imh0dHBzOi8vbWRrLmZyIj5odHRwczovL21kay5mcjwvYT48L2NvZGU+PGJyPjwvZGl2PjwvZGl2 +PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXByb3RvbiBwcm90b25tYWls +X3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+PGJyPjwvZGl2PjwvZGl2PjwvZGl2Pg== +-----------------------ce717c368c2d3981c954ad7c46cd7bf2-- +-----------------------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> + +wsBcBAEBCAAQBQJbjodcCRDdVS3pfakUGgAAlhoH/2jBRjOSx5EdkiyyNcCT +4DVm+ACoF1KTWE5fLRuDPvSiD934cFoZShJs32r0Wcj/4W4tVhLYzjjtf6xO +Ymqe0p3o4oYxxMXIAd4COrnOPGjeD1ausqT6iUCAadqXoDYfowEg4f0Wd0RK +ElsP/OZaDjsNoRE3WeRcHTr5XWZxhEsIMgnW591iaTliYvbysLoQ08i3G53c +p6q+IANRznx5rDhMdo+shFvhcI2Zszg6X2WuCMhFtUyrqEN8WlZYXMX8PGPO +1kNDRl7B7/r4Ap+FffLpeYw+8rG6lQXGc3RCOAnfMq9X/9Ziqzxr7flYtRJH +RIzX2CG47PuGl/uvImFW/Iw= + +-----------------------f0e64db835d0f5c3674df52a164b06bb +Content-Type: application/pgp-keys; filename="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc"; name="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc"; name="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc" + +LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tDQpWZXJzaW9uOiBPcGVuUEdQLmpz +IHYzLjEuMg0KQ29tbWVudDogaHR0cHM6Ly9vcGVucGdwanMub3JnDQoNCnhzQk5CRnQ2N2I4QkNB +QzBTMlU3VUJ3dnkyUnZqcEpwOHlyMlE0R25oNzk0QjUwVG1TMlViTXJRaEovaQpUWFRTZmJhb21l +NkdkcmJhSjlvVW0xY2hhMU5hVlNnWHE0RVB2WWxmSWVVdkt1U0tnTVdxaVMzT2xSdGYKT3JwMVZp +dXNpZWtldVczL2JtYUZBbzFLQWZvajBMNlF6cVVIY3ZZNnY1RExoWGV2MWlaUW9TN2lrQk9PCmor +Z05lUDJabjVIOXdQTFlxdWlqK0pnWFY4djRRQk5uNU82NUNYRkZIbjl1Mzl1Yng2bDJtY2tqaDVm +VQppU1FiUkh2RzhIdG96Y2xIcWV1bjBpanNjZEtoOTBsaENsWHlDcHlkZ2JwT2pjL2dDQ2R3WmVh +djNNSG0KZnhsd2VReDhmc0xxZ1h5TlNFd3pyODh0MTFWMFQ4TmRjeFdia1lRNllQN2R4UU10RkU0 +bWM5akxBQkVCCkFBSE5PU0pyWVhsclpYbDBaWE4wTTBCd2NtOTBiMjV0WVdsc0xtTnZiU0lnUEd0 +aGVXdGxlWFJsYzNRegpRSEJ5YjNSdmJtMWhhV3d1WTI5dFBzTEFkUVFRQVFnQUtRVUNXM3J0dndZ +TENRY0lBd0lKRU4xVkxlbDkKcVJRYUJCVUlDZ0lERmdJQkFoa0JBaHNEQWg0QkFBQ0dEZ2Y5SHVq +aG9QUGN5SHpLd25JbFJJaENRL1dLCm13SVVFdEw4eU03aGRvci9zK0kyL1A3WkJHWE1ReXBKb3JD +YTU1NUY2ODRMZnpWdUtBUVFMdXZpNHR6aQo5N2JSNE1ldHY4a2ptSUI1Rk0vNUhpWllWd090SDFK +dU5iM2RQWG9CVjJha09ScnFCeVRjbjEyeUIwOVkKd0tZR0Y4K25XNFhLU1F2V3VBWWVIZ0dQKzMz +ZTdCc0VvODhuRmJJUXl2cmUzUTN0TkJrWTVVVnA2L1M2ClpLZWJwaEFRQVlzM0ZVVlZVeThlZkhr +aEFmZFlSdUFBUUFoWnJGanU0V1J0RGZkUHdLR0UzZDZsUGswSApNSFNTUXNHb2thQTVoTnUycE1S +Mnl5UmtUa3JyNnRXbmlET2ZvSmtKcmc2VllHNVRwaHZnOE43dVdzZHQKZkhDdDBwWW9uYTRzclZx +a0RzN0FUUVJiZXUyL0FRZ0F6b2tCSGZucnFXUXE4RUVSYUVlOUpmZkQ3V0FpCnZyU29lNDdxQ05l +S2I2MmcvLzh0Q1dKMHVlRExTMFRyY0V4cnVzUi8rSFdoYUFTV2srL0lPTGhjRTcrVgp4V2Nab0dp +bzNuc1puTktWdnFhendNSW1vMnFqdUllUnVCVkJqRW5OOERhOXlxNkJ3YVkvSEs2czM5Y3IKQVVV +d2FtWjVuMDNtSFR6MzBMQllYR1VldVc5c3Z4NDIzbTlXb3N2WlQvUWRKWW1qMG44NmpGNGs2Tnh2 +ClV4ejR1bmV1YWt1Wld5NmNURHg1WFdWVWF1YzlyV1Z5Uzk3cjJ0NllpRlF6MTFiOU5oZm4rcDVs +UmdZbgpzMzQwT1MyVmppK1BVeUVKZ2YzYi9MQlJ5V3Erbk1Vc2lRV09yYlQxblVCVzNRdDViWWhH +MUhnT1o5eEoKVjd2dndSZHFwQ1p2cVFBUkFRQUJ3c0JmQkJnQkNBQVRCUUpiZXUyL0NSRGRWUzNw +ZmFrVUdnSWJEQUFBClhrTUgvaTBoTnRwRWIrYUZONC9Ba0JCam1GOFJZV243anRyK2UyU05STHdF +RUMxcmdmakhjWXZnanJFTQo2Y3hBcXVzSWJKSEdnSUVYZ0s1YUlNOHBGLzRuamN2VXVxa0x1b1o3 +QVJsanpwUTcvaHhyc0FNWnFYR2gKeEU4eXJZVlY0dGhhZWw0Q1NRUTRvRWlpbXB4Z28velE5bzhk +NGNQUTM4Ni9VNEgvMm9OczFUMElCOTZWCkJsTy9pM0w5eGM4K2w4RG8vcm5ieVV1Ym9LUVNKZmtp +RXNLMkRabEZoOVArdVltQ1AzVGJSb2NIUFZtVwpBT3NHVVhJSXROaVNTc05ORUlHeVBSNE8rMXRq +VUJPMFNHRnZoVVZUNnJLYTlaYUVyMzI2ZmU2S0pmZU8KYTl4a21WZEdaQm9SdENmbHhiakdnYjRq +dTJ3Z1E1TE5KWUNWZy9WRkxIRGI3MjQ9DQo9YmNvMw0KLS0tLS1FTkQgUEdQIFBVQkxJQyBLRVkg +QkxPQ0stLS0tLQ0KDQo= +-----------------------f0e64db835d0f5c3674df52a164b06bb-- +-----------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0 +Content-Type: application/pgp-signature; name="signature.asc" +Content-Description: OpenPGP digital signature +Content-Disposition: attachment; filename="signature.asc" + +-----BEGIN PGP SIGNATURE----- + Version: ProtonMail +Comment: https://protonmail.com + +wsBcBAEBCAAQBQJbjodcCRDdVS3pfakUGgAAlhoH/2jBRjOSx5EdkiyyNcCT +4DVm+ACoF1KTWE5fLRuDPvSiD934cFoZShJs32r0Wcj/4W4tVhLYzjjtf6xO +Ymqe0p3o4oYxxMXIAd4COrnOPGjeD1ausqT6iUCAadqXoDYfowEg4f0Wd0RK +ElsP/OZaDjsNoRE3WeRcHTr5XWZxhEsIMgnW591iaTliYvbysLoQ08i3G53c +p6q+IANRznx5rDhMdo+shFvhcI2Zszg6X2WuCMhFtUyrqEN8WlZYXMX8PGPO +1kNDRl7B7/r4Ap+FffLpeYw+8rG6lQXGc3RCOAnfMq9X/9Ziqzxr7flYtRJH +RIzX2CG47PuGl/uvImFW/Iw= +=t7ak +-----END PGP SIGNATURE----- + + +-----------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0-- +` + body, atts, attHeaders, err := ParseMIME(testMessage) + if err != nil { + t.Error(err) + } + + fmt.Println() + fmt.Println("==BODY:") + fmt.Println(body.GetBody()) + fmt.Println("==BODY HEADERS:") + fmt.Println(body.GetHeaders()) + + fmt.Println("==ATTACHMENTS:") + fmt.Println(len(atts)) + for i, attachment := range atts { + fmt.Println("==ATTACHMENT HEADERS:") + fmt.Println(attHeaders[i]) + fmt.Println("==ATTACHMENT:") + fmt.Println(attachment) + } +} +