forked from gopasspw/gopass
/
stream.go
169 lines (148 loc) · 3.92 KB
/
stream.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
168
169
package xc
import (
"context"
crypto_rand "crypto/rand"
stdbin "encoding/binary"
"fmt"
"io"
"github.com/alecthomas/binary"
"github.com/gopasspw/gopass/pkg/backend/crypto/xc/xcpb"
"github.com/pkg/errors"
"golang.org/x/crypto/nacl/secretbox"
)
// EncryptStream encrypts the plaintext using a slightly modified on disk-format
// suitable for streaming
func (x *XC) EncryptStream(ctx context.Context, plaintext io.Reader, recipients []string, ciphertext io.Writer) error {
privKeyIDs := x.secring.KeyIDs()
if len(privKeyIDs) < 1 {
return fmt.Errorf("no signing keys available on our keyring")
}
privKey := x.secring.Get(privKeyIDs[0])
// generate session / encryption key
var sessionKey [32]byte
if _, err := crypto_rand.Read(sessionKey[:]); err != nil {
return err
}
// encrypt the session key per recipient
header, err := x.encryptHeader(ctx, privKey, sessionKey[:], recipients)
if err != nil {
return errors.Wrapf(err, "failed to encrypt header: %s", err)
}
// create the encoder
enc := binary.NewEncoder(ciphertext)
// write verion
if err := enc.Encode(0x1); err != nil {
return err
}
// write header
if err := enc.Encode(header); err != nil {
return err
}
// write body
num := 0
buf := make([]byte, chunkSizeMax)
encbuf := make([]byte, 8)
for {
n, err := plaintext.Read(buf)
if err := x.encryptChunk(sessionKey, num, buf[:n], encbuf, ciphertext); err != nil {
return err
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
num++
}
}
func (x *XC) encryptChunk(sessionKey [32]byte, num int, buf, encbuf []byte, ciphertext io.Writer) error {
// use a sequential nonce to prevent chunk reordering.
// since the pair of key and nonce has to be unique and we're
// generating a new random key for each message, this is OK
var nonce [24]byte
binary.BigEndian.PutUint64(nonce[:], uint64(num))
// encrypt the plaintext using the random nonce
cipherBlock := secretbox.Seal(nil, buf, &nonce, &sessionKey)
// write ciphertext block length
l := stdbin.PutUvarint(encbuf, uint64(len(cipherBlock)))
if _, err := ciphertext.Write(encbuf[:l]); err != nil {
return err
}
// write ciphertext block data
_, err := ciphertext.Write(cipherBlock)
return err
}
// DecryptStream decrypts an stream encrypted with EncryptStream
func (x *XC) DecryptStream(ctx context.Context, ciphertext io.Reader, plaintext io.Writer) error {
dec := binary.NewDecoder(ciphertext)
// read version
ver := 0
if err := dec.Decode(&ver); err != nil {
return err
}
if ver != 0x1 {
return fmt.Errorf("wrong version")
}
// read header
header := &xcpb.Header{}
if err := dec.Decode(header); err != nil {
return err
}
// try to find a suiteable decryption key in the header
sk, err := x.decryptSessionKey(ctx, header)
if err != nil {
return err
}
var secretKey [32]byte
copy(secretKey[:], sk)
// read body
num := 0
var buf []byte
br := &byteReader{ciphertext}
for {
l, err := stdbin.ReadUvarint(br)
if err != nil {
if err == io.EOF {
return nil
}
return err
}
buf = make([]byte, l)
n, err := br.Read(buf)
if err := x.decryptChunk(secretKey, num, buf[:n], plaintext); err != nil {
return err
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
num++
}
}
func (x *XC) decryptChunk(secretKey [32]byte, num int, buf []byte, plaintext io.Writer) error {
// reconstruct nonce from chunk number
// in case chunks have been reordered by some adversary
// decryption will fail
var nonce [24]byte
binary.BigEndian.PutUint64(nonce[:], uint64(num))
// decrypt and verify the ciphertext
plain, ok := secretbox.Open(nil, buf, &nonce, &secretKey)
if !ok {
return fmt.Errorf("failed to decrypt chunk %d", num)
}
_, err := plaintext.Write(plain)
return err
}
type byteReader struct {
io.Reader
}
func (b *byteReader) ReadByte() (byte, error) {
var buf [1]byte
if _, err := io.ReadFull(b, buf[:]); err != nil {
return 0, err
}
return buf[0], nil
}