From d117249a29af31a51ae56f64943635cbc0104cea Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 19 Jul 2017 15:41:23 -0700 Subject: pam_fscrypt: The actual PAM module and config --- pam_fscrypt/pam_fscrypt.go | 367 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 pam_fscrypt/pam_fscrypt.go (limited to 'pam_fscrypt/pam_fscrypt.go') diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go new file mode 100644 index 0000000..09e9664 --- /dev/null +++ b/pam_fscrypt/pam_fscrypt.go @@ -0,0 +1,367 @@ +/* + * pam_fscrypt.go - Checks the validity of a login token key against PAM. + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@google.com) + * + * 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 main + +/* +#cgo LDFLAGS: -lpam -fPIC + +#include +#include + +#include +*/ +import "C" +import ( + "fmt" + "io" + "io/ioutil" + "log" + "log/syslog" + "os" + "unsafe" + + "github.com/pkg/errors" + + "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/crypto" + "github.com/google/fscrypt/filesystem" + "github.com/google/fscrypt/metadata" + "github.com/google/fscrypt/pam" + "github.com/google/fscrypt/util" +) + +const ( + // These labels are used to tag items in the PAM data. + authtokLabel = "fscrypt_authtok" + descriptorLabel = "fscrypt_descriptor" + provisionedKeysLabel = "fscrypt_provisioned_keys" + moduleName = "pam_fscrypt" +) + +// parseArgs takes a list of C arguments into a PAM function and returns a map +// where a key has a value of true if it appears in the argument list. +func parseArgs(argc C.int, argv **C.char) map[string]bool { + args := make(map[string]bool) + for _, cString := range util.PointerSlice(unsafe.Pointer(argv))[:argc] { + args[C.GoString((*C.char)(cString))] = true + } + return args +} + +// setupLogging directs turns off standard logging (or redirects it to debug +// syslog if the "debug" argument is passed) and returns a writer to the error +// syslog. +func setupLogging(args map[string]bool) io.Writer { + log.SetFlags(0) // Syslog already includes time data itself + log.SetOutput(ioutil.Discard) + if args["debug"] { + debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) + if err == nil { + log.SetOutput(debugWriter) + } + } + + errorWriter, err := syslog.New(syslog.LOG_ERR, moduleName) + if err != nil { + return ioutil.Discard + } + return errorWriter +} + +// loginProtector returns the login protector corresponding to the PAM_USER if +// one exists. This protector descriptor (if found) will be cached in the pam +// data, under descriptorLabel. +func loginProtector(handle *pam.Handle) (*actions.Protector, error) { + ctx, err := actions.NewContextFromMountpoint("/") + if err != nil { + return nil, err + } + + // Retrieve the cached value if one exists. + if descriptor, err := handle.GetString(descriptorLabel); err == nil { + log.Printf("using cached descriptor %q", descriptor) + return actions.GetProtector(ctx, descriptor) + } + + // Find the user's PAM protector. + pamUID, err := handle.GetUID() + if err != nil { + return nil, err + } + options, err := ctx.ProtectorOptions() + if err != nil { + return nil, err + } + for _, option := range options { + if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != pamUID { + continue + } + + log.Printf("caching descriptor %q", option.Descriptor()) + if err = handle.SetString(descriptorLabel, option.Descriptor()); err != nil { + log.Printf("could not set descriptor data: %s", err) + // We can still get the protector, so no error. + } + + return actions.GetProtectorFromOption(ctx, option) + } + return nil, fmt.Errorf("no PAM protector on %q", ctx.Mount.Path) +} + +// pam_sm_authenticate copies the AUTHTOK (if necessary) into the PAM data so it +// can be used in pam_sm_open_session. +//export pam_sm_authenticate +func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + errWriter := setupLogging(parseArgs(argc, argv)) + + // If this user doesn't have a login protector, no unlocking is needed. + if _, err := loginProtector(handle); err != nil { + log.Printf("no need to copy AUTHTOK: %s", err) + return C.PAM_SUCCESS + } + + log.Print("copying AUTHTOK in pam_sm_authenticate()") + authtok, err := handle.GetItem(pam.Authtok) + if err != nil { + fmt.Fprintf(errWriter, "could not get AUTHTOK: %s", err) + return C.PAM_SERVICE_ERR + } + if err = handle.SetSecret(authtokLabel, authtok); err != nil { + fmt.Fprintf(errWriter, "could not set AUTHTOK data: %s", err) + return C.PAM_SERVICE_ERR + } + return C.PAM_SUCCESS +} + +// pam_sm_stecred needed because we use pam_sm_authenticate. +//export pam_sm_setcred +func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return C.PAM_SUCCESS +} + +// policiesToProvision searches all the mountpoints for any unprovisioned +// policies protected with the specified protector. An error during this search +// does not halt the search, instead the errors are written to errWriter. +func policiesToProvision(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { + mounts, err := filesystem.AllFilesystems() + if err != nil { + fmt.Fprint(errWriter, err) + return nil + } + + var policies []*actions.Policy + for _, mount := range mounts { + // Skip mountpoints that do not use the protector. + if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil { + continue + } + policyDescriptors, err := mount.ListPolicies() + if err != nil { + fmt.Fprintf(errWriter, "listing policies: %s", err) + continue + } + + ctx := &actions.Context{Config: protector.Context.Config, Mount: mount} + for _, policyDescriptor := range policyDescriptors { + policy, err := actions.GetPolicy(ctx, policyDescriptor) + if err != nil { + fmt.Fprintf(errWriter, "reading policy: %s", err) + continue + } + + if policy.UsesProtector(protector) && !policy.IsProvisioned() { + policies = append(policies, policy) + } + } + } + return policies +} + +// pam_sm_open_session provisions policies protected with the login protector. +//export pam_sm_open_session +func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + errWriter := setupLogging(parseArgs(argc, argv)) + + protector, err := loginProtector(handle) + if err != nil { + log.Printf("no directories to unlock: %s", err) + return C.PAM_SUCCESS + } + + 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 + } + + 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 ". We could prompt for the + // login passphrase here, but we currently don't. + return nil, errors.Wrap(err, "AUTHTOK data missing") + } + defer handle.ClearData(authtokLabel) + return crypto.NewKeyFromCString(authtok) + } + + log.Print("searching for policies to provision in pam_sm_open_session()") + policies := policiesToProvision(protector, errWriter) + + if len(policies) == 0 { + log.Print("no policies to provision") + return C.PAM_SUCCESS + } + + if err := protector.Unlock(keyFn); err != nil { + fmt.Fprintf(errWriter, "unlocking protector %s: %s", protector.Descriptor(), err) + return C.PAM_SERVICE_ERR + } + defer protector.Lock() + + var provisionedKeys []string + for _, policy := range policies { + if err := policy.UnlockWithProtector(protector); err != nil { + fmt.Fprintf(errWriter, "unlocking policy %s: %s", policy.Descriptor(), err) + continue + } + defer policy.Lock() + + if err := policy.Provision(); err != nil { + fmt.Fprintf(errWriter, "provisioning policy %s: %s", policy.Descriptor(), err) + continue + } + + log.Printf("policy %s provisioned", policy.Descriptor()) + provisionedKeys = append(provisionedKeys, policy.Description()) + } + + if len(provisionedKeys) == 0 { + fmt.Fprint(errWriter, "could not provision any policies") + return C.PAM_SERVICE_ERR + } + + if err := handle.SetSlice(provisionedKeysLabel, provisionedKeys); err != nil { + fmt.Fprintf(errWriter, "setting key list data: %s", err) + return C.PAM_SERVICE_ERR + } + return C.PAM_SUCCESS +} + +// pam_sm_close_session deprovisions all keys provisioned at the start of the +// session. It also clears the cache so these changes take effect. +//export pam_sm_close_session +func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + args := parseArgs(argc, argv) + errWriter := setupLogging(args) + + provisionedKeys, err := handle.GetSlice(provisionedKeysLabel) + if err != nil { + log.Printf("no directories to lock: %s", err) + return C.PAM_SUCCESS + } + + log.Print("locking directories in pam_sm_close_session()") + for _, provisionedKey := range provisionedKeys { + if err := crypto.RemovePolicyKey(provisionedKey); err != nil { + fmt.Fprintf(errWriter, "can't remove %s: %s", provisionedKey, err) + } + } + + if args["drop_caches"] { + log.Print("dropping page caches") + // See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt + f, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0) + if err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + defer f.Close() + // "3" clears slab objects and the page cache + if _, err := f.WriteString("3"); err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + } + + return C.PAM_SUCCESS +} + +// pam_sm_chauthtok rewraps the login protector when the passphrase changes. +//export pam_sm_chauthtok +func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + handle := pam.NewHandle(pamh) + errWriter := setupLogging(parseArgs(argc, argv)) + + // Only do rewrapping if we have both AUTHTOKs and a login protector. + if pam.Flag(flags)&pam.PrelimCheck != 0 { + log.Print("no preliminary checks need to run") + return C.PAM_SUCCESS + } + protector, err := loginProtector(handle) + if err != nil { + log.Printf("no protector to rewrap: %s", err) + return C.PAM_SUCCESS + } + + oldKeyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { + if retry { + // If the OLDAUTHTOK disagrees with the login protector, + // we do nothing, as the protector will (probably) still + // disagree after the login passphrase changes. + return nil, pam.ErrPassphrase + } + authtok, err := handle.GetItem(pam.Oldauthtok) + if err != nil { + return nil, errors.Wrap(err, "could not get OLDAUTHTOK") + } + return crypto.NewKeyFromCString(authtok) + } + + newKeyFn := func(_ actions.ProtectorInfo, _ bool) (*crypto.Key, error) { + authtok, err := handle.GetItem(pam.Authtok) + if err != nil { + return nil, errors.Wrap(err, "could not get AUTHTOK") + } + return crypto.NewKeyFromCString(authtok) + } + + log.Print("rewrapping protector in pam_sm_chauthtok()") + if err = protector.Unlock(oldKeyFn); err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + defer protector.Lock() + if err = protector.Rewrap(newKeyFn); err != nil { + fmt.Fprint(errWriter, err) + return C.PAM_SERVICE_ERR + } + + return C.PAM_SUCCESS +} + +// main() is needed to make a shared library compile +func main() {} -- cgit v1.2.3 From ca70b81fddb340ed5212741a773c3a4a0c4ea3e2 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Tue, 22 Aug 2017 14:15:58 -0700 Subject: pam_fscrypt: Updated module to use new APIs --- pam_fscrypt/pam_fscrypt.go | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'pam_fscrypt/pam_fscrypt.go') diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 09e9664..ad7cfdc 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -34,9 +34,10 @@ import ( "io/ioutil" "log" "log/syslog" - "os" "unsafe" + "golang.org/x/sys/unix" + "github.com/pkg/errors" "github.com/google/fscrypt/actions" @@ -44,6 +45,7 @@ import ( "github.com/google/fscrypt/filesystem" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/pam" + "github.com/google/fscrypt/security" "github.com/google/fscrypt/util" ) @@ -101,7 +103,7 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { } // Find the user's PAM protector. - pamUID, err := handle.GetUID() + uid := int64(unix.Geteuid()) if err != nil { return nil, err } @@ -110,7 +112,7 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { return nil, err } for _, option := range options { - if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != pamUID { + if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != uid { continue } @@ -286,22 +288,13 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) log.Print("locking directories in pam_sm_close_session()") for _, provisionedKey := range provisionedKeys { - if err := crypto.RemovePolicyKey(provisionedKey); err != nil { + if err := security.RemoveKey(provisionedKey); err != nil { fmt.Fprintf(errWriter, "can't remove %s: %s", provisionedKey, err) } } if args["drop_caches"] { - log.Print("dropping page caches") - // See: https://www.kernel.org/doc/Documentation/sysctl/vm.txt - f, err := os.OpenFile("/proc/sys/vm/drop_caches", os.O_WRONLY|os.O_SYNC, 0) - if err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR - } - defer f.Close() - // "3" clears slab objects and the page cache - if _, err := f.WriteString("3"); err != nil { + if err = security.DropInodeCache(); err != nil { fmt.Fprint(errWriter, err) return C.PAM_SERVICE_ERR } -- cgit v1.2.3 From ef5cc07774674c66b5dbeb7c655a26ac6371e378 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Tue, 22 Aug 2017 15:41:18 -0700 Subject: pam_fscrypt: lock all PAM policies w/ flag --- pam_fscrypt/pam_fscrypt.go | 84 ++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 47 deletions(-) (limited to 'pam_fscrypt/pam_fscrypt.go') diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index ad7cfdc..84b848e 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -50,11 +50,14 @@ import ( ) const ( + moduleName = "pam_fscrypt" // These labels are used to tag items in the PAM data. - authtokLabel = "fscrypt_authtok" - descriptorLabel = "fscrypt_descriptor" - provisionedKeysLabel = "fscrypt_provisioned_keys" - moduleName = "pam_fscrypt" + authtokLabel = "fscrypt_authtok" + descriptorLabel = "fscrypt_descriptor" + // These flags are used to toggle behavior of the PAM module. + debugFlag = "debug" + lockFlag = "lock_policies" + cacheFlag = "drop_caches" ) // parseArgs takes a list of C arguments into a PAM function and returns a map @@ -73,7 +76,7 @@ func parseArgs(argc C.int, argv **C.char) map[string]bool { func setupLogging(args map[string]bool) io.Writer { log.SetFlags(0) // Syslog already includes time data itself log.SetOutput(ioutil.Discard) - if args["debug"] { + if args[debugFlag] { debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) if err == nil { log.SetOutput(debugWriter) @@ -96,12 +99,6 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { return nil, err } - // Retrieve the cached value if one exists. - if descriptor, err := handle.GetString(descriptorLabel); err == nil { - log.Printf("using cached descriptor %q", descriptor) - return actions.GetProtector(ctx, descriptor) - } - // Find the user's PAM protector. uid := int64(unix.Geteuid()) if err != nil { @@ -112,17 +109,9 @@ func loginProtector(handle *pam.Handle) (*actions.Protector, error) { return nil, err } for _, option := range options { - if option.Source() != metadata.SourceType_pam_passphrase || option.UID() != uid { - continue + if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { + return actions.GetProtectorFromOption(ctx, option) } - - log.Printf("caching descriptor %q", option.Descriptor()) - if err = handle.SetString(descriptorLabel, option.Descriptor()); err != nil { - log.Printf("could not set descriptor data: %s", err) - // We can still get the protector, so no error. - } - - return actions.GetProtectorFromOption(ctx, option) } return nil, fmt.Errorf("no PAM protector on %q", ctx.Mount.Path) } @@ -159,10 +148,10 @@ func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int return C.PAM_SUCCESS } -// policiesToProvision searches all the mountpoints for any unprovisioned -// policies protected with the specified protector. An error during this search -// does not halt the search, instead the errors are written to errWriter. -func policiesToProvision(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { +// policiesUsingProtector searches all the mountpoints for any policies +// protected with the specified protector. An error during this search does not +// halt the search, instead the errors are written to errWriter. +func policiesUsingProtector(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { mounts, err := filesystem.AllFilesystems() if err != nil { fmt.Fprint(errWriter, err) @@ -189,7 +178,7 @@ func policiesToProvision(protector *actions.Protector, errWriter io.Writer) []*a continue } - if policy.UsesProtector(protector) && !policy.IsProvisioned() { + if policy.UsesProtector(protector) { policies = append(policies, policy) } } @@ -205,7 +194,7 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) protector, err := loginProtector(handle) if err != nil { - log.Printf("no directories to unlock: %s", err) + log.Printf("no pam protector for this user: %s", err) return C.PAM_SUCCESS } @@ -229,11 +218,10 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) return crypto.NewKeyFromCString(authtok) } - log.Print("searching for policies to provision in pam_sm_open_session()") - policies := policiesToProvision(protector, errWriter) - + log.Print("searching for policies to unlock in pam_sm_open_session()") + policies := policiesUsingProtector(protector, errWriter) if len(policies) == 0 { - log.Print("no policies to provision") + log.Print("no policies to unlock") return C.PAM_SUCCESS } @@ -243,8 +231,11 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) } defer protector.Lock() - var provisionedKeys []string for _, policy := range policies { + if policy.IsProvisioned() { + log.Printf("policy %s already provisioned", policy.Descriptor()) + continue + } if err := policy.UnlockWithProtector(protector); err != nil { fmt.Fprintf(errWriter, "unlocking policy %s: %s", policy.Descriptor(), err) continue @@ -257,18 +248,8 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) } log.Printf("policy %s provisioned", policy.Descriptor()) - provisionedKeys = append(provisionedKeys, policy.Description()) } - if len(provisionedKeys) == 0 { - fmt.Fprint(errWriter, "could not provision any policies") - return C.PAM_SERVICE_ERR - } - - if err := handle.SetSlice(provisionedKeysLabel, provisionedKeys); err != nil { - fmt.Fprintf(errWriter, "setting key list data: %s", err) - return C.PAM_SERVICE_ERR - } return C.PAM_SUCCESS } @@ -280,10 +261,19 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) args := parseArgs(argc, argv) errWriter := setupLogging(args) - provisionedKeys, err := handle.GetSlice(provisionedKeysLabel) - if err != nil { - log.Printf("no directories to lock: %s", err) - return C.PAM_SUCCESS + if args[lockFlag] { + protector, err := loginProtector(handle) + if err != nil { + log.Printf("no pam protector for this user: %s", err) + return C.PAM_SUCCESS + } + + policies := policiesUsingProtector(protector, errWriter) + + if len(policies) == 0 { + log.Print("no policies to lock") + return C.PAM_SUCCESS + } } log.Print("locking directories in pam_sm_close_session()") @@ -293,7 +283,7 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) } } - if args["drop_caches"] { + if args[cacheFlag] { if err = security.DropInodeCache(); err != nil { fmt.Fprint(errWriter, err) return C.PAM_SERVICE_ERR -- cgit v1.2.3 From d617d7725ce8b91df2152d6539da10c401c59325 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 23 Aug 2017 18:51:23 -0700 Subject: pam_fscrypt: Session accounting completed --- pam_fscrypt/pam_fscrypt.go | 322 ++++++++++++++++++--------------------------- 1 file changed, 129 insertions(+), 193 deletions(-) (limited to 'pam_fscrypt/pam_fscrypt.go') diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 84b848e..2eecd3a 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -29,175 +29,77 @@ package main */ import "C" import ( - "fmt" - "io" - "io/ioutil" "log" - "log/syslog" "unsafe" - "golang.org/x/sys/unix" - "github.com/pkg/errors" "github.com/google/fscrypt/actions" "github.com/google/fscrypt/crypto" - "github.com/google/fscrypt/filesystem" - "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/pam" "github.com/google/fscrypt/security" - "github.com/google/fscrypt/util" ) const ( moduleName = "pam_fscrypt" - // These labels are used to tag items in the PAM data. - authtokLabel = "fscrypt_authtok" - descriptorLabel = "fscrypt_descriptor" + // authtokLabel tags the AUTHTOK in the PAM data. + authtokLabel = "fscrypt_authtok" // These flags are used to toggle behavior of the PAM module. debugFlag = "debug" lockFlag = "lock_policies" cacheFlag = "drop_caches" ) -// parseArgs takes a list of C arguments into a PAM function and returns a map -// where a key has a value of true if it appears in the argument list. -func parseArgs(argc C.int, argv **C.char) map[string]bool { - args := make(map[string]bool) - for _, cString := range util.PointerSlice(unsafe.Pointer(argv))[:argc] { - args[C.GoString((*C.char)(cString))] = true - } - return args -} - -// setupLogging directs turns off standard logging (or redirects it to debug -// syslog if the "debug" argument is passed) and returns a writer to the error -// syslog. -func setupLogging(args map[string]bool) io.Writer { - log.SetFlags(0) // Syslog already includes time data itself - log.SetOutput(ioutil.Discard) - if args[debugFlag] { - debugWriter, err := syslog.New(syslog.LOG_DEBUG, moduleName) - if err == nil { - log.SetOutput(debugWriter) - } - } - - errorWriter, err := syslog.New(syslog.LOG_ERR, moduleName) - if err != nil { - return ioutil.Discard - } - return errorWriter -} - -// loginProtector returns the login protector corresponding to the PAM_USER if -// one exists. This protector descriptor (if found) will be cached in the pam -// data, under descriptorLabel. -func loginProtector(handle *pam.Handle) (*actions.Protector, error) { - ctx, err := actions.NewContextFromMountpoint("/") - if err != nil { - return nil, err - } - - // Find the user's PAM protector. - uid := int64(unix.Geteuid()) - if err != nil { - return nil, err - } - options, err := ctx.ProtectorOptions() - if err != nil { - return nil, err - } - for _, option := range options { - if option.Source() == metadata.SourceType_pam_passphrase && option.UID() == uid { - return actions.GetProtectorFromOption(ctx, option) - } +// Authenticate copies the AUTHTOK (if necessary) into the PAM data so it can be +// used in pam_sm_open_session. +func Authenticate(handle *pam.Handle, _ map[string]bool) error { + if err := handle.DropThreadPrivileges(); err != nil { + return err } - return nil, fmt.Errorf("no PAM protector on %q", ctx.Mount.Path) -} - -// pam_sm_authenticate copies the AUTHTOK (if necessary) into the PAM data so it -// can be used in pam_sm_open_session. -//export pam_sm_authenticate -func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - errWriter := setupLogging(parseArgs(argc, argv)) + defer handle.RaiseThreadPrivileges() // If this user doesn't have a login protector, no unlocking is needed. if _, err := loginProtector(handle); err != nil { log.Printf("no need to copy AUTHTOK: %s", err) - return C.PAM_SUCCESS + return nil } - log.Print("copying AUTHTOK in pam_sm_authenticate()") + log.Print("Authenticate: copying AUTHTOK for use in the session") authtok, err := handle.GetItem(pam.Authtok) if err != nil { - fmt.Fprintf(errWriter, "could not get AUTHTOK: %s", err) - return C.PAM_SERVICE_ERR - } - if err = handle.SetSecret(authtokLabel, authtok); err != nil { - fmt.Fprintf(errWriter, "could not set AUTHTOK data: %s", err) - return C.PAM_SERVICE_ERR + return errors.Wrap(err, "could not get AUTHTOK") } - return C.PAM_SUCCESS + err = handle.SetSecret(authtokLabel, authtok) + return errors.Wrap(err, "could not set AUTHTOK data") } -// pam_sm_stecred needed because we use pam_sm_authenticate. -//export pam_sm_setcred -func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - return C.PAM_SUCCESS -} - -// policiesUsingProtector searches all the mountpoints for any policies -// protected with the specified protector. An error during this search does not -// halt the search, instead the errors are written to errWriter. -func policiesUsingProtector(protector *actions.Protector, errWriter io.Writer) []*actions.Policy { - mounts, err := filesystem.AllFilesystems() - if err != nil { - fmt.Fprint(errWriter, err) - return nil +// OpenSession provisions any policies protected with the login protector. +func OpenSession(handle *pam.Handle, _ map[string]bool) error { + // We will always clear the the AUTHTOK data + defer handle.ClearData(authtokLabel) + // Increment the count as we add a session + if _, err := AdjustCount(handle, 1); err != nil { + return err } - var policies []*actions.Policy - for _, mount := range mounts { - // Skip mountpoints that do not use the protector. - if _, _, err := mount.GetProtector(protector.Descriptor()); err != nil { - continue - } - policyDescriptors, err := mount.ListPolicies() - if err != nil { - fmt.Fprintf(errWriter, "listing policies: %s", err) - continue - } - - ctx := &actions.Context{Config: protector.Context.Config, Mount: mount} - for _, policyDescriptor := range policyDescriptors { - policy, err := actions.GetPolicy(ctx, policyDescriptor) - if err != nil { - fmt.Fprintf(errWriter, "reading policy: %s", err) - continue - } - - if policy.UsesProtector(protector) { - policies = append(policies, policy) - } - } + if err := handle.DropThreadPrivileges(); err != nil { + return err } - return policies -} - -// pam_sm_open_session provisions policies protected with the login protector. -//export pam_sm_open_session -func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - errWriter := setupLogging(parseArgs(argc, argv)) + defer handle.RaiseThreadPrivileges() + // If there are no polices for the login protector, no unlocking needed. protector, err := loginProtector(handle) if err != nil { - log.Printf("no pam protector for this user: %s", err) - return C.PAM_SUCCESS + log.Printf("nothing to unlock: %s", err) + return nil + } + policies := policiesUsingProtector(protector) + if len(policies) == 0 { + log.Print("no policies to unlock") + return nil } + log.Print("OpenSession: unlocking policies protected with AUTHTOK") keyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { if retry { // Login passphrase and login protector have diverged. @@ -214,100 +116,107 @@ func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) // login passphrase here, but we currently don't. return nil, errors.Wrap(err, "AUTHTOK data missing") } - defer handle.ClearData(authtokLabel) - return crypto.NewKeyFromCString(authtok) - } - log.Print("searching for policies to unlock in pam_sm_open_session()") - policies := policiesUsingProtector(protector, errWriter) - if len(policies) == 0 { - log.Print("no policies to unlock") - return C.PAM_SUCCESS + return crypto.NewKeyFromCString(authtok) } - if err := protector.Unlock(keyFn); err != nil { - fmt.Fprintf(errWriter, "unlocking protector %s: %s", protector.Descriptor(), err) - return C.PAM_SERVICE_ERR + return errors.Wrapf(err, "unlocking protector %s", protector.Descriptor()) } defer protector.Lock() + // We don't stop provisioning polices on error, we try all of them. for _, policy := range policies { if policy.IsProvisioned() { log.Printf("policy %s already provisioned", policy.Descriptor()) continue } if err := policy.UnlockWithProtector(protector); err != nil { - fmt.Fprintf(errWriter, "unlocking policy %s: %s", policy.Descriptor(), err) + log.Printf("unlocking policy %s: %s", policy.Descriptor(), err) continue } defer policy.Lock() if err := policy.Provision(); err != nil { - fmt.Fprintf(errWriter, "provisioning policy %s: %s", policy.Descriptor(), err) + log.Printf("provisioning policy %s: %s", policy.Descriptor(), err) continue } - log.Printf("policy %s provisioned", policy.Descriptor()) } - - return C.PAM_SUCCESS + return nil } -// pam_sm_close_session deprovisions all keys provisioned at the start of the -// session. It also clears the cache so these changes take effect. -//export pam_sm_close_session -func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - args := parseArgs(argc, argv) - errWriter := setupLogging(args) +// CloseSession can deprovision all keys provisioned at the start of the +// session. It can also clear the cache so these changes take effect. +func CloseSession(handle *pam.Handle, args map[string]bool) error { + // Only do stuff on session close when we are the last session + if count, err := AdjustCount(handle, -1); err != nil || count != 0 { + return err + } + var errLock, errCache error + // Don't automatically drop privileges, we may need them to drop caches. if args[lockFlag] { - protector, err := loginProtector(handle) - if err != nil { - log.Printf("no pam protector for this user: %s", err) - return C.PAM_SUCCESS - } + log.Print("CloseSession: locking polices protected with login") + errLock = lockLoginPolicies(handle) + } - policies := policiesUsingProtector(protector, errWriter) + if args[cacheFlag] { + log.Print("CloseSession: dropping inode caches") + errCache = security.DropInodeCache() + } - if len(policies) == 0 { - log.Print("no policies to lock") - return C.PAM_SUCCESS - } + if errLock != nil { + return errLock } + return errCache +} - log.Print("locking directories in pam_sm_close_session()") - for _, provisionedKey := range provisionedKeys { - if err := security.RemoveKey(provisionedKey); err != nil { - fmt.Fprintf(errWriter, "can't remove %s: %s", provisionedKey, err) - } +// lockLoginPolicies deprovisions all policy keys that are protected by +// the user's login protector. +func lockLoginPolicies(handle *pam.Handle) error { + if err := handle.DropThreadPrivileges(); err != nil { + return err } + defer handle.RaiseThreadPrivileges() - if args[cacheFlag] { - if err = security.DropInodeCache(); err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR - } + // If there are no polices for the login protector, no locking needed. + protector, err := loginProtector(handle) + if err != nil { + log.Printf("nothing to lock: %s", err) + return nil + } + policies := policiesUsingProtector(protector) + if len(policies) == 0 { + log.Print("no policies to lock") + return nil } - return C.PAM_SUCCESS + // We will try to deprovision all of the policies. + for _, policy := range policies { + if !policy.IsProvisioned() { + log.Printf("policy %s not provisioned", policy.Descriptor()) + continue + } + if err := policy.Deprovision(); err != nil { + log.Printf("deprovisioning policy %s: %s", policy.Descriptor(), err) + continue + } + log.Printf("policy %s deprovisioned", policy.Descriptor()) + } + return nil } -// pam_sm_chauthtok rewraps the login protector when the passphrase changes. -//export pam_sm_chauthtok -func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { - handle := pam.NewHandle(pamh) - errWriter := setupLogging(parseArgs(argc, argv)) - - // Only do rewrapping if we have both AUTHTOKs and a login protector. - if pam.Flag(flags)&pam.PrelimCheck != 0 { - log.Print("no preliminary checks need to run") - return C.PAM_SUCCESS +// Chauthtok rewraps the login protector when the passphrase changes. +func Chauthtok(handle *pam.Handle, _ map[string]bool) error { + if err := handle.DropThreadPrivileges(); err != nil { + return err } + defer handle.RaiseThreadPrivileges() + protector, err := loginProtector(handle) if err != nil { - log.Printf("no protector to rewrap: %s", err) - return C.PAM_SUCCESS + log.Printf("nothing to rewrap: %s", err) + return nil } oldKeyFn := func(_ actions.ProtectorInfo, retry bool) (*crypto.Key, error) { @@ -332,19 +241,46 @@ func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.i return crypto.NewKeyFromCString(authtok) } - log.Print("rewrapping protector in pam_sm_chauthtok()") + log.Print("Chauthtok: rewrapping login protector") if err = protector.Unlock(oldKeyFn); err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR + return err } defer protector.Lock() - if err = protector.Rewrap(newKeyFn); err != nil { - fmt.Fprint(errWriter, err) - return C.PAM_SERVICE_ERR - } + return protector.Rewrap(newKeyFn) +} + +//export pam_sm_authenticate +func pam_sm_authenticate(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return RunPamFunc(Authenticate, pamh, argc, argv) +} + +// pam_sm_stecred needed because we use pam_sm_authenticate. +//export pam_sm_setcred +func pam_sm_setcred(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { return C.PAM_SUCCESS } +//export pam_sm_open_session +func pam_sm_open_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return RunPamFunc(OpenSession, pamh, argc, argv) +} + +//export pam_sm_close_session +func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + return RunPamFunc(CloseSession, pamh, argc, argv) +} + +//export pam_sm_chauthtok +func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { + // Only do rewrapping if we have both AUTHTOKs and a login protector. + if pam.Flag(flags)&pam.PrelimCheck != 0 { + log.Print("no preliminary checks need to run") + return C.PAM_SUCCESS + } + + return RunPamFunc(Chauthtok, pamh, argc, argv) +} + // main() is needed to make a shared library compile func main() {} -- cgit v1.2.3 From 19c13e861996c3503be5b0dc5a2cecfe186b1744 Mon Sep 17 00:00:00 2001 From: Joseph Richey Date: Thu, 24 Aug 2017 00:29:54 -0700 Subject: Updated documentation for PAM module help --- pam_fscrypt/pam_fscrypt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pam_fscrypt/pam_fscrypt.go') diff --git a/pam_fscrypt/pam_fscrypt.go b/pam_fscrypt/pam_fscrypt.go index 2eecd3a..21bc779 100644 --- a/pam_fscrypt/pam_fscrypt.go +++ b/pam_fscrypt/pam_fscrypt.go @@ -78,7 +78,7 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { // We will always clear the the AUTHTOK data defer handle.ClearData(authtokLabel) // Increment the count as we add a session - if _, err := AdjustCount(handle, 1); err != nil { + if _, err := AdjustCount(handle, +1); err != nil { return err } @@ -150,6 +150,7 @@ func OpenSession(handle *pam.Handle, _ map[string]bool) error { func CloseSession(handle *pam.Handle, args map[string]bool) error { // Only do stuff on session close when we are the last session if count, err := AdjustCount(handle, -1); err != nil || count != 0 { + log.Printf("count is %d and we are not locking", count) return err } @@ -275,7 +276,6 @@ func pam_sm_close_session(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) func pam_sm_chauthtok(pamh unsafe.Pointer, flags, argc C.int, argv **C.char) C.int { // Only do rewrapping if we have both AUTHTOKs and a login protector. if pam.Flag(flags)&pam.PrelimCheck != 0 { - log.Print("no preliminary checks need to run") return C.PAM_SUCCESS } -- cgit v1.2.3