180 lines
3.7 KiB
Go
180 lines
3.7 KiB
Go
|
// writer.go largerly mimics github.com/emersion/go-pgpmail, with changes made
|
||
|
// to interface with the gpg package in aerc
|
||
|
|
||
|
package gpg
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"mime"
|
||
|
|
||
|
"git.sr.ht/~rjarry/aerc/lib/crypto/gpg/gpgbin"
|
||
|
"github.com/emersion/go-message/textproto"
|
||
|
)
|
||
|
|
||
|
type EncrypterSigner struct {
|
||
|
msgBuf bytes.Buffer
|
||
|
encryptedWriter io.Writer
|
||
|
to []string
|
||
|
from string
|
||
|
}
|
||
|
|
||
|
func (es *EncrypterSigner) Write(p []byte) (int, error) {
|
||
|
return es.msgBuf.Write(p)
|
||
|
}
|
||
|
|
||
|
func (es *EncrypterSigner) Close() (err error) {
|
||
|
r := bytes.NewReader(es.msgBuf.Bytes())
|
||
|
enc, err := gpgbin.Encrypt(r, es.to, es.from)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
es.encryptedWriter.Write(enc)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type Signer struct {
|
||
|
mw *textproto.MultipartWriter
|
||
|
signedMsg bytes.Buffer
|
||
|
w io.Writer
|
||
|
from string
|
||
|
header textproto.Header
|
||
|
}
|
||
|
|
||
|
func (s *Signer) Write(p []byte) (int, error) {
|
||
|
return s.signedMsg.Write(p)
|
||
|
}
|
||
|
|
||
|
func (s *Signer) Close() (err error) {
|
||
|
// TODO should write the whole message up here so we can get the proper micalg from the signature packet
|
||
|
|
||
|
sig, micalg, err := gpgbin.Sign(bytes.NewReader(s.signedMsg.Bytes()), s.from)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
params := map[string]string{
|
||
|
"boundary": s.mw.Boundary(),
|
||
|
"protocol": "application/pgp-signature",
|
||
|
"micalg": micalg,
|
||
|
}
|
||
|
s.header.Set("Content-Type", mime.FormatMediaType("multipart/signed", params))
|
||
|
|
||
|
if err = textproto.WriteHeader(s.w, s.header); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
boundary := s.mw.Boundary()
|
||
|
fmt.Fprintf(s.w, "--%s\r\n", boundary)
|
||
|
s.w.Write(s.signedMsg.Bytes())
|
||
|
s.w.Write([]byte("\r\n"))
|
||
|
|
||
|
var signedHeader textproto.Header
|
||
|
signedHeader.Set("Content-Type", "application/pgp-signature")
|
||
|
signatureWriter, err := s.mw.CreatePart(signedHeader)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
_, err = signatureWriter.Write(sig)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// 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, rcpts []string, from string) (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
|
||
|
}
|
||
|
|
||
|
var buf bytes.Buffer
|
||
|
plaintext := &EncrypterSigner{
|
||
|
msgBuf: buf,
|
||
|
encryptedWriter: encryptedWriter,
|
||
|
to: rcpts,
|
||
|
from: from,
|
||
|
}
|
||
|
|
||
|
return struct {
|
||
|
io.Writer
|
||
|
io.Closer
|
||
|
}{
|
||
|
plaintext,
|
||
|
multiCloser{
|
||
|
plaintext,
|
||
|
mw,
|
||
|
},
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func Sign(w io.Writer, h textproto.Header, from string) (io.WriteCloser, error) {
|
||
|
mw := textproto.NewMultipartWriter(w)
|
||
|
|
||
|
if forceBoundary != "" {
|
||
|
mw.SetBoundary(forceBoundary)
|
||
|
}
|
||
|
|
||
|
var msg bytes.Buffer
|
||
|
plaintext := &Signer{
|
||
|
mw: mw,
|
||
|
signedMsg: msg,
|
||
|
w: w,
|
||
|
from: from,
|
||
|
header: h,
|
||
|
}
|
||
|
|
||
|
return struct {
|
||
|
io.Writer
|
||
|
io.Closer
|
||
|
}{
|
||
|
plaintext,
|
||
|
multiCloser{
|
||
|
plaintext,
|
||
|
mw,
|
||
|
},
|
||
|
}, nil
|
||
|
}
|