diff options
| author | Joseph Richey <joerichey@google.com> | 2017-07-17 18:26:19 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-07-17 18:26:19 -0700 |
| commit | 3d08d9f6891db3ca94337e9b987ef62cba535fe1 (patch) | |
| tree | ca9476a0aecaf79cfc8716875db073ea54d5f748 /pam | |
| parent | 6f32bbc8bf51d615ef23ed37aa40910ec23cd587 (diff) | |
| parent | 1a4a020ad5766fce3b3ad719d85593a3e8159733 (diff) | |
Merge pull request #21 from google/fix
Add PAM package
Diffstat (limited to 'pam')
| -rw-r--r-- | pam/constants.go | 110 | ||||
| -rw-r--r-- | pam/login.go | 117 | ||||
| -rw-r--r-- | pam/pam.c | 49 | ||||
| -rw-r--r-- | pam/pam.go | 190 | ||||
| -rw-r--r-- | pam/pam.h | 23 |
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 } @@ -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() +} @@ -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 |