mirror of
https://github.com/minio/minio.git
synced 2025-11-10 05:59:43 -05:00
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:
committed by
Dee Koder
parent
7e7ae29d89
commit
ca6b4773ed
223
vendor/github.com/minio/sio/DARE.md
generated
vendored
Normal file
223
vendor/github.com/minio/sio/DARE.md
generated
vendored
Normal 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
202
vendor/github.com/minio/sio/LICENSE
generated
vendored
Normal 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
66
vendor/github.com/minio/sio/README.md
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
[](https://godoc.org/github.com/minio/sio)
|
||||
[](https://travis-ci.org/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
277
vendor/github.com/minio/sio/dare.go
generated
vendored
Normal 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
39
vendor/github.com/minio/sio/header.go
generated
vendored
Normal 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
169
vendor/github.com/minio/sio/reader.go
generated
vendored
Normal 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
193
vendor/github.com/minio/sio/writer.go
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user