From 97aa83135272893a1760fadede6577e7ab6be959 Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Wed, 14 Apr 2021 17:29:56 +0200 Subject: [PATCH] add new pkg/fips for FIPS 140-2 (#12051) This commit introduces a new package `pkg/fips` that bundles functionality to handle and configure cryptographic protocols in case of FIPS 140. If it is compiled with `--tags=fips` it assumes that a FIPS 140-2 cryptographic module is used to implement all FIPS compliant cryptographic primitives - like AES, SHA-256, ... In "FIPS mode" it excludes all non-FIPS compliant cryptographic primitives from the protocol parameters. --- cmd/bucket-metadata.go | 5 ++-- cmd/crypto/key.go | 11 ++++---- cmd/crypto/sse.go | 5 ++-- cmd/disk-cache-backend.go | 3 +- cmd/encryption-v1.go | 4 ++- cmd/http/server.go | 35 ++++------------------- cmd/object-handlers.go | 5 ++-- cmd/server-main.go | 9 ++++-- pkg/fips/api.go | 59 +++++++++++++++++++++++++++++++++++++++ pkg/fips/fips.go | 42 ++++++++++++++++++++++++++++ pkg/fips/no_fips.go | 44 +++++++++++++++++++++++++++++ 11 files changed, 178 insertions(+), 44 deletions(-) create mode 100644 pkg/fips/api.go create mode 100644 pkg/fips/fips.go create mode 100644 pkg/fips/no_fips.go diff --git a/cmd/bucket-metadata.go b/cmd/bucket-metadata.go index 450677416..0ab50ee4d 100644 --- a/cmd/bucket-metadata.go +++ b/cmd/bucket-metadata.go @@ -38,6 +38,7 @@ import ( "github.com/minio/minio/pkg/bucket/replication" "github.com/minio/minio/pkg/bucket/versioning" "github.com/minio/minio/pkg/event" + "github.com/minio/minio/pkg/fips" "github.com/minio/minio/pkg/madmin" "github.com/minio/sio" ) @@ -421,7 +422,7 @@ func encryptBucketMetadata(bucket string, input []byte, kmsContext crypto.Contex objectKey := crypto.GenerateKey(key, rand.Reader) sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "") crypto.S3.CreateMetadata(metadata, GlobalKMS.DefaultKeyID(), encKey, sealedKey) - _, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20}) + _, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()}) if err != nil { return output, metabytes, err } @@ -451,6 +452,6 @@ func decryptBucketMetadata(input []byte, bucket string, meta map[string]string, } outbuf := bytes.NewBuffer(nil) - _, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20}) + _, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()}) return outbuf.Bytes(), err } diff --git a/cmd/crypto/key.go b/cmd/crypto/key.go index b4f9fafc5..1d40f751a 100644 --- a/cmd/crypto/key.go +++ b/cmd/crypto/key.go @@ -26,6 +26,7 @@ import ( "path" "github.com/minio/minio/cmd/logger" + "github.com/minio/minio/pkg/fips" "github.com/minio/sio" ) @@ -86,7 +87,7 @@ func (key ObjectKey) Seal(extKey, iv [32]byte, domain, bucket, object string) Se 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 { + if n, err := sio.Encrypt(&encryptedKey, bytes.NewReader(key[:]), sio.Config{Key: sealingKey[:], CipherSuites: fips.CipherSuitesDARE()}); n != 64 || err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to generate sealed key")) } sealedKey := SealedKey{ @@ -113,12 +114,12 @@ func (key *ObjectKey) Unseal(extKey [32]byte, sealedKey SealedKey, domain, bucke 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)} + unsealConfig = sio.Config{MinVersion: sio.Version20, Key: mac.Sum(nil), CipherSuites: fips.CipherSuitesDARE()} case InsecureSealAlgorithm: sha := sha256.New() sha.Write(extKey[:]) sha.Write(sealedKey.IV[:]) - unsealConfig = sio.Config{MinVersion: sio.Version10, Key: sha.Sum(nil)} + unsealConfig = sio.Config{MinVersion: sio.Version10, Key: sha.Sum(nil), CipherSuites: fips.CipherSuitesDARE()} } if out, err := sio.DecryptBuffer(key[:0], sealedKey.Key[:], unsealConfig); len(out) != 32 || err != nil { @@ -149,7 +150,7 @@ func (key ObjectKey) SealETag(etag []byte) []byte { 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 { + if _, err := sio.Encrypt(&buffer, bytes.NewReader(etag), sio.Config{Key: mac.Sum(nil), CipherSuites: fips.CipherSuitesDARE()}); err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to encrypt ETag using object key")) } return buffer.Bytes() @@ -165,5 +166,5 @@ func (key ObjectKey) UnsealETag(etag []byte) ([]byte, error) { } 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)}) + return sio.DecryptBuffer(make([]byte, 0, len(etag)), etag, sio.Config{Key: mac.Sum(nil), CipherSuites: fips.CipherSuitesDARE()}) } diff --git a/cmd/crypto/sse.go b/cmd/crypto/sse.go index 4462a4584..3e451daf7 100644 --- a/cmd/crypto/sse.go +++ b/cmd/crypto/sse.go @@ -22,6 +22,7 @@ import ( "net/http" "github.com/minio/minio/cmd/logger" + "github.com/minio/minio/pkg/fips" "github.com/minio/minio/pkg/ioutil" "github.com/minio/sio" ) @@ -93,7 +94,7 @@ func unsealObjectKey(clientKey [32]byte, metadata map[string]string, bucket, obj // EncryptSinglePart encrypts an io.Reader which must be the // the body of a single-part PUT request. func EncryptSinglePart(r io.Reader, key ObjectKey) io.Reader { - r, err := sio.EncryptReader(r, sio.Config{MinVersion: sio.Version20, Key: key[:]}) + r, err := sio.EncryptReader(r, sio.Config{MinVersion: sio.Version20, Key: key[:], CipherSuites: fips.CipherSuitesDARE()}) if err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to encrypt io.Reader using object key")) } @@ -115,7 +116,7 @@ func DecryptSinglePart(w io.Writer, offset, length int64, key ObjectKey) io.Writ const PayloadSize = 1 << 16 // DARE 2.0 w = ioutil.LimitedWriter(w, offset%PayloadSize, length) - decWriter, err := sio.DecryptWriter(w, sio.Config{Key: key[:]}) + decWriter, err := sio.DecryptWriter(w, sio.Config{Key: key[:], CipherSuites: fips.CipherSuitesDARE()}) if err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to decrypt io.Writer using object key")) } diff --git a/cmd/disk-cache-backend.go b/cmd/disk-cache-backend.go index ca8b37999..8d3bc238d 100644 --- a/cmd/disk-cache-backend.go +++ b/cmd/disk-cache-backend.go @@ -39,6 +39,7 @@ import ( xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/disk" + "github.com/minio/minio/pkg/fips" "github.com/minio/sio" ) @@ -661,7 +662,7 @@ func newCacheEncryptReader(content io.Reader, bucket, object string, metadata ma return nil, err } - reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20}) + reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()}) if err != nil { return nil, crypto.ErrInvalidCustomerKey } diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index d15c42f47..b75d01161 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -34,6 +34,7 @@ import ( "github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" + "github.com/minio/minio/pkg/fips" "github.com/minio/sio" ) @@ -198,7 +199,7 @@ func newEncryptReader(content io.Reader, key []byte, bucket, object string, meta return nil, crypto.ObjectKey{}, err } - reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20}) + reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()}) if err != nil { return nil, crypto.ObjectKey{}, crypto.ErrInvalidCustomerKey } @@ -333,6 +334,7 @@ func newDecryptReaderWithObjectKey(client io.Reader, objectEncryptionKey []byte, reader, err := sio.DecryptReader(client, sio.Config{ Key: objectEncryptionKey, SequenceNumber: seqNumber, + CipherSuites: fips.CipherSuitesDARE(), }) if err != nil { return nil, crypto.ErrInvalidCustomerKey diff --git a/cmd/http/server.go b/cmd/http/server.go index 42bcf664e..8a177762b 100644 --- a/cmd/http/server.go +++ b/cmd/http/server.go @@ -33,6 +33,7 @@ import ( "github.com/minio/minio/cmd/config/api" "github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/env" + "github.com/minio/minio/pkg/fips" ) const ( @@ -159,28 +160,6 @@ func (srv *Server) Shutdown() error { } } -// Secure Go implementations of modern TLS ciphers -// The following ciphers are excluded because: -// - RC4 ciphers: RC4 is broken -// - 3DES ciphers: Because of the 64 bit blocksize of DES (Sweet32) -// - CBC-SHA256 ciphers: No countermeasures against Lucky13 timing attack -// - CBC-SHA ciphers: Legacy ciphers (SHA-1) and non-constant time -// implementation of CBC. -// (CBC-SHA ciphers can be enabled again if required) -// - RSA key exchange ciphers: Disabled because of dangerous PKCS1-v1.5 RSA -// padding scheme. See Bleichenbacher attacks. -var secureCipherSuites = []uint16{ - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, -} - -// Go only provides constant-time implementations of Curve25519 and NIST P-256 curve. -var secureCurves = []tls.CurveID{tls.X25519, tls.CurveP256} - // NewServer - creates new HTTP server using given arguments. func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificateFunc) *Server { secureCiphers := env.Get(api.EnvAPISecureCiphers, config.EnableOn) == config.EnableOn @@ -188,17 +167,15 @@ func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificat var tlsConfig *tls.Config if getCert != nil { tlsConfig = &tls.Config{ - // TLS hardening PreferServerCipherSuites: true, MinVersion: tls.VersionTLS12, NextProtos: []string{"http/1.1", "h2"}, + GetCertificate: getCert, + } + if secureCiphers || fips.Enabled() { + tlsConfig.CipherSuites = fips.CipherSuitesTLS() + tlsConfig.CurvePreferences = fips.EllipticCurvesTLS() } - tlsConfig.GetCertificate = getCert - } - - if secureCiphers && tlsConfig != nil { - tlsConfig.CipherSuites = secureCipherSuites - tlsConfig.CurvePreferences = secureCurves } httpServer := &Server{ diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index e72131ca1..b3a45ca1a 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -50,6 +50,7 @@ import ( "github.com/minio/minio/pkg/bucket/replication" "github.com/minio/minio/pkg/etag" "github.com/minio/minio/pkg/event" + "github.com/minio/minio/pkg/fips" "github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/hash" iampolicy "github.com/minio/minio/pkg/iam/policy" @@ -2403,7 +2404,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt copy(objectEncryptionKey[:], key) partEncryptionKey := objectEncryptionKey.DerivePartKey(uint32(partID)) - encReader, err := sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey[:]}) + encReader, err := sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.CipherSuitesDARE()}) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return @@ -2667,7 +2668,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http // We add a buffer on bigger files to reduce the number of syscalls upstream. in = bufio.NewReaderSize(hashReader, encryptBufferSize) } - reader, err = sio.EncryptReader(in, sio.Config{Key: partEncryptionKey[:]}) + reader, err = sio.EncryptReader(in, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.CipherSuitesDARE()}) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return diff --git a/cmd/server-main.go b/cmd/server-main.go index 306bfb2ca..d808a5c87 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -40,6 +40,7 @@ import ( "github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/color" "github.com/minio/minio/pkg/env" + "github.com/minio/minio/pkg/fips" "github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/sync/errgroup" ) @@ -157,11 +158,15 @@ func serverHandleCmdArgs(ctx *cli.Context) { // allow transport to be HTTP/1.1 for proxying. globalProxyTransport = newCustomHTTPProxyTransport(&tls.Config{ - RootCAs: globalRootCAs, + RootCAs: globalRootCAs, + CipherSuites: fips.CipherSuitesTLS(), + CurvePreferences: fips.EllipticCurvesTLS(), }, rest.DefaultTimeout)() globalProxyEndpoints = GetProxyEndpoints(globalEndpoints) globalInternodeTransport = newInternodeHTTPTransport(&tls.Config{ - RootCAs: globalRootCAs, + RootCAs: globalRootCAs, + CipherSuites: fips.CipherSuitesTLS(), + CurvePreferences: fips.EllipticCurvesTLS(), }, rest.DefaultTimeout)() // On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back diff --git a/pkg/fips/api.go b/pkg/fips/api.go new file mode 100644 index 000000000..e132650dc --- /dev/null +++ b/pkg/fips/api.go @@ -0,0 +1,59 @@ +// MinIO Cloud Storage, (C) 2021 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 fips provides functionality to configure cryptographic +// implementations compliant with FIPS 140. +// +// FIPS 140 [1] is a US standard for data processing that specifies +// requirements for cryptographic modules. Software that is "FIPS 140 +// compliant" must use approved cryptographic primitives only and that +// are implemented by a FIPS 140 certified cryptographic module. +// +// So, FIPS 140 requires that a certified implementation of e.g. AES +// is used to implement more high-level cryptographic protocols. +// It does not require any specific security criteria for those +// high-level protocols. FIPS 140 focuses only on the implementation +// and usage of the most low-level cryptographic building blocks. +// +// [1]: https://en.wikipedia.org/wiki/FIPS_140 +package fips + +import "crypto/tls" + +// Enabled returns true if and only if FIPS 140-2 support +// is enabled. +// +// FIPS 140-2 requires that only specifc cryptographic +// primitives, like AES or SHA-256, are used and that +// those primitives are implemented by a FIPS 140-2 +// certified cryptographic module. +func Enabled() bool { return enabled } + +// CipherSuitesDARE returns the supported cipher suites +// for the DARE object encryption. +func CipherSuitesDARE() []byte { + return cipherSuitesDARE() +} + +// CipherSuitesTLS returns the supported cipher suites +// used by the TLS stack. +func CipherSuitesTLS() []uint16 { + return cipherSuitesTLS() +} + +// EllipticCurvesTLS returns the supported elliptic +// curves used by the TLS stack. +func EllipticCurvesTLS() []tls.CurveID { + return ellipticCurvesTLS() +} diff --git a/pkg/fips/fips.go b/pkg/fips/fips.go new file mode 100644 index 000000000..54b941bac --- /dev/null +++ b/pkg/fips/fips.go @@ -0,0 +1,42 @@ +// MinIO Cloud Storage, (C) 2021 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. + +//+build fips + +package fips + +import ( + "crypto/tls" + + "github.com/minio/sio" +) + +var enabled = true + +func cipherSuitesDARE() []byte { + return []byte{sio.AES_256_GCM} +} + +func cipherSuitesTLS() []uint16 { + return []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + } +} + +func ellipticCurvesTLS() []tls.Curve { + return []tls.CurveID{tls.CurveP256} +} diff --git a/pkg/fips/no_fips.go b/pkg/fips/no_fips.go new file mode 100644 index 000000000..2ffd7e493 --- /dev/null +++ b/pkg/fips/no_fips.go @@ -0,0 +1,44 @@ +// MinIO Cloud Storage, (C) 2021 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. + +//+build !fips + +package fips + +import ( + "crypto/tls" + + "github.com/minio/sio" +) + +var enabled = false + +func cipherSuitesDARE() []byte { + return []byte{sio.AES_256_GCM, sio.CHACHA20_POLY1305} +} + +func cipherSuitesTLS() []uint16 { + return []uint16{ + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + } +} + +func ellipticCurvesTLS() []tls.CurveID { + return []tls.CurveID{tls.X25519, tls.CurveP256} +}