diff --git a/cmd/bucket-metadata.go b/cmd/bucket-metadata.go index 7029fa6a2..2e97027b5 100644 --- a/cmd/bucket-metadata.go +++ b/cmd/bucket-metadata.go @@ -465,7 +465,7 @@ func encryptBucketMetadata(bucket string, input []byte, kmsContext kms.Context) objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader) sealedKey := objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "") crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey) - _, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()}) + _, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()}) if err != nil { return output, metabytes, err } @@ -495,6 +495,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, CipherSuites: fips.CipherSuitesDARE()}) + _, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()}) return outbuf.Bytes(), err } diff --git a/cmd/disk-cache-backend.go b/cmd/disk-cache-backend.go index b3a09e0e1..73ad09d46 100644 --- a/cmd/disk-cache-backend.go +++ b/cmd/disk-cache-backend.go @@ -803,7 +803,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, CipherSuites: fips.CipherSuitesDARE()}) + reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey, MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()}) if err != nil { return nil, crypto.ErrInvalidCustomerKey } @@ -1454,7 +1454,7 @@ func newCachePartEncryptReader(ctx context.Context, bucket, object string, partI return nil, err } - reader, err := sio.EncryptReader(content, sio.Config{Key: partEncryptionKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()}) + reader, err := sio.EncryptReader(content, sio.Config{Key: partEncryptionKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()}) if err != nil { return nil, crypto.ErrInvalidCustomerKey } diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 73bda297f..d48c7c7ed 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -406,7 +406,7 @@ func newEncryptReader(content io.Reader, kind crypto.Type, keyID string, key []b return nil, crypto.ObjectKey{}, err } - reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20, CipherSuites: fips.CipherSuitesDARE()}) + reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()}) if err != nil { return nil, crypto.ObjectKey{}, crypto.ErrInvalidCustomerKey } @@ -553,7 +553,7 @@ func newDecryptReaderWithObjectKey(client io.Reader, objectEncryptionKey []byte, reader, err := sio.DecryptReader(client, sio.Config{ Key: objectEncryptionKey, SequenceNumber: seqNumber, - CipherSuites: fips.CipherSuitesDARE(), + CipherSuites: fips.DARECiphers(), }) if err != nil { return nil, crypto.ErrInvalidCustomerKey diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 0d6ddaca2..d7d930e00 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -2622,7 +2622,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[:], CipherSuites: fips.CipherSuitesDARE()}) + encReader, err := sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.DARECiphers()}) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return @@ -2885,7 +2885,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[:], CipherSuites: fips.CipherSuitesDARE()}) + reader, err = sio.EncryptReader(in, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.DARECiphers()}) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) return diff --git a/cmd/server-main.go b/cmd/server-main.go index b4094119b..5be160cff 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -206,15 +206,15 @@ func serverHandleCmdArgs(ctx *cli.Context) { // allow transport to be HTTP/1.1 for proxying. globalProxyTransport = newCustomHTTPProxyTransport(&tls.Config{ RootCAs: globalRootCAs, - CipherSuites: fips.CipherSuitesTLS(), - CurvePreferences: fips.EllipticCurvesTLS(), + CipherSuites: fips.TLSCiphers(), + CurvePreferences: fips.TLSCurveIDs(), ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize), }, rest.DefaultTimeout)() globalProxyEndpoints = GetProxyEndpoints(globalEndpoints) globalInternodeTransport = newInternodeHTTPTransport(&tls.Config{ RootCAs: globalRootCAs, - CipherSuites: fips.CipherSuitesTLS(), - CurvePreferences: fips.EllipticCurvesTLS(), + CipherSuites: fips.TLSCiphers(), + CurvePreferences: fips.TLSCurveIDs(), ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize), }, rest.DefaultTimeout)() diff --git a/cmd/utils.go b/cmd/utils.go index 69ca691ee..8ef0ef0cf 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -1067,17 +1067,12 @@ func newTLSConfig(getCert certs.GetCertificateFunc) *tls.Config { tlsConfig.ClientAuth = tls.RequestClientCert } - secureCiphers := env.Get(api.EnvAPISecureCiphers, config.EnableOn) == config.EnableOn - if secureCiphers || fips.Enabled { - // Hardened ciphers - tlsConfig.CipherSuites = fips.CipherSuitesTLS() - tlsConfig.CurvePreferences = fips.EllipticCurvesTLS() + if secureCiphers := env.Get(api.EnvAPISecureCiphers, config.EnableOn) == config.EnableOn; secureCiphers { + tlsConfig.CipherSuites = fips.TLSCiphers() } else { - // Default ciphers while excluding those with security issues - for _, cipher := range tls.CipherSuites() { - tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, cipher.ID) - } + tlsConfig.CipherSuites = fips.TLSCiphersBackwardCompatible() } + tlsConfig.CurvePreferences = fips.TLSCurveIDs() return tlsConfig } diff --git a/internal/crypto/key.go b/internal/crypto/key.go index e781de150..c25664ba2 100644 --- a/internal/crypto/key.go +++ b/internal/crypto/key.go @@ -96,7 +96,7 @@ func (key ObjectKey) Seal(extKey []byte, iv [32]byte, domain, bucket, object str 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[:], CipherSuites: fips.CipherSuitesDARE()}); n != 64 || err != nil { + if n, err := sio.Encrypt(&encryptedKey, bytes.NewReader(key[:]), sio.Config{Key: sealingKey[:], CipherSuites: fips.DARECiphers()}); n != 64 || err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to generate sealed key")) } sealedKey := SealedKey{ @@ -121,12 +121,12 @@ func (key *ObjectKey) Unseal(extKey []byte, sealedKey SealedKey, domain, bucket, 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), CipherSuites: fips.CipherSuitesDARE()} + unsealConfig = sio.Config{MinVersion: sio.Version20, Key: mac.Sum(nil), CipherSuites: fips.DARECiphers()} case InsecureSealAlgorithm: sha := sha256.New() sha.Write(extKey) sha.Write(sealedKey.IV[:]) - unsealConfig = sio.Config{MinVersion: sio.Version10, Key: sha.Sum(nil), CipherSuites: fips.CipherSuitesDARE()} + unsealConfig = sio.Config{MinVersion: sio.Version10, Key: sha.Sum(nil), CipherSuites: fips.DARECiphers()} } if out, err := sio.DecryptBuffer(key[:0], sealedKey.Key[:], unsealConfig); len(out) != 32 || err != nil { @@ -157,7 +157,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), CipherSuites: fips.CipherSuitesDARE()}); err != nil { + if _, err := sio.Encrypt(&buffer, bytes.NewReader(etag), sio.Config{Key: mac.Sum(nil), CipherSuites: fips.DARECiphers()}); err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to encrypt ETag using object key")) } return buffer.Bytes() @@ -173,5 +173,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), CipherSuites: fips.CipherSuitesDARE()}) + return sio.DecryptBuffer(make([]byte, 0, len(etag)), etag, sio.Config{Key: mac.Sum(nil), CipherSuites: fips.DARECiphers()}) } diff --git a/internal/crypto/sse.go b/internal/crypto/sse.go index ec7f33ec2..35c2ef016 100644 --- a/internal/crypto/sse.go +++ b/internal/crypto/sse.go @@ -96,7 +96,7 @@ func unsealObjectKey(clientKey []byte, metadata map[string]string, bucket, objec // 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[:], CipherSuites: fips.CipherSuitesDARE()}) + r, err := sio.EncryptReader(r, sio.Config{MinVersion: sio.Version20, Key: key[:], CipherSuites: fips.DARECiphers()}) if err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to encrypt io.Reader using object key")) } @@ -118,7 +118,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[:], CipherSuites: fips.CipherSuitesDARE()}) + decWriter, err := sio.DecryptWriter(w, sio.Config{Key: key[:], CipherSuites: fips.DARECiphers()}) if err != nil { logger.CriticalIf(context.Background(), errors.New("Unable to decrypt io.Writer using object key")) } diff --git a/internal/etag/etag.go b/internal/etag/etag.go index 60caa1065..2e2d91f8d 100644 --- a/internal/etag/etag.go +++ b/internal/etag/etag.go @@ -340,7 +340,7 @@ func Decrypt(key []byte, etag ETag) (ETag, error) { plaintext := make([]byte, 0, 16) etag, err := sio.DecryptBuffer(plaintext, etag, sio.Config{ Key: decryptionKey, - CipherSuites: fips.CipherSuitesDARE(), + CipherSuites: fips.DARECiphers(), }) if err != nil { return nil, err diff --git a/internal/fips/api.go b/internal/fips/api.go index 0fe86ab05..39acbfa68 100644 --- a/internal/fips/api.go +++ b/internal/fips/api.go @@ -32,7 +32,11 @@ // [1]: https://en.wikipedia.org/wiki/FIPS_140 package fips -import "crypto/tls" +import ( + "crypto/tls" + + "github.com/minio/sio" +) // Enabled indicates whether cryptographic primitives, // like AES or SHA-256, are implemented using a FIPS 140 @@ -42,20 +46,101 @@ import "crypto/tls" // primitives must be used. const Enabled = enabled -// CipherSuitesDARE returns the supported cipher suites +// DARECiphers returns a list of supported cipher suites // for the DARE object encryption. -func CipherSuitesDARE() []byte { - return cipherSuitesDARE() +func DARECiphers() []byte { + if Enabled { + return []byte{sio.AES_256_GCM} + } + return []byte{sio.AES_256_GCM, sio.CHACHA20_POLY1305} } -// CipherSuitesTLS returns the supported cipher suites -// used by the TLS stack. -func CipherSuitesTLS() []uint16 { - return cipherSuitesTLS() +// TLSCiphers returns a list of supported TLS transport +// cipher suite IDs. +// +// The list contains only ciphers that use AES-GCM or +// (non-FIPS) CHACHA20-POLY1305 and ellitpic curve key +// exchange. +func TLSCiphers() []uint16 { + if Enabled { + return []uint16{ + tls.TLS_AES_128_GCM_SHA256, // TLS 1.3 + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // TLS 1.2 + 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, + } + } + return []uint16{ + tls.TLS_CHACHA20_POLY1305_SHA256, // TLS 1.3 + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // TLS 1.2 + 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, + } } -// EllipticCurvesTLS returns the supported elliptic -// curves used by the TLS stack. -func EllipticCurvesTLS() []tls.CurveID { - return ellipticCurvesTLS() +// TLSCiphersBackwardCompatible returns a list of supported +// TLS transport cipher suite IDs. +// +// In contrast to TLSCiphers, the list contains additional +// ciphers for backward compatibility. In particular, AES-CBC +// and non-ECDHE ciphers. +func TLSCiphersBackwardCompatible() []uint16 { + if Enabled { + return []uint16{ + tls.TLS_AES_128_GCM_SHA256, // TLS 1.3 + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // TLS 1.2 ECDHE GCM + 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, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // TLS 1.2 ECDHE CBC + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // TLS 1.2 non-ECDHE + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + } + } + return []uint16{ + tls.TLS_CHACHA20_POLY1305_SHA256, // TLS 1.3 + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, // TLS 1.2 ECDHE GCM / POLY1305 + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + 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, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // TLS 1.2 ECDHE CBC + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // TLS 1.2 non-ECDHE + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + } +} + +// TLSCurveIDs returns a list of supported elliptic curve IDs +// in preference order. +func TLSCurveIDs() []tls.CurveID { + // TODO(aead): Once MinIO switches to Go 1.18 + // enable CurveP384 and CurveP512. + // + // See: https://go.dev/doc/go1.18 Changes to crypto/elliptic + + if Enabled { + return []tls.CurveID{tls.CurveP256} + } + return []tls.CurveID{tls.X25519, tls.CurveP256} } diff --git a/internal/fips/fips.go b/internal/fips/fips.go index 526c92f1d..94b3ed00c 100644 --- a/internal/fips/fips.go +++ b/internal/fips/fips.go @@ -20,29 +20,4 @@ package fips -import ( - "crypto/tls" - - "github.com/minio/sio" -) - const enabled = true - -func cipherSuitesDARE() []byte { - return []byte{sio.AES_256_GCM} -} - -func cipherSuitesTLS() []uint16 { - return []uint16{ - tls.TLS_AES_128_GCM_SHA256, - tls.TLS_AES_256_GCM_SHA384, - 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.CurveP256} -} diff --git a/internal/fips/no_fips.go b/internal/fips/no_fips.go index 12b944b01..96cfd3aa8 100644 --- a/internal/fips/no_fips.go +++ b/internal/fips/no_fips.go @@ -20,32 +20,4 @@ package fips -import ( - "crypto/tls" - - "github.com/minio/sio" -) - const enabled = false - -func cipherSuitesDARE() []byte { - return []byte{sio.AES_256_GCM, sio.CHACHA20_POLY1305} -} - -func cipherSuitesTLS() []uint16 { - return []uint16{ - tls.TLS_CHACHA20_POLY1305_SHA256, - tls.TLS_AES_128_GCM_SHA256, - tls.TLS_AES_256_GCM_SHA384, - 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} -}