294 lines
6.5 KiB
Go
294 lines
6.5 KiB
Go
|
/*
|
||
|
* Minio Go Library for Amazon S3 Compatible Cloud Storage (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 encrypt
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/aes"
|
||
|
"crypto/cipher"
|
||
|
"crypto/rand"
|
||
|
"encoding/base64"
|
||
|
"errors"
|
||
|
"io"
|
||
|
)
|
||
|
|
||
|
// Crypt mode - encryption or decryption
|
||
|
type cryptMode int
|
||
|
|
||
|
const (
|
||
|
encryptMode cryptMode = iota
|
||
|
decryptMode
|
||
|
)
|
||
|
|
||
|
// CBCSecureMaterials encrypts/decrypts data using AES CBC algorithm
|
||
|
type CBCSecureMaterials struct {
|
||
|
|
||
|
// Data stream to encrypt/decrypt
|
||
|
stream io.Reader
|
||
|
|
||
|
// Last internal error
|
||
|
err error
|
||
|
|
||
|
// End of file reached
|
||
|
eof bool
|
||
|
|
||
|
// Holds initial data
|
||
|
srcBuf *bytes.Buffer
|
||
|
|
||
|
// Holds transformed data (encrypted or decrypted)
|
||
|
dstBuf *bytes.Buffer
|
||
|
|
||
|
// Encryption algorithm
|
||
|
encryptionKey Key
|
||
|
|
||
|
// Key to encrypts/decrypts data
|
||
|
contentKey []byte
|
||
|
|
||
|
// Encrypted form of contentKey
|
||
|
cryptedKey []byte
|
||
|
|
||
|
// Initialization vector
|
||
|
iv []byte
|
||
|
|
||
|
// matDesc - currently unused
|
||
|
matDesc []byte
|
||
|
|
||
|
// Indicate if we are going to encrypt or decrypt
|
||
|
cryptMode cryptMode
|
||
|
|
||
|
// Helper that encrypts/decrypts data
|
||
|
blockMode cipher.BlockMode
|
||
|
}
|
||
|
|
||
|
// NewCBCSecureMaterials builds new CBC crypter module with
|
||
|
// the specified encryption key (symmetric or asymmetric)
|
||
|
func NewCBCSecureMaterials(key Key) (*CBCSecureMaterials, error) {
|
||
|
if key == nil {
|
||
|
return nil, errors.New("Unable to recognize empty encryption properties")
|
||
|
}
|
||
|
return &CBCSecureMaterials{
|
||
|
srcBuf: bytes.NewBuffer([]byte{}),
|
||
|
dstBuf: bytes.NewBuffer([]byte{}),
|
||
|
encryptionKey: key,
|
||
|
matDesc: []byte("{}"),
|
||
|
}, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
// Close implements closes the internal stream.
|
||
|
func (s *CBCSecureMaterials) Close() error {
|
||
|
closer, ok := s.stream.(io.Closer)
|
||
|
if ok {
|
||
|
return closer.Close()
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetupEncryptMode - tells CBC that we are going to encrypt data
|
||
|
func (s *CBCSecureMaterials) SetupEncryptMode(stream io.Reader) error {
|
||
|
// Set mode to encrypt
|
||
|
s.cryptMode = encryptMode
|
||
|
|
||
|
// Set underlying reader
|
||
|
s.stream = stream
|
||
|
|
||
|
s.eof = false
|
||
|
s.srcBuf.Reset()
|
||
|
s.dstBuf.Reset()
|
||
|
|
||
|
var err error
|
||
|
|
||
|
// Generate random content key
|
||
|
s.contentKey = make([]byte, aes.BlockSize*2)
|
||
|
if _, err := rand.Read(s.contentKey); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Encrypt content key
|
||
|
s.cryptedKey, err = s.encryptionKey.Encrypt(s.contentKey)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// Generate random IV
|
||
|
s.iv = make([]byte, aes.BlockSize)
|
||
|
if _, err = rand.Read(s.iv); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// New cipher
|
||
|
encryptContentBlock, err := aes.NewCipher(s.contentKey)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
s.blockMode = cipher.NewCBCEncrypter(encryptContentBlock, s.iv)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetupDecryptMode - tells CBC that we are going to decrypt data
|
||
|
func (s *CBCSecureMaterials) SetupDecryptMode(stream io.Reader, iv string, key string) error {
|
||
|
// Set mode to decrypt
|
||
|
s.cryptMode = decryptMode
|
||
|
|
||
|
// Set underlying reader
|
||
|
s.stream = stream
|
||
|
|
||
|
// Reset
|
||
|
s.eof = false
|
||
|
s.srcBuf.Reset()
|
||
|
s.dstBuf.Reset()
|
||
|
|
||
|
var err error
|
||
|
|
||
|
// Get IV
|
||
|
s.iv, err = base64.StdEncoding.DecodeString(iv)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Get encrypted content key
|
||
|
s.cryptedKey, err = base64.StdEncoding.DecodeString(key)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Decrypt content key
|
||
|
s.contentKey, err = s.encryptionKey.Decrypt(s.cryptedKey)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// New cipher
|
||
|
decryptContentBlock, err := aes.NewCipher(s.contentKey)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
s.blockMode = cipher.NewCBCDecrypter(decryptContentBlock, s.iv)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// GetIV - return randomly generated IV (per S3 object), base64 encoded.
|
||
|
func (s *CBCSecureMaterials) GetIV() string {
|
||
|
return base64.StdEncoding.EncodeToString(s.iv)
|
||
|
}
|
||
|
|
||
|
// GetKey - return content encrypting key (cek) in encrypted form, base64 encoded.
|
||
|
func (s *CBCSecureMaterials) GetKey() string {
|
||
|
return base64.StdEncoding.EncodeToString(s.cryptedKey)
|
||
|
}
|
||
|
|
||
|
// GetDesc - user provided encryption material description in JSON (UTF8) format.
|
||
|
func (s *CBCSecureMaterials) GetDesc() string {
|
||
|
return string(s.matDesc)
|
||
|
}
|
||
|
|
||
|
// Fill buf with encrypted/decrypted data
|
||
|
func (s *CBCSecureMaterials) Read(buf []byte) (n int, err error) {
|
||
|
// Always fill buf from bufChunk at the end of this function
|
||
|
defer func() {
|
||
|
if s.err != nil {
|
||
|
n, err = 0, s.err
|
||
|
} else {
|
||
|
n, err = s.dstBuf.Read(buf)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
// Return
|
||
|
if s.eof {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Fill dest buffer if its length is less than buf
|
||
|
for !s.eof && s.dstBuf.Len() < len(buf) {
|
||
|
|
||
|
srcPart := make([]byte, aes.BlockSize)
|
||
|
dstPart := make([]byte, aes.BlockSize)
|
||
|
|
||
|
// Fill src buffer
|
||
|
for s.srcBuf.Len() < aes.BlockSize*2 {
|
||
|
_, err = io.CopyN(s.srcBuf, s.stream, aes.BlockSize)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Quit immediately for errors other than io.EOF
|
||
|
if err != nil && err != io.EOF {
|
||
|
s.err = err
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Mark current encrypting/decrypting as finished
|
||
|
s.eof = (err == io.EOF)
|
||
|
|
||
|
if s.eof && s.cryptMode == encryptMode {
|
||
|
if srcPart, err = pkcs5Pad(s.srcBuf.Bytes(), aes.BlockSize); err != nil {
|
||
|
s.err = err
|
||
|
return
|
||
|
}
|
||
|
} else {
|
||
|
_, _ = s.srcBuf.Read(srcPart)
|
||
|
}
|
||
|
|
||
|
// Crypt srcPart content
|
||
|
for len(srcPart) > 0 {
|
||
|
|
||
|
// Crypt current part
|
||
|
s.blockMode.CryptBlocks(dstPart, srcPart[:aes.BlockSize])
|
||
|
|
||
|
// Unpad when this is the last part and we are decrypting
|
||
|
if s.eof && s.cryptMode == decryptMode {
|
||
|
dstPart, err = pkcs5Unpad(dstPart, aes.BlockSize)
|
||
|
if err != nil {
|
||
|
s.err = err
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Send crypted data to dstBuf
|
||
|
if _, wErr := s.dstBuf.Write(dstPart); wErr != nil {
|
||
|
s.err = wErr
|
||
|
return
|
||
|
}
|
||
|
// Move to the next part
|
||
|
srcPart = srcPart[aes.BlockSize:]
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Unpad a set of bytes following PKCS5 algorithm
|
||
|
func pkcs5Unpad(buf []byte, blockSize int) ([]byte, error) {
|
||
|
len := len(buf)
|
||
|
if len == 0 {
|
||
|
return nil, errors.New("buffer is empty")
|
||
|
}
|
||
|
pad := int(buf[len-1])
|
||
|
if pad > len || pad > blockSize {
|
||
|
return nil, errors.New("invalid padding size")
|
||
|
}
|
||
|
return buf[:len-pad], nil
|
||
|
}
|
||
|
|
||
|
// Pad a set of bytes following PKCS5 algorithm
|
||
|
func pkcs5Pad(buf []byte, blockSize int) ([]byte, error) {
|
||
|
len := len(buf)
|
||
|
pad := blockSize - (len % blockSize)
|
||
|
padText := bytes.Repeat([]byte{byte(pad)}, pad)
|
||
|
return append(buf, padText...), nil
|
||
|
}
|