diff options
Diffstat (limited to 'pam')
| -rw-r--r-- | pam/login.go | 120 | ||||
| -rw-r--r-- | pam/pam.c | 80 | ||||
| -rw-r--r-- | pam/pam.h | 31 |
3 files changed, 231 insertions, 0 deletions
diff --git a/pam/login.go b/pam/login.go new file mode 100644 index 0000000..63041de --- /dev/null +++ b/pam/login.go @@ -0,0 +1,120 @@ +/* + * login.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 pam contains all the functionality for interfacing with Linux +// Pluggable Authentication Modules (PAM). Currently, all this package does is +// check the validity of a user's login passphrase. +// 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" + + "fscrypt/crypto" + "fscrypt/util" +) + +// 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. +var ( + loginLock 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)) +} + +// 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) + + // 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) +} + +// 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) { + 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() + tokenToCheck = token + defer func() { + tokenToCheck = nil + loginLock.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, util.SystemError(fmt.Sprintf("pam_start returned %d", returnCode)) + } + + 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 = util.SystemError(fmt.Sprintf("pam_end returned %d", returnCode)) + } + }() + + // 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, util.SystemError(fmt.Sprintf("pam_authenticate returned %d", returnCode)) + } +} diff --git a/pam/pam.c b/pam/pam.c new file mode 100644 index 0000000..ce640e8 --- /dev/null +++ b/pam/pam.c @@ -0,0 +1,80 @@ +/* + * pam.c - Functions to let us call into libpam from Go. + * + * 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. + */ + +#include "pam.h" + +#include <stdio.h> +#include <stdlib.h> + +#include "_cgo_export.h" // for pamInput callback + +const char* fscrypt_service = "fscrypt"; + +static int pam_conv(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; + } + + // Allocate the response table with num_msg entries. + *resp = calloc(num_msg, sizeof **resp); + if (!*resp) { + return PAM_BUF_ERR; + } + + // Check each message to see if we need to run a callback. + char* callback_msg = NULL; + char* callback_resp = NULL; + int i; + for (i = 0; i < num_msg; ++i) { + callback_msg = (char*)msg[i]->msg; + + // We run our input callback if the style tells us we need data. Otherwise, + // 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); + break; + case PAM_PROMPT_ECHO_ON: + // We should never have a request for non-secret data + unexpectedMessage(callback_msg); + callback_resp = NULL; + break; + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + printf("%s\n", callback_msg); + continue; + } + + if (!callback_resp) { + // If the callback failed, free each nonempty response in the response + // table and the response table itself. + while (--i >= 0) { + free((*resp)[i].resp); + } + free(*resp); + return PAM_CONV_ERR; + } + + (*resp)[i].resp = callback_resp; + } + return PAM_SUCCESS; +} + +void pam_init(struct pam_conv* conv) { conv->conv = pam_conv; } diff --git a/pam/pam.h b/pam/pam.h new file mode 100644 index 0000000..83ef2a9 --- /dev/null +++ b/pam/pam.h @@ -0,0 +1,31 @@ +/* + * pam.h - Functions to let us call into libpam from Go. + * + * 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. + */ + +#ifndef FSCRYPT_PAM_H +#define FSCRYPT_PAM_H + +#include <security/pam_appl.h> + +// fscrypt_service is the display name of the service requesting the passphrase. +const char* fscrypt_service; + +// pam_init initializes the pam_conv structure for use with our Go callbacks. +void pam_init(struct pam_conv* conv); + +#endif |