aboutsummaryrefslogtreecommitdiff
path: root/pam
diff options
context:
space:
mode:
authorJoseph Richey <joerichey@google.com>2017-07-17 18:26:19 -0700
committerGitHub <noreply@github.com>2017-07-17 18:26:19 -0700
commit3d08d9f6891db3ca94337e9b987ef62cba535fe1 (patch)
treeca9476a0aecaf79cfc8716875db073ea54d5f748 /pam
parent6f32bbc8bf51d615ef23ed37aa40910ec23cd587 (diff)
parent1a4a020ad5766fce3b3ad719d85593a3e8159733 (diff)
Merge pull request #21 from google/fix
Add PAM package
Diffstat (limited to 'pam')
-rw-r--r--pam/constants.go110
-rw-r--r--pam/login.go117
-rw-r--r--pam/pam.c49
-rw-r--r--pam/pam.go190
-rw-r--r--pam/pam.h23
5 files changed, 411 insertions, 78 deletions
diff --git a/pam/constants.go b/pam/constants.go
new file mode 100644
index 0000000..5c57e06
--- /dev/null
+++ b/pam/constants.go
@@ -0,0 +1,110 @@
+/*
+ * constants.go - PAM flags and item types from github.com/msteinert/pam
+ *
+ * Modifications Copyright 2017 Google Inc.
+ * Modifications 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.
+ */
+/*
+ * Copyright 2011, krockot
+ * Copyright 2015, Michael Steinert <mike.steinert@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package pam
+
+/*
+#cgo LDFLAGS: -lpam
+
+#include <security/pam_modules.h>
+*/
+import "C"
+
+// Item is a an PAM information type.
+type Item int
+
+// PAM Item types.
+const (
+ // Service is the name which identifies the PAM stack.
+ Service Item = C.PAM_SERVICE
+ // User identifies the username identity used by a service.
+ User = C.PAM_USER
+ // Tty is the terminal name.
+ Tty = C.PAM_TTY
+ // Rhost is the requesting host name.
+ Rhost = C.PAM_RHOST
+ // Authtok is the currently active authentication token.
+ Authtok = C.PAM_AUTHTOK
+ // Oldauthtok is the old authentication token.
+ Oldauthtok = C.PAM_OLDAUTHTOK
+ // Ruser is the requesting user name.
+ Ruser = C.PAM_RUSER
+ // UserPrompt is the string use to prompt for a username.
+ UserPrompt = C.PAM_USER_PROMPT
+)
+
+// Flag is used as input to various PAM functions. Flags can be combined with a
+// bitwise or. Refer to the official PAM documentation for which flags are
+// accepted by which functions.
+type Flag int
+
+// PAM Flag types.
+const (
+ // Silent indicates that no messages should be emitted.
+ Silent Flag = C.PAM_SILENT
+ // DisallowNullAuthtok indicates that authorization should fail
+ // if the user does not have a registered authentication token.
+ DisallowNullAuthtok = C.PAM_DISALLOW_NULL_AUTHTOK
+ // EstablishCred indicates that credentials should be established
+ // for the user.
+ EstablishCred = C.PAM_ESTABLISH_CRED
+ // DeleteCred inidicates that credentials should be deleted.
+ DeleteCred = C.PAM_DELETE_CRED
+ // ReinitializeCred indicates that credentials should be fully
+ // reinitialized.
+ ReinitializeCred = C.PAM_REINITIALIZE_CRED
+ // RefreshCred indicates that the lifetime of existing credentials
+ // should be extended.
+ RefreshCred = C.PAM_REFRESH_CRED
+ // ChangeExpiredAuthtok indicates that the authentication token
+ // should be changed if it has expired.
+ ChangeExpiredAuthtok = C.PAM_CHANGE_EXPIRED_AUTHTOK
+ // PrelimCheck indicates that the modules are being probed as to their
+ // ready status for altering the user's authentication token.
+ PrelimCheck = C.PAM_PRELIM_CHECK
+ // UpdateAuthtok informs the module that this is the call it should
+ // change the authorization tokens.
+ UpdateAuthtok = C.PAM_UPDATE_AUTHTOK
+)
diff --git a/pam/login.go b/pam/login.go
index 2d79223..e89ee01 100644
--- a/pam/login.go
+++ b/pam/login.go
@@ -23,17 +23,12 @@
// See http://www.linux-pam.org/Linux-PAM-html/ for more information.
package pam
-/*
-#cgo LDFLAGS: -lpam
-#include <stdlib.h>
-#include "pam.h"
-*/
import "C"
import (
+ "fmt"
"log"
"sync"
- "unsafe"
"github.com/pkg/errors"
@@ -41,82 +36,78 @@ import (
"github.com/google/fscrypt/util"
)
+// Pam error values
+var (
+ ErrPAMPassphrase = errors.New("incorrect login passphrase")
+)
+
// Global state is needed for the PAM callback, so we guard this function with a
-// lock. tokenToCheck is only ever non-nil when loginLock is held.
+// lock. tokenToCheck is only ever non-nil when tokenLock is held.
var (
- ErrPamInternal = util.SystemError("internal pam error")
- loginLock sync.Mutex
- tokenToCheck *crypto.Key
+ tokenLock sync.Mutex
+ tokenToCheck *crypto.Key
)
-// unexpectedMessage logs an error encountered in the PAM callback.
-//export unexpectedMessage
-func unexpectedMessage(msg *C.char) {
- log.Printf("pam encountered unexpected %q", C.GoString(msg))
+// userInput is run when the the callback needs some input from the user. We
+// prompt the user for information and return their answer. A return value of
+// nil indicates an error occurred.
+//export userInput
+func userInput(prompt *C.char) *C.char {
+ fmt.Print(C.GoString(prompt))
+ input, err := util.ReadLine()
+ if err != nil {
+ log.Printf("getting input for PAM: %s", err)
+ return nil
+ }
+ return C.CString(input)
}
-// pamInput is run when the PAM module needs some input from the user. The
-// message parameter is the prompt that would be displayed to the user.
-//export pamInput
-func pamInput(msg *C.char) *C.char {
- log.Printf("requesting secret data with %q", C.GoString(msg))
-
- // Memory for the key must be moved into a C string allocated by C.
- cLen := C.size_t(tokenToCheck.Len())
- cData := C.malloc(cLen + 1)
+// passphraseInput is run when the callback needs a passphrase from the user. We
+// pass along the tokenToCheck without prompting. A return value of nil
+// indicates an error occurred.
+//export passphraseInput
+func passphraseInput(prompt *C.char) *C.char {
+ log.Printf("getting secret data for PAM: %q", C.GoString(prompt))
+ if tokenToCheck == nil {
+ log.Print("secret data requested multiple times")
+ return nil
+ }
- // View the cData as a go slice
- goData := (*[1 << 30]byte)(cData)
- copy(goData[:cLen], tokenToCheck.UnsafeData())
- goData[cLen] = 0 // Null terminator
- return (*C.char)(cData)
+ // Subsequent calls to passphrase input should fail
+ input := (*C.char)(tokenToCheck.UnsafeToCString())
+ tokenToCheck = nil
+ return input
}
-// IsUserLoginToken returns true if the presented token is the user's login key,
-// false if it is not their login key, and an error if this cannot be
-// determined. Note that unless the currently running process is root, this
-// check will only work for the user running this process.
-func IsUserLoginToken(username string, token *crypto.Key) (_ bool, err error) {
+// IsUserLoginToken returns nil if the presented token is the user's login key,
+// and returns an error otherwise. Note that unless we are currently running as
+// root, this check will only work for the user running this process.
+func IsUserLoginToken(username string, token *crypto.Key, quiet bool) error {
log.Printf("Checking login token for %s", username)
+
// We require global state for the function. This function never takes
// ownership of the token, so it is not responsible for wiping it.
- loginLock.Lock()
+ tokenLock.Lock()
tokenToCheck = token
defer func() {
tokenToCheck = nil
- loginLock.Unlock()
+ tokenLock.Unlock()
}()
- cUsername := C.CString(username)
- defer C.free(unsafe.Pointer(cUsername))
-
- var conv C.struct_pam_conv
- var handle *C.struct_pam_handle
- C.pam_init(&conv)
-
- // Start the pam transaction with the desired conversation and handle.
- returnCode := C.pam_start(C.fscrypt_service, cUsername, &conv, &handle)
- if returnCode != C.PAM_SUCCESS {
- return false, errors.Wrapf(ErrPamInternal, "pam_start() = %d", returnCode)
+ transaction, err := Start("fscrypt", username)
+ if err != nil {
+ return err
}
+ defer transaction.End()
- defer func() {
- // End the PAM transaction, setting the error if appropriate.
- returnCode = C.pam_end(handle, returnCode)
- if returnCode != C.PAM_SUCCESS && err == nil {
- err = errors.Wrapf(ErrPamInternal, "pam_end() = %d", returnCode)
- }
- }()
+ // Ask PAM to authenticate the token.
+ authenticated, err := transaction.Authenticate(quiet)
+ if err != nil {
+ return err
+ }
- // Ask PAM to authenticate the token. We either get an answer or an error
- returnCode = C.pam_authenticate(handle, 0)
- switch returnCode {
- case C.PAM_SUCCESS:
- return true, nil
- case C.PAM_AUTH_ERR:
- return false, nil
- default:
- // PAM didn't give us an answer to the authentication question
- return false, errors.Wrapf(ErrPamInternal, "pam_authenticate() = %d", returnCode)
+ if !authenticated {
+ return ErrPAMPassphrase
}
+ return nil
}
diff --git a/pam/pam.c b/pam/pam.c
index ce640e8..e32770f 100644
--- a/pam/pam.c
+++ b/pam/pam.c
@@ -21,13 +21,15 @@
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
-#include "_cgo_export.h" // for pamInput callback
+#include <security/pam_appl.h>
+#include <sys/mman.h> // mlock/munlock
-const char* fscrypt_service = "fscrypt";
+#include "_cgo_export.h" // for input callbacks
-static int pam_conv(int num_msg, const struct pam_message** msg,
- struct pam_response** resp, void* appdata_ptr) {
+static int conversation(int num_msg, const struct pam_message** msg,
+ struct pam_response** resp, void* appdata_ptr) {
if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
return PAM_CONV_ERR;
}
@@ -49,16 +51,14 @@ static int pam_conv(int num_msg, const struct pam_message** msg,
// we just print the error messages or text info to standard output.
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
- callback_resp = pamInput(callback_msg);
+ callback_resp = passphraseInput(callback_msg);
break;
case PAM_PROMPT_ECHO_ON:
- // We should never have a request for non-secret data
- unexpectedMessage(callback_msg);
- callback_resp = NULL;
+ callback_resp = userInput(callback_msg);
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
- printf("%s\n", callback_msg);
+ fprintf(stderr, "%s\n", callback_msg);
continue;
}
@@ -69,12 +69,41 @@ static int pam_conv(int num_msg, const struct pam_message** msg,
free((*resp)[i].resp);
}
free(*resp);
+ *resp = NULL;
return PAM_CONV_ERR;
}
(*resp)[i].resp = callback_resp;
}
+
return PAM_SUCCESS;
}
-void pam_init(struct pam_conv* conv) { conv->conv = pam_conv; }
+const struct pam_conv conv = {conversation, NULL};
+
+void freeData(pam_handle_t* pamh, void* data, int error_status) { free(data); }
+
+void freeArray(pam_handle_t* pamh, void** array, int error_status) {
+ int i;
+ for (i = 0; array[i]; ++i) {
+ free(array[i]);
+ }
+ free(array);
+}
+
+void* copyIntoSecret(void* data) {
+ size_t size = strlen(data) + 1; // include null terminator
+ void* copy = malloc(size);
+ mlock(copy, size);
+ memcpy(copy, data, size);
+ return copy;
+}
+
+void freeSecret(pam_handle_t* pamh, char* data, int error_status) {
+ size_t size = strlen(data) + 1; // Include null terminator
+ // Use volitile function pointer to actually clear the memory.
+ static void* (*const volatile memset_sec)(void*, int, size_t) = &memset;
+ memset_sec(data, 0, size);
+ munlock(data, size);
+ free(data);
+} \ No newline at end of file
diff --git a/pam/pam.go b/pam/pam.go
new file mode 100644
index 0000000..010d4d2
--- /dev/null
+++ b/pam/pam.go
@@ -0,0 +1,190 @@
+/*
+ * pam.go - Utility functions for interfacing with the PAM libraries.
+ *
+ * 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 pam
+
+/*
+#cgo LDFLAGS: -lpam
+#include "pam.h"
+
+#include <pwd.h>
+#include <stdlib.h>
+#include <security/pam_modules.h>
+*/
+import "C"
+import (
+ "errors"
+ "fmt"
+ "unsafe"
+
+ "github.com/google/fscrypt/util"
+)
+
+// Handle wraps the C pam_handle_t type. This is used from within modules.
+type Handle struct {
+ handle *C.pam_handle_t
+ status C.int
+}
+
+// NewHandle creates a Handle from a raw pointer.
+func NewHandle(pamh unsafe.Pointer) *Handle {
+ return &Handle{
+ handle: (*C.pam_handle_t)(pamh),
+ status: C.PAM_SUCCESS,
+ }
+}
+
+func (h *Handle) setData(name string, data unsafe.Pointer, cleanup C.CleanupFunc) error {
+ cName := C.CString(name)
+ defer C.free(unsafe.Pointer(cName))
+ h.status = C.pam_set_data(h.handle, cName, data, cleanup)
+ return h.err()
+}
+
+func (h *Handle) getData(name string) (unsafe.Pointer, error) {
+ var data unsafe.Pointer
+ cName := C.CString(name)
+ defer C.free(unsafe.Pointer(cName))
+ h.status = C.pam_get_data(h.handle, cName, &data)
+ return data, h.err()
+}
+
+func (h *Handle) SetSecret(name string, secret unsafe.Pointer) error {
+ return h.setData(name, C.copyIntoSecret(secret), C.CleanupFunc(C.freeSecret))
+}
+
+func (h *Handle) GetSecret(name string) (unsafe.Pointer, error) {
+ return h.getData(name)
+}
+
+func (h *Handle) ClearSecret(name string) error {
+ return h.setData(name, unsafe.Pointer(C.CString("")), C.CleanupFunc(C.freeData))
+}
+
+func (h *Handle) SetString(name string, s string) error {
+ return h.setData(name, unsafe.Pointer(C.CString(s)), C.CleanupFunc(C.freeData))
+}
+
+func (h *Handle) GetString(name string) (string, error) {
+ data, err := h.getData(name)
+ if err != nil {
+ return "", err
+ }
+ return C.GoString((*C.char)(data)), nil
+}
+
+func (h *Handle) SetSlice(name string, slice []string) error {
+ sliceLength := uintptr(len(slice))
+ memorySize := (sliceLength + 1) * unsafe.Sizeof(uintptr(0))
+ data := C.malloc(C.size_t(memorySize))
+
+ cSlice := util.PointerSlice(data)
+ for i, str := range slice {
+ cSlice[i] = unsafe.Pointer(C.CString(str))
+ }
+ cSlice[sliceLength] = nil
+
+ return h.setData(name, data, C.CleanupFunc(C.freeArray))
+}
+
+func (h *Handle) GetSlice(name string) ([]string, error) {
+ data, err := h.getData(name)
+ if err != nil {
+ return nil, err
+ }
+
+ var slice []string
+ for _, cString := range util.PointerSlice(data) {
+ if cString == nil {
+ return slice, nil
+ }
+ slice = append(slice, C.GoString((*C.char)(cString)))
+ }
+ panic("We will never get here")
+}
+
+// GetItem retrieves a PAM information item. This a pointer directory to the
+// data, so it shouldn't be modified.
+func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) {
+ var data unsafe.Pointer
+ h.status = C.pam_get_item(h.handle, C.int(i), &data)
+ return data, h.err()
+}
+
+// GetUID retrieves the UID of the corresponding PAM_USER.
+func (h *Handle) GetUID() (int64, error) {
+ var pamUsername *C.char
+ h.status = C.pam_get_user(h.handle, &pamUsername, nil)
+ if err := h.err(); err != nil {
+ return 0, err
+ }
+
+ pwd := C.getpwnam(pamUsername)
+ if pwd == nil {
+ return 0, fmt.Errorf("unknown user %q", C.GoString(pamUsername))
+ }
+ return int64(pwd.pw_uid), nil
+}
+
+func (h *Handle) err() error {
+ if h.status == C.PAM_SUCCESS {
+ return nil
+ }
+ s := C.GoString(C.pam_strerror(h.handle, C.int(h.status)))
+ return errors.New(s)
+}
+
+// Transaction represents a wrapped pam_handle_t type created with pam_start
+// form an application.
+type Transaction Handle
+
+// Start initializes a pam Transaction. End() should be called after the
+// Transaction is no longer needed.
+func Start(service, username string) (*Transaction, error) {
+ cService := C.CString(service)
+ defer C.free(unsafe.Pointer(cService))
+ cUsername := C.CString(username)
+ defer C.free(unsafe.Pointer(cUsername))
+
+ t := &Transaction{
+ handle: nil,
+ status: C.PAM_SUCCESS,
+ }
+ t.status = C.pam_start(cService, cUsername, &C.conv, &t.handle)
+ return t, (*Handle)(t).err()
+}
+
+// End finalizes a pam Transaction with pam_end().
+func (t *Transaction) End() {
+ C.pam_end(t.handle, t.status)
+}
+
+// Authenticate returns a boolean indicating if the user authenticated correctly
+// or not. If the authentication check did not complete, an error is returned.
+func (t *Transaction) Authenticate(quiet bool) (bool, error) {
+ var flags C.int = C.PAM_DISALLOW_NULL_AUTHTOK
+ if quiet {
+ flags |= C.PAM_SILENT
+ }
+ t.status = C.pam_authenticate(t.handle, flags)
+ if t.status == C.PAM_AUTH_ERR {
+ return false, nil
+ }
+ return true, (*Handle)(t).err()
+}
diff --git a/pam/pam.h b/pam/pam.h
index 83ef2a9..9f3cdb2 100644
--- a/pam/pam.h
+++ b/pam/pam.h
@@ -22,10 +22,23 @@
#include <security/pam_appl.h>
-// fscrypt_service is the display name of the service requesting the passphrase.
-const char* fscrypt_service;
+// Conversation that will call back into Go code when appropriate.
+const struct pam_conv conv;
-// pam_init initializes the pam_conv structure for use with our Go callbacks.
-void pam_init(struct pam_conv* conv);
+// CleaupFuncs are used to cleanup specific PAM data.
+typedef void (*CleanupFunc)(pam_handle_t *pamh, void *data, int error_status);
-#endif
+// CleaupFunc that calls free() on data.
+void freeData(pam_handle_t *pamh, void *data, int error_status);
+
+// CleaupFunc that frees each item in a null terminated array of pointers and
+// then frees the array itself.
+void freeArray(pam_handle_t *pamh, void **array, int error_status);
+
+// Creates a copy of a C string, which resides in an locked buffer.
+void *copyIntoSecret(void *data);
+
+// CleaupFunc that Zeros wipes a C string and unlocks and frees its memory.
+void freeSecret(pam_handle_t *pamh, char *data, int error_status);
+
+#endif // FSCRYPT_PAM_H