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)
|
[![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
|
## 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