aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--README.md2
-rw-r--r--filesystem/mountpoint.go2
-rw-r--r--pam/login.go120
-rw-r--r--pam/pam.c80
-rw-r--r--pam/pam.h31
6 files changed, 235 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 22da24a..ab5cad0 100644
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ CMD_DIR = $(NAME)/cmd/$(NAME)
# The code below lets the caller of the makefile change the build flags for
# fscrypt in a familiar manner. For example, to force the program to statically
# link its C components, run "make fscrypt" with:
-# make fscrypt "LDFLAGS += -static"
+# make fscrypt "LDFLAGS += -static -luuid -ldl -laudit -lpthread"
#
# Similarly, to modify the flags passed to the C components, just modify CFLAGS
# or LDFLAGS as you would with a C program. To modify the Go flags, either
@@ -59,7 +59,7 @@ $(NAME):
go:
govendor generate +local
govendor build $(GOFLAGS) +local
- govendor test $(GOFLAGS) +local
+ govendor test $(GOFLAGS) -p 1 +local
update:
@govendor fetch +missing
diff --git a/README.md b/README.md
index be0e3e3..caad7fe 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ You will also want to add `$GOPATH/bin` to your `$PATH`.
`libblkid-devel` for RPM, should already be part of `util-linux` for Arch).
Once this is setup, you can run `make fscrypt` to build the executable in
-the root directory. Pass `"LDFLAGS += -static"` to `make` to get a static
+the root directory. See the `Makefile` for instructions on building a static
executable. If a Go project contains C code, the go compiler produces a
dynamically linked binary by default.
diff --git a/filesystem/mountpoint.go b/filesystem/mountpoint.go
index a421058..ddcc243 100644
--- a/filesystem/mountpoint.go
+++ b/filesystem/mountpoint.go
@@ -22,7 +22,7 @@
package filesystem
/*
-#cgo LDFLAGS: -lblkid -luuid
+#cgo LDFLAGS: -lblkid
#include <blkid/blkid.h> // blkid functions
#include <stdlib.h> // free()
#include <mntent.h> // setmntent, getmntent, endmntent
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