Refactor the whole library.
* Replace NewEncrypter with Encrypt. * Add Sign * Remove {Encode,Decode}ArmoredMessage Fixes #1
This commit is contained in:
parent
4360180c2a
commit
d579059ccf
@ -2,7 +2,7 @@
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/emersion/go-pgpmime?status.svg)](https://godoc.org/github.com/emersion/go-pgpmime)
|
||||
|
||||
A [PGP/MIME](https://tools.ietf.org/html/rfc3156) library written in Go
|
||||
A [PGP/MIME](https://tools.ietf.org/html/rfc3156) library written in Go.
|
||||
|
||||
## License
|
||||
|
||||
|
4
armor.go
Normal file
4
armor.go
Normal file
@ -0,0 +1,4 @@
|
||||
package pgpmime
|
||||
|
||||
// MessageType is the armored type for PGP encrypted messages.
|
||||
const MessageType = "PGP MESSAGE"
|
103
encrypt.go
Normal file
103
encrypt.go
Normal file
@ -0,0 +1,103 @@
|
||||
package pgpmime
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
type encryptWriter struct {
|
||||
multipart *multipart.Writer
|
||||
armored io.WriteCloser
|
||||
cleartext io.WriteCloser
|
||||
|
||||
h textproto.MIMEHeader
|
||||
to []*openpgp.Entity
|
||||
signed *openpgp.Entity
|
||||
config *packet.Config
|
||||
}
|
||||
|
||||
func (ew *encryptWriter) open() error {
|
||||
// Create control information
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Add("Content-Type", "application/pgp-encrypted")
|
||||
w, err := ew.multipart.CreatePart(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(w, "Version: 1\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create body part
|
||||
h = make(textproto.MIMEHeader)
|
||||
h.Add("Content-Type", "application/octet-stream")
|
||||
h.Add("Content-Disposition", "inline")
|
||||
w, err = ew.multipart.CreatePart(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create encrypted part
|
||||
ew.armored, err = armor.Encode(w, MessageType, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ew.cleartext, err = openpgp.Encrypt(ew.armored, ew.to, ew.signed, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeMIMEHeader(ew.cleartext, ew.h)
|
||||
}
|
||||
|
||||
func (ew *encryptWriter) Write(b []byte) (n int, err error) {
|
||||
// Make sure parts required at the begining of the message have been written
|
||||
if ew.cleartext == nil {
|
||||
if err := ew.open(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return ew.cleartext.Write(b)
|
||||
}
|
||||
|
||||
func (ew *encryptWriter) Close() error {
|
||||
if ew.cleartext == nil {
|
||||
if err := ew.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ew.cleartext.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ew.armored.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return ew.multipart.Close()
|
||||
}
|
||||
|
||||
func (ew *encryptWriter) ContentType() string {
|
||||
return mime.FormatMediaType("multipart/encrypted", map[string]string{
|
||||
"boundary": ew.multipart.Boundary(),
|
||||
"protocol": "application/pgp-encrypted",
|
||||
})
|
||||
}
|
||||
|
||||
// Encrypt creates a new encrypted PGP/MIME message writer.
|
||||
func Encrypt(w io.Writer, h textproto.MIMEHeader, to []*openpgp.Entity, signed *openpgp.Entity, config *packet.Config) (cleartext Writer) {
|
||||
return &encryptWriter{
|
||||
multipart: multipart.NewWriter(w),
|
||||
|
||||
h: h,
|
||||
to: to,
|
||||
signed: signed,
|
||||
config: config,
|
||||
}
|
||||
}
|
102
encrypted.go
102
encrypted.go
@ -1,102 +0,0 @@
|
||||
// Implements MIME security with OpenPGP, as defined in RFC 3156.
|
||||
package pgpmime
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
)
|
||||
|
||||
// A PGP/MIME encrypter.
|
||||
type Encrypter struct {
|
||||
multipart *multipart.Writer
|
||||
armored io.WriteCloser
|
||||
encrypted io.WriteCloser
|
||||
|
||||
to []*openpgp.Entity
|
||||
signed *openpgp.Entity
|
||||
|
||||
opened bool
|
||||
}
|
||||
|
||||
// Write control information and create encrypted part.
|
||||
func (ew *Encrypter) open() (err error) {
|
||||
// Create control information
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Add("Content-Type", "application/pgp-encrypted")
|
||||
hw, err := ew.multipart.CreatePart(h)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.WriteString(hw, "Version: 1\r\n"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create body part
|
||||
h = make(textproto.MIMEHeader)
|
||||
h.Add("Content-Type", "application/octet-stream")
|
||||
h.Add("Content-Disposition", "inline")
|
||||
bw, err := ew.multipart.CreatePart(h)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create encrypted part
|
||||
if ew.armored, err = EncodeArmoredMessage(bw); err != nil {
|
||||
return
|
||||
}
|
||||
if ew.encrypted, err = openpgp.Encrypt(ew.armored, ew.to, ew.signed, nil, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Write encrypted data.
|
||||
func (ew *Encrypter) Write(b []byte) (n int, err error) {
|
||||
// Make sure parts required at the begining of the message have been written
|
||||
if !ew.opened {
|
||||
if err = ew.open(); err != nil {
|
||||
return
|
||||
}
|
||||
ew.opened = true
|
||||
}
|
||||
|
||||
return ew.encrypted.Write(b)
|
||||
}
|
||||
|
||||
// Finish the PGP/MIME message.
|
||||
func (ew *Encrypter) Close() (err error) {
|
||||
if !ew.opened {
|
||||
if err = ew.open(); err != nil {
|
||||
return
|
||||
}
|
||||
ew.opened = true
|
||||
}
|
||||
|
||||
if err = ew.encrypted.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = ew.armored.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
err = ew.multipart.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Get the Content-Type of this PGP/MIME message.
|
||||
func (ew *Encrypter) ContentType() string {
|
||||
return "multipart/encrypted; boundary=" + ew.multipart.Boundary() + "; protocol=\"application/pgp-encrypted\""
|
||||
}
|
||||
|
||||
// Create a new PGP/MIME encrypter.
|
||||
func NewEncrypter(w io.Writer, to []*openpgp.Entity, signed *openpgp.Entity) *Encrypter {
|
||||
return &Encrypter{
|
||||
multipart: multipart.NewWriter(w),
|
||||
|
||||
to: to,
|
||||
signed: signed,
|
||||
}
|
||||
}
|
100
example_test.go
Normal file
100
example_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
package pgpmime_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"net/textproto"
|
||||
|
||||
"github.com/emersion/go-message"
|
||||
"github.com/emersion/go-message/mail"
|
||||
"github.com/emersion/go-pgpmime"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
)
|
||||
|
||||
var to []*openpgp.Entity
|
||||
|
||||
func ExampleEncrypt() {
|
||||
var b bytes.Buffer
|
||||
|
||||
// Create the mail header
|
||||
mh := mail.NewHeader()
|
||||
mh.SetAddressList("From", []*mail.Address{{"Mitsuha Miyamizu", "mitsuha.miyamizu@example.org"}})
|
||||
mh.SetSubject("Your Name")
|
||||
|
||||
// Create the text part header
|
||||
th := mail.NewTextHeader()
|
||||
th.SetContentType("text/plain", nil)
|
||||
|
||||
// Create a new PGP/MIME writer
|
||||
var ciphertext struct{*message.Writer}
|
||||
cleartext := pgpmime.Encrypt(&ciphertext, textproto.MIMEHeader(th.Header), to, nil, nil)
|
||||
|
||||
// Add the PGP/MIME Content-Type header field to the mail header
|
||||
mh.Set("Content-Type", cleartext.ContentType())
|
||||
|
||||
// Create a new mail writer with our mail header
|
||||
mw, err := message.CreateWriter(&b, mh.Header)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Set the PGP/MIME writer output to the mail body
|
||||
ciphertext.Writer = mw
|
||||
|
||||
// Write the cleartext body
|
||||
_, err = io.WriteString(cleartext, "What's your name?")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Close all writers
|
||||
if err := cleartext.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := mw.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println(b.String())
|
||||
}
|
||||
|
||||
func ExampleSign() {
|
||||
var b bytes.Buffer
|
||||
|
||||
e, err := openpgp.NewEntity("Mitsuha Miyamizu", "", "mitsuha.miyamizu@example.org", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
mh := mail.NewHeader()
|
||||
mh.SetAddressList("From", []*mail.Address{{"Mitsuha Miyamizu", "mitsuha.miyamizu@example.org"}})
|
||||
mh.SetSubject("Your Name")
|
||||
|
||||
bh := mail.NewTextHeader()
|
||||
bh.SetContentType("text/plain", nil)
|
||||
|
||||
var signed struct{*message.Writer}
|
||||
body := pgpmime.Sign(&signed, textproto.MIMEHeader(bh.Header), e, nil)
|
||||
|
||||
mh.Set("Content-Type", body.ContentType())
|
||||
|
||||
mw, err := message.CreateWriter(&b, mh.Header)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
signed.Writer = mw
|
||||
|
||||
_, err = io.WriteString(body, "What's your name?")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := body.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := mw.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println(b.String())
|
||||
}
|
32
message.go
32
message.go
@ -1,32 +0,0 @@
|
||||
package pgpmime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
)
|
||||
|
||||
// Armored type for PGP encrypted messages.
|
||||
const MessageType = "PGP MESSAGE"
|
||||
|
||||
// Encode a PGP message armor.
|
||||
func EncodeArmoredMessage(w io.Writer) (io.WriteCloser, error) {
|
||||
return armor.Encode(w, MessageType, nil)
|
||||
}
|
||||
|
||||
// Decode an armored PGP message.
|
||||
func DecodeArmoredMessage(in io.Reader) (out io.Reader, err error) {
|
||||
block, err := armor.Decode(in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if block.Type != MessageType {
|
||||
err = errors.New("Not an armored PGP message")
|
||||
return
|
||||
}
|
||||
|
||||
out = block.Body
|
||||
return
|
||||
}
|
27
mime.go
Normal file
27
mime.go
Normal file
@ -0,0 +1,27 @@
|
||||
package pgpmime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Borrowed from https://golang.org/src/mime/multipart/writer.go?s=2140:2215#L76
|
||||
func writeMIMEHeader(w io.Writer, header textproto.MIMEHeader) error {
|
||||
var b bytes.Buffer
|
||||
keys := make([]string, 0, len(header))
|
||||
for k := range header {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
for _, v := range header[k] {
|
||||
fmt.Fprintf(&b, "%s: %s\r\n", k, v)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&b, "\r\n")
|
||||
_, err := io.Copy(w, &b)
|
||||
return err
|
||||
}
|
14
pgpmime.go
Normal file
14
pgpmime.go
Normal file
@ -0,0 +1,14 @@
|
||||
// pgpmime implements MIME security with OpenPGP, as defined in RFC 3156.
|
||||
package pgpmime
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Writer writes a PGP/MIME message body.
|
||||
type Writer interface {
|
||||
io.WriteCloser
|
||||
|
||||
// ContentType returns the content type of the PGP/MIME message.
|
||||
ContentType() string
|
||||
}
|
125
sign.go
Normal file
125
sign.go
Normal file
@ -0,0 +1,125 @@
|
||||
package pgpmime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"crypto"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
func hashName(h crypto.Hash) string {
|
||||
switch h {
|
||||
case crypto.MD5:
|
||||
return "md5"
|
||||
case crypto.SHA1:
|
||||
return "sha1"
|
||||
case crypto.RIPEMD160:
|
||||
return "ripemd160"
|
||||
case crypto.SHA224:
|
||||
return "sha224"
|
||||
case crypto.SHA256:
|
||||
return "sha256"
|
||||
case crypto.SHA384:
|
||||
return "sha384"
|
||||
case crypto.SHA512:
|
||||
return "sha512"
|
||||
default:
|
||||
panic("pgpmime: unknown hash algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
type signWriter struct {
|
||||
multipart *multipart.Writer
|
||||
body io.WriteCloser
|
||||
signature <-chan io.Reader
|
||||
|
||||
h textproto.MIMEHeader
|
||||
signer *openpgp.Entity
|
||||
config *packet.Config
|
||||
}
|
||||
|
||||
func (sw *signWriter) open() error {
|
||||
w, err := sw.multipart.CreatePart(sw.h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
ch := make(chan io.Reader, 1)
|
||||
sw.signature = ch
|
||||
go func() {
|
||||
var b bytes.Buffer
|
||||
err := openpgp.ArmoredDetachSign(&b, sw.signer, pr, sw.config)
|
||||
pr.CloseWithError(err)
|
||||
ch <- &b
|
||||
}()
|
||||
|
||||
sw.body = struct{
|
||||
io.Writer
|
||||
io.Closer
|
||||
}{
|
||||
io.MultiWriter(w, pw),
|
||||
pw,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sw *signWriter) Write(b []byte) (n int, err error) {
|
||||
if sw.body == nil {
|
||||
if err := sw.open(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return sw.body.Write(b)
|
||||
}
|
||||
|
||||
func (sw *signWriter) Close() error {
|
||||
if sw.body == nil {
|
||||
if err := sw.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := sw.body.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig := <-sw.signature
|
||||
|
||||
// Create signature part
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Add("Content-Type", "application/pgp-signature")
|
||||
w, err := sw.multipart.CreatePart(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(w, sig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sw.multipart.Close()
|
||||
}
|
||||
|
||||
func (sw *signWriter) ContentType() string {
|
||||
return mime.FormatMediaType("multipart/signed", map[string]string{
|
||||
"boundary": sw.multipart.Boundary(),
|
||||
"micalg": "pgp-" + hashName(sw.config.Hash()),
|
||||
"protocol": "application/pgp-signature",
|
||||
})
|
||||
}
|
||||
|
||||
// Sign creates a new signed PGP/MIME message writer.
|
||||
func Sign(w io.Writer, h textproto.MIMEHeader, signer *openpgp.Entity, config *packet.Config) (message Writer) {
|
||||
return &signWriter{
|
||||
multipart: multipart.NewWriter(w),
|
||||
|
||||
h: h,
|
||||
signer: signer,
|
||||
config: config,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user