1package pgpmail 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "mime" 8 9 "github.com/emersion/go-message/textproto" 10 "golang.org/x/crypto/openpgp" 11 "golang.org/x/crypto/openpgp/armor" 12 "golang.org/x/crypto/openpgp/packet" 13 "golang.org/x/text/transform" 14) 15 16// for tests 17var forceBoundary = "" 18 19type multiCloser []io.Closer 20 21func (mc multiCloser) Close() error { 22 for _, c := range mc { 23 if err := c.Close(); err != nil { 24 return err 25 } 26 } 27 return nil 28} 29 30func Encrypt(w io.Writer, h textproto.Header, to []*openpgp.Entity, signed *openpgp.Entity, config *packet.Config) (io.WriteCloser, error) { 31 mw := textproto.NewMultipartWriter(w) 32 33 if forceBoundary != "" { 34 mw.SetBoundary(forceBoundary) 35 } 36 37 params := map[string]string{ 38 "boundary": mw.Boundary(), 39 "protocol": "application/pgp-encrypted", 40 } 41 h.Set("Content-Type", mime.FormatMediaType("multipart/encrypted", params)) 42 43 if err := textproto.WriteHeader(w, h); err != nil { 44 return nil, err 45 } 46 47 var controlHeader textproto.Header 48 controlHeader.Set("Content-Type", "application/pgp-encrypted") 49 controlWriter, err := mw.CreatePart(controlHeader) 50 if err != nil { 51 return nil, err 52 } 53 if _, err := controlWriter.Write([]byte("Version: 1\r\n")); err != nil { 54 return nil, err 55 } 56 57 var encryptedHeader textproto.Header 58 encryptedHeader.Set("Content-Type", "application/octet-stream") 59 encryptedWriter, err := mw.CreatePart(encryptedHeader) 60 if err != nil { 61 return nil, err 62 } 63 64 // armor uses LF lines endings, but we need CRLF 65 crlfWriter := transform.NewWriter(encryptedWriter, &crlfTransformer{}) 66 67 armorWriter, err := armor.Encode(crlfWriter, "PGP MESSAGE", nil) 68 if err != nil { 69 return nil, err 70 } 71 72 plaintext, err := openpgp.Encrypt(armorWriter, to, signed, nil, config) 73 if err != nil { 74 return nil, err 75 } 76 77 return struct { 78 io.Writer 79 io.Closer 80 }{ 81 plaintext, 82 multiCloser{ 83 plaintext, 84 armorWriter, 85 mw, 86 }, 87 }, nil 88} 89 90type signer struct { 91 io.Writer 92 pw *io.PipeWriter 93 done <-chan error 94 sigBuf bytes.Buffer 95 mw *textproto.MultipartWriter 96} 97 98func (s *signer) Close() error { 99 // Close the pipe to let openpgp.DetachSign finish 100 if err := s.pw.Close(); err != nil { 101 return err 102 } 103 if err := <-s.done; err != nil { 104 return err 105 } 106 // At this point s.sigBuf contains the complete signature 107 108 var sigHeader textproto.Header 109 sigHeader.Set("Content-Type", "application/pgp-signature") 110 sigWriter, err := s.mw.CreatePart(sigHeader) 111 if err != nil { 112 return err 113 } 114 115 armorWriter, err := armor.Encode(sigWriter, "PGP MESSAGE", nil) 116 if err != nil { 117 return err 118 } 119 120 if _, err := io.Copy(armorWriter, &s.sigBuf); err != nil { 121 return err 122 } 123 124 if err := armorWriter.Close(); err != nil { 125 return err 126 } 127 return s.mw.Close() 128} 129 130func Sign(w io.Writer, header, signedHeader textproto.Header, signed *openpgp.Entity, config *packet.Config) (io.WriteCloser, error) { 131 mw := textproto.NewMultipartWriter(w) 132 133 var micalg string 134 for name, hash := range hashAlgs { 135 if hash == config.Hash() { 136 micalg = name 137 break 138 } 139 } 140 if micalg == "" { 141 return nil, fmt.Errorf("pgpmail: unknown hash algorithm %v", config.Hash()) 142 } 143 144 params := map[string]string{ 145 "boundary": mw.Boundary(), 146 "protocol": "application/pgp-signature", 147 "micalg": micalg, 148 } 149 header.Set("Content-Type", mime.FormatMediaType("multipart/signed", params)) 150 151 if err := textproto.WriteHeader(w, header); err != nil { 152 return nil, err 153 } 154 155 signedWriter, err := mw.CreatePart(signedHeader) 156 if err != nil { 157 return nil, err 158 } 159 // TODO: canonicalize text written to signedWriter 160 161 pr, pw := io.Pipe() 162 done := make(chan error, 1) 163 s := &signer{ 164 Writer: signedWriter, 165 pw: pw, 166 done: done, 167 mw: mw, 168 } 169 170 go func() { 171 done <- openpgp.DetachSign(&s.sigBuf, signed, pr, config) 172 }() 173 174 return s, nil 175} 176 177// crlfTranformer transforms lone LF characters with CRLF. 178type crlfTransformer struct { 179 cr bool 180} 181 182func (tr *crlfTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { 183 for _, c := range src { 184 if c == '\r' { 185 tr.cr = true 186 } 187 188 if c == '\n' { 189 if tr.cr { 190 tr.cr = false 191 } else { 192 if nDst+1 >= len(dst) { 193 err = transform.ErrShortDst 194 break 195 } 196 dst[nDst] = '\r' 197 nDst++ 198 } 199 } 200 201 if nDst >= len(dst) { 202 err = transform.ErrShortDst 203 break 204 } 205 dst[nDst] = c 206 nDst++ 207 nSrc++ 208 } 209 return nDst, nSrc, err 210} 211 212func (tr *crlfTransformer) Reset() { 213 tr.cr = false 214} 215 216var _ transform.Transformer = (*crlfTransformer)(nil) 217