Files
silo/vendor/github.com/minio/sio/reader.go
T
Andreas Auernhammer ca6b4773ed 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
2017-11-07 15:18:59 -08:00

170 lines
4.2 KiB
Go

// 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
}