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:
parent
163aa57e5c
commit
d306d7a983
19 changed files with 701 additions and 169 deletions
39
pkg/fips/fips.go
Normal file
39
pkg/fips/fips.go
Normal 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
15
pkg/fips/fips_test.go
Normal 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())
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
56
pkg/libcrypto/encrypt_test.go
Normal file
56
pkg/libcrypto/encrypt_test.go
Normal 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)
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue