forked from ProtonMail/gopenpgp
/
attachment.go
167 lines (141 loc) · 4.49 KB
/
attachment.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package crypto
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"runtime"
"sync"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// AttachmentProcessor keeps track of the progress of encrypting an attachment
// (optimized for encrypting large files).
type AttachmentProcessor struct {
w *io.WriteCloser
pipe *io.PipeWriter
done sync.WaitGroup
split *models.EncryptedSplit
garbageCollector int
err error
}
// Process writes attachment data to be encrypted
func (ap *AttachmentProcessor) Process(plainData []byte) {
if _, err := (*ap.w).Write(plainData); err != nil {
panic(err)
}
}
// Finish closes the attachment and returns the encrypted data
func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {
if ap.err != nil {
return nil, ap.err
}
(*ap.w).Close()
(*ap.pipe).Close()
ap.done.Wait()
if ap.garbageCollector > 0 {
runtime.GC()
}
return ap.split, nil
}
// encryptAttachment creates an AttachmentProcessor which can be used to encrypt
// a file. It takes an estimatedSize and fileName as hints about the file.
func (pgp *GopenPGP) encryptAttachment(
estimatedSize int, fileName string, publicKey *KeyRing, garbageCollector int,
) (*AttachmentProcessor, error) {
attachmentProc := &AttachmentProcessor{}
// You could also add these one at a time if needed.
attachmentProc.done.Add(1)
attachmentProc.garbageCollector = garbageCollector
hints := &openpgp.FileHints{
FileName: fileName,
}
config := &packet.Config{
DefaultCipher: packet.CipherAES256,
Time: pgp.getTimeGenerator(),
}
reader, writer := io.Pipe()
go func() {
defer attachmentProc.done.Done()
split, splitError := SeparateKeyAndData(nil, reader, estimatedSize, garbageCollector)
if attachmentProc.err != nil {
attachmentProc.err = splitError
}
split.Algo = constants.AES256
attachmentProc.split = split
}()
var ew io.WriteCloser
var encryptErr error
ew, encryptErr = openpgp.Encrypt(writer, publicKey.entities, nil, hints, config)
if encryptErr != nil {
return nil, encryptErr
}
attachmentProc.w = &ew
attachmentProc.pipe = writer
return attachmentProc, nil
}
// EncryptAttachment encrypts a file. fileName
func (pgp *GopenPGP) EncryptAttachment(
plainData []byte, fileName string, publicKey *KeyRing,
) (*models.EncryptedSplit, error) {
ap, err := pgp.encryptAttachment(len(plainData), fileName, publicKey, -1)
if err != nil {
return nil, err
}
ap.Process(plainData)
split, err := ap.Finish()
if err != nil {
return nil, err
}
return split, nil
}
// EncryptAttachmentLowMemory creates an AttachmentProcessor which can be used
// to encrypt a file. It takes an estimatedSize and fileName as hints about the
// file. It is optimized for low-memory environments and collects garbage every
// megabyte.
func (pgp *GopenPGP) EncryptAttachmentLowMemory(
estimatedSize int, fileName string, publicKey *KeyRing,
) (*AttachmentProcessor, error) {
return pgp.encryptAttachment(estimatedSize, fileName, publicKey, 1<<20)
}
// SplitArmor is a helper method which splits an armored message into its
// session key packet and symmetrically encrypted data packet.
func SplitArmor(encrypted string) (*models.EncryptedSplit, error) {
var err error
encryptedRaw, err := armorUtils.Unarmor(encrypted)
if err != nil {
return nil, err
}
encryptedReader := bytes.NewReader(encryptedRaw)
return SeparateKeyAndData(nil, encryptedReader, len(encrypted), -1)
}
// DecryptAttachment takes a session key packet and symmetrically encrypted data
// packet. privateKeys is a KeyRing that can contain multiple keys. The
// passphrase is used to unlock keys in privateKeys.
func (pgp *GopenPGP) DecryptAttachment(
keyPacket, dataPacket []byte,
kr *KeyRing, passphrase string,
) ([]byte, error) {
privKeyEntries := kr.entities
if err := kr.Unlock([]byte(passphrase)); err != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt attachment: %v", err)
return nil, err
}
keyReader := bytes.NewReader(keyPacket)
dataReader := bytes.NewReader(dataPacket)
encryptedReader := io.MultiReader(keyReader, dataReader)
config := &packet.Config{Time: pgp.getTimeGenerator()}
md, err := openpgp.ReadMessage(encryptedReader, privKeyEntries, nil, config)
if err != nil {
return nil, err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
if err != nil {
return nil, err
}
return b, nil
}