Skip to content

Commit ba3d5cf

Browse files
committed
Add basic Encrypt and Sign functions
1 parent 7fc8c21 commit ba3d5cf

File tree

3 files changed

+189
-21
lines changed

3 files changed

+189
-21
lines changed

pgpmail.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,18 @@
22
//
33
// PGP/MIME is defined in RFC 3156.
44
package pgpmail
5+
6+
import (
7+
"crypto"
8+
)
9+
10+
// See RFC 4880, section 9.4.
11+
var hashAlgs = map[string]crypto.Hash{
12+
"pgp-md5": crypto.MD5,
13+
"pgp-sha1": crypto.SHA1,
14+
"pgp-ripemd160": crypto.RIPEMD160,
15+
"pgp-sha256": crypto.SHA256,
16+
"pgp-sha384": crypto.SHA384,
17+
"pgp-sha512": crypto.SHA512,
18+
"pgp-sha224": crypto.SHA224,
19+
}

reader.go

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import (
44
"bufio"
55
"crypto"
66
"fmt"
7+
"hash"
78
"io"
89
"mime"
9-
"hash"
1010
"strings"
1111

1212
"github.com/emersion/go-message/textproto"
@@ -105,24 +105,13 @@ func newEncryptedReader(h textproto.Header, mr *textproto.MultipartReader, keyri
105105
}, nil
106106
}
107107

108-
// See RFC 4880, section 9.4.
109-
var hashAlgs = map[string]crypto.Hash{
110-
"pgp-md5": crypto.MD5,
111-
"pgp-sha1": crypto.SHA1,
112-
"pgp-ripemd160": crypto.RIPEMD160,
113-
"pgp-sha256": crypto.SHA256,
114-
"pgp-sha384": crypto.SHA384,
115-
"pgp-sha512": crypto.SHA512,
116-
"pgp-sha224": crypto.SHA224,
117-
}
118-
119108
type signedReader struct {
120-
keyring openpgp.KeyRing
109+
keyring openpgp.KeyRing
121110
multipart *textproto.MultipartReader
122-
signed io.Reader
123-
hashFunc crypto.Hash
124-
hash hash.Hash
125-
md *openpgp.MessageDetails
111+
signed io.Reader
112+
hashFunc crypto.Hash
113+
hash hash.Hash
114+
md *openpgp.MessageDetails
126115
}
127116

128117
func (r *signedReader) Read(b []byte) (int, error) {
@@ -234,10 +223,10 @@ func newSignedReader(h textproto.Header, mr *textproto.MultipartReader, micalg s
234223

235224
sr := &signedReader{
236225
multipart: mr,
237-
signed: p,
238-
hashFunc: hashFunc,
239-
hash: hash,
240-
md: md,
226+
signed: p,
227+
hashFunc: hashFunc,
228+
hash: hash,
229+
md: md,
241230
}
242231
md.UnverifiedBody = sr
243232

writer.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package pgpmail
2+
3+
import (
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+
)
14+
15+
type multiCloser []io.Closer
16+
17+
func (mc multiCloser) Close() error {
18+
for _, c := range mc {
19+
if err := c.Close(); err != nil {
20+
return err
21+
}
22+
}
23+
return nil
24+
}
25+
26+
func Encrypt(w io.Writer, h textproto.Header, to []*openpgp.Entity, signed *openpgp.Entity, config *packet.Config) (io.WriteCloser, error) {
27+
mw := textproto.NewMultipartWriter(w)
28+
29+
params := map[string]string{
30+
"boundary": mw.Boundary(),
31+
"protocol": "application/pgp-encrypted",
32+
}
33+
h.Set("Content-Type", mime.FormatMediaType("multipart/encrypted", params))
34+
35+
if err := textproto.WriteHeader(w, h); err != nil {
36+
return nil, err
37+
}
38+
39+
var controlHeader textproto.Header
40+
controlHeader.Set("Content-Type", "application/pgp-encrypted")
41+
controlWriter, err := mw.CreatePart(controlHeader)
42+
if err != nil {
43+
return nil, err
44+
}
45+
if _, err := controlWriter.Write([]byte("Version: 1\r\n")); err != nil {
46+
return nil, err
47+
}
48+
49+
var encryptedHeader textproto.Header
50+
encryptedHeader.Set("Content-Type", "application/octet-stream")
51+
encryptedWriter, err := mw.CreatePart(encryptedHeader)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
armorWriter, err := armor.Encode(encryptedWriter, "PGP MESSAGE", nil)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
plaintext, err := openpgp.Encrypt(armorWriter, to, signed, nil, config)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
return struct {
67+
io.Writer
68+
io.Closer
69+
}{
70+
plaintext,
71+
multiCloser{
72+
plaintext,
73+
armorWriter,
74+
mw,
75+
},
76+
}, nil
77+
}
78+
79+
type signer struct {
80+
io.Writer
81+
pw *io.PipeWriter
82+
done <-chan error
83+
sigBuf bytes.Buffer
84+
mw *textproto.MultipartWriter
85+
}
86+
87+
func (s *signer) Close() error {
88+
// Close the pipe to let openpgp.DetachSign finish
89+
if err := s.pw.Close(); err != nil {
90+
return err
91+
}
92+
if err := <-s.done; err != nil {
93+
return err
94+
}
95+
// At this point s.sigBuf contains the complete signature
96+
97+
var sigHeader textproto.Header
98+
sigHeader.Set("Content-Type", "application/pgp-signature")
99+
sigWriter, err := s.mw.CreatePart(sigHeader)
100+
if err != nil {
101+
return err
102+
}
103+
104+
armorWriter, err := armor.Encode(sigWriter, "PGP MESSAGE", nil)
105+
if err != nil {
106+
return err
107+
}
108+
109+
if _, err := io.Copy(armorWriter, &s.sigBuf); err != nil {
110+
return err
111+
}
112+
113+
if err := armorWriter.Close(); err != nil {
114+
return err
115+
}
116+
return s.mw.Close()
117+
}
118+
119+
func Sign(w io.Writer, header, signedHeader textproto.Header, signed *openpgp.Entity, config *packet.Config) (io.WriteCloser, error) {
120+
mw := textproto.NewMultipartWriter(w)
121+
122+
var micalg string
123+
for name, hash := range hashAlgs {
124+
if hash == config.Hash() {
125+
micalg = name
126+
break
127+
}
128+
}
129+
if micalg == "" {
130+
return nil, fmt.Errorf("pgpmail: unknown hash algorithm %v", config.Hash())
131+
}
132+
133+
params := map[string]string{
134+
"boundary": mw.Boundary(),
135+
"protocol": "application/pgp-signature",
136+
"micalg": micalg,
137+
}
138+
header.Set("Content-Type", mime.FormatMediaType("multipart/signed", params))
139+
140+
if err := textproto.WriteHeader(w, header); err != nil {
141+
return nil, err
142+
}
143+
144+
signedWriter, err := mw.CreatePart(signedHeader)
145+
if err != nil {
146+
return nil, err
147+
}
148+
// TODO: canonicalize text written to signedWriter
149+
150+
pr, pw := io.Pipe()
151+
done := make(chan error, 1)
152+
s := &signer{
153+
Writer: signedWriter,
154+
pw: pw,
155+
done: done,
156+
mw: mw,
157+
}
158+
159+
go func() {
160+
done <- openpgp.DetachSign(&s.sigBuf, signed, pr, config)
161+
}()
162+
163+
return s, nil
164+
}

0 commit comments

Comments
 (0)