mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-01-30 16:49:29 +00:00
util: fscrypt requires keys of 32 bytes
It seems that fscrypt expects a key with exactly 32 bytes. In order to use a random length key from a KMS, either repeat the key until the length is reached, or trim the key when needed. See: https://github.com/google/fscrypt/tree/v0.3.4#using-a-raw-key-protector Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
parent
003e0b36a6
commit
d9d3858db1
@ -60,7 +60,8 @@ var policyV2Support = []util.KernelVersion{
|
|||||||
|
|
||||||
// error values
|
// error values
|
||||||
var (
|
var (
|
||||||
ErrBadAuth = errors.New("key authentication check failed")
|
ErrBadAuth = errors.New("key authentication check failed")
|
||||||
|
ErrEmptyPassphrase = errors.New("empty passphrase given")
|
||||||
)
|
)
|
||||||
|
|
||||||
func AppendEncyptedSubdirectory(dir string) string {
|
func AppendEncyptedSubdirectory(dir string) string {
|
||||||
@ -94,14 +95,35 @@ func getPassphrase(ctx context.Context, encryption util.VolumeEncryption, volID
|
|||||||
return passphrase, nil
|
return passphrase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resizePassphrase makes sure that the given passphrase will be of [size]
|
||||||
|
// bytes. In case the passphrase is shorter, it will be repeated as many times
|
||||||
|
// as needed. When a passphrase is (or becomes) longer than the requested
|
||||||
|
// [size], the passphrase in truncated.
|
||||||
|
func resizePassphrase(passphrase string, size int) (string, error) {
|
||||||
|
if passphrase == "" || size <= 0 {
|
||||||
|
return "", ErrEmptyPassphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(passphrase) < size {
|
||||||
|
passphrase += passphrase
|
||||||
|
}
|
||||||
|
if len(passphrase) > size {
|
||||||
|
passphrase = string([]byte(passphrase)[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
return passphrase, nil
|
||||||
|
}
|
||||||
|
|
||||||
// createKeyFuncFromVolumeEncryption returns an fscrypt key function returning
|
// createKeyFuncFromVolumeEncryption returns an fscrypt key function returning
|
||||||
// encryption keys from a VolumeEncryption struct.
|
// encryption keys from a VolumeEncryption struct.
|
||||||
func createKeyFuncFromVolumeEncryption(
|
func createKeyFuncFromVolumeEncryption(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
encryption util.VolumeEncryption,
|
encryption util.VolumeEncryption,
|
||||||
volID string,
|
volID string,
|
||||||
keySize int,
|
|
||||||
) (func(fscryptactions.ProtectorInfo, bool) (*fscryptcrypto.Key, error), error) {
|
) (func(fscryptactions.ProtectorInfo, bool) (*fscryptcrypto.Key, error), error) {
|
||||||
|
// keys must be 32 bytes, see https://github.com/google/fscrypt?tab=readme-ov-file#using-a-raw-key-protector
|
||||||
|
keySize := encryptionPassphraseSize / 2
|
||||||
|
|
||||||
keyFunc := func(info fscryptactions.ProtectorInfo, retry bool) (*fscryptcrypto.Key, error) {
|
keyFunc := func(info fscryptactions.ProtectorInfo, retry bool) (*fscryptcrypto.Key, error) {
|
||||||
if retry {
|
if retry {
|
||||||
return nil, ErrBadAuth
|
return nil, ErrBadAuth
|
||||||
@ -112,9 +134,11 @@ func createKeyFuncFromVolumeEncryption(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if keySize < 0 {
|
passphrase, err = resizePassphrase(passphrase, keySize)
|
||||||
keySize = len(passphrase)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := fscryptcrypto.NewBlankKey(keySize)
|
key, err := fscryptcrypto.NewBlankKey(keySize)
|
||||||
copy(key.Data(), passphrase)
|
copy(key.Data(), passphrase)
|
||||||
|
|
||||||
@ -171,7 +195,7 @@ func unlockExisting(
|
|||||||
errMsg := fmt.Sprintf("fscrypt: unlock with protector error: %v", err)
|
errMsg := fmt.Sprintf("fscrypt: unlock with protector error: %v", err)
|
||||||
log.ErrorLog(ctx, "%s, retry using a null padded passphrase", errMsg)
|
log.ErrorLog(ctx, "%s, retry using a null padded passphrase", errMsg)
|
||||||
|
|
||||||
keyFn, err = createKeyFuncFromVolumeEncryption(ctx, *volEncryption, volID, encryptionPassphraseSize/2)
|
keyFn, err = createKeyFuncFromVolumeEncryption(ctx, *volEncryption, volID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLog(ctx, "fscrypt: could not create key function: %v", err)
|
log.ErrorLog(ctx, "fscrypt: could not create key function: %v", err)
|
||||||
|
|
||||||
@ -376,7 +400,7 @@ func Unlock(
|
|||||||
stagingTargetPath string, volID string,
|
stagingTargetPath string, volID string,
|
||||||
) error {
|
) error {
|
||||||
// Fetches keys from KMS. Do this first to catch KMS errors before setting up anything.
|
// Fetches keys from KMS. Do this first to catch KMS errors before setting up anything.
|
||||||
keyFn, err := createKeyFuncFromVolumeEncryption(ctx, *volEncryption, volID, -1)
|
keyFn, err := createKeyFuncFromVolumeEncryption(ctx, *volEncryption, volID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLog(ctx, "fscrypt: could not create key function: %v", err)
|
log.ErrorLog(ctx, "fscrypt: could not create key function: %v", err)
|
||||||
|
|
||||||
|
88
internal/util/fscrypt/fscrypt_test.go
Normal file
88
internal/util/fscrypt/fscrypt_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 fscrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResizePassphrase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
passphrase string
|
||||||
|
size int
|
||||||
|
ret string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"matching passphrase size",
|
||||||
|
"secret",
|
||||||
|
6,
|
||||||
|
"secret",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"short passphrase",
|
||||||
|
"secret",
|
||||||
|
64,
|
||||||
|
"secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecr",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long passphrase",
|
||||||
|
"secret",
|
||||||
|
2,
|
||||||
|
"se",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty passphrase",
|
||||||
|
"",
|
||||||
|
16,
|
||||||
|
"",
|
||||||
|
ErrEmptyPassphrase,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zero length requested",
|
||||||
|
"secret",
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
ErrEmptyPassphrase,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"negative length requested",
|
||||||
|
"secret",
|
||||||
|
-32,
|
||||||
|
"",
|
||||||
|
ErrEmptyPassphrase,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ret, err := resizePassphrase(tt.passphrase, tt.size)
|
||||||
|
if ret != tt.ret {
|
||||||
|
t.Errorf("resizePassphrase() returned %q of %d bytes, expected %q of %d bytes", tt.ret, len(tt.ret), ret, len(ret))
|
||||||
|
}
|
||||||
|
if !errors.Is(err, tt.err) {
|
||||||
|
t.Errorf("resizePassphrase() returned %v as error, expected %v", err, tt.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user