This commit is contained in:
Niels de Vos 2025-04-10 09:45:32 +00:00 committed by GitHub
commit 8fcf548e43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 135 additions and 7 deletions

View File

@ -62,7 +62,8 @@ var policyV2Support = []kernel.KernelVersion{
// error values
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 {
@ -96,14 +97,35 @@ func getPassphrase(ctx context.Context, encryption util.VolumeEncryption, volID
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
// encryption keys from a VolumeEncryption struct.
func createKeyFuncFromVolumeEncryption(
ctx context.Context,
encryption util.VolumeEncryption,
volID string,
keySize int,
) (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) {
if retry {
return nil, ErrBadAuth
@ -114,9 +136,11 @@ func createKeyFuncFromVolumeEncryption(
return nil, err
}
if keySize < 0 {
keySize = len(passphrase)
passphrase, err = resizePassphrase(passphrase, keySize)
if err != nil {
return nil, err
}
key, err := fscryptcrypto.NewBlankKey(keySize)
copy(key.Data(), passphrase)
@ -173,7 +197,7 @@ func unlockExisting(
errMsg := fmt.Sprintf("fscrypt: unlock with protector error: %v", err)
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 {
log.ErrorLog(ctx, "fscrypt: could not create key function: %v", err)
@ -219,12 +243,14 @@ func initializeAndUnlock(
}
protector, err := fscryptactions.CreateProtector(fscryptContext, protectorName, keyFn, owner)
if err != nil {
if err != nil && protector != nil {
log.ErrorLog(ctx, "fscrypt: protector name=%s create failed: %v. reverting.", protectorName, err)
if revertErr := protector.Revert(); revertErr != nil {
return revertErr
}
return err
} else if err != nil {
return err
}
@ -376,7 +402,7 @@ func Unlock(
stagingTargetPath string, volID string,
) error {
// 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 {
log.ErrorLog(ctx, "fscrypt: could not create key function: %v", err)

View File

@ -0,0 +1,102 @@
/*
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,
},
{
"half a passphrase",
"secret",
3,
"sec",
nil,
},
{
"a little too shot passphrase",
"secret",
7,
"secrets",
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)
}
})
}
}