forked from wal-g/wal-g
/
reader.go
125 lines (95 loc) · 2.49 KB
/
reader.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
package libsodium
// #cgo CFLAGS: -I../../../tmp/libsodium/include
// #cgo LDFLAGS: -L../../../tmp/libsodium/lib -lsodium
// #include <sodium.h>
import "C"
import (
"io"
"sync"
"github.com/pkg/errors"
)
// Reader wraps ordinary reader with libsodium decryption
type Reader struct {
io.Reader
state C.crypto_secretstream_xchacha20poly1305_state
in []byte
out []byte
outIdx int
outLen int
// In case of using io.Pipe we can't read header until writer doesn't write, therefor we use these sync
onceHeader sync.Once
key []byte
headerErr error
}
// NewReader creates Reader from ordinary reader and key
func NewReader(reader io.Reader, key []byte) io.Reader {
return &Reader{
Reader: reader,
in: make([]byte, chunkSize+C.crypto_secretstream_xchacha20poly1305_ABYTES),
out: make([]byte, chunkSize),
key: key,
}
}
func (reader *Reader) readHeader() {
header := make([]byte, C.crypto_secretstream_xchacha20poly1305_HEADERBYTES)
if _, err := io.ReadFull(reader.Reader, header); err != nil {
reader.headerErr = errors.Wrap(err, "failed to read libsodium header")
return
}
var state C.crypto_secretstream_xchacha20poly1305_state
returnCode := C.crypto_secretstream_xchacha20poly1305_init_pull(
&state,
(*C.uchar)(&header[0]),
(*C.uchar)(&reader.key[0]),
)
if returnCode != 0 {
reader.headerErr = errors.New("corrupted libsodium header")
return
}
reader.state = state
}
// Read implements io.Reader
func (reader *Reader) Read(p []byte) (n int, err error) {
reader.onceHeader.Do(reader.readHeader)
if reader.headerErr != nil {
return 0, reader.headerErr
}
if reader.outIdx >= reader.outLen {
if err = reader.readNextChunk(); err != nil {
return
}
}
n = copy(p, reader.out[reader.outIdx:reader.outLen])
reader.outIdx += n
return
}
func (reader *Reader) readNextChunk() (err error) {
n, err := io.ReadFull(reader.Reader, reader.in)
if err != nil && err != io.ErrUnexpectedEOF {
return
}
var outLen C.ulonglong
var tag C.uchar
returnCode := C.crypto_secretstream_xchacha20poly1305_pull(
&reader.state,
(*C.uchar)(&reader.out[0]),
&outLen,
&tag,
(*C.uchar)(&reader.in[0]),
(C.ulonglong)(n),
(*C.uchar)(C.NULL),
(C.ulonglong)(0),
)
if returnCode != 0 {
err = errors.New("corrupted chunk")
}
if tag == C.crypto_secretstream_xchacha20poly1305_TAG_FINAL && err != io.ErrUnexpectedEOF {
err = errors.New("premature end")
}
if err == io.ErrUnexpectedEOF {
err = nil
}
reader.outIdx = 0
reader.outLen = int(outLen)
return err
}