add SSE-C support for HEAD, GET, PUT (#4894)

This change adds server-side-encryption support for HEAD, GET and PUT
operations. This PR only addresses single-part PUTs and GETs without
HTTP ranges.

Further this change adds the concept of reserved object metadata which is required
to make encrypted objects tamper-proof and provide API compatibility to AWS S3.
This PR adds the following reserved metadata entries:
- X-Minio-Internal-Server-Side-Encryption-Iv          ('guarantees' tamper-proof property)
- X-Minio-Internal-Server-Side-Encryption-Kdf         (makes Key-MAC computation negotiable in future)
- X-Minio-Internal-Server-Side-Encryption-Key-Mac     (provides AWS S3 API compatibility)

The prefix `X-Minio_Internal` specifies an internal metadata entry which must not
send to clients. All client requests containing a metadata key starting with `X-Minio-Internal`
must also rejected. This is implemented by a generic-handler.

This PR implements SSE-C separated from client-side-encryption (CSE). This cannot decrypt
server-side-encrypted objects on the client-side. However, clients can encrypted the same object
with CSE and SSE-C.

This PR does not address:
 - SSE-C Copy and Copy part
 - SSE-C GET with HTTP ranges
 - SSE-C multipart PUT
 - SSE-C Gateway

Each point must be addressed in a separate PR.

Added to vendor dir:
 - x/crypto/chacha20poly1305
 - x/crypto/poly1305
 - github.com/minio/sio
This commit is contained in:
Andreas Auernhammer
2017-11-08 00:18:59 +01:00
committed by Dee Koder
parent 7e7ae29d89
commit ca6b4773ed
35 changed files with 6321 additions and 197 deletions

223
vendor/github.com/minio/sio/DARE.md generated vendored Normal file
View File

@@ -0,0 +1,223 @@
# Data At Rest Encryption (DARE) - Version 1.0
**This is a draft**
## 1. Introduction
This document describes the Data At Rest Encryption (DARE) format for encrypting
data in a tamper-resistant way. DARE is designed to securely encrypt data stored
on (untrusted) storage providers.
## 2. Overview
DARE specifies how to split an arbitrary data stream into small chunks (packages)
and concatenate them into a tamper-proof chain. Tamper-proof means that an attacker
is not able to:
- decrypt one or more packages.
- modify the content of one or more packages.
- reorder/rearrange one or more packages.
An attacker is defined as somebody who has full access to the encrypted data
but not to the encryption key. An attacker can also act as storage provider.
### 2.1 Cryptographic Notation
DARE will use the following notations:
- The set **{a,b}** means select **one** of the provided values **a**, **b**.
- The concatenation of the byte sequences **a** and **b** is **a || b**.
- The function **len(seq)** returns the length of a byte sequence **seq** in bytes.
- The index access **seq[i]** accesses one byte at index **i** of the sequence **seq**.
- The range access **seq[i : j]** accesses a range of bytes starting at **i** (inclusive)
and ending at **j** (exclusive).
- The compare functions **a == b => f** and **a != b => f** succeed when **a**
is equal to **b** and **a** is not equal to **b** respectively and execute the command **f**.
- The function **CTC(a, b)** returns **1** only if **a** and **b** are equal, 0 otherwise.
CTC compares both values in **constant time**.
- **ENC(key, nonce, plaintext, addData)** represents the byte sequence which is
the output from an AEAD cipher authenticating the *addData*, encrypting and
authenticating the *plaintext* with the secret encryption *key* and the *nonce*.
- **DEC(key, nonce, ciphertext, addData)** represents the byte sequence which is
the output from an AEAD cipher verifying the integrity of the *ciphertext* &
*addData* and decrypting the *ciphertext* with the secret encryption *key* and
the *nonce*. The decryption **always** fails if the integrity check fails.
All numbers must be converted into byte sequences by using the little endian byte
order. An AEAD cipher will be either AES-256_GCM or CHACHA20_POLY1305.
## 2.2 Keys
Both ciphers - AES-256_GCM and CHACHA20_POLY1305 - require a 32 byte key. The key
**must** be unique for one encrypted data stream. Reusing a key **compromises**
some security properties provided by DARE. See Appendix A for recommendations
about generating keys and preventing key reuse.
## 2.3 Errors
DARE defines the following errors:
- **err_unsupported_version**: Indicates that the header version is not supported.
- **err_unsupported_cipher**: Indicates that the cipher suite is not supported.
- **err_missing_header**: Indicates that the payload header is missing or incomplete.
- **err_payload_too_short**: Indicates that the actual payload size is smaller than the
payload size field of the header.
- **err_package_out_of_order**: Indicates that the sequence number of the package does
not match the expected sequence number.
- **err_tag_mismatch**: Indicates that the tag of the package does not match the tag
computed while decrypting the package.
## 3. Package Format
DARE splits an arbitrary data stream into a sequence of packages. Each package is
encrypted separately. A package consists of a header, a payload and an authentication
tag.
Header | Payload | Tag
---------|----------------|---------
16 bytes | 1 byte - 64 KB | 16 bytes
The header contains information about the package. It consists of:
Version | Cipher suite | Payload size | Sequence number | nonce
--------|--------------|------------------|------------------|---------
1 byte | 1 byte | 2 bytes / uint16 | 4 bytes / uint32 | 8 bytes
The first byte specifies the version of the format and is equal to 0x10 for DARE
version 1.0. The second byte specifies the cipher used to encrypt the package.
Cipher | Value
------------------|-------
AES-256_GCM | 0x00
CHACHA20_POLY1305 | 0x01
The payload size is an uint16 number. The real payload size is defined as the payload
size field as uint32 + 1. This ensures that the payload can be exactly 64 KB long and
prevents empty packages without a payload.
The sequence number is an uint32 number identifying the package within a sequence of
packages. It is a monotonically increasing number. The sequence number **must** be 0 for
the first package and **must** be incremented for every subsequent package. The
sequence number of the n-th package is n-1. This means a sequence of packages can consist
of 2 ^ 32 packages and each package can hold up to 64 KB data. The maximum size
of a data stream is limited by `64 KB * 2^32 = 256 TB`. This should be sufficient
for current use cases. However, if necessary, the maximum size of a data stream can increased
in the future by slightly tweaking the header (with a new version).
The nonce **should** be a random value for each data stream and **should** be kept constant
for all its packages. Even if a key is accidentally used
twice to encrypt two different data streams an attacker should not be able to decrypt one
of those data streams. However, an attacker is always able to exchange corresponding packages
between the streams whenever a key is reused. DARE is only tamper-proof when the encryption
key is unique. See Appendix A.
The payload contains the encrypted data. It must be at least 1 byte long and can contain a maximum of 64 KB.
The authentication tag is generated by the AEAD cipher while encrypting and authenticating the
package. The authentication tag **must** always be verified while decrypting the package.
Decrypted content **must never** be returned before the authentication tag is successfully
verified.
## 4. Encryption
DARE encrypts every package separately. The header version, cipher suite and nonce **should**
be the same for all encrypted packages of one data stream. It is **recommended** to not change
this values within one sequence of packages. The nonce **should** be generated randomly once
at the beginning of the encryption process and repeated in every header. See Appendix B for
recommendations about generating random numbers.
The sequence number is the sequence number of the previous package plus 1. The sequence number
**must** be a monotonically increasing number within one sequence of packages. The sequence number
of the first package is **always** 0.
The payload field is the length of the plaintext in bytes minus 1. The encryption process is
defined as following:
```
header[0] = 0x10
header[1] = {AES-256_GCM, CHACHA20_POLY1305}
header[2:4] = little_endian( len(plaintext) - 1 )
header[4:8] = little_endian( sequence_number )
header[8:16] = nonce
payload || tag = ENC(key, header[4:16], plaintext, header[0:4])
sequence_number = sequence_number + 1
```
## 5. Decryption
DARE decrypts every package separately. Every package **must** be successfully decrypted **before**
plaintext is returned. The decryption happens in three steps:
1. Verify that the header version is correct (`header[0] == 0x10`) and the cipher suite is supported.
2. Verify that the sequence number of the packages matches the expected sequence number. It is required
to save the first expected sequence number at the beginning of the decryption process. After every
successfully decrypted package this sequence number is incremented by 1. The sequence number of
all packages **must** match the saved / expected number.
3. Verify that the authentication tag at the end of the package is equal to the authentication tag
computed while decrypting the package. This **must** happen in constant time.
The decryption is defined as following:
```
header[0] != 0x10 => err_unsupported_version
header[1] != {AES-256_GCM,CHACHA20_POLY1305} => err_unsupported_cipher
little_endian_uint32(header[4:8]) != expected_sequence_number => err_package_out_of_order
payload_size := little_endian_uint32(header[2:4]) + 1
plaintext || tag := DEC(key, header[4:16], ciphertext, header[0:4])
CTC(ciphertext[len(plaintext) : len(plaintext) + 16], tag) != 1 => err_tag_mismatch
expected_sequence_number = expected_sequence_number + 1
```
## Security
DARE provides confidentiality and integrity of the encrypted data as long as the encryption key
is never reused. This means that a **different** encryption key **must** be used for every data
stream. See Appendix A for recommendations.
If the same encryption key is used to encrypt two different data streams, an attacker is able to
exchange packages with the same sequence number. This means that the attacker is able to replace
any package of a sequence with another package as long as:
- Both packages are encrypted with the same key.
- The sequence numbers of both packages are equal.
If two data streams are encrypted with the same key the attacker will not be able to decrypt any
package of those streams without breaking the cipher as long as the nonces are different. To be
more precise the attacker may only be able to decrypt a package if:
- There is another package encrypted with the same key.
- The sequence number and nonce of those two packages (encrypted with the same key) are equal.
As long as the nonce of a sequence of packages differs from every other nonce (and the nonce is
repeated within one sequence - which is **recommended**) the attacker will not be able to decrypt
any package. It is not required that the nonce is indistinguishable from a truly random bit sequence.
It is sufficient when the nonces differ from each other in at least one bit.
## Appendices
### Appendix A - Key Derivation from a Master Key
DARE needs a unique encryption key per data stream. The best approach to ensure that the keys
are unique is to derive every encryption key from a master key. Therefore a key derivation function
(KDF) - e.g. HKDF, BLAKE2X or HChaCha20 - can be used. The master key itself may be derived from
a password using functions like Argon2 or scrypt. Deriving those keys is the responsibility of the
users of DARE.
It is **not recommended** to derive encryption keys from a master key and an identifier (like the
file path). If a different data stream is stored under the same identifier - e.g. overwriting the
data - the derived key would be the same for both streams.
Instead encryption keys should be derived from a master key and a random value. It is not required
that the random value is indistinguishable from a truly random bit sequence. The random value **must**
be unique but need not be secret - depending on the security properties of the KDF.
To keep this simple: The combination of master key and random value used to derive the encryption key
must be unique all the time.
### Appendix B - Generating random values
DARE does not require random values which are indistinguishable from a truly random bit sequence.
However, a random value **must** never be repeated. Therefore it is **recommended** to use a
cryptographically secure pseudorandom number generator (CSPRNG) to generate random values. Many
operating systems and cryptographic libraries already provide appropriate PRNG implementations.
These implementations should always be preferred over crafting a new one.

202
vendor/github.com/minio/sio/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

66
vendor/github.com/minio/sio/README.md generated vendored Normal file
View File

@@ -0,0 +1,66 @@
[![Godoc Reference](https://godoc.org/github.com/minio/sio?status.svg)](https://godoc.org/github.com/minio/sio)
[![Travis CI](https://travis-ci.org/minio/sio.svg?branch=master)](https://travis-ci.org/minio/sio)
[![Go Report Card](https://goreportcard.com/badge/minio/sio)](https://goreportcard.com/report/minio/sio)
# Secure IO
## Go implementation of the Data At Rest Encryption (DARE) format.
## Introduction
It is a common problem to store data securely - especially on untrusted remote storage.
One solution to this problem is cryptography. Before data is stored it is encrypted
to ensure that the data is confidential. Unfortunately encrypting data is not enough to
prevent more sophisticated attacks. Anyone who has access to the stored data can try to
manipulate the data - even if the data is encrypted.
To prevent these kinds of attacks the data must be encrypted in a tamper-resistant way.
This means an attacker should not be able to:
- Read the stored data - this is achieved by modern encryption algorithms.
- Modify the data by changing parts of the encrypted data.
- Rearrange or reorder parts of the encrypted data.
Authenticated encryption schemes (AE) - like AES-GCM or ChaCha20-Poly1305 - encrypt and
authenticate data. Any modification to the encrypted data (ciphertext) is detected while
decrypting the data. But even an AE scheme alone is not sufficiently enough to prevent all
kinds of data manipulation.
All modern AE schemes produce an authentication tag which is verified after the ciphertext
is decrypted. If a large amount of data is decrypted it is not always possible to buffer
all decrypted data until the authentication tag is verified. Returning unauthenticated
data has the same issues like encrypting data without authentication.
Splitting the data into small chunks fixes the problem of deferred authentication checks
but introduces a new one. The chunks can be reordered - e.g. exchanging chunk 1 and 2 -
because every chunk is encrypted separately. Therefore the order of the chunks must be
encoded somehow into the chunks itself to be able to detect rearranging any number of
chunks.
This project specifies a [format](https://github.com/minio/sio/blob/master/DARE.md) for
en/decrypting an arbitrary data stream and gives some [recommendations](https://github.com/minio/sio/blob/master/DARE.md#appendices)
about how to use and implement data at rest encryption (DARE). Additionally this project
provides a reference implementation in Go.
## Applications
DARE is designed with simplicity and efficiency in mind. It combines modern AE schemes
with a very simple reorder protection mechanism to build a tamper-resistant encryption
scheme. DARE can be used to encrypt files, backups and even large object storage systems.
Its main properties are:
- Security and high performance by relying on modern AEAD ciphers
- Small overhead - encryption increases the amount of data by ~0.05%
- Support for long data streams - up to 256 TB under the same key
- Random access - arbitrary sequences / ranges can be decrypted independently
**Install:** `go get -u github.com/minio/sio`
DARE and `github.com/minio/sio` are finalized and can be used in production.
## Performance
Cipher | 8 KB | 64 KB | 512 KB | 1 MB
----------------- | -------- | --------- | --------- | --------
AES_256_GCM | 90 MB/s | 1.96 GB/s | 2.64 GB/s | 2.83 GB/s
CHACHA20_POLY1305 | 97 MB/s | 1.23 GB/s | 1.54 GB/s | 1.57 GB/s
*On i7-6500U 2 x 2.5 GHz | Linux 4.10.0-32-generic | Go 1.8.3 | AES-NI & AVX2*

277
vendor/github.com/minio/sio/dare.go generated vendored Normal file
View File

@@ -0,0 +1,277 @@
// Copyright (C) 2017 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.
package sio // import "github.com/minio/sio"
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"golang.org/x/crypto/chacha20poly1305"
)
// Version10 specifies version 1.0
const 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
)
const (
headerSize = 16
payloadSize = 64 * 1024
tagSize = 16
)
var (
errMissingHeader = errors.New("sio: incomplete header")
errPayloadTooShort = errors.New("sio: payload too short")
errPackageOutOfOrder = errors.New("sio: sequence number mismatch")
errTagMismatch = errors.New("sio: authentication failed")
errUnsupportedVersion = errors.New("sio: unsupported version")
errUnsupportedCipher = errors.New("sio: unsupported cipher suite")
)
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,
}
// 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
}
return io.CopyBuffer(dst, encReader, make([]byte, payloadSize))
}
// 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.
func Decrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error) {
decReader, err := DecryptReader(src, config)
if err != nil {
return
}
return io.CopyBuffer(dst, decReader, make([]byte, headerSize+payloadSize+tagSize))
}
// 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
}
cipher, err := supportedCiphers[config.CipherSuites[0]](config.Key)
if err != nil {
return nil, err
}
nonce, err := config.generateNonce()
if err != nil {
return nil, err
}
return &encryptedReader{
src: src,
config: config,
nonce: nonce,
cipher: cipher,
sequenceNumber: config.SequenceNumber,
}, nil
}
// 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.
func DecryptReader(src io.Reader, config Config) (io.Reader, error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
ciphers, err := config.createCiphers()
if err != nil {
return nil, err
}
return &decryptedReader{
src: src,
config: config,
ciphers: ciphers,
sequenceNumber: config.SequenceNumber,
}, 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
}
cipher, err := supportedCiphers[config.CipherSuites[0]](config.Key)
if err != nil {
return nil, err
}
nonce, err := config.generateNonce()
if err != nil {
return nil, err
}
return &encryptedWriter{
dst: dst,
config: config,
nonce: nonce,
cipher: cipher,
sequenceNumber: config.SequenceNumber,
}, nil
}
// 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.
func DecryptWriter(dst io.Writer, config Config) (io.WriteCloser, error) {
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
ciphers, err := config.createCiphers()
if err != nil {
return nil, err
}
return &decryptedWriter{
dst: dst,
config: config,
ciphers: ciphers,
sequenceNumber: config.SequenceNumber,
}, nil
}
func setConfigDefaults(config *Config) error {
if config.MinVersion > Version10 {
return errors.New("sio: unknown minimum version")
}
if config.MaxVersion > Version10 {
return errors.New("dare: unknown maximum version")
}
if len(config.Key) != 32 {
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.MinVersion < Version10 {
config.MinVersion = Version10
}
if config.MaxVersion < Version10 {
config.MaxVersion = config.MinVersion
}
if len(config.CipherSuites) == 0 {
config.CipherSuites = []byte{AES_256_GCM, CHACHA20_POLY1305}
}
if config.Rand == nil {
config.Rand = rand.Reader
}
return nil
}
// 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 is used.
MinVersion byte
// The highest supported version of the format. If
// not set the default value 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 appending data to an existing
// sequence of packages or decrypting a range within
// a sequence of packages.
SequenceNumber uint32
// The RNG used to generate random values. If not set
// the default value (crypto/rand.Reader) is used.
Rand io.Reader
}
func (c *Config) verifyHeader(header headerV10) error {
if version := header.Version(); version < c.MinVersion || version > c.MaxVersion {
return errUnsupportedVersion
}
if !c.isCipherSuiteSupported(header.Cipher()) {
return errUnsupportedCipher
}
return nil
}
func (c *Config) createCiphers() ([]cipher.AEAD, error) {
ciphers := make([]cipher.AEAD, len(supportedCiphers))
for _, v := range c.CipherSuites {
aeadCipher, err := supportedCiphers[v](c.Key)
if err != nil {
return nil, err
}
ciphers[v] = aeadCipher
}
return ciphers, nil
}
func (c *Config) generateNonce() (nonce [8]byte, err error) {
_, err = io.ReadFull(c.Rand, nonce[:])
return nonce, err
}
func (c *Config) isCipherSuiteSupported(suite byte) bool {
for _, c := range c.CipherSuites {
if suite == c {
return true
}
}
return false
}

39
vendor/github.com/minio/sio/header.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
// Copyright (C) 2017 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
import "encoding/binary"
type headerV10 []byte
func header(b []byte) headerV10 { return headerV10(b[:headerSize]) }
func (h headerV10) Version() byte { return h[0] }
func (h headerV10) Cipher() byte { return h[1] }
func (h headerV10) Len() int { return int(binary.LittleEndian.Uint16(h[2:])) + 1 }
func (h headerV10) SequenceNumber() uint32 { return binary.LittleEndian.Uint32(h[4:]) }
func (h headerV10) SetVersion(version byte) { h[0] = version }
func (h headerV10) SetCipher(suite byte) { h[1] = suite }
func (h headerV10) SetLen(length int) { binary.LittleEndian.PutUint16(h[2:], uint16(length-1)) }
func (h headerV10) SetSequenceNumber(num uint32) { binary.LittleEndian.PutUint32(h[4:], num) }
func (h headerV10) SetNonce(nonce [8]byte) { copy(h[8:], nonce[:]) }

169
vendor/github.com/minio/sio/reader.go generated vendored Normal file
View File

@@ -0,0 +1,169 @@
// Copyright (C) 2017 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
import (
"crypto/cipher"
"io"
)
type encryptedReader struct {
src io.Reader
config Config
sequenceNumber uint32
nonce [8]byte
cipher cipher.AEAD
pack [headerSize + payloadSize + tagSize]byte
offset int
}
func (r *encryptedReader) Read(p []byte) (n int, err error) {
if r.offset > 0 {
remaining := headerSize + header(r.pack[:]).Len() + tagSize - r.offset
if len(p) < remaining {
n = copy(p, r.pack[r.offset:r.offset+len(p)])
r.offset += n
return
}
n = copy(p, r.pack[r.offset:r.offset+remaining])
p = p[remaining:]
r.offset = 0
}
for len(p) >= headerSize+payloadSize+tagSize {
nn, err := io.ReadFull(r.src, r.pack[headerSize:headerSize+payloadSize])
if err != nil && err != io.ErrUnexpectedEOF {
return n, err
}
r.encrypt(p, nn)
n += headerSize + nn + tagSize
p = p[headerSize+nn+tagSize:]
}
if len(p) > 0 {
nn, err := io.ReadFull(r.src, r.pack[headerSize:headerSize+payloadSize])
if err != nil && err != io.ErrUnexpectedEOF {
return n, err
}
r.encrypt(r.pack[:], nn)
if headerSize+nn+tagSize < len(p) {
r.offset = copy(p, r.pack[:headerSize+nn+tagSize])
} else {
r.offset = copy(p, r.pack[:len(p)])
}
n += r.offset
}
return
}
func (r *encryptedReader) encrypt(dst []byte, length int) {
header := header(dst)
header.SetVersion(r.config.MaxVersion)
header.SetCipher(r.config.CipherSuites[0])
header.SetLen(length)
header.SetSequenceNumber(r.sequenceNumber)
header.SetNonce(r.nonce)
copy(dst[:headerSize], header)
r.cipher.Seal(dst[headerSize:headerSize], header[4:headerSize], r.pack[headerSize:headerSize+length], header[:4])
r.sequenceNumber++
}
type decryptedReader struct {
src io.Reader
config Config
sequenceNumber uint32
ciphers []cipher.AEAD
pack [headerSize + payloadSize + tagSize]byte
offset int
}
func (r *decryptedReader) Read(p []byte) (n int, err error) {
if r.offset > 0 {
remaining := header(r.pack[:]).Len() - r.offset
if len(p) < remaining {
n = copy(p, r.pack[headerSize+r.offset:headerSize+r.offset+len(p)])
r.offset += n
return
}
n = copy(p, r.pack[headerSize+r.offset:headerSize+r.offset+remaining])
p = p[remaining:]
r.offset = 0
}
for len(p) >= payloadSize {
if err = r.readHeader(); err != nil {
return n, err
}
nn, err := r.decrypt(p[:payloadSize])
if err != nil {
return n, err
}
p = p[nn:]
n += nn
}
if len(p) > 0 {
if err = r.readHeader(); err != nil {
return n, err
}
nn, err := r.decrypt(r.pack[headerSize:])
if err != nil {
return n, err
}
if nn < len(p) {
r.offset = copy(p, r.pack[headerSize:headerSize+nn])
} else {
r.offset = copy(p, r.pack[headerSize:headerSize+len(p)])
}
n += r.offset
}
return
}
func (r *decryptedReader) readHeader() error {
n, err := io.ReadFull(r.src, header(r.pack[:]))
if n != headerSize && err == io.ErrUnexpectedEOF {
return errMissingHeader
} else if err != nil {
return err
}
return nil
}
func (r *decryptedReader) decrypt(dst []byte) (n int, err error) {
header := header(r.pack[:])
if err = r.config.verifyHeader(header); err != nil {
return 0, err
}
if header.SequenceNumber() != r.sequenceNumber {
return 0, errPackageOutOfOrder
}
ciphertext := r.pack[headerSize : headerSize+header.Len()+tagSize]
n, err = io.ReadFull(r.src, ciphertext)
if err == io.EOF || err == io.ErrUnexpectedEOF {
return 0, errPayloadTooShort
} else if err != nil {
return 0, err
}
aeadCipher := r.ciphers[header.Cipher()]
plaintext, err := aeadCipher.Open(dst[:0], header[4:], ciphertext, header[:4])
if err != nil {
return 0, errTagMismatch
}
r.sequenceNumber++
return len(plaintext), nil
}

193
vendor/github.com/minio/sio/writer.go generated vendored Normal file
View File

@@ -0,0 +1,193 @@
// Copyright (C) 2017 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
import (
"crypto/cipher"
"io"
)
type decryptedWriter struct {
dst io.Writer
config Config
sequenceNumber uint32
ciphers []cipher.AEAD
pack [headerSize + payloadSize + tagSize]byte
offset int
}
func (w *decryptedWriter) Write(p []byte) (n int, err error) {
if w.offset > 0 && w.offset < headerSize {
remaining := headerSize - w.offset
if len(p) < remaining {
n = copy(w.pack[w.offset:], p)
w.offset += n
return
}
n = copy(w.pack[w.offset:], p[:remaining])
p = p[remaining:]
w.offset += n
}
if w.offset >= headerSize {
remaining := headerSize + header(w.pack[:]).Len() + tagSize - w.offset
if len(p) < remaining {
nn := copy(w.pack[w.offset:], p)
w.offset += nn
return n + nn, err
}
n += copy(w.pack[w.offset:], p[:remaining])
if err = w.decrypt(w.pack[:]); err != nil {
return n, err
}
p = p[remaining:]
w.offset = 0
}
for len(p) > headerSize {
header := header(p)
if len(p) < headerSize+header.Len()+tagSize {
w.offset = copy(w.pack[:], p)
n += w.offset
return
}
if err = w.decrypt(p); err != nil {
return n, err
}
p = p[headerSize+header.Len()+tagSize:]
n += headerSize + header.Len() + tagSize
}
w.offset = copy(w.pack[:], p)
n += w.offset
return
}
func (w *decryptedWriter) Close() error {
if w.offset > 0 {
if w.offset < headerSize {
return errMissingHeader
}
if w.offset < headerSize+header(w.pack[:]).Len()+tagSize {
return errPayloadTooShort
}
if err := w.decrypt(w.pack[:]); err != nil {
return err
}
}
if dst, ok := w.dst.(io.Closer); ok {
return dst.Close()
}
return nil
}
func (w *decryptedWriter) decrypt(src []byte) error {
header := header(src)
if err := w.config.verifyHeader(header); err != nil {
return err
}
if header.SequenceNumber() != w.sequenceNumber {
return errPackageOutOfOrder
}
aeadCipher := w.ciphers[header.Cipher()]
plaintext, err := aeadCipher.Open(w.pack[headerSize:headerSize], header[4:headerSize], src[headerSize:headerSize+header.Len()+tagSize], header[:4])
if err != nil {
return errTagMismatch
}
n, err := w.dst.Write(plaintext)
if err != nil {
return err
}
if n != len(plaintext) {
return io.ErrShortWrite
}
w.sequenceNumber++
return nil
}
type encryptedWriter struct {
dst io.Writer
config Config
nonce [8]byte
sequenceNumber uint32
cipher cipher.AEAD
pack [headerSize + payloadSize + tagSize]byte
offset int
}
func (w *encryptedWriter) Write(p []byte) (n int, err error) {
if w.offset > 0 {
remaining := payloadSize - w.offset
if len(p) < remaining {
n = copy(w.pack[headerSize+w.offset:], p)
w.offset += n
return
}
n = copy(w.pack[headerSize+w.offset:], p[:remaining])
if err = w.encrypt(w.pack[headerSize : headerSize+payloadSize]); err != nil {
return
}
p = p[remaining:]
w.offset = 0
}
for len(p) >= payloadSize {
if err = w.encrypt(p[:payloadSize]); err != nil {
return
}
p = p[payloadSize:]
n += payloadSize
}
if len(p) > 0 {
w.offset = copy(w.pack[headerSize:], p)
n += w.offset
}
return
}
func (w *encryptedWriter) Close() error {
if w.offset > 0 {
return w.encrypt(w.pack[headerSize : headerSize+w.offset])
}
if dst, ok := w.dst.(io.Closer); ok {
return dst.Close()
}
return nil
}
func (w *encryptedWriter) encrypt(src []byte) error {
header := header(w.pack[:])
header.SetVersion(w.config.MaxVersion)
header.SetCipher(w.config.CipherSuites[0])
header.SetLen(len(src))
header.SetSequenceNumber(w.sequenceNumber)
header.SetNonce(w.nonce)
w.cipher.Seal(w.pack[headerSize:headerSize], header[4:headerSize], src, header[:4])
n, err := w.dst.Write(w.pack[:headerSize+len(src)+tagSize])
if err != nil {
return err
}
if n != headerSize+len(src)+tagSize {
return io.ErrShortWrite
}
w.sequenceNumber++
return nil
}