diff options
| author | Eric Biggers <ebiggers@google.com> | 2021-12-20 11:14:08 -0600 |
|---|---|---|
| committer | Eric Biggers <ebiggers@google.com> | 2021-12-21 13:00:58 -0600 |
| commit | dce50d2a55525ec1309fd60a3c240d243d5f7145 (patch) | |
| tree | 2fbe4381eae0b234f82671a7d62998e063fa3c8b | |
| parent | 9a8ce15408edae0c92128fd36f50dafa81013266 (diff) | |
[BROKEN] pam_fscrypt: save unlocked protector keys in root user keyring
| -rw-r--r-- | actions/protector.go | 10 | ||||
| -rw-r--r-- | keyring/user_keyring.go | 75 | ||||
| -rw-r--r-- | pam_fscrypt/pam_fscrypt.go | 61 |
3 files changed, 114 insertions, 32 deletions
diff --git a/actions/protector.go b/actions/protector.go index 3278e63..7d51f79 100644 --- a/actions/protector.go +++ b/actions/protector.go @@ -260,6 +260,16 @@ func (protector *Protector) Unlock(keyFn KeyFunc) (err error) { return } +// RawKey returns the internal key of an unlocked protector. +func (protector *Protector) InternalKey() *crypto.Key { + return protector.key +} + +// UnlockFromRawKey unlocks the protector directly from the internal key. +func (protector *Protector) UnlockFromInternalKey(key *crypto.Key) { + protector.key = key +} + // Lock wipes a Protector's internal Key. It should always be called after using // an unlocked Protector. This is often done with a defer statement. There is // no effect if called multiple times. diff --git a/keyring/user_keyring.go b/keyring/user_keyring.go index 0ea4689..416872f 100644 --- a/keyring/user_keyring.go +++ b/keyring/user_keyring.go @@ -32,6 +32,7 @@ import ( "log" "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) @@ -181,6 +182,80 @@ func UserKeyringID(targetUser *user.User, checkSession bool) (int, error) { return targetKeyring, nil } +func protectorKeyDescription(user *user.User) string { + return fmt.Sprintf("fscrypt-protector-key.uid.%s", user.Uid) +} + +func SaveProtectorKey(key *crypto.Key, user *user.User) error { + runtime.LockOSThread() // ensure the thread keyring doesn't change + defer runtime.UnlockOSThread() + + keyringID, err := userKeyringIDLookup(0) + if err != nil { + return errors.Wrap(err, "could not find root user keyring") + } + + description := protectorKeyDescription(user) + _, err = unix.AddKey("user", description, key.Data(), keyringID) + if err != nil { + return err + } + log.Printf("saved protector key for %s", user.Username) + return nil +} + +func RestoreProtectorKey(user *user.User) (*crypto.Key, error) { + runtime.LockOSThread() // ensure the thread keyring doesn't change + defer runtime.UnlockOSThread() + + keyringID, err := userKeyringIDLookup(0) + if err != nil { + return nil, errors.Wrap(err, "could not find root user keyring") + } + + description := protectorKeyDescription(user) + keyID, err := unix.KeyctlSearch(keyringID, "user", description, 0) + if err != nil { + return nil, errors.Wrap(err, "could not find saved protector key") + } + + key, err := crypto.NewBlankKey(metadata.InternalKeyLen) + if err != nil { + return nil, err + } + ret, err := unix.KeyctlBuffer(unix.KEYCTL_READ, keyID, key.Data(), key.Len()) + if err != nil { + return nil, errors.Wrap(err, "could not read saved protector key") + } + if ret != metadata.InternalKeyLen { + return nil, errors.New("bad saved protector key") + } + log.Printf("loaded saved protector key for %s", user.Username) + return key, nil +} + +func DeleteSavedProtectorKey(user *user.User) error { + runtime.LockOSThread() // ensure the thread keyring doesn't change + defer runtime.UnlockOSThread() + + keyringID, err := userKeyringIDLookup(0) + if err != nil { + return nil + } + + description := protectorKeyDescription(user) + keyID, err := unix.KeyctlSearch(keyringID, "user", description, 0) + if err != nil { + return nil + } + + if _, err := unix.KeyctlInt(unix.KEYCTL_UNLINK, keyID, keyringID, 0, 0); err != nil { + return errors.Wrap(err, "could not delete saved protector key") + } + log.Printf("deleted saved protector key for %s", user.Username) + return nil +} + func userKeyringIDLookup(uid int) (keyringID int, err error) { // Our goals here are to: diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 2e31af9..7a3f25c 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -43,8 +43,6 @@ import ( const ( moduleName = "pam_fscrypt" - // authtokLabel tags the AUTHTOK in the PAM data. - authtokLabel = "fscrypt_authtok" // These flags are used to toggle behavior of the PAM module. debugFlag = "debug" @@ -76,18 +74,31 @@ func Authenticate(handle *pam.Handle, _ map[string]bool) error { defer handle.StopAsPamUser() // If this user doesn't have a login protector, no unlocking is needed. - if _, err := loginProtector(handle); err != nil { + protector, err := loginProtector(handle) + if err != nil { log.Printf("no protector, no need for AUTHTOK: %s", err) return nil } - log.Print("copying AUTHTOK for use in the session open") - authtok, err := handle.GetItem(pam.Authtok) - if err != nil { - return errors.Wrap(err, "could not get AUTHTOK") + log.Print("unlocking user's login protector") + keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { + if retry { + return nil, pam.ErrPassphrase + } + authtok, err := handle.GetItem(pam.Authtok) + if err != nil { + return nil, errors.Wrap(err, "could not get AUTHTOK") + } + return crypto.NewKeyFromCString(authtok) + } + if err := protector.Unlock(keyFn); err != nil { + return errors.Wrap(err, "could not unlock login protector") } - err = handle.SetSecret(authtokLabel, authtok) - return errors.Wrap(err, "could not set AUTHTOK data") + handle.StopAsPamUser() + if err := keyring.SaveProtectorKey(protector.InternalKey(), handle.PamUser); err != nil { + return errors.Wrap(err, "could not save protector key") + } + return nil } func beginProvisioningOp(handle *pam.Handle, policy *actions.Policy) error { @@ -129,13 +140,16 @@ func setupUserKeyringIfNeeded(handle *pam.Handle, policies []*actions.Policy) er // OpenSession provisions any policies protected with the login protector. func OpenSession(handle *pam.Handle, _ map[string]bool) error { - // We will always clear the AUTHTOK data - defer handle.ClearData(authtokLabel) + // We will always delete the saved protector key + defer keyring.DeleteSavedProtectorKey(handle.PamUser) // Increment the count as we add a session if _, err := AdjustCount(handle, +1); err != nil { return err } + protectorKey, protectorKeyErr := keyring.RestoreProtectorKey(handle.PamUser) + defer protectorKey.Wipe() + if err := handle.StartAsPamUser(); err != nil { return err } @@ -157,29 +171,12 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { return errors.Wrapf(err, "setting up user keyring") } - log.Printf("unlocking %d policies protected with AUTHTOK", len(policies)) - keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { - if retry { - // Login passphrase and login protector have diverged. - // We could prompt the user for the old passphrase and - // rewrap, but we currently don't. - return nil, pam.ErrPassphrase - } + log.Printf("unlocking %d policies protected by login protector", len(policies)) - authtok, err := handle.GetSecret(authtokLabel) - if err != nil { - // pam_sm_authenticate was not run before the session is - // opened. This can happen when a user does something - // like "sudo su <user>". We could prompt for the - // login passphrase here, but we currently don't. - return nil, errors.Wrap(err, "AUTHTOK data missing") - } - - return crypto.NewKeyFromCString(authtok) - } - if err := protector.Unlock(keyFn); err != nil { - return errors.Wrapf(err, "unlocking protector %s", protector.Descriptor()) + if protectorKeyErr != nil { + return protectorKeyErr } + protector.UnlockFromInternalKey(protectorKey) defer protector.Lock() // We don't stop provisioning polices on error, we try all of them. |