go-pgpmail-0.2.0/0000755000175000017500000000000014152654154013102 5ustar nileshnileshgo-pgpmail-0.2.0/go.sum0000644000175000017500000000400114152654154014230 0ustar nileshnileshgithub.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 h1:XcF0cTDJeiuZ5NU8w7WUDge0HRwwNRmxj/GGk6KSA6g= github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/emersion/go-message v0.15.0 h1:urgKGqt2JAc9NFJcgncQcohHdiYb803YTH9OQwHBHIY= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= go-pgpmail-0.2.0/README.md0000644000175000017500000000052214152654154014360 0ustar nileshnilesh# go-pgpmail [![godocs.io](https://godocs.io/github.com/emersion/go-pgpmail?status.svg)](https://godocs.io/github.com/emersion/go-pgpmail) [![builds.sr.ht status](https://builds.sr.ht/~emersion/go-pgpmail/commits.svg)](https://builds.sr.ht/~emersion/go-pgpmail/commits?) A mail library that encrypts messages with PGP. ## License MIT go-pgpmail-0.2.0/pgpmail_data_test.go0000644000175000017500000001241314152654154017113 0ustar nileshnileshpackage pgpmail const testPrivateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK----- 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 +5anTnUn =gemU -----END PGP PRIVATE KEY BLOCK----- ` const testPublicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBF5FJf8BCACvlKhSSsv4P8C3Wbv391SrNUBtFquoMuWKtuCr/Ks6KHuofGLn bM55uBSQp908aITBDPkaOPsQ3OvwgF7SM8bNIDVpO7FHzCEg2Ysp99iPET/+LsbY ugc8oYSuvA5aFFIOMYbAbI+HmbIBuCs+xp0AcU1cemAPzPBDCZs4xl5Y+/ce2yQz ZGK9O/tQQIKoBUOWLo/0byAWyD6Gwn/Le3fVxxK6RPeeizDV6VfzHLxhxBNkMgmd QUkBkvqF154wYxhzsHn72ushbJpspKz1LQN7d5u6QOq3h2sLwcLbT457qbMfZsZs HLhoOibOd+yJ7C6TRbbyC4sQRr+K1CNGcvhJABEBAAG0NEpvaG4gRG9lIChUaGlz IGlzIGEgdGVzdCBrZXkpIDxqb2huLmRvZUBleGFtcGxlLm9yZz6JAU4EEwEIADgW IQSxqGaTVBU7eZ8iF78wchXBPfepZAUCXkUl/wIbAwULCQgHAgYVCgkICwIEFgID AQIeAQIXgAAKCRAwchXBPfepZF4iB/49B7q4AfO3xHEa8LK2H+f7Mnm4dRfS2YPo v2p6TRe1h2DxwpTevNQUhXw2U0nfRIEKBAZqgb7NVktkoh0DWtKatms2yHMAS+ah lQoHb2gRgXa9M9Tq0x5u9sl0NYnx7Wu5uu6Ybw9luPKoAfO91T0vei0p3eMn3fIV 0O012ITvmgKJPppQDKFJHGZJMbVDO4TNxP89HgyhB41RO7AZadvu73S00x2K6x+O R4s/++4Y98vScCPm3DUOXeoHXKGqFcNYTxJL9bsE2I0uYgvJSxNoK1dVnmvxp3zz hcxAdzizgMz0ufY6YLMCjy5MDOzPARkmYPXdkJ6jceOIqGLUw1kquQENBF5FJf8B CACpsh5cyHB7eEwQvLzJVsXpTW0Rh/Fe36AwC2Vz13WeE6GFrOvw1qATvtYB1919 M4B44YH9J7I5SrFZad86Aw4n5Gi0BwLlGNa/oCMvYzlNHaTXURA271ghJqdZizqV UETj3WNoaYm4mYMfb0dcayDJvVPWP7InzOsdIRU9WXBUSyVMxNMXccr2UvIuhdPg lmVT8NtsWR+q8xBoL2Dp0ojYLVD3MlwKe1pE5mEwasYCkWePLWyGdfDW1MhUDsPH 3K1IjpPLWU9FBk8KM4z8WooY9/kyMIyRw39MvOHGfgcFBpiZwlELNZGSFhbRun03 PMk2Qd3k+0FGV1IhFAYsr7QRABEBAAGJATYEGAEIACAWIQSxqGaTVBU7eZ8iF78w chXBPfepZAUCXkUl/wIbDAAKCRAwchXBPfepZOtqB/9xsGEgQgm70KYID39H91k4 ef/RlpRDY1ndC0MoPfqE03IEXTC/MjtU+ksPKEoZeQsxVaUJ2WBueI5WGJ3Y73pO HAd7N0SyGHT5s6gK1FSx29be1qiPwUu5KR2jpm3RjgpbymnOWe4C6iiYCFQ85IX+ LzpE+p9bB02PUrmzOb4MBV6E5mg30UjXIX01+bwZq5XSB4/FaUrQOAxLuRvVRjK0 CEcFbPGIlkPSW6s4M9xCC2sQi7caFKVK6Zqf78KbOwAHqfS0x9u2jtTIhsgCjGTI AOQ5lNwpLEMjwLias6e5sM6hcK9Wo+A9Sw23f8lMau5clOZTJeyAUAff+5anTnUn =ZjQT -----END PGP PUBLIC KEY BLOCK----- ` go-pgpmail-0.2.0/writer.go0000644000175000017500000001563014152654154014752 0ustar nileshnileshpackage pgpmail import ( "bufio" "bytes" "fmt" "io" "mime" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/emersion/go-message/textproto" "golang.org/x/text/transform" ) // for tests var forceBoundary = "" type multiCloser []io.Closer func (mc multiCloser) Close() error { for _, c := range mc { if err := c.Close(); err != nil { return err } } return nil } func Encrypt(w io.Writer, h textproto.Header, to []*openpgp.Entity, signed *openpgp.Entity, config *packet.Config) (io.WriteCloser, error) { mw := textproto.NewMultipartWriter(w) if forceBoundary != "" { mw.SetBoundary(forceBoundary) } params := map[string]string{ "boundary": mw.Boundary(), "protocol": "application/pgp-encrypted", } h.Set("Content-Type", mime.FormatMediaType("multipart/encrypted", params)) if err := textproto.WriteHeader(w, h); err != nil { return nil, err } var controlHeader textproto.Header controlHeader.Set("Content-Type", "application/pgp-encrypted") controlWriter, err := mw.CreatePart(controlHeader) if err != nil { return nil, err } if _, err := controlWriter.Write([]byte("Version: 1\r\n")); err != nil { return nil, err } var encryptedHeader textproto.Header encryptedHeader.Set("Content-Type", "application/octet-stream") encryptedWriter, err := mw.CreatePart(encryptedHeader) if err != nil { return nil, err } // armor uses LF lines endings, but we need CRLF crlfWriter := transform.NewWriter(encryptedWriter, &crlfTransformer{}) armorWriter, err := armor.Encode(crlfWriter, "PGP MESSAGE", nil) if err != nil { return nil, err } plaintext, err := openpgp.EncryptText(armorWriter, to, signed, nil, config) if err != nil { return nil, err } return struct { io.Writer io.Closer }{ plaintext, multiCloser{ plaintext, armorWriter, mw, }, }, nil } type signer struct { io.Writer pw *io.PipeWriter done <-chan error sigBuf bytes.Buffer mw *textproto.MultipartWriter closed bool } func (s *signer) Close() error { if s.closed { return fmt.Errorf("pgpmail: signer already closed") } s.closed = true // Close the pipe to let openpgp.DetachSign finish if err := s.pw.Close(); err != nil { return err } if err := <-s.done; err != nil { return err } // At this point s.sigBuf contains the complete signature var sigHeader textproto.Header sigHeader.Set("Content-Type", "application/pgp-signature") sigWriter, err := s.mw.CreatePart(sigHeader) if err != nil { return err } // armor uses LF lines endings, but we need CRLF crlfWriter := transform.NewWriter(sigWriter, &crlfTransformer{}) armorWriter, err := armor.Encode(crlfWriter, "PGP MESSAGE", nil) if err != nil { return err } if _, err := io.Copy(armorWriter, &s.sigBuf); err != nil { return err } if err := armorWriter.Close(); err != nil { return err } return s.mw.Close() } func Sign(w io.Writer, header textproto.Header, signed *openpgp.Entity, config *packet.Config) (io.WriteCloser, error) { // We need to grab the header written to the returned io.WriteCloser, then // use it to create a new part in the multipart/signed message mw := textproto.NewMultipartWriter(w) if forceBoundary != "" { mw.SetBoundary(forceBoundary) } var micalg string for name, hash := range hashAlgs { if hash == config.Hash() { micalg = name break } } if micalg == "" { return nil, fmt.Errorf("pgpmail: unknown hash algorithm %v", config.Hash()) } params := map[string]string{ "boundary": mw.Boundary(), "protocol": "application/pgp-signature", "micalg": micalg, } header.Set("Content-Type", mime.FormatMediaType("multipart/signed", params)) if err := textproto.WriteHeader(w, header); err != nil { return nil, err } handleHeader := func(signedHeader textproto.Header) (io.WriteCloser, error) { signedWriter, err := mw.CreatePart(signedHeader) if err != nil { return nil, err } // TODO: canonicalize text written to signedWriter pr, pw := io.Pipe() done := make(chan error, 1) s := &signer{ Writer: io.MultiWriter(pw, signedWriter), pw: pw, done: done, mw: mw, } go func() { err := openpgp.DetachSignText(&s.sigBuf, signed, pr, config) // Close the pipe to make sure textproto.WriteHeader doesn't block pr.CloseWithError(err) done <- err }() if err := textproto.WriteHeader(pw, signedHeader); err != nil { pw.Close() return nil, err } return s, nil } return &headerWriter{handle: handleHeader}, nil } var ( doubleCRLF = []byte("\r\n\r\n") doubleLF = []byte("\n\n") ) func hasDoubleCRLFSuffix(b []byte) bool { return bytes.HasSuffix(b, doubleCRLF) || bytes.HasSuffix(b, doubleLF) } // headerWriter collects a header written to itself, calls handle, and writes the // body to the returned io.WriteCloser. // // If handle returns an io.WriteCloser, its Close method is guaranteed to be // called when the headerWriter is closed. type headerWriter struct { handle func(textproto.Header) (io.WriteCloser, error) headerBuf bytes.Buffer headerComplete bool bodyWriter io.WriteCloser err error } func (hw *headerWriter) Write(buf []byte) (int, error) { if hw.headerComplete { if hw.err != nil { return 0, hw.err } return hw.bodyWriter.Write(buf) } hw.headerBuf.Grow(len(buf)) gotDoubleCRLF := false N := 0 for _, b := range buf { hw.headerBuf.WriteByte(b) N++ if b == '\n' && hasDoubleCRLFSuffix(hw.headerBuf.Bytes()) { gotDoubleCRLF = true break } } if gotDoubleCRLF { if err := hw.parseHeader(); err != nil { return N, err } n, err := hw.bodyWriter.Write(buf[N:]) return N + n, err } return N, nil } func (hw *headerWriter) Close() error { // Ensure we always close the underlying io.WriterCloser, to avoid leaking // resources if hw.bodyWriter != nil { defer hw.bodyWriter.Close() } if !hw.headerComplete { if err := hw.parseHeader(); err != nil { return err } } if hw.err != nil { return hw.err } return hw.bodyWriter.Close() } func (hw *headerWriter) parseHeader() error { hw.headerComplete = true h, err := textproto.ReadHeader(bufio.NewReader(&hw.headerBuf)) if err != nil { hw.err = err return err } hw.bodyWriter, hw.err = hw.handle(h) return hw.err } // crlfTranformer transforms lone LF characters with CRLF. type crlfTransformer struct { cr bool } func (tr *crlfTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { for _, c := range src { if c == '\r' { tr.cr = true } if c == '\n' { if tr.cr { tr.cr = false } else { if nDst+1 >= len(dst) { err = transform.ErrShortDst break } dst[nDst] = '\r' nDst++ } } if nDst >= len(dst) { err = transform.ErrShortDst break } dst[nDst] = c nDst++ nSrc++ } return nDst, nSrc, err } func (tr *crlfTransformer) Reset() { tr.cr = false } var _ transform.Transformer = (*crlfTransformer)(nil) go-pgpmail-0.2.0/reader_test.go0000644000175000017500000002263114152654154015736 0ustar nileshnileshpackage pgpmail import ( "bytes" "io" "io/ioutil" "strings" "testing" "github.com/ProtonMail/go-crypto/openpgp" pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" ) func checkSignature(t *testing.T, md *openpgp.MessageDetails) { primaryKeyId := testPrivateKey.PrimaryKey.KeyId if md.SignatureError != nil { t.Errorf("MessageDetails.SignatureError = %v", md.SignatureError) } if !md.IsSigned { t.Errorf("MessageDetails.IsSigned != true") } if md.SignedBy == nil { t.Errorf("MessageDetails.SignedBy == nil") } if md.SignedByKeyId != primaryKeyId { t.Errorf("MessageDetails.SignedByKeyId = %v, want %v", md.SignedByKeyId, primaryKeyId) } } func checkEncryption(t *testing.T, md *openpgp.MessageDetails) { encryptedTo := testPrivateKey.Subkeys[0].PublicKey.KeyId if !md.IsEncrypted { t.Errorf("MessageDetails.IsEncrypted != true") } if len(md.EncryptedToKeyIds) != 1 { t.Errorf("MessageDetails.EncryptedToKeyIds = %v, want exactly one key", md.EncryptedToKeyIds) } else if md.EncryptedToKeyIds[0] != encryptedTo { t.Errorf("MessageDetails.EncryptedToKeyIds = %v, want key %v", md.EncryptedToKeyIds, encryptedTo) } } func TestReader_encryptedSignedPGPMIME(t *testing.T) { sr := strings.NewReader(testPGPMIMEEncryptedSigned) r, err := Read(sr, openpgp.EntityList{testPrivateKey}, nil, nil) if err != nil { t.Fatalf("pgpmail.Read() = %v", err) } var buf bytes.Buffer if _, err := io.Copy(&buf, r.MessageDetails.UnverifiedBody); err != nil { t.Fatalf("io.Copy() = %v", err) } checkSignature(t, r.MessageDetails) checkEncryption(t, r.MessageDetails) if s := buf.String(); s != testEncryptedBody { t.Errorf("MessagesDetails.UnverifiedBody = \n%v\n but want \n%v", s, testEncryptedBody) } } func TestReader_encryptedSignedEncapsulatedPGPMIME(t *testing.T) { sr := strings.NewReader(testPGPMIMEEncryptedSignedEncapsulated) r, err := Read(sr, openpgp.EntityList{testPrivateKey}, nil, nil) if err != nil { t.Fatalf("pgpmail.Read() = %v", err) } var buf bytes.Buffer if _, err := io.Copy(&buf, r.MessageDetails.UnverifiedBody); err != nil { t.Fatalf("io.Copy() = %v", err) } checkSignature(t, r.MessageDetails) checkEncryption(t, r.MessageDetails) if s := buf.String(); s != testSignedBody { t.Errorf("MessagesDetails.UnverifiedBody = \n%v\n but want \n%v", s, testSignedBody) } } func TestReader_signedPGPMIME(t *testing.T) { sr := strings.NewReader(testPGPMIMESigned) r, err := Read(sr, openpgp.EntityList{testPublicKey}, nil, nil) if err != nil { t.Fatalf("pgpmail.Read() = %v", err) } var buf bytes.Buffer if _, err := io.Copy(&buf, r.MessageDetails.UnverifiedBody); err != nil { t.Fatalf("io.Copy() = %v", err) } if r.MessageDetails.IsEncrypted { t.Errorf("MessageDetails.IsEncrypted != false") } checkSignature(t, r.MessageDetails) if s := buf.String(); s != testSignedBody { t.Errorf("MessagesDetails.UnverifiedBody = \n%v\n but want \n%v", s, testSignedBody) } } func TestReader_signedPGPMIMEInvalid(t *testing.T) { sr := strings.NewReader(testPGPMIMESignedInvalid) r, err := Read(sr, openpgp.EntityList{testPrivateKey}, nil, nil) if err != nil { t.Fatalf("pgpmail.Read() = %v", err) } if _, err := io.Copy(ioutil.Discard, r.MessageDetails.UnverifiedBody); err != nil { t.Fatalf("io.Copy() = %v", err) } if err := r.MessageDetails.SignatureError; err == nil { t.Errorf("MessageDetails.SignatureError = nil") } } func TestReader_signedPGPMIMEUnknownIssuer(t *testing.T) { sr := strings.NewReader(testPGPMIMESigned) r, err := Read(sr, openpgp.EntityList{}, nil, nil) if err != nil { t.Fatalf("pgpmail.Read() = %v", err) } if _, err := io.Copy(ioutil.Discard, r.MessageDetails.UnverifiedBody); err != nil { t.Fatalf("io.Copy() = %v", err) } if err := r.MessageDetails.SignatureError; err != pgperrors.ErrUnknownIssuer { t.Errorf("MessageDetails.SignatureError = %v, want ErrUnknownIssuer", err) } } func TestReader_plaintext(t *testing.T) { sr := strings.NewReader(testPlaintext) r, err := Read(sr, openpgp.EntityList(nil), nil, nil) if err != nil { t.Fatalf("pgpmail.Read() = %v", err) } var buf bytes.Buffer if _, err := io.Copy(&buf, r.MessageDetails.UnverifiedBody); err != nil { t.Fatalf("io.Copy() = %v", err) } if r.MessageDetails.IsEncrypted { t.Errorf("MessageDetails.IsEncrypted != false") } if r.MessageDetails.IsSigned { t.Errorf("MessageDetails.IsSigned != false") } if s := buf.String(); s != testPlaintext { t.Errorf("MessagesDetails.UnverifiedBody = \n%v\n but want \n%v", s, testPlaintext) } } var testEncryptedBody = toCRLF(`Content-Type: text/plain This is an encrypted message! `) var testSignedBody = toCRLF(`Content-Type: text/plain This is a signed message! `) var testPGPMIMEEncryptedSigned = toCRLF(`From: John Doe To: John Doe Mime-Version: 1.0 Content-Type: multipart/encrypted; boundary=foo; protocol="application/pgp-encrypted" --foo Content-Type: application/pgp-encrypted Version: 1 --foo Content-Type: application/octet-stream -----BEGIN PGP MESSAGE----- hQEMAxF0jxulHQ8+AQf/SBK2FIIgMA4OkCvlqty/1GmAumWq6J0T+pRLppXHvYFb jbXRzz2h3pE/OoouI6vWzBwb8xU/5f8neen+fvdsF1N6PyLjZcHRB91oPvP8TuHA 0vEpiQDbP+0wlQ8BmMnnV06HokWJoKXGmIle0L4QszT/QCbrT80UgKrqXNVHKQtN DUcytFsUCmolZRj074FEpEetjH6QGEX5hAYNBUJziXmOv7vdd4AFgNbbgC5j5ezz h8tCAKUqeUiproYaAMrI0lfqh/t8bacJNkljI2LOxYfdJ/2317Npwly0OqpCM3YT Q4dHuuGM6IuZHtIc9sneIBRhKf8WnWt14hLkHUT80dLA/AHKl0jGYqO34Dxd9JNB EEwQ4j6rxauOEbKLAuYYaEqCzNYBasBrPmpNb4Fx2syWkCoYzwvzv7nj4I8vIBmm FGsAQLX4c18qtZI4XaG4FPUvFQ01Y0rjTxAV3u51lrYjCxFuI5ZEtiT0J/Tv2Unw R6xwtARkEf3W0agegmohEjjkAexKNxGrlulLiPk2j9/dnlAxeGpOuhYuYU2kYbKq x3TkcVYRs1FkmCX0YHNJ2zVWLfDYd2f3UVkXINe7mODGx2A2BxvK9Ig7NMuNmWZE ELiLSIvQk9jlgqWUMwSGPQKaHPrac02EjcBHef2zCoFbTg0TXQeDr5SV7yguX8jB zZnoNs+6+GR1gA6poKzFdiG4NRr0SNgEHazPPkXp3P2KyOINyFJ7SA+HX8iegTqL CTPYPK7UNRmb5s2u5B4e9NiQB9L85W4p7p7uemCSu9bxjs8rkCJpvx9Kb8jzPW17 wnEUe10A4JNDBhxiMg+Fm5oM2VxQVy+eDVFOOq7pDYVcSmZc36wO+EwAKph9shby O4sDS4l/8eQTEYUxTavdtQ9O9ZMXvf/L3Rl1uFJXw1lFwPReXwtpA485e031/A== =P0jf -----END PGP MESSAGE----- --foo-- `) var testPGPMIMEEncryptedSignedEncapsulated = toCRLF(`From: John Doe To: John Doe Mime-Version: 1.0 Content-Type: multipart/encrypted; boundary=foo; protocol="application/pgp-encrypted" --foo Content-Type: application/pgp-encrypted Version: 1 --foo Content-Type: application/octet-stream -----BEGIN PGP MESSAGE----- hQEMAxF0jxulHQ8+AQf9FCth8p+17rzWL0AtKP+aWndvVUYmaKiUZd+Ya8D9cRnc FAP//JnRvTPhdOyl8x1FQkVxyuKcgpjaClb6/OLgD0lGYLC15p43G4QyU+jtOOQW FFjZj2z8wUuiev8ejNd7DMiOQRSm4d+IIK+Qa2BJ10Y9AuLQtMI8D+joP1D11NeX 4FO3SYFEuwH5VWlXGo3bRjg8fKFVG/r/xCwBibqRpfjVnS4EgI04XCsnhqdaCRvE Bw2XEaF62m2MUNbaan410WajzVSbSIqIHw8U7vpR/1nisS+SZmScuCXWFa6W9YgR 0nSWi1io2Ratf4F9ORCy0o7QPh7FlpsIUGmp4paF39LpAQ2q0OUnFhkIdLVQscQT JJXLbZwp0CYTAgqwdRWFwY7rEPm2k/Oe4cHKJLEn0hS+X7wch9FAYEMifeqa0FcZ GjxocAlyhmlM0sXIDYP8xx49t4O8JIQU1ep/SX2+rUAKIh2WRdYDy8GrrHba8V8U aBCU9zIMhmOtu7r+FE1djMUhcaSbbvC9zLDMLV8QxogGhxrqaUM8Pj+q1H6myaAr o1xd65b6r2Bph6GUmcMwl28i78u9bKoM0mI+EdUuLwS9EbmjtIwEgxNv4LqK8xw2 /tjCe9JSqg+HDaBYnO4QTM29Y+PltRIe6RxpnBcYULTLcSt1UK3YV1KvhqfXMjoZ THsvtxLbmPYFv+g0hiUpuKtyG9NGidKCxrjvNq30KCSUWzNFkh+qv6CPm26sXr5F DTsVpFTM/lomg4Po8sE20BZsk/9IzEh4ERSOu3k0m3mI4QAyJmrOpVGUjd//4cqz Zhhc3tV78BtEYNh0a+78fAHGtdLocLj5IfOCYQWW//EtOY93TnVAtP0puaiNOc8q Vvb5WMamiRJZ9nQXP3paDoqD14B9X6bvNWsDQDkkrWls2sYg7KzqpOM/nlXLBKQd Ok4EJfOpd0hICPwo6tJ6sK2meRcDLxtGJybADE7UHJ4t0SrQBfn/sQhRytQtg2wr U1Thy6RujlrrrdUryo3Mi+xc9Ot1o35JszCjNQGL6BCFsGi9fx5pjWM+lLiJ15aJ jh02mSd/8j7IaJCGgTuyq6uK45EoVqWd1WRSYl4s5tg1g1jckigYYjJdAKNnU/rZ iTk5F8GSyv30EXnqvrs= =Ibxd -----END PGP MESSAGE----- --foo-- `) var testPGPMIMESigned = toCRLF(`From: John Doe To: John Doe Mime-Version: 1.0 Content-Type: multipart/signed; boundary=bar; micalg=pgp-sha256; protocol="application/pgp-signature" --bar Content-Type: text/plain This is a signed message! --bar Content-Type: application/pgp-signature -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEsahmk1QVO3mfIhe/MHIVwT33qWQFAl5FRLgACgkQMHIVwT33 qWSEQQf/YgRlKlQzSyvm6A52lGIRU3F/z9EGjhCryxj+hSdPlk8O7iZFIjnco4Ea 7QIlsOj6D4AlLdhyK6c8IZV7rZoTNE5rc6I5UZjM4Qa0XoyLjao28zR252TtwwWJ e4+wrTQKcVhCyHO6rkvcCpru4qF5CU+Mi8+sf8CNJJyBgw1Pri35rJWMdoTPTqqz kcIGN1JySaI8bbVitJQmnm0FtFTiB7zznv94rMBCiPmPUWd9BSpSBJteJoBLZ+K7 Y7ws2Dzp2sBo/RLUM18oXd0N9PLXvFGI3IuF8ey1SPzQH3QbBdJSTmLzRlPjK7A1 HVHFb3vTjd71z9j5IGQQ3Awdw30zMg== =gOul -----END PGP SIGNATURE----- --bar-- `) var testPGPMIMESignedInvalid = toCRLF(`From: John Doe To: John Doe Mime-Version: 1.0 Content-Type: multipart/signed; boundary=bar; micalg=pgp-sha256; protocol="application/pgp-signature" --bar Content-Type: text/plain This is a signed message, but the signature is invalid. --bar Content-Type: application/pgp-signature -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEsahmk1QVO3mfIhe/MHIVwT33qWQFAl5FRLgACgkQMHIVwT33 qWSEQQf/YgRlKlQzSyvm6A52lGIRU3F/z9EGjhCryxj+hSdPlk8O7iZFIjnco4Ea 7QIlsOj6D4AlLdhyK6c8IZV7rZoTNE5rc6I5UZjM4Qa0XoyLjao28zR252TtwwWJ e4+wrTQKcVhCyHO6rkvcCpru4qF5CU+Mi8+sf8CNJJyBgw1Pri35rJWMdoTPTqqz kcIGN1JySaI8bbVitJQmnm0FtFTiB7zznv94rMBCiPmPUWd9BSpSBJteJoBLZ+K7 Y7ws2Dzp2sBo/RLUM18oXd0N9PLXvFGI3IuF8ey1SPzQH3QbBdJSTmLzRlPjK7A1 HVHFb3vTjd71z9j5IGQQ3Awdw30zMg== =gOul -----END PGP SIGNATURE----- --bar-- `) var testPlaintext = toCRLF(`From: John Doe To: John Doe Mime-Version: 1.0 Content-Type: text/plain This is a plaintext message! `) go-pgpmail-0.2.0/example_test.go0000644000175000017500000000635214152654154016131 0ustar nileshnileshpackage pgpmail_test import ( "bytes" "io" "log" "github.com/ProtonMail/go-crypto/openpgp" "github.com/emersion/go-message" "github.com/emersion/go-message/mail" "github.com/emersion/go-pgpmail" ) func ExampleRead() { // Let's assume r contains an e-mail, which is maybe encrypted or signed var r io.Reader // A private key is needed in case the message is encrypted var privateKey *openpgp.Entity pgpReader, err := pgpmail.Read(r, openpgp.EntityList{privateKey}, nil, nil) if err != nil { log.Fatal(err) } log.Printf("Header: %v", pgpReader.Header) // pgpReader.MessageDetails.UnverifiedBody contains the whole wrapped e-mail entity, err := message.Read(pgpReader.MessageDetails.UnverifiedBody) if err != nil { log.Fatal(err) } // Do something with the wrapped e-mail log.Printf("Wrapped header: %v", entity.Header) var buf bytes.Buffer if _, err := io.Copy(&buf, entity.Body); err != nil { log.Fatal(err) } // Now that the wrapped e-mail has been read, we can check the signature. // We can only do this if the wrapped e-mail has been fully consumed. if err := pgpReader.MessageDetails.SignatureError; err != nil { log.Fatal(err) } log.Printf("Signed: %v", pgpReader.MessageDetails.IsSigned) log.Printf("Encrypted: %v", pgpReader.MessageDetails.IsEncrypted) } func ExampleEncrypt() { // to are the recipients' keys, signer is the sender's key var to []*openpgp.Entity var signer *openpgp.Entity var mailHeader mail.Header mailHeader.SetAddressList("From", []*mail.Address{{"Mitsuha Miyamizu", "mitsuha.miyamizu@example.org"}}) mailHeader.SetAddressList("To", []*mail.Address{{"Taki Tachibana", "taki.tachibana@example.org"}}) var encryptedHeader mail.Header encryptedHeader.SetContentType("text/plain", nil) encryptedText := "Hi! I'm Mitsuha Miyamizu." var buf bytes.Buffer cleartext, err := pgpmail.Encrypt(&buf, mailHeader.Header.Header, to, signer, nil) if err != nil { log.Fatal(err) } defer cleartext.Close() body, err := mail.CreateSingleInlineWriter(cleartext, encryptedHeader) if err != nil { log.Fatal(err) } defer body.Close() if _, err := io.WriteString(body, encryptedText); err != nil { log.Fatal(err) } if err := body.Close(); err != nil { log.Fatal(err) } if err := cleartext.Close(); err != nil { log.Fatal(err) } log.Print(buf.String()) } func ExampleSign() { // signer is the sender's key var signer *openpgp.Entity var mailHeader mail.Header mailHeader.SetAddressList("From", []*mail.Address{{"Mitsuha Miyamizu", "mitsuha.miyamizu@example.org"}}) mailHeader.SetAddressList("To", []*mail.Address{{"Taki Tachibana", "taki.tachibana@example.org"}}) var signedHeader mail.Header signedHeader.SetContentType("text/plain", nil) signedText := "Hi! I'm Mitsuha Miyamizu." var buf bytes.Buffer cleartext, err := pgpmail.Sign(&buf, mailHeader.Header.Header, signer, nil) if err != nil { log.Fatal(err) } defer cleartext.Close() body, err := mail.CreateSingleInlineWriter(cleartext, signedHeader) if err != nil { log.Fatal(err) } defer body.Close() if _, err := io.WriteString(body, signedText); err != nil { log.Fatal(err) } if err := body.Close(); err != nil { log.Fatal(err) } if err := cleartext.Close(); err != nil { log.Fatal(err) } log.Print(buf.String()) } go-pgpmail-0.2.0/pgpmail_test.go0000644000175000017500000000165714152654154016132 0ustar nileshnileshpackage pgpmail import ( "fmt" "strings" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/packet" ) type zeroReader struct{} func (zeroReader) Read(buf []byte) (int, error) { for i := range buf { buf[i] = 0 } return len(buf), nil } var testPrivateKey = mustReadArmoredEntity(testPrivateKeyArmored) var testPublicKey = mustReadArmoredEntity(testPublicKeyArmored) var testConfig = &packet.Config{ Rand: &zeroReader{}, Time: func() time.Time { return time.Date(2020, 2, 20, 0, 0, 0, 0, time.UTC) }, } func mustReadArmoredEntity(s string) *openpgp.Entity { el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(s)) if err != nil { panic(fmt.Errorf("pgpmail: failed to read test key: %v", err)) } if len(el) != 1 { panic("pgpmail: test keyring doesn't contain exactly one key") } return el[0] } func toCRLF(s string) string { return strings.ReplaceAll(s, "\n", "\r\n") } go-pgpmail-0.2.0/go.mod0000644000175000017500000000040614152654154014210 0ustar nileshnileshmodule github.com/emersion/go-pgpmail go 1.12 require ( github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 github.com/emersion/go-message v0.15.0 golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect golang.org/x/text v0.3.7 ) go-pgpmail-0.2.0/writer_test.go0000644000175000017500000000777714152654154016026 0ustar nileshnileshpackage pgpmail import ( "bytes" "io" "testing" "github.com/ProtonMail/go-crypto/openpgp" "github.com/emersion/go-message/textproto" ) func init() { forceBoundary = "foo" } func TestEncrypt(t *testing.T) { var h textproto.Header h.Set("From", "John Doe ") h.Set("To", "John Doe ") var encryptedHeader textproto.Header encryptedHeader.Set("Content-Type", "text/plain") var encryptedBody = "This is an encrypted message!" to := []*openpgp.Entity{testPublicKey} var buf bytes.Buffer cleartext, err := Encrypt(&buf, h, to, testPrivateKey, testConfig) if err != nil { t.Fatalf("Encrypt() = %v", err) } if err := textproto.WriteHeader(cleartext, encryptedHeader); err != nil { t.Fatalf("textproto.WriteHeader() = %v", err) } if _, err := io.WriteString(cleartext, encryptedBody); err != nil { t.Fatalf("io.WriteString() = %v", err) } if err := cleartext.Close(); err != nil { t.Fatalf("ciphertext.Close() = %v", err) } if s := buf.String(); s != wantEncrypted { t.Errorf("Encrypt() = \n%v\n but want \n%v", s, wantEncrypted) } } func TestSign(t *testing.T) { var h textproto.Header h.Set("From", "John Doe ") h.Set("To", "John Doe ") var signedHeader textproto.Header signedHeader.Set("Content-Type", "text/plain") var signedBody = "This is a signed message!" var buf bytes.Buffer cleartext, err := Sign(&buf, h, testPrivateKey, testConfig) if err != nil { t.Fatalf("Encrypt() = %v", err) } if err := textproto.WriteHeader(cleartext, signedHeader); err != nil { t.Fatalf("textproto.WriteHeader() = %v", err) } if _, err := io.WriteString(cleartext, signedBody); err != nil { t.Fatalf("io.WriteString() = %v", err) } if err := cleartext.Close(); err != nil { t.Fatalf("ciphertext.Close() = %v", err) } if s := buf.String(); s != wantSigned { t.Errorf("Encrypt() = \n%v\n but want \n%v", s, wantSigned) } } var wantEncrypted = toCRLF(`Content-Type: multipart/encrypted; boundary=foo; protocol="application/pgp-encrypted" To: John Doe From: John Doe --foo Content-Type: application/pgp-encrypted Version: 1 --foo Content-Type: application/octet-stream -----BEGIN PGP MESSAGE----- wcBMAxF0jxulHQ8+AQf+MKEqgZA3ZR6K79wGFa67rAxC9NudHUXFaXKAxOZqKmt9 dSH+jIbVrnM/5+/noaHY+3/YbPcow/E0XIfb/G0TDfLI1y5NyLRN5u8ms293ONqL xbEBp1f/mert3UTvi3ewCd4V/bP7+s2XcwgpRFZE6wYV+iFHS1IgMdqNHR2lNhNW wszcVy6rRCdhiYsgz56YASPfJmGroPARzh1LIPoTKwXisLnAaM0JUb6f2E2/K2Jp Z6OMrPfiGPl/XGhr80B9UaQjSkMZx8cH3L7Av3Q+q7llRmBK2Y5Skgl96RDDX0Pr x/6tBxa96LXINovCGS/BZ1jbv9xL175G7x4iXH9VYNLA7wFm6UvU74osO4hM+lnK NCsu95V5R1HjltYjoQY7HR4k2KplLD7fqWrm/dj8IrwqSrSa6ZgHZCEpROx/goiQ oqyhtF3XXohwM0C6Mr+ojdmXbBNdOmv3sm+isdFGCIGgiYhcAyBRDMehx3sBLWLY 8oeqR759MhzKztHC0sZa08OMeIpCEINy4eDEAQdbWDg1l+J9W9Bqd5vqx9FI82np FMueiumFwi+zjV17M/taOLeLGVJudwsH9eWcX2NdyHvTfNWRfx20Z50GB0nwkb9n 4vTfow0vXbcT+1ajnOyrOljwBGfgvcpBG1/9WEQxMoA5tvH3i7y9T4SxpJ2+DjqG dxGdo+sj0PiQObhCj3sHVIoRHYSCLWid78VY8GUZrBdBA6NAlxj6Pk36Lkp66/55 JaJo2G7ZVnezLkPlr9gFbdc4kkel5ABAD8/1zLIG4LcrCHBBgH5lIP7uv+dAwtsE jQfrJzA1FD4ZRprc7qhbcIq6NRBIj8amu/KHvBBi+zNOUW4QtrC23LHOGYldrcu1 o3q42OYigPcRIYlmmqkyBmj16Kj5jPnjDry9iv68Z6ot =TtuG -----END PGP MESSAGE----- --foo-- `) var wantSigned = toCRLF(`Content-Type: multipart/signed; boundary=foo; micalg=pgp-sha256; protocol="application/pgp-signature" To: John Doe From: John Doe --foo Content-Type: text/plain This is a signed message! --foo Content-Type: application/pgp-signature -----BEGIN PGP MESSAGE----- wsBzBAEBCAAnBQJeTcwACZAwchXBPfepZBahBLGoZpNUFTt5nyIXvzByFcE996lk AACmXQgAiu/yJb2o3AX/GYt/GUSEWkYb1GI41ogLpoicrX6UPoUhuIwzNQHvSG62 DDsMrNBKUZfymp6iYFRBEs9Au0o8WwqMFGWWgaDxvI2144gSDN4CDKtyCVRGNcIf PeL+vfpZIEV1JzzRKLl3nGlFbnSTfpxUg3EYNy51RHNmbvJGRzi43CTYJUp7Lh+/ ibogULsL0ZH3M6QtGhUNcujjqUmVAvAqVxwf7BjBta/G2hOPPCQeVjFsOgcWuIQr GudsXpoK1FQ+NUrGcXJGgV+bq6r9IGEUafjGJ3087q9hz5drBoUgqlyl62wn7krB Ql3Afgbl74/eTZO7Mr5cx3us80F3AQ== =6GTz -----END PGP MESSAGE----- --foo-- `) go-pgpmail-0.2.0/reader.go0000644000175000017500000001750514152654154014703 0ustar nileshnileshpackage pgpmail import ( "bufio" "bytes" "crypto" "fmt" "hash" "io" "mime" "strings" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/armor" pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/emersion/go-message/textproto" ) type Reader struct { Header textproto.Header MessageDetails *openpgp.MessageDetails } func NewReader(h textproto.Header, body io.Reader, keyring openpgp.KeyRing, prompt openpgp.PromptFunction, config *packet.Config) (*Reader, error) { t, params, err := mime.ParseMediaType(h.Get("Content-Type")) if err != nil { return nil, err } if strings.EqualFold(t, "multipart/encrypted") && strings.EqualFold(params["protocol"], "application/pgp-encrypted") { mr := textproto.NewMultipartReader(body, params["boundary"]) return newEncryptedReader(h, mr, keyring, prompt, config) } if strings.EqualFold(t, "multipart/signed") && strings.EqualFold(params["protocol"], "application/pgp-signature") { micalg := params["micalg"] mr := textproto.NewMultipartReader(body, params["boundary"]) return newSignedReader(h, mr, micalg, keyring, prompt, config) } var headerBuf bytes.Buffer textproto.WriteHeader(&headerBuf, h) return &Reader{ Header: h, MessageDetails: &openpgp.MessageDetails{ UnverifiedBody: io.MultiReader(&headerBuf, body), }, }, nil } func Read(r io.Reader, keyring openpgp.KeyRing, prompt openpgp.PromptFunction, config *packet.Config) (*Reader, error) { br := bufio.NewReader(r) h, err := textproto.ReadHeader(br) if err != nil { return nil, err } return NewReader(h, br, keyring, prompt, config) } func newEncryptedReader(h textproto.Header, mr *textproto.MultipartReader, keyring openpgp.KeyRing, prompt openpgp.PromptFunction, config *packet.Config) (*Reader, error) { p, err := mr.NextPart() if err != nil { return nil, fmt.Errorf("pgpmail: failed to read first part in multipart/encrypted message: %v", err) } t, _, err := mime.ParseMediaType(p.Header.Get("Content-Type")) if err != nil { return nil, fmt.Errorf("pgpmail: failed to parse Content-Type of first part in multipart/encrypted message: %v", err) } if !strings.EqualFold(t, "application/pgp-encrypted") { return nil, fmt.Errorf("pgpmail: first part in multipart/encrypted message has type %q, not application/pgp-encrypted", t) } metadata, err := textproto.ReadHeader(bufio.NewReader(p)) if err != nil { return nil, fmt.Errorf("pgpmail: failed to parse application/pgp-encrypted part: %v", err) } if s := metadata.Get("Version"); s != "1" { return nil, fmt.Errorf("pgpmail: unsupported PGP/MIME version: %q", s) } p, err = mr.NextPart() if err != nil { return nil, fmt.Errorf("pgpmail: failed to read second part in multipart/encrypted message: %v", err) } t, _, err = mime.ParseMediaType(p.Header.Get("Content-Type")) if err != nil { return nil, fmt.Errorf("pgpmail: failed to parse Content-Type of second part in multipart/encrypted message: %v", err) } if !strings.EqualFold(t, "application/octet-stream") { return nil, fmt.Errorf("pgpmail: second part in multipart/encrypted message has type %q, not application/octet-stream", t) } block, err := armor.Decode(p) if err != nil { return nil, fmt.Errorf("pgpmail: failed to parse encrypted armored data: %v", err) } md, err := openpgp.ReadMessage(block.Body, keyring, prompt, config) if err != nil { return nil, fmt.Errorf("pgpmail: failed to read PGP message: %v", err) } cleartext := bufio.NewReader(md.UnverifiedBody) cleartextHeader, err := textproto.ReadHeader(cleartext) if err != nil { return nil, fmt.Errorf("pgpmail: failed to read encrypted header: %v", err) } t, params, err := mime.ParseMediaType(cleartextHeader.Get("Content-Type")) if err != nil { return nil, err } if md.IsEncrypted && !md.IsSigned && strings.EqualFold(t, "multipart/signed") && strings.EqualFold(params["protocol"], "application/pgp-signature") { // RFC 1847 encapsulation, see RFC 3156 section 6.1 micalg := params["micalg"] mr := textproto.NewMultipartReader(cleartext, params["boundary"]) sr, err := newSignedReader(cleartextHeader, mr, micalg, keyring, prompt, config) if err != nil { return nil, fmt.Errorf("pgpmail: failed to read encapsulated multipart/signed message: %v", err) } sr.MessageDetails.IsEncrypted = md.IsEncrypted sr.MessageDetails.EncryptedToKeyIds = md.EncryptedToKeyIds sr.MessageDetails.IsSymmetricallyEncrypted = md.IsSymmetricallyEncrypted sr.MessageDetails.DecryptedWith = md.DecryptedWith return sr, nil } var headerBuf bytes.Buffer textproto.WriteHeader(&headerBuf, cleartextHeader) md.UnverifiedBody = io.MultiReader(&headerBuf, cleartext) return &Reader{ Header: h, MessageDetails: md, }, nil } type signedReader struct { keyring openpgp.KeyRing multipart *textproto.MultipartReader signed io.Reader hashFunc crypto.Hash hash hash.Hash md *openpgp.MessageDetails } func (r *signedReader) Read(b []byte) (int, error) { n, err := r.signed.Read(b) r.hash.Write(b[:n]) if err == io.EOF { r.md.SignatureError = r.check() } return n, err } func (r *signedReader) check() error { part, err := r.multipart.NextPart() if err != nil { return fmt.Errorf("pgpmail: failed to read signature part of multipart/signed message: %v", err) } t, _, err := mime.ParseMediaType(part.Header.Get("Content-Type")) if err != nil { return fmt.Errorf("pgpmail: failed to parse Content-Type of signature part in multipart/encrypted message: %v", err) } if !strings.EqualFold(t, "application/pgp-signature") { return fmt.Errorf("pgpmail: signature part in multipart/encrypted message has type %q, not application/pgp-signature", t) } block, err := armor.Decode(part) if err != nil { return fmt.Errorf("pgpmail: failed to read armored signature block: %v", err) } var p packet.Packet var keys []openpgp.Key var sigErr error pr := packet.NewReader(block.Body) for { p, err = pr.Next() if err == io.EOF { break } else if err != nil { return fmt.Errorf("pgpmail: failed to read signature: %v", err) } sig, ok := p.(*packet.Signature) if !ok { return fmt.Errorf("pgpmail: non signature packet found") } if sig.IssuerKeyId == nil { return fmt.Errorf("pgpmail: signature doesn't have an issuer") } issuerKeyId := *sig.IssuerKeyId hashFunc := sig.Hash r.md.SignedByKeyId = issuerKeyId if hashFunc != r.hashFunc { return fmt.Errorf("pgpmail: micalg mismatch: multipart header indicates %v but signature packet indicates %v", r.hashFunc, hashFunc) } keys = r.keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign) if len(keys) == 0 { continue } for i, key := range keys { sigErr := key.PublicKey.VerifySignature(r.hash, sig) if sigErr == nil { r.md.SignedBy = &keys[i] return nil } } } if sigErr != nil { return sigErr } return pgperrors.ErrUnknownIssuer } func newSignedReader(h textproto.Header, mr *textproto.MultipartReader, micalg string, keyring openpgp.KeyRing, prompt openpgp.PromptFunction, config *packet.Config) (*Reader, error) { hashFunc, ok := hashAlgs[micalg] if !ok { return nil, fmt.Errorf("pgpmail: unsupported micalg %q", micalg) } if !hashFunc.Available() { return nil, fmt.Errorf("pgpmail: micalg %q unavailable", micalg) } hash := hashFunc.New() p, err := mr.NextPart() if err != nil { return nil, fmt.Errorf("pgpmail: failed to read signed part in multipart/signed message: %v", err) } var headerBuf bytes.Buffer textproto.WriteHeader(&headerBuf, p.Header) // TODO: convert line endings to CRLF md := &openpgp.MessageDetails{IsSigned: true} sr := &signedReader{ keyring: keyring, multipart: mr, signed: io.MultiReader(&headerBuf, p), hashFunc: hashFunc, hash: hash, md: md, } md.UnverifiedBody = sr return &Reader{ Header: h, MessageDetails: md, }, nil } go-pgpmail-0.2.0/pgpmail.go0000644000175000017500000000067414152654154015071 0ustar nileshnilesh// Package pgpmail implements PGP encryption for e-mail messages. // // PGP/MIME is defined in RFC 3156. package pgpmail import ( "crypto" ) // See RFC 4880, section 9.4. var hashAlgs = map[string]crypto.Hash{ "pgp-md5": crypto.MD5, "pgp-sha1": crypto.SHA1, "pgp-ripemd160": crypto.RIPEMD160, "pgp-sha256": crypto.SHA256, "pgp-sha384": crypto.SHA384, "pgp-sha512": crypto.SHA512, "pgp-sha224": crypto.SHA224, } go-pgpmail-0.2.0/.gitignore0000644000175000017500000000044114152654154015071 0ustar nileshnilesh# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # editors stuff .idea # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof go-pgpmail-0.2.0/LICENSE0000644000175000017500000000205214152654154014106 0ustar nileshnileshMIT License Copyright (c) 2020 Simon Ser Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-pgpmail-0.2.0/.build.yml0000644000175000017500000000032114152654154014776 0ustar nileshnileshimage: alpine/latest packages: - go sources: - https://github.com/emersion/go-pgpmail tasks: - build: | cd go-pgpmail go build -v ./... - test: | cd go-pgpmail go test -v ./...