This commit replaces the usage of github.com/minio/sha256-simd with crypto/sha256 of the standard library in all non-performance critical paths. This is necessary for FIPS 140-2 compliance which requires that all crypto. primitives are implemented by a FIPS-validated module. Go can use the Google FIPS module. The boringcrypto branch of the Go standard library uses the BoringSSL FIPS module to implement crypto. primitives like AES or SHA256. We only keep github.com/minio/sha256-simd when computing the content-SHA256 of an object. Therefore, this commit relies on a build tag `fips`. When MinIO is compiled without the `fips` flag it will use github.com/minio/sha256-simd. When MinIO is compiled with the fips flag (go build --tags "fips") then MinIO uses crypto/sha256 to compute the content-SHA256.
169 lines
5.7 KiB
Go
169 lines
5.7 KiB
Go
// MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"path"
|
|
|
|
"github.com/minio/minio/cmd/logger"
|
|
"github.com/minio/sio"
|
|
)
|
|
|
|
// ObjectKey is a 256 bit secret key used to encrypt the object.
|
|
// It must never be stored in plaintext.
|
|
type ObjectKey [32]byte
|
|
|
|
// GenerateKey generates a unique ObjectKey from a 256 bit external key
|
|
// and a source of randomness. If random is nil the default PRNG of the
|
|
// system (crypto/rand) is used.
|
|
func GenerateKey(extKey [32]byte, random io.Reader) (key ObjectKey) {
|
|
if random == nil {
|
|
random = rand.Reader
|
|
}
|
|
var nonce [32]byte
|
|
if _, err := io.ReadFull(random, nonce[:]); err != nil {
|
|
logger.CriticalIf(context.Background(), errOutOfEntropy)
|
|
}
|
|
sha := sha256.New()
|
|
sha.Write(extKey[:])
|
|
sha.Write(nonce[:])
|
|
sha.Sum(key[:0])
|
|
return key
|
|
}
|
|
|
|
// GenerateIV generates a new random 256 bit IV from the provided source
|
|
// of randomness. If random is nil the default PRNG of the system
|
|
// (crypto/rand) is used.
|
|
func GenerateIV(random io.Reader) (iv [32]byte) {
|
|
if random == nil {
|
|
random = rand.Reader
|
|
}
|
|
if _, err := io.ReadFull(random, iv[:]); err != nil {
|
|
logger.CriticalIf(context.Background(), errOutOfEntropy)
|
|
}
|
|
return iv
|
|
}
|
|
|
|
// SealedKey represents a sealed object key. It can be stored
|
|
// at an untrusted location.
|
|
type SealedKey struct {
|
|
Key [64]byte // The encrypted and authenticted object-key.
|
|
IV [32]byte // The random IV used to encrypt the object-key.
|
|
Algorithm string // The sealing algorithm used to encrypt the object key.
|
|
}
|
|
|
|
// Seal encrypts the ObjectKey using the 256 bit external key and IV. The sealed
|
|
// key is also cryptographically bound to the object's path (bucket/object) and the
|
|
// domain (SSE-C or SSE-S3).
|
|
func (key ObjectKey) Seal(extKey, iv [32]byte, domain, bucket, object string) SealedKey {
|
|
var (
|
|
sealingKey [32]byte
|
|
encryptedKey bytes.Buffer
|
|
)
|
|
mac := hmac.New(sha256.New, extKey[:])
|
|
mac.Write(iv[:])
|
|
mac.Write([]byte(domain))
|
|
mac.Write([]byte(SealAlgorithm))
|
|
mac.Write([]byte(path.Join(bucket, object))) // use path.Join for canonical 'bucket/object'
|
|
mac.Sum(sealingKey[:0])
|
|
if n, err := sio.Encrypt(&encryptedKey, bytes.NewReader(key[:]), sio.Config{Key: sealingKey[:]}); n != 64 || err != nil {
|
|
logger.CriticalIf(context.Background(), errors.New("Unable to generate sealed key"))
|
|
}
|
|
sealedKey := SealedKey{
|
|
IV: iv,
|
|
Algorithm: SealAlgorithm,
|
|
}
|
|
copy(sealedKey.Key[:], encryptedKey.Bytes())
|
|
return sealedKey
|
|
}
|
|
|
|
// Unseal decrypts a sealed key using the 256 bit external key. Since the sealed key
|
|
// may be cryptographically bound to the object's path the same bucket/object as during sealing
|
|
// must be provided. On success the ObjectKey contains the decrypted sealed key.
|
|
func (key *ObjectKey) Unseal(extKey [32]byte, sealedKey SealedKey, domain, bucket, object string) error {
|
|
var (
|
|
unsealConfig sio.Config
|
|
)
|
|
switch sealedKey.Algorithm {
|
|
default:
|
|
return Errorf("The sealing algorithm '%s' is not supported", sealedKey.Algorithm)
|
|
case SealAlgorithm:
|
|
mac := hmac.New(sha256.New, extKey[:])
|
|
mac.Write(sealedKey.IV[:])
|
|
mac.Write([]byte(domain))
|
|
mac.Write([]byte(SealAlgorithm))
|
|
mac.Write([]byte(path.Join(bucket, object))) // use path.Join for canonical 'bucket/object'
|
|
unsealConfig = sio.Config{MinVersion: sio.Version20, Key: mac.Sum(nil)}
|
|
case InsecureSealAlgorithm:
|
|
sha := sha256.New()
|
|
sha.Write(extKey[:])
|
|
sha.Write(sealedKey.IV[:])
|
|
unsealConfig = sio.Config{MinVersion: sio.Version10, Key: sha.Sum(nil)}
|
|
}
|
|
|
|
if out, err := sio.DecryptBuffer(key[:0], sealedKey.Key[:], unsealConfig); len(out) != 32 || err != nil {
|
|
return ErrSecretKeyMismatch
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DerivePartKey derives an unique 256 bit key from an ObjectKey and the part index.
|
|
func (key ObjectKey) DerivePartKey(id uint32) (partKey [32]byte) {
|
|
var bin [4]byte
|
|
binary.LittleEndian.PutUint32(bin[:], id)
|
|
|
|
mac := hmac.New(sha256.New, key[:])
|
|
mac.Write(bin[:])
|
|
mac.Sum(partKey[:0])
|
|
return partKey
|
|
}
|
|
|
|
// SealETag seals the etag using the object key.
|
|
// It does not encrypt empty ETags because such ETags indicate
|
|
// that the S3 client hasn't sent an ETag = MD5(object) and
|
|
// the backend can pick an ETag value.
|
|
func (key ObjectKey) SealETag(etag []byte) []byte {
|
|
if len(etag) == 0 { // don't encrypt empty ETag - only if client sent ETag = MD5(object)
|
|
return etag
|
|
}
|
|
var buffer bytes.Buffer
|
|
mac := hmac.New(sha256.New, key[:])
|
|
mac.Write([]byte("SSE-etag"))
|
|
if _, err := sio.Encrypt(&buffer, bytes.NewReader(etag), sio.Config{Key: mac.Sum(nil)}); err != nil {
|
|
logger.CriticalIf(context.Background(), errors.New("Unable to encrypt ETag using object key"))
|
|
}
|
|
return buffer.Bytes()
|
|
}
|
|
|
|
// UnsealETag unseals the etag using the provided object key.
|
|
// It does not try to decrypt the ETag if len(etag) == 16
|
|
// because such ETags indicate that the S3 client hasn't sent
|
|
// an ETag = MD5(object) and the backend has picked an ETag value.
|
|
func (key ObjectKey) UnsealETag(etag []byte) ([]byte, error) {
|
|
if !IsETagSealed(etag) {
|
|
return etag, nil
|
|
}
|
|
mac := hmac.New(sha256.New, key[:])
|
|
mac.Write([]byte("SSE-etag"))
|
|
return sio.DecryptBuffer(make([]byte, 0, len(etag)), etag, sio.Config{Key: mac.Sum(nil)})
|
|
}
|