Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

overlord/fdestate: keep FDE state up to date #14516

Open
wants to merge 4 commits into
base: fde-manager-features
Choose a base branch
from

Conversation

valentindavid
Copy link
Contributor

@valentindavid valentindavid commented Sep 17, 2024

Ensure() initializes the empty profiles, and reseal updates them.

This is on top of #14400

@valentindavid valentindavid added the FDE Manager Pull requests that target FDE manager branch label Sep 17, 2024
@github-actions github-actions bot added the Run Nested -auto- Label automatically added in case nested tests need to be executed label Sep 17, 2024
@valentindavid valentindavid changed the title overlord/fdestate: Keep FDE state up to date overlord/fdestate: keep FDE state up to date Sep 17, 2024
@valentindavid valentindavid force-pushed the valentindavid/sync-fde-state branch 12 times, most recently from 728ff63 to 14d22b2 Compare September 20, 2024 14:23
}
}

type KeyDigest struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a type that should be handled in secboot. And we just use json.RawMessage for it.

Comment on lines 61 to 70
if !locked {
m.state.Lock()
defer m.state.Unlock()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not elegant. But I am not sure how to handle it correctly. We do resealing sometimes locked, sometimes not.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we know where is it called with the state really already unlocked?

@valentindavid valentindavid marked this pull request as ready for review September 20, 2024 14:46
Comment on lines 54 to 56
m.state.Lock()
defer m.state.Unlock()
return ensureState(m.state)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bit unclear if this will really run before any reseal op? maybe it would be better to use StartUp for this?

secboot/secboot.go Outdated Show resolved Hide resolved
@valentindavid valentindavid added the Run nested The PR also runs tests inluded in nested suite label Oct 1, 2024
@valentindavid valentindavid reopened this Oct 1, 2024
@valentindavid valentindavid force-pushed the valentindavid/sync-fde-state branch 2 times, most recently from 425117e to 5d69be1 Compare October 2, 2024 11:35
Comment on lines 65 to 78
func (m *FDEManager) resealKeyForBootChains(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error {
doUpdate := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error {
if unlocker != nil {
m.state.Lock()
defer m.state.Unlock()
}
return updateParameters(m.state, role, containerRole, bootModes, models, tpmPCRProfile)
}
if unlocker != nil {
locker := unlocker()
defer locker()
}
return backend.ResealKeyForBootChains(doUpdate, method, rootdir, params, expectReseal)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is not covered by tests in this package, but still covered by tests from overlord/managers_test.go. I wonder if we should add tests in the current package. Or move them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally it should be tested in the package directly too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. It was easier than expected.

Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by comment

secboot/secboot_tpm.go Show resolved Hide resolved
type ResealKeysParams struct {
// The snap model parameters
ModelParams []*SealKeyModelParams
PCRProfile SerializedPCRProfile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be []byte ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to convert a json into base64 to put it in another json?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added marshalling/unmarshalling methods here.

@@ -291,3 +295,32 @@ func DeleteKeys(node string, matches map[string]bool) error {

return nil
}

func GetPrimaryKeyHash(devicePath string, alg crypto.Hash) ([]byte, []byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func GetPrimaryKeyHash(devicePath string, alg crypto.Hash) ([]byte, []byte, error) {
func GetPrimaryKeyHash(devicePath string, alg crypto.Hash) (salt []byte, digest []byte, err error) {

return nil, nil, err
}

h := hmac.New(func() hash.Hash { return alg.New() }, salt[:])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should work:

Suggested change
h := hmac.New(func() hash.Hash { return alg.New() }, salt[:])
h := hmac.New(alg.New, salt[:])

return false, err
}

h := hmac.New(func() hash.Hash { return alg.New() }, salt[:])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
h := hmac.New(func() hash.Hash { return alg.New() }, salt[:])
h := hmac.New(alg.New, salt[:])


// ErrMountPointNotFound is return by DMCryptUUIDFromMountPoint a path
// is not a mount point
type ErrMountPointNotFound struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to keep uses or errors.Is() simple, you could have:

var ErrMountPointNotFound = errors.New("cannot find mountpoint")

type errMountPointNotFound struct{
    Path string
}

func (e *errMountPointNotFound) Error() string {
	return fmt.Sprintf("cannot find mountpoint %q", e.Path)
}

func (e *errMountPointNotFound) Unwrap() { return ErrMountPintNotFound }

Comment on lines +188 to +192
var models []secboot.ModelForSealing
for _, m := range modelParams {
models = append(models, m.Model)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var models []secboot.ModelForSealing
for _, m := range modelParams {
models = append(models, m.Model)
}
func modelsFromModelParams() []secboot.ModelForSealing {
var models []secboot.ModelForSealing
for _, m := range modelParams {
models = append(models, m.Model)
}
return models
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand. Github is probably not showing me the right thing. Do you want me to factor the similar code from resealFallbackObjectKeys and resealRunObjectKeys?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, not a big thing though, but both functions are close to each other and maybe it would be a bit nicer

}

if errors.Is(dataErr, disks.ErrNoDmUUID) && errors.Is(saveErr, disks.ErrNoDmUUID) {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could use a comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return err
}
if !sameDigest {
return fmt.Errorf("Primary key for data and save partition are not the same")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("Primary key for data and save partition are not the same")
return fmt.Errorf("primary key for data and save partition are not the same")


// Note that Parameters will be updated on first update
s.KeyslotRoles = map[string]KeyslotRoleInfo{
"run+recover": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like this should be a const somewhere to rule out use of say recover+run

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that we need that value both in overlord/fdestate and overlord/fdestate/backend. But overlord/fdestate/backend can not import overlord/fdestate. So it has to be in the backend. Unless we create new package. @pedronis what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's leave TODOs for now, I wonder which other places will need these string constants

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

KeyFiles: keyFiles,
TPMPolicyAuthKeyFile: authKeyFile,
}
if err := secbootResealKeys(resealKeyParams); err != nil {
return fmt.Errorf("cannot reseal the encryption key: %v", err)
}

if err := updateState("run+recover", "all", []string{"run", "recover"}, models, pcrProfile); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps same thing about bare "all", what if we simply have type for it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave a TODO about maybe. We'll try to figure it out later when we have a better feeling of what's needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

@bboozzoo bboozzoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

// BootModes are the optional list of approved modes (run, recover, ...)
BootModes []string `json:"boot-modes,omitempty"`
// Tpm2PCRProfile is an optional serialized PCR profile
Tpm2PCRProfile secboot.SerializedPCRProfile `json:"tpm2-pcr-profile,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Tpm2PCRProfile secboot.SerializedPCRProfile `json:"tpm2-pcr-profile,omitempty"`
TPM2PCRProfile secboot.SerializedPCRProfile `json:"tpm2-pcr-profile,omitempty"`

// Tpm2PCRPolicyRevocationCounter is the handle for the TPM
// policy revocation counter. A value of 0 means it is not
// set.
Tpm2PCRPolicyRevocationCounter uint32 `json:"tpm2-pcr-policy-revocation-counter,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Tpm2PCRPolicyRevocationCounter uint32 `json:"tpm2-pcr-policy-revocation-counter,omitempty"`
TPM2PCRPolicyRevocationCounter uint32 `json:"tpm2-pcr-policy-revocation-counter,omitempty"`

type KeyslotRoleInfo struct {
// PrimaryKeyId is the ID for the primary key found in
// PrimaryKeys field of FdeState
PrimaryKeyId int `json:"primary-key-id"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PrimaryKeyId int `json:"primary-key-id"`
PrimaryKeyID int `json:"primary-key-id"`

&mockModel{},
}

fdestate.UpdateParameters(st, "run+recover", "container-role", []string{"run"}, models, secboot.SerializedPCRProfile(`"serialized-profile"`))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fdestate.UpdateParameters(st, "run+recover", "container-role", []string{"run"}, models, secboot.SerializedPCRProfile(`"serialized-profile"`))
fdestate.UpdateParameters(st, "run+recover", "container-role", []string{"run"}, models, secboot.SerializedPCRProfile(`{"something":"serialized-profile"}`))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why? It was valid json.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose just to make a bit more realistic in terms of the kind of object we expect here, "aaa" is valid JSON but is also valid many other things

fdestate.UpdateParameters(st, "run+recover", "container-role", []string{"run"}, models, secboot.SerializedPCRProfile(`"serialized-profile"`))

var fdeSt fdestate.FdeState
err := st.Get("fde", &fdeSt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and to make sure serialization for SerialziedPCRProfile is working correctly I think I'd add something like:

var raw map[string]any
c.Assert(t.Get("fde", &raw), Nil)
c.Check(raw, DeepEquals, map[string]any{
	...
	"tpm2-prc-profile": map[string]any {
		"something": "serialized-profile",
	}
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think that would work. The TPM2PCRProfile field is kept serialized.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I get it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have put a test in secboot because this is where we define that type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

@valentindavid valentindavid force-pushed the valentindavid/sync-fde-state branch 2 times, most recently from 284c647 to 00abb2d Compare October 7, 2024 09:59
StartUp() initializes the empty profiles, and reseal updates them.
Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, bunch of small comments

@@ -237,3 +238,23 @@ func RegisterDeviceMapperBackResolver(name string, f func(dmUUID, dmName []byte)
func unregisterDeviceMapperBackResolver(name string) {
delete(deviceMapperBackResolvers, name)
}

// ErrNoDmUUID is return by DMCryptUUIDFromMountPoint when the device
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: returned

@@ -237,3 +238,23 @@ func RegisterDeviceMapperBackResolver(name string, f func(dmUUID, dmName []byte)
func unregisterDeviceMapperBackResolver(name string) {
delete(deviceMapperBackResolvers, name)
}

// ErrNoDmUUID is return by DMCryptUUIDFromMountPoint when the device
// at the mount point is not a device mapper device
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing final .

var ErrNoDmUUID = errors.New("device has no DM_UUID")

// ErrMountPointNotFound is return by DMCryptUUIDFromMountPoint a path
// is not a mount point
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same issues

@@ -628,6 +625,16 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
return pcrProfile, nil
}

// BuildPCRProtectionProfile builds and serializes a PCR profile from
// a list of SealKeyModelParams
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing final .


roleInfo, hasRole := s.KeyslotRoles[role]
if !hasRole {
return fmt.Errorf("Cannot find role %s", role)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot find key role ?

case "sha256":
*h = hashAlg(crypto.SHA256)
default:
return fmt.Errorf("Unknown algorithm %s", s)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lowercase: unknown ...

case crypto.SHA256:
return json.Marshal("sha256")
default:
return nil, fmt.Errorf("Unknown algorithm %v", h)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lowercase: unknown ...

Comment on lines 65 to 78
func (m *FDEManager) resealKeyForBootChains(unlocker boot.Unlocker, method device.SealingMethod, rootdir string, params *boot.ResealKeyForBootChainsParams, expectReseal bool) error {
doUpdate := func(role string, containerRole string, bootModes []string, models []secboot.ModelForSealing, tpmPCRProfile []byte) error {
if unlocker != nil {
m.state.Lock()
defer m.state.Unlock()
}
return updateParameters(m.state, role, containerRole, bootModes, models, tpmPCRProfile)
}
if unlocker != nil {
locker := unlocker()
defer locker()
}
return backend.ResealKeyForBootChains(doUpdate, method, rootdir, params, expectReseal)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally it should be tested in the package directly too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
FDE Manager Pull requests that target FDE manager branch Run Nested -auto- Label automatically added in case nested tests need to be executed Run nested The PR also runs tests inluded in nested suite
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants