1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-07 14:55:27 +02:00

fix(encryption): replace encryption related methods for fips mode [be-11933] (#919)

Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
This commit is contained in:
Malcolm Lockyer 2025-08-04 17:04:03 +12:00 committed by GitHub
parent 163aa57e5c
commit d306d7a983
19 changed files with 701 additions and 169 deletions

39
pkg/fips/fips.go Normal file
View file

@ -0,0 +1,39 @@
package fips
import (
"crypto/fips140"
"sync"
"github.com/rs/zerolog/log"
)
var fipsMode, isInitialised bool
var once sync.Once
func InitFIPS(enabled bool) {
once.Do(func() {
isInitialised = true
fipsMode = enabled
if enabled && !fips140.Enabled() {
log.Fatal().Msg("If FIPS mode is enabled then the fips140 GODEBUG environment variable must be set")
}
})
}
func FIPSMode() bool {
if !isInitialised {
log.Fatal().Msg("Could not determine if FIPS mode is enabled because InitFIPS was never called")
}
return fipsMode
}
func CanTLSSkipVerify() bool {
if !isInitialised {
log.Fatal().Msg("Could not determine if FIPS mode is enabled because InitFIPS was never called")
}
return !fipsMode
}

15
pkg/fips/fips_test.go Normal file
View file

@ -0,0 +1,15 @@
package fips
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestInitFIPS(t *testing.T) {
InitFIPS(false)
require.False(t, FIPSMode())
require.True(t, CanTLSSkipVerify())
}

View file

@ -4,6 +4,8 @@ import (
"crypto/aes"
"crypto/cipher"
"errors"
"github.com/portainer/portainer/pkg/fips"
)
// Decrypt decrypts data using 256-bit AES-GCM. This both hides the content of
@ -11,14 +13,25 @@ import (
// form nonce|ciphertext|tag where '|' indicates concatenation.
// Creates a 32bit hash of the key before decrypting the data.
func Decrypt(data []byte, key []byte) ([]byte, error) {
hashKey := Hash32Bit(key)
return decrypt(data, key, fips.FIPSMode())
}
func decrypt(data []byte, key []byte, fips bool) ([]byte, error) {
var hashKey []byte
if fips {
// sha256 hash 32 bytes
hashKey = HashFromBytes(key)
} else {
// 16 byte hash, hex encoded is 32 bytes
hashKey = InsecureHash32Bytes(key)
}
block, err := aes.NewCipher(hashKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
gcm, err := cipher.NewGCMWithRandomNonce(block)
if err != nil {
return nil, err
}
@ -28,8 +41,8 @@ func Decrypt(data []byte, key []byte) ([]byte, error) {
}
return gcm.Open(nil,
data[:gcm.NonceSize()],
data[gcm.NonceSize():],
nil,
data,
nil,
)
}

View file

@ -3,8 +3,8 @@ package libcrypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"io"
"github.com/portainer/portainer/pkg/fips"
)
// Encrypt encrypts data using 256-bit AES-GCM. This both hides the content of
@ -12,23 +12,26 @@ import (
// form nonce|ciphertext|tag where '|' indicates concatenation.
// Creates a 32bit hash of the key before encrypting the data.
func Encrypt(data, key []byte) ([]byte, error) {
hashKey := Hash32Bit(key)
return encrypt(data, key, fips.FIPSMode())
}
func encrypt(data, key []byte, fips bool) ([]byte, error) {
var hashKey []byte
if fips {
hashKey = HashFromBytes(key)
} else {
hashKey = InsecureHash32Bytes(key)
}
block, err := aes.NewCipher(hashKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
gcm, err := cipher.NewGCMWithRandomNonce(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, data, nil), nil
return gcm.Seal(nil, nil, data, nil), nil
}

View file

@ -0,0 +1,56 @@
package libcrypto
import (
"crypto/rand"
"io"
"testing"
"github.com/portainer/portainer/pkg/fips"
"github.com/stretchr/testify/require"
)
func init() {
fips.InitFIPS(false)
}
func TestEncryptDecrypt(t *testing.T) {
dataFn := func() []byte {
data := make([]byte, 1024)
_, err := io.ReadFull(rand.Reader, data)
require.NoError(t, err)
return data
}
fn := func(t *testing.T, fips bool) {
data := dataFn()
encrypted, err := encrypt(data, []byte("test"), fips)
require.NoError(t, err)
decrypted, err := decrypt(encrypted, []byte("test"), fips)
require.NoError(t, err)
require.Equal(t, data, decrypted)
}
t.Run("fips", func(t *testing.T) {
fn(t, true)
})
t.Run("non-fips", func(t *testing.T) {
fn(t, false)
})
t.Run("system_fips_mode", func(t *testing.T) {
data := dataFn()
encrypted, err := Encrypt(data, []byte("test"))
require.NoError(t, err)
decrypted, err := Decrypt(encrypted, []byte("test"))
require.NoError(t, err)
require.Equal(t, data, decrypted)
})
}

View file

@ -2,18 +2,26 @@ package libcrypto
import (
"crypto/md5"
"crypto/sha256"
"encoding/hex"
)
// HashFromBytes returns the hash of the specified data
func HashFromBytes(data []byte) []byte {
// InsecureHashFromBytes returns the 16 byte md5 hash of the specified data
func InsecureHashFromBytes(data []byte) []byte {
digest := md5.New()
digest.Write(data)
return digest.Sum(nil)
}
// Hash32Bit returns a hexadecimal encoded hash
func Hash32Bit(data []byte) []byte {
hash := HashFromBytes(data)
// InsecureHash32Bytes returns a hexadecimal encoded hash to make a 16 byte md5 hash into 32 bytes
func InsecureHash32Bytes(data []byte) []byte {
hash := InsecureHashFromBytes(data)
return []byte(hex.EncodeToString(hash))
}
// HashFromBytes returns the 32 byte sha256 hash of the specified data
func HashFromBytes(data []byte) []byte {
hash := sha256.New()
hash.Write(data)
return hash.Sum(nil)
}