-
Notifications
You must be signed in to change notification settings - Fork 54
/
Copy pathsio.go
357 lines (319 loc) · 11.6 KB
/
sio.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// Copyright (C) 2018 Minio Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package sio implements the DARE format. It provides an API for secure
// en/decrypting IO operations using io.Reader and io.Writer.
package sio // import "github.com/minio/sio"
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"runtime"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/sys/cpu"
)
const (
// Version20 specifies version 2.0
Version20 byte = 0x20
// Version10 specifies version 1.0
Version10 byte = 0x10
)
const (
// AES_256_GCM specifies the cipher suite AES-GCM with 256 bit keys.
AES_256_GCM byte = iota
// CHACHA20_POLY1305 specifies the cipher suite ChaCha20Poly1305 with 256 bit keys.
CHACHA20_POLY1305
)
// supportsAES indicates whether the CPU provides hardware support for AES-GCM.
// AES-GCM should only be selected as default cipher if there's hardware support.
var supportsAES = (cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ) || runtime.GOARCH == "s390x"
const (
keySize = 32
headerSize = 16
maxPayloadSize = 1 << 16
tagSize = 16
maxPackageSize = headerSize + maxPayloadSize + tagSize
maxDecryptedSize = 1 << 48
maxEncryptedSize = maxDecryptedSize + ((headerSize + tagSize) * 1 << 32)
)
var newAesGcm = func(key []byte) (cipher.AEAD, error) {
aes256, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(aes256)
}
var supportedCiphers = [...]func([]byte) (cipher.AEAD, error){
AES_256_GCM: newAesGcm,
CHACHA20_POLY1305: chacha20poly1305.New,
}
var (
errUnsupportedVersion = Error{"sio: unsupported version"}
errUnsupportedCipher = Error{"sio: unsupported cipher suite"}
errInvalidPayloadSize = Error{"sio: invalid payload size"}
errTagMismatch = Error{"sio: authentication failed"}
errUnexpectedSize = Error{"sio: size is too large for DARE"}
// Version 1.0 specific
errPackageOutOfOrder = Error{"sio: sequence number mismatch"}
// Version 2.0 specific
errNonceMismatch = Error{"sio: header nonce mismatch"}
errUnexpectedEOF = Error{"sio: unexpected EOF"}
errUnexpectedData = Error{"sio: unexpected data after final package"}
)
// Error is the error returned by an io.Reader or io.Writer
// if the encrypted data cannot be decrypted because it is
// malformed or not authentic.
type Error struct{ msg string }
func (e Error) Error() string { return e.msg }
// Config contains the format configuration. The only field
// which must always be set manually is the secret key.
type Config struct {
// The minimal supported version of the format. If
// not set the default value - Version10 - is used.
MinVersion byte
// The highest supported version of the format. If
// not set the default value - Version20 - is used.
MaxVersion byte
// A list of supported cipher suites. If not set the
// default value is used.
CipherSuites []byte
// The secret encryption key. It must be 32 bytes long.
Key []byte
// The first expected sequence number. It should only
// be set manually when decrypting a range within a
// stream.
SequenceNumber uint32
// The RNG used to generate random values. If not set
// the default value (crypto/rand.Reader) is used.
Rand io.Reader
// Nonce will override the nonce if set non-nil.
// V2 will use all 12 bytes, V1 first 8 bytes.
Nonce *[12]byte
// The size of the encrypted payload in bytes. The
// default value is 64KB. It should be used to restrict
// the size of encrypted packages. The payload size
// must be between 1 and 64 KB.
//
// This field is specific for version 1.0 and is
// deprecated.
PayloadSize int
}
// EncryptedSize computes the size of an encrypted data stream
// from the plaintext size. It is the inverse of DecryptedSize().
//
// EncryptedSize returns an error if the provided size is to large.
func EncryptedSize(size uint64) (uint64, error) {
if size > maxDecryptedSize {
return 0, errUnexpectedSize
}
encSize := (size / maxPayloadSize) * maxPackageSize
if mod := size % maxPayloadSize; mod > 0 {
encSize += mod + (headerSize + tagSize)
}
return encSize, nil
}
// DecryptedSize computes the size of a decrypted data stream
// from the encrypted stream size. It is the inverse of EncryptedSize().
//
// DecryptedSize returns an error if the provided size is to large
// or if the provided size is an invalid encrypted stream size.
func DecryptedSize(size uint64) (uint64, error) {
if size > maxEncryptedSize {
return 0, errUnexpectedSize
}
decSize := (size / maxPackageSize) * maxPayloadSize
if mod := size % maxPackageSize; mod > 0 {
if mod <= headerSize+tagSize {
return 0, errors.New("sio: size is not valid") // last package is not valid
}
decSize += mod - (headerSize + tagSize)
}
return decSize, nil
}
// Encrypt reads from src until it encounters an io.EOF and encrypts all received
// data. The encrypted data is written to dst. It returns the number of bytes
// encrypted and the first error encountered while encrypting, if any.
//
// Encrypt returns the number of bytes written to dst.
func Encrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error) {
encReader, err := EncryptReader(src, config)
if err != nil {
return 0, err
}
return io.CopyBuffer(dst, encReader, make([]byte, headerSize+maxPayloadSize+tagSize))
}
// Decrypt reads from src until it encounters an io.EOF and decrypts all received
// data. The decrypted data is written to dst. It returns the number of bytes
// decrypted and the first error encountered while decrypting, if any.
//
// Decrypt returns the number of bytes written to dst. Decrypt only writes data to
// dst if the data was decrypted successfully. It returns an error of type sio.Error
// if decryption fails.
func Decrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error) {
decReader, err := DecryptReader(src, config)
if err != nil {
return 0, err
}
return io.CopyBuffer(dst, decReader, make([]byte, maxPayloadSize))
}
// DecryptBuffer decrypts all received data in src.
// The decrypted data is appended to dst.
// If the number of output bytes is unknown,
// making a dst with capacity of len(src) is reasonable.
//
// DecryptBuffer only returns data to if the data was decrypted successfully.
// It returns an error of type sio.Error if decryption fails.
func DecryptBuffer(dst, src []byte, config Config) (output []byte, err error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
if config.MinVersion == Version10 && config.MaxVersion == Version10 {
buf := bytes.NewBuffer(dst)
if _, err := Decrypt(buf, bytes.NewBuffer(src), config); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
if config.MinVersion == Version20 && config.MaxVersion == Version20 {
return decryptBufferV20(dst, src, &config)
}
return decryptBuffer(dst, src, &config)
}
// EncryptReader wraps the given src and returns an io.Reader which encrypts
// all received data. EncryptReader returns an error if the provided encryption
// configuration is invalid.
func EncryptReader(src io.Reader, config Config) (io.Reader, error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
if config.MaxVersion == Version20 {
return encryptReaderV20(src, &config)
}
return encryptReaderV10(src, &config)
}
// DecryptReader wraps the given src and returns an io.Reader which decrypts
// all received data. DecryptReader returns an error if the provided decryption
// configuration is invalid. The returned io.Reader returns an error of
// type sio.Error if the decryption fails.
func DecryptReader(src io.Reader, config Config) (io.Reader, error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
if config.MinVersion == Version10 && config.MaxVersion == Version10 {
return decryptReaderV10(src, &config)
}
if config.MinVersion == Version20 && config.MaxVersion == Version20 {
return decryptReaderV20(src, &config)
}
return decryptReader(src, &config), nil
}
// DecryptReaderAt wraps the given src and returns an io.ReaderAt which decrypts
// all received data. DecryptReaderAt returns an error if the provided decryption
// configuration is invalid. The returned io.ReaderAt returns an error of
// type sio.Error if the decryption fails.
func DecryptReaderAt(src io.ReaderAt, config Config) (io.ReaderAt, error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
if config.MinVersion == Version10 && config.MaxVersion == Version10 {
return decryptReaderAtV10(src, &config)
}
if config.MinVersion == Version20 && config.MaxVersion == Version20 {
return decryptReaderAtV20(src, &config)
}
return decryptReaderAt(src, &config), nil
}
// EncryptWriter wraps the given dst and returns an io.WriteCloser which
// encrypts all data written to it. EncryptWriter returns an error if the
// provided decryption configuration is invalid.
//
// The returned io.WriteCloser must be closed successfully to finalize the
// encryption process.
func EncryptWriter(dst io.Writer, config Config) (io.WriteCloser, error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
if config.MaxVersion == Version20 {
return encryptWriterV20(dst, &config)
}
return encryptWriterV10(dst, &config)
}
// DecryptWriter wraps the given dst and returns an io.WriteCloser which
// decrypts all data written to it. DecryptWriter returns an error if the
// provided decryption configuration is invalid.
//
// The returned io.WriteCloser must be closed successfully to finalize the
// decryption process. The returned io.WriteCloser returns an error of
// type sio.Error if the decryption fails.
func DecryptWriter(dst io.Writer, config Config) (io.WriteCloser, error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
if config.MinVersion == Version10 && config.MaxVersion == Version10 {
return decryptWriterV10(dst, &config)
}
if config.MinVersion == Version20 && config.MaxVersion == Version20 {
return decryptWriterV20(dst, &config)
}
return decryptWriter(dst, &config), nil
}
func defaultCipherSuites() []byte {
if supportsAES {
return []byte{AES_256_GCM, CHACHA20_POLY1305}
}
return []byte{CHACHA20_POLY1305, AES_256_GCM}
}
func setConfigDefaults(config *Config) error {
if config.MinVersion > Version20 {
return errors.New("sio: unknown minimum version")
}
if config.MaxVersion > Version20 {
return errors.New("sio: unknown maximum version")
}
if len(config.Key) != keySize {
return errors.New("sio: invalid key size")
}
if len(config.CipherSuites) > 2 {
return errors.New("sio: too many cipher suites")
}
for _, c := range config.CipherSuites {
if int(c) >= len(supportedCiphers) {
return errors.New("sio: unknown cipher suite")
}
}
if config.PayloadSize > maxPayloadSize {
return errors.New("sio: payload size is too large")
}
if config.MinVersion < Version10 {
config.MinVersion = Version10
}
if config.MaxVersion < Version10 {
config.MaxVersion = Version20
}
if config.MinVersion > config.MaxVersion {
return errors.New("sio: minimum version cannot be larger than maximum version")
}
if len(config.CipherSuites) == 0 {
config.CipherSuites = defaultCipherSuites()
}
if config.Rand == nil {
config.Rand = rand.Reader
}
if config.PayloadSize == 0 {
config.PayloadSize = maxPayloadSize
}
return nil
}