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