# parts are depend on linux platform.
#
AC_DEFUN([LC_CONFIG_GSS], [
-AC_MSG_CHECKING([whether to enable gss/krb5 support])
+AC_MSG_CHECKING([whether to enable gss support])
AC_ARG_ENABLE([gss],
- [AC_HELP_STRING([--enable-gss], [enable gss/krb5 support])],
+ [AC_HELP_STRING([--enable-gss], [enable gss support])],
[], [enable_gss="auto"])
AC_MSG_RESULT([$enable_gss])
])
]) # LC_CONFIG_GSS
+# LC_HAVE_VOID_OPENSSL_HMAC_FUNCS
#
+# OpenSSL 1.0+ return int for HMAC functions but previous versions do not
+AC_DEFUN([LC_HAVE_VOID_OPENSSL_HMAC_FUNCS], [
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([
+ #include <openssl/hmac.h>
+ #include <openssl/evp.h>
+
+ int main(void) {
+ int rc;
+ HMAC_CTX ctx;
+ HMAC_CTX_init(&ctx);
+ rc = HMAC_Init_ex(&ctx, "test", 4, EVP_md_null(), NULL);
+ }
+])],[],[AC_DEFINE(HAVE_VOID_OPENSSL_HMAC_FUNCS, 1,
+ [OpenSSL HMAC functions return void instead of int])
+])
+]) # LC_HAVE_VOID_OPENSSL_HMAC_FUNCS
+
# LC_INODE_PERMISION_2ARGS
#
# up to v2.6.27 had a 3 arg version (inode, mask, nameidata)
LC_GLIBC_SUPPORT_FHANDLES
LC_CONFIG_RMTCLIENT
LC_CONFIG_GSS
+ LC_HAVE_VOID_OPENSSL_HMAC_FUNCS
# 2.6.32
LC_BLK_QUEUE_MAX_SEGMENTS
--- /dev/null
+.TH lgss_sk 8 "2016 Jan 12" Lustre "configuration utilities"
+.SH NAME
+lgss_sk \- Lustre GSS Shared-Key tool
+.SH SYNOPSIS
+.B "lgss_sk [OPTIONS] -r <keyfile> | -w <keyfile> | -m <keyfile> | -l <keyfile>"
+.br
+.SH DESCRIPTION
+.B lgss_sk
+can be used to read, write, modify, and load the contents of a shared-key keyfile.
+.SH OPTIONS
+.B lgss_sk
+accepts the following options:
+.TP
+.I "-l, --load <keyfile>"
+Load key from file into user's session keyring.
+.TP
+.I "-m, --modify <keyfile>"
+Modify a file's key attributes.
+.TP
+.I "-r, --read <keyfile>"
+Show file's key attributes.
+.TP
+.I "-w, --write <keyfile>"
+Generate key file.
+.HP
+Load Options:
+.TP
+.I "-t, --type <type>"
+Key type (mgs, server, client).
+.HP
+Modify/Write Options:
+.TP
+.I "-c, --crypt <num>"
+Cipher for encryption (Default: AES-256-CTR)
+.RS
+AES-256-CTR
+.RE
+.TP
+.I "-h, --hmac <num>"
+Hash alg for HMAC (Default: SHA256)
+.RS
+SHA256
+.br
+SHA512
+.RE
+.TP
+.I "-e, --expire <num>"
+Seconds before contexts from key expire (Default: 604800 seconds).
+.TP
+.I "-f, --fsname <name>"
+File system name for key.
+.TP
+.I "-g, --mgsnids <nids>"
+Comma seperated list of MGS NIDs. Only required when mgssec is used (Default: "").
+.TP
+.I "-n, --nodemap <name>"
+Nodemap name for key (Default: "default").
+.TP
+.I "-s, --session <len>"
+Session key length in bits (Default: 1024).
+.TP
+.I "-k, --shared <len>"
+Shared key length in bits (Default: 256).
+.TP
+.I "-d, --data <file>"
+Shared key entopy data source (default: /dev/random). It is possible to
+use /dev/urandom for testing, but this may provide less security in some
+cases. You may need to press keys on the keyboard or move the mouse
+(if directly attached to the system) or cause disk IO (if system is remote),
+in order to generate entropy for the key if there is not a hardware random
+number generator on the system.
+.HP
+Other Options:
+.TP
+.I "-v, --verbose"
+Increase verbosity for errors.
+.SH EXAMPLES
+Write a key for file system 'tank' for a client in the biology nodemap:
+.IP
+.nf
+[root@server ~]# lgss_sk -f tank -n biology -w tank.biology.key
+.fi
+.LP
+Add MGS NIDs to existing key:
+.IP
+.nf
+[root@server ~]# lgss_sk -g 192.168.1.101@tcp,10.10.0.101@o2ib \\
+-m tank.biology.key
+.fi
+.LP
+Show key attributes:
+.IP
+.nf
+[root@server ~]# lgss_sk -r tank.biology.key
+Version: 1
+HMAC alg: SHA256
+Crypt alg: AES-256-CTR
+Ctx Expiration: 2147483647 seconds
+Shared keylen: 256 bits
+Session keylen: 1024 bits
+File system: tank
+MGS NIDs: 192.168.1.101@tcp 10.10.0.101@o2ib
+Nodemap name: biology
+Shared key:
+ 0000: e486 65a8 b0d6 a8bc 17c4 8316 7f5a 701d ..e..........Zp.
+ 0010: 5d6a 7b42 ed35 49cf 5ae9 0638 b12d e3d6 ]j{B.5I.Z..8.-..
+.fi
+.br
+.SH "SEE ALSO"
+.BR nids (5)
/* Shared key */
enum sk_crypt_alg {
- SK_CRYPT_AES_CTR = 0,
- SK_CRYPT_MAX = 1,
+ SK_CRYPT_INVALID = -1,
+ SK_CRYPT_EMPTY = 0,
+ SK_CRYPT_AES256_CTR = 1,
+ SK_CRYPT_MAX = 2,
};
enum sk_hmac_alg {
- SK_HMAC_SHA256 = 0,
- SK_HMAC_SHA512 = 1,
- SK_HMAC_MAX = 2,
+ SK_HMAC_INVALID = -1,
+ SK_HMAC_EMPTY = 0,
+ SK_HMAC_SHA256 = 1,
+ SK_HMAC_SHA512 = 2,
+ SK_HMAC_MAX = 3,
};
struct sk_crypt_type {
};
static struct sk_crypt_type sk_crypt_types[] = {
- [SK_CRYPT_AES_CTR] = {
- .sct_name = "ctr(aes)",
+ [SK_CRYPT_AES256_CTR] = {
+ .sct_name = "ctr(aes256)",
.sct_bytes = 32,
},
};
*dst = *src;
}
-static void sptlrpc_import_sec_adapt_inplace(struct obd_import *imp,
- struct ptlrpc_sec *sec,
- struct sptlrpc_flavor *sf)
-{
- char str1[32], str2[32];
-
- if (sec->ps_flvr.sf_flags != sf->sf_flags)
- CDEBUG(D_SEC, "changing sec flags: %s -> %s\n",
- sptlrpc_secflags2str(sec->ps_flvr.sf_flags,
- str1, sizeof(str1)),
- sptlrpc_secflags2str(sf->sf_flags,
- str2, sizeof(str2)));
-
- spin_lock(&sec->ps_lock);
- flavor_copy(&sec->ps_flvr, sf);
- spin_unlock(&sec->ps_lock);
-}
-
/**
* To get an appropriate ptlrpc_sec for the \a imp, according to the current
* configuration. Upon called, imp->imp_sec may or may not be NULL.
obd_uuid2str(&conn->c_remote_uuid),
sptlrpc_flavor2name(&sec->ps_flvr, str, sizeof(str)),
sptlrpc_flavor2name(&sf, str2, sizeof(str2)));
-
- if (SPTLRPC_FLVR_POLICY(sf.sf_rpc) ==
- SPTLRPC_FLVR_POLICY(sec->ps_flvr.sf_rpc) &&
- SPTLRPC_FLVR_MECH(sf.sf_rpc) ==
- SPTLRPC_FLVR_MECH(sec->ps_flvr.sf_rpc)) {
- sptlrpc_import_sec_adapt_inplace(imp, sec, &sf);
- GOTO(out, rc);
- }
} else if (SPTLRPC_FLVR_BASE(sf.sf_rpc) !=
SPTLRPC_FLVR_BASE(SPTLRPC_FLVR_NULL)) {
CDEBUG(D_SEC, "import %s->%s netid %x: select flavor %s\n",
. /etc/init.d/functions
LOCKFILE="/var/lock/subsys/lsvcgssd"
-LSVCGSSDARGS="-k"
+# -k -- Enable kerberos support
+# -s -- Enable shared key support
+LSVCGSSDARGS="-k -s"
# Check for and source configuration file
[ -f /etc/sysconfig/lsvcgss ] && . /etc/sysconfig/lsvcgss
sbin_PROGRAMS = lsvcgssd l_idmap
if GSS_KEYRING
-sbin_PROGRAMS += lgss_keyring
+sbin_PROGRAMS += lgss_keyring lgss_sk
endif
if GSS_PIPEFS
context_heimdal.c \
context_spkm3.c \
gss_util.c \
+ sk_utils.c \
gss_oids.c \
err_util.c \
lsupport.c \
err_util.h \
gss_oids.h \
gss_util.h \
+ sk_utils.h \
lsupport.h
lgssd_SOURCES = \
svcgssd.h
lsvcgssd_CFLAGS = $(AM_CFLAGS) $(CFLAGS) $(KRBCFLAGS)
-lsvcgssd_LDADD = $(LIBCFS) $(GSSAPI_LIBS) $(KRBLIBS)
+lsvcgssd_LDADD = $(LIBCFS) $(GSSAPI_LIBS) $(KRBLIBS) -lcrypto -lssl -lkeyutils -lm
lsvcgssd_LDFLAGS = $(KRBLDFLAGS)
lsvcgssd_DEPENDENCIES = $(LIBCFS)
context_mit.c \
context_heimdal.c \
lgss_krb5_utils.c \
+ lgss_null_utils.c \
+ lgss_sk_utils.c \
lgss_utils.c \
lsupport.c \
- \
+ err_util.c \
+ sk_utils.c \
lgss_krb5_utils.h \
lgss_utils.h \
+ sk_utils.h \
+ err_util.h \
lsupport.h
lgss_keyring_CFLAGS = $(AM_CFLAGS) $(CFLAGS) $(KRBCFLAGS) -D _NEW_BUILD_
-lgss_keyring_LDADD = $(LIBCFS) -lkeyutils $(GSSAPI_LIBS) $(KRBLIBS)
+lgss_keyring_LDADD = $(LIBCFS) $(GSSAPI_LIBS) $(KRBLIBS) -lcrypto -lssl -lm -lkeyutils
lgss_keyring_LDFLAGS = $(KRBLDFLAGS)
lgss_keyring_DEPENDENCIES = $(LIBCFS)
+lgss_sk_SOURCES = \
+ lgss_sk.c \
+ err_util.c \
+ sk_utils.c \
+ sk_utils.h
+
+lgss_sk_CFLAGS = $(AM_CFLAGS) $(CFLAGS) $(KRBCFLAGS)
+lgss_sk_LDADD = $(LIBCFS) $(GSSAPI_LIBS) $(KRBLIBS) -lcrypto -lssl -lm -lkeyutils
+lgss_sk_LDFLAGS = $(KRBLDFLAGS)
+lgss_sk_DEPENDENCIES = $(LIBCFS)
+
EXTRA_DIST =
return rc;
}
+/* This is used by incomplete GSSAPI implementations that can't use
+ * gss_init_sec_context and will parse the token themselves (gssnull and sk).
+ * Callers should have cred->lc_mech_token pointing to a gss_buffer_desc
+ * token to send to the peer as part of the SEC_CTX_INIT operation. The return
+ * RPC's token with be in gr.gr_token which is validated using
+ * lgss_validate_cred. */
+static int lgssc_negotiation_manual(struct lgss_nego_data *lnd,
+ struct lgss_cred *cred)
+{
+ struct lgss_init_res gr;
+ OM_uint32 min_stat;
+ int rc;
+
+ logmsg(LL_TRACE, "starting gss negotation\n");
+ memset(&gr, 0, sizeof(gr));
+
+ lnd->lnd_rpc_err = do_nego_rpc(lnd, &cred->lc_mech_token, &gr);
+ if (lnd->lnd_rpc_err) {
+ logmsg(LL_ERR, "negotiation rpc error %d\n", lnd->lnd_rpc_err);
+ rc = lnd->lnd_rpc_err;
+ goto out_error;
+ }
+
+ if (gr.gr_major == GSS_S_CONTINUE_NEEDED) {
+ rc = -EAGAIN;
+ goto out_error;
+
+ } else if (gr.gr_major != GSS_S_COMPLETE) {
+ lnd->lnd_gss_err = gr.gr_major;
+ logmsg(LL_ERR, "negotiation gss error %x\n", lnd->lnd_gss_err);
+ rc = -ENOTCONN;
+ goto out_error;
+ }
+
+ if (gr.gr_ctx.length == 0 || gr.gr_token.length == 0) {
+ logmsg(LL_ERR, "zero length context or token received\n");
+ rc = -EINVAL;
+ goto out_error;
+ }
+
+ rc = lgss_validate_cred(cred, &gr.gr_token, &lnd->lnd_ctx_token);
+ if (rc) {
+ logmsg(LL_ERR, "peer token failed validation\n");
+ goto out_error;
+ }
+
+ lnd->lnd_established = 1;
+ lnd->lnd_seq_win = gr.gr_win;
+ lnd->lnd_rmt_ctx = gr.gr_ctx;
+
+ if (gr.gr_token.length != 0)
+ gss_release_buffer(&min_stat, &gr.gr_token);
+
+ logmsg(LL_DEBUG, "successfully negotiated a context\n");
+ return 0;
+
+out_error:
+ if (gr.gr_ctx.length != 0)
+ gss_release_buffer(&min_stat, &gr.gr_ctx);
+ if (gr.gr_token.length != 0)
+ gss_release_buffer(&min_stat, &gr.gr_token);
+
+ return rc;
+}
+
/*
* if return error, the lnd_rpc_err or lnd_gss_err is set.
*/
lnd->lnd_seq_win = 0;
switch (mech) {
- case LGSS_MECH_KRB5:
- lnd->lnd_mech = (gss_OID) &krb5oid;
- lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
- break;
- default:
- logmsg(LL_ERR, "invalid mech: %d\n", mech);
- lnd->lnd_rpc_err = -EACCES;
- return -1;
+ case LGSS_MECH_KRB5:
+ lnd->lnd_mech = (gss_OID)&krb5oid;
+ lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
+ break;
+ case LGSS_MECH_NULL:
+ lnd->lnd_mech = (gss_OID)&nulloid;
+ break;
+ case LGSS_MECH_SK:
+ lnd->lnd_mech = (gss_OID)&skoid;
+ lnd->lnd_req_flags = GSS_C_MUTUAL_FLAG;
+ break;
+ default:
+ logmsg(LL_ERR, "invalid mech: %d\n", mech);
+ lnd->lnd_rpc_err = -EACCES;
+ return -1;
}
sname.value = g_service;
return rc;
}
+static int lgssc_kr_negotiate_manual(key_serial_t keyid, struct lgss_cred *cred,
+ struct keyring_upcall_param *kup)
+{
+ struct lgss_nego_data lnd;
+ OM_uint32 min_stat;
+ int rc;
+
+retry:
+ memset(&lnd, 0, sizeof(lnd));
+
+ rc = lgss_get_service_str(&g_service, kup->kup_svc, kup->kup_nid);
+ if (rc) {
+ logmsg(LL_ERR, "key %08x: failed to construct service "
+ "string\n", keyid);
+ error_kernel_key(keyid, -EACCES, 0);
+ goto out_cred;
+ }
+
+ rc = lgss_using_cred(cred);
+ if (rc) {
+ logmsg(LL_ERR, "key %08x: can't use cred\n", keyid);
+ error_kernel_key(keyid, -EACCES, 0);
+ goto out_cred;
+ }
+
+ rc = lgssc_init_nego_data(&lnd, kup, cred->lc_mech->lmt_mech_n);
+ if (rc) {
+ logmsg(LL_ERR, "key %08x: failed to initialize "
+ "negotiation data\n", keyid);
+ error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+ goto out_cred;
+ }
+
+ /*
+ * Handles the negotiation but then calls lgss_validate to make sure
+ * the token is valid. It also populates the lnd_ctx_token for the
+ * update to the kernel key
+ */
+ rc = lgssc_negotiation_manual(&lnd, cred);
+ if (rc == -EAGAIN) {
+ logmsg(LL_ERR, "Failed negotiation must retry\n");
+ goto retry;
+
+ } else if (rc) {
+ logmsg(LL_ERR, "key %08x: failed to negotiate\n", keyid);
+ error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err);
+ goto out;
+ }
+
+ rc = update_kernel_key(keyid, &lnd, &lnd.lnd_ctx_token);
+ if (rc)
+ goto out;
+
+ logmsg(LL_INFO, "key %08x for user %u is updated OK!\n",
+ keyid, kup->kup_uid);
+out:
+ if (lnd.lnd_ctx_token.length != 0)
+ gss_release_buffer(&min_stat, &lnd.lnd_ctx_token);
+
+ lgssc_fini_nego_data(&lnd);
+
+out_cred:
+ lgss_release_cred(cred);
+ return rc;
+}
+
/*
* note we inherited assumed authority from parent process
*/
kup->kup_uid, kup->kup_gid, kup->kup_fsuid, kup->kup_fsgid);
switch (cred->lc_mech->lmt_mech_n) {
+ case LGSS_MECH_NULL:
+ case LGSS_MECH_SK:
+ rc = lgssc_kr_negotiate_manual(keyid, cred, kup);
+ break;
case LGSS_MECH_KRB5:
default:
rc = lgssc_kr_negotiate_krb(keyid, cred, kup);
#include "lgss_utils.h"
+extern struct lgss_mech_type lgss_mech_null;
+extern struct lgss_mech_type lgss_mech_sk;
extern struct lgss_mech_type lgss_mech_krb5;
/*
--- /dev/null
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (C) 2015, Trustees of Indiana University
+ *
+ * Author: Jeremy Filizetti <jfilizet@iu.edu>
+ */
+
+#include <string.h>
+#include <time.h>
+#include <libcfs/byteorder.h>
+#include "lgss_utils.h"
+
+static int lgss_null_prepare_cred(struct lgss_cred *cred)
+{
+ uint64_t tmp;
+
+ cred->lc_mech_token.value = malloc(sizeof(uint64_t));
+ if (!cred->lc_mech_token.value)
+ return -1;
+ cred->lc_mech_token.length = sizeof(uint64_t);
+
+ /* random token so it's not cached by the other side */
+ tmp = random();
+ tmp <<= 32;
+
+ /* Sec part flags needed on the other end */
+ tmp |= cred->lc_root_flags;
+
+ /* big-endian for the wire */
+ tmp = cpu_to_be64(tmp);
+ memcpy(cred->lc_mech_token.value, &tmp, cred->lc_mech_token.length);
+
+ return 0;
+}
+
+static void lgss_null_release_cred(struct lgss_cred *cred)
+{
+ free(cred->lc_mech_token.value);
+}
+
+static int lgss_null_validate_cred(struct lgss_cred *cred,
+ gss_buffer_desc *token,
+ gss_buffer_desc *ctx_token)
+{
+ if (token->length <= 0)
+ return -1;
+
+ ctx_token->length = token->length;
+ ctx_token->value = malloc(ctx_token->length);
+ memcpy(ctx_token->value, token->value, ctx_token->length);
+
+ return 0;
+}
+struct lgss_mech_type lgss_mech_null = {
+ .lmt_name = "gssnull",
+ .lmt_mech_n = LGSS_MECH_NULL,
+ .lmt_prepare_cred = lgss_null_prepare_cred,
+ .lmt_release_cred = lgss_null_release_cred,
+ .lmt_validate_cred = lgss_null_validate_cred,
+};
--- /dev/null
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (C) 2015, Trustees of Indiana University
+ *
+ * Author: Jeremy Filizetti <jfilizet@iu.edu>
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <lnet/nidstr.h>
+#include <lustre/lustre_idl.h>
+
+#include "sk_utils.h"
+#include "err_util.h"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+/* One week default expiration */
+#define SK_DEFAULT_EXPIRE 604800
+#define SK_DEFAULT_SK_KEYLEN 256
+#define SK_DEFAULT_DH_KEYLEN 1024
+#define SK_DEFAULT_NODEMAP "default"
+
+/* Names match up with openssl enc and dgst commands */
+char *sk_crypt2name[] = {
+ [SK_CRYPT_EMPTY] = "NONE",
+ [SK_CRYPT_AES256_CTR] = "AES-256-CTR",
+};
+
+char *sk_hmac2name[] = {
+ [SK_HMAC_EMPTY] = "NONE",
+ [SK_HMAC_SHA256] = "SHA256",
+ [SK_HMAC_SHA512] = "SHA512",
+};
+
+int sk_name2crypt(char *name)
+{
+ int i;
+
+ for (i = 0; i < SK_CRYPT_MAX; i++) {
+ if (strcasecmp(name, sk_crypt2name[i]) == 0)
+ return i;
+ }
+
+ return SK_CRYPT_INVALID;
+}
+
+int sk_name2hmac(char *name)
+{
+ int i;
+
+ for (i = 0; i < SK_HMAC_MAX; i++) {
+ if (strcasecmp(name, sk_hmac2name[i]) == 0)
+ return i;
+ }
+
+ return SK_HMAC_INVALID;
+}
+
+void usage(FILE *fp, char *program)
+{
+ int i;
+
+ fprintf(fp, "Usage %s [OPTIONS] -l <file> | -m <file> | "
+ "-r <file> | -w <file>\n", program);
+ fprintf(fp, "-l|--load <file> Load key from file into user's "
+ "session keyring\n");
+ fprintf(fp, "-m|--modify <file> Modify a file's key "
+ "attributes\n");
+ fprintf(fp, "-r|--read <file> Show file's key attributes\n");
+ fprintf(fp, "-w|--write <file> Generate key file\n\n");
+ fprintf(fp, "Load Options:\n");
+ fprintf(fp, "-t|--type <type> Key type (mgs, server, "
+ "client)\n\n");
+ fprintf(fp, "Modify/Write Options:\n");
+ fprintf(fp, "-c|--crypt <num> Cipher for encryption "
+ "(Default: AES Counter mode)\n");
+ for (i = 0; i < SK_CRYPT_MAX; i++)
+ fprintf(fp, " %s\n", sk_crypt2name[i]);
+
+ fprintf(fp, "-h|--hmac <num> Hash alg for HMAC "
+ "(Default: SHA256)\n");
+ for (i = 0; i < SK_HMAC_MAX; i++)
+ fprintf(fp, " %s\n", sk_hmac2name[i]);
+
+ fprintf(fp, "-e|--expire <num> Seconds before contexts from "
+ "key expire (Default: %d seconds)\n", SK_DEFAULT_EXPIRE);
+ fprintf(fp, "-f|--fsname <name> File system name for key\n");
+ fprintf(fp, "-g|--mgsnids <nids> Comma seperated list of MGS "
+ "NIDs. Only required when mgssec is used (Default: "
+ "\"\")\n");
+ fprintf(fp, "-n|--nodemap <name> Nodemap name for key "
+ "(Default: \"%s\")\n", SK_DEFAULT_NODEMAP);
+ fprintf(fp, "-s|--session <len> DHKE Public key length in bits "
+ "(Default: %d)\n", SK_DEFAULT_DH_KEYLEN);
+ fprintf(fp, "-k|--shared <len> Shared key length in bits "
+ "(Default: %d)\n", SK_DEFAULT_SK_KEYLEN);
+ fprintf(fp, "-d|--data <file> Shared key data source "
+ "(Default: /dev/random)\n\n");
+ fprintf(fp, "Other Options:\n");
+ fprintf(fp, "-v|--verbose Increase verbosity for "
+ "errors\n");
+ exit(EXIT_FAILURE);
+}
+
+ssize_t get_key_data(char *src, void *buffer, size_t bits)
+{
+ char *ptr = buffer;
+ size_t remain;
+ ssize_t rc;
+ int fd;
+
+ /* convert bits to minimum number of bytes */
+ remain = (bits + 7) / 8;
+
+ fd = open(src, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open %s: %s\n", src,
+ strerror(errno));
+ return -errno;
+ }
+
+ while (remain > 0) {
+ rc = read(fd, ptr, remain);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "Error reading from %s: %s\n", src,
+ strerror(errno));
+ rc = -errno;
+ goto out;
+
+ } else if (rc == 0) {
+ fprintf(stderr, "Key source too short for key size\n");
+ rc = -ENODATA;
+ goto out;
+ }
+ ptr += rc;
+ remain -= rc;
+ }
+ rc = 0;
+
+out:
+ close(fd);
+ return rc;
+}
+
+int write_config_file(char *output_file, struct sk_keyfile_config *config,
+ bool overwrite)
+{
+ size_t rc;
+ int fd;
+ int flags = O_WRONLY | O_CREAT;
+
+ if (!overwrite)
+ flags |= O_EXCL;
+
+ sk_config_cpu_to_disk(config);
+
+ fd = open(output_file, flags, 0400);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open %s: %s\n", output_file,
+ strerror(errno));
+ return -errno;
+ }
+
+ rc = write(fd, config, sizeof(*config));
+ if (rc < 0) {
+ fprintf(stderr, "Error writing to %s: %s\n", output_file,
+ strerror(errno));
+ rc = -errno;
+
+ } else if (rc != sizeof(*config)) {
+ fprintf(stderr, "Short write to %s\n", output_file);
+ rc = -ENOSPC;
+
+ } else {
+ rc = 0;
+ }
+
+ close(fd);
+ return rc;
+}
+
+int print_config(char *filename)
+{
+ struct sk_keyfile_config *config;
+ int i;
+
+ config = sk_read_file(filename);
+ if (!config)
+ return EXIT_FAILURE;
+
+ if (sk_validate_config(config)) {
+ fprintf(stderr, "Key configuration failed validation\n");
+ free(config);
+ return EXIT_FAILURE;
+ }
+
+ printf("Version: %u\n", config->skc_version);
+ printf("HMAC alg: %s\n", sk_hmac2name[config->skc_hmac_alg]);
+ printf("Crypt alg: %s\n", sk_crypt2name[config->skc_crypt_alg]);
+ printf("Ctx Expiration: %u seconds\n", config->skc_expire);
+ printf("Shared keylen: %u bits\n", config->skc_shared_keylen);
+ printf("Session keylen: %u bits\n", config->skc_session_keylen);
+ printf("File system: %s\n", config->skc_fsname);
+ printf("MGS NIDs: ");
+ for (i = 0; i < MAX_MGSNIDS; i++) {
+ if (config->skc_mgsnids[i] == LNET_NID_ANY)
+ continue;
+ printf("%s ", libcfs_nid2str(config->skc_mgsnids[i]));
+ }
+ printf("\n");
+ printf("Nodemap name: %s\n", config->skc_nodemap);
+ printf("Shared key:\n");
+ print_hex(0, config->skc_shared_key, config->skc_shared_keylen / 8);
+
+ free(config);
+ return EXIT_SUCCESS;
+}
+
+int parse_mgsnids(char *mgsnids, struct sk_keyfile_config *config)
+{
+ lnet_nid_t nid;
+ char *ptr;
+ char *sep;
+ char *end;
+ int rc = 0;
+ int i;
+
+ /* replace all old values */
+ for (i = 0; i < MAX_MGSNIDS; i++)
+ config->skc_mgsnids[i] = LNET_NID_ANY;
+
+ i = 0;
+ end = mgsnids + strlen(mgsnids);
+ ptr = mgsnids;
+ while (ptr < end && i < MAX_MGSNIDS) {
+ sep = strstr(ptr, ",");
+ if (sep != NULL)
+ *sep = '\0';
+
+ nid = libcfs_str2nid(ptr);
+ if (nid == LNET_NID_ANY) {
+ fprintf(stderr, "Invalid MGS NID: %s\n", ptr);
+ rc = -EINVAL;
+ break;
+ }
+
+ config->skc_mgsnids[i++] = nid;
+ ptr += strlen(ptr) + 1;
+ }
+
+ if (i == MAX_MGSNIDS) {
+ fprintf(stderr, "Too many MGS NIDs provided\n");
+ rc = -E2BIG;
+ }
+
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ struct sk_keyfile_config *config;
+ char *data = NULL;
+ char *input = NULL;
+ char *load = NULL;
+ char *modify = NULL;
+ char *output = NULL;
+ char *mgsnids = NULL;
+ char *nodemap = NULL;
+ char *fsname = NULL;
+ int type = SK_TYPE_INVALID;
+ int crypt = SK_CRYPT_EMPTY;
+ int hmac = SK_HMAC_EMPTY;
+ int expire = -1;
+ int shared_keylen = -1;
+ int session_keylen = -1;
+ int verbose = 0;
+ int i;
+ int opt;
+
+ static struct option long_opt[] = {
+ {"crypt", 1, 0, 'c'},
+ {"data", 1, 0, 'd'},
+ {"expire", 1, 0, 'e'},
+ {"fsname", 1, 0, 'f'},
+ {"mgsnids", 1, 0, 'g'},
+ {"hmac", 1, 0, 'h'},
+ {"load", 1, 0, 'l'},
+ {"modify", 1, 0, 'm'},
+ {"nodemap", 1, 0, 'n'},
+ {"read", 1, 0, 'r'},
+ {"session", 1, 0, 's'},
+ {"shared", 1, 0, 'k'},
+ {"type", 1, 0, 't'},
+ {"write", 1, 0, 'w'},
+ {"verbose", 0, 0, 'v'},
+ {"help", 0, 0, 'p'},
+ {0, 0, 0, 0},
+ };
+
+ while ((opt = getopt_long(argc, argv, "c:d:e:f:g:h:l:m:n:pr:s:k:t:w:v",
+ long_opt, NULL)) != EOF) {
+ switch (opt) {
+ case 'c':
+ crypt = sk_name2crypt(optarg);
+ break;
+ case 'd':
+ data = optarg;
+ break;
+ case 'e':
+ expire = atoi(optarg);
+ if (expire < 60)
+ fprintf(stderr, "WARNING: Using a short key "
+ "expiration may cause issues during "
+ "key renegotiation\n");
+ break;
+ case 'f':
+ fsname = optarg;
+ if (strlen(fsname) > MTI_NAME_MAXLEN) {
+ fprintf(stderr, "File system name too long\n");
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'g':
+ mgsnids = optarg;
+ break;
+ case 'h':
+ hmac = sk_name2hmac(optarg);
+ break;
+ case 'l':
+ load = optarg;
+ break;
+ case 'n':
+ nodemap = optarg;
+ if (strlen(nodemap) > LUSTRE_NODEMAP_NAME_LENGTH) {
+ fprintf(stderr, "Nodemap name too long\n");
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'm':
+ modify = optarg;
+ break;
+ case 'p':
+ usage(stdout, argv[0]);
+ break;
+ case 'r':
+ input = optarg;
+ break;
+ case 's':
+ session_keylen = atoi(optarg);
+ break;
+ case 'k':
+ shared_keylen = atoi(optarg);
+ break;
+ case 't':
+ if (!strcasecmp(optarg, "server")) {
+ type = SK_TYPE_SERVER;
+ } else if (!strcasecmp(optarg, "mgs")) {
+ type = SK_TYPE_MGS;
+ } else if (!strcasecmp(optarg, "client")) {
+ type = SK_TYPE_CLIENT;
+ } else {
+ fprintf(stderr, "type must be mgs, server, or "
+ "client\n");
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'w':
+ output = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ fprintf(stderr, "Unknown option: %c\n", opt);
+ return EXIT_FAILURE;
+ break;
+ }
+ }
+
+ if (optind != argc) {
+ fprintf(stderr, "Extraneous arguments provided, check usage\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!input && !output && !load && !modify) {
+ usage(stderr, argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ /* init gss logger for foreground (no syslog) which prints to stderr */
+ initerr(NULL, verbose, 1);
+
+ if (input)
+ return print_config(input);
+
+ if (load) {
+ if (type == SK_TYPE_INVALID) {
+ fprintf(stderr, "type must be specified when loading "
+ "a key\n");
+ return EXIT_FAILURE;
+ }
+
+ if (sk_load_keyfile(load, type))
+ return EXIT_FAILURE;
+ return EXIT_SUCCESS;
+ }
+
+ if (crypt == SK_CRYPT_INVALID) {
+ fprintf(stderr, "Invalid crypt algorithm specified\n");
+ return EXIT_FAILURE;
+ }
+ if (hmac == SK_HMAC_INVALID) {
+ fprintf(stderr, "Invalid HMAC algorithm specified\n");
+ return EXIT_FAILURE;
+ }
+
+ if (modify) {
+ config = sk_read_file(modify);
+ if (!config)
+ return EXIT_FAILURE;
+
+ if (crypt != SK_CRYPT_EMPTY)
+ config->skc_crypt_alg = crypt;
+ if (hmac != SK_HMAC_EMPTY)
+ config->skc_hmac_alg = hmac;
+ if (expire != -1)
+ config->skc_expire = expire;
+ if (shared_keylen != -1)
+ config->skc_shared_keylen = shared_keylen;
+ if (session_keylen != -1)
+ config->skc_session_keylen = session_keylen;
+ if (fsname)
+ strncpy(config->skc_fsname, fsname, strlen(fsname));
+ if (nodemap)
+ strncpy(config->skc_nodemap, nodemap, strlen(nodemap));
+ if (mgsnids && parse_mgsnids(mgsnids, config))
+ goto error;
+ if (data && get_key_data(data, config->skc_shared_key,
+ config->skc_shared_keylen)) {
+ fprintf(stderr, "Failure getting data for key\n");
+ goto error;
+ }
+
+ if (sk_validate_config(config)) {
+ fprintf(stderr, "Key configuration failed "
+ "validation\n");
+ goto error;
+ }
+
+ if (write_config_file(modify, config, true))
+ goto error;
+
+ return EXIT_SUCCESS;
+ }
+
+ /* write mode for a new key */
+ if (!fsname && !mgsnids) {
+ fprintf(stderr, "Must provide --fsname, "
+ "--mgsnids, or both\n");
+ return EXIT_FAILURE;
+ }
+
+ config = malloc(sizeof(*config));
+ if (!config)
+ return EXIT_FAILURE;
+
+ /* Set the defaults */
+ memset(config, 0, sizeof(*config));
+ config->skc_version = SK_CONF_VERSION;
+ config->skc_expire = SK_DEFAULT_EXPIRE;
+ config->skc_shared_keylen = SK_DEFAULT_SK_KEYLEN;
+ config->skc_session_keylen = SK_DEFAULT_DH_KEYLEN;
+ config->skc_crypt_alg = SK_CRYPT_AES256_CTR;
+ config->skc_hmac_alg = SK_HMAC_SHA256;
+ for (i = 0; i < MAX_MGSNIDS; i++)
+ config->skc_mgsnids[i] = LNET_NID_ANY;
+
+ if (crypt != SK_CRYPT_EMPTY)
+ config->skc_crypt_alg = crypt;
+ if (hmac != SK_HMAC_EMPTY)
+ config->skc_hmac_alg = hmac;
+ if (expire != -1)
+ config->skc_expire = expire;
+ if (shared_keylen != -1)
+ config->skc_shared_keylen = shared_keylen;
+ if (session_keylen != -1)
+ config->skc_session_keylen = session_keylen;
+ if (fsname)
+ strncpy(config->skc_fsname, fsname, strlen(fsname));
+ if (nodemap)
+ strncpy(config->skc_nodemap, nodemap, strlen(nodemap));
+ else
+ strncpy(config->skc_nodemap, SK_DEFAULT_NODEMAP,
+ strlen(SK_DEFAULT_NODEMAP));
+
+ if (mgsnids && parse_mgsnids(mgsnids, config))
+ goto error;
+ if (!data)
+ data = "/dev/random";
+ if (get_key_data(data, config->skc_shared_key,
+ config->skc_shared_keylen)) {
+ fprintf(stderr, "Failure getting data for key\n");
+ goto error;
+ }
+
+ if (sk_validate_config(config)) {
+ fprintf(stderr, "Key configuration failed validation\n");
+ goto error;
+ }
+
+ if (write_config_file(output, config, false))
+ goto error;
+
+ return EXIT_SUCCESS;
+
+error:
+ if (config)
+ free(config);
+ return EXIT_FAILURE;
+}
--- /dev/null
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (C) 2015, Trustees of Indiana University
+ *
+ * Author: Jeremy Filizetti <jfilizet@iu.edu>
+ */
+
+#include <limits.h>
+#include <string.h>
+#include <openssl/dh.h>
+#include <openssl/engine.h>
+#include <openssl/err.h>
+
+#include "sk_utils.h"
+#include "lgss_utils.h"
+
+/**
+ * Create the initial shared key credentials
+ */
+static int lgss_sk_prepare_cred(struct lgss_cred *cred)
+{
+ uint32_t flags = cred->lc_root_flags;
+
+ switch (cred->lc_svc_type) {
+ case 'n':
+ flags |= LGSS_SVC_NULL;
+ break;
+ case 'a':
+ flags |= LGSS_SVC_AUTH;
+ break;
+ case 'i':
+ flags |= LGSS_SVC_INTG;
+ break;
+ case 'p':
+ flags |= LGSS_SVC_PRIV;
+ break;
+ default:
+ break;
+ }
+
+ cred->lc_mech_cred = sk_create_cred(cred->lc_tgt_uuid, NULL, flags);
+ if (cred->lc_mech_cred == NULL) {
+ printerr(0, "sk: cannot create credential: %s\n",
+ cred->lc_tgt_uuid);
+ return -ENOKEY;
+ }
+
+ return 0;
+}
+
+/* Free all the sk_cred resources */
+static void lgss_sk_release_cred(struct lgss_cred *cred)
+{
+ struct sk_cred *skc = cred->lc_mech_cred;
+
+ sk_free_cred(skc);
+ cred->lc_mech_cred = NULL;
+ free(cred->lc_mech_token.value);
+ return;
+}
+
+/**
+ * Session key parameter generation is deferred until here because if privacy
+ * mode is enabled the session key parameter generation can take a while
+ * depending on the key size used and prepare is called before returning
+ * from the request_key upcall by lgss_keyring
+ */
+static int lgss_sk_using_cred(struct lgss_cred *cred)
+{
+ struct sk_cred *skc = cred->lc_mech_cred;
+ gss_buffer_desc bufs[7];
+ uint32_t flags;
+ int numbufs = 7;
+ int rc;
+
+ rc = sk_gen_params(skc, true);
+ if (rc)
+ return rc;
+
+ /* HMAC is generated in this order */
+ bufs[0] = skc->sc_kctx.skc_iv;
+ bufs[1] = skc->sc_p;
+ bufs[2] = skc->sc_pub_key;
+ bufs[3] = skc->sc_tgt;
+ bufs[4] = skc->sc_nodemap_hash;
+
+ /* big endian flags for the wire */
+ flags = cpu_to_be32(skc->sc_flags);
+ bufs[5].value = &flags;
+ bufs[5].length = sizeof(flags);
+
+ /* sign all the bufs except HMAC */
+ rc = sk_sign_bufs(&skc->sc_kctx.skc_shared_key, bufs, numbufs - 1,
+ EVP_sha256(), &skc->sc_hmac);
+ if (rc)
+ return rc;
+
+ bufs[6] = skc->sc_hmac;
+ rc = sk_encode_netstring(bufs, numbufs, &cred->lc_mech_token);
+ if (rc)
+ return rc;
+
+ printerr(2, "Created netstring of %zd bytes\n",
+ cred->lc_mech_token.length);
+
+ return 0;
+}
+
+static int lgss_sk_validate_cred(struct lgss_cred *cred, gss_buffer_desc *token,
+ gss_buffer_desc *ctx_token)
+{
+ struct sk_cred *skc = cred->lc_mech_cred;
+ gss_buffer_desc bufs[2];
+ int numbufs = 2;
+ int i;
+ uint32_t rc;
+
+ i = sk_decode_netstring(bufs, numbufs, token);
+ if (i < numbufs) {
+ printerr(0, "Failed to decode netstring\n");
+ return -1;
+ }
+
+ /* decoded buffers from server should be:
+ * bufs[0] = sc_pub_key
+ * bufs[1] = sc_hmac */
+ rc = sk_verify_hmac(skc, bufs, numbufs - 1, EVP_sha256(), &bufs[1]);
+ if (rc != GSS_S_COMPLETE) {
+ printerr(0, "Invalid HMAC receieved: 0x%x\n", rc);
+ return -1;
+ }
+
+ rc = sk_compute_key(skc, &bufs[0]);
+ if (rc == GSS_S_DEFECTIVE_TOKEN) {
+ /* Defective token for short key means we need to retry
+ * because there is a chance that the parameters generated
+ * resulted in a key that is 1 byte short */
+ printerr(0, "Short key computed, must retry\n");
+ return -EAGAIN;
+ } else if (rc != GSS_S_COMPLETE) {
+ printerr(0, "Failed to compute session key: 0x%x\n", rc);
+ return -1;
+ }
+
+ rc = sk_kdf(skc, cred->lc_self_nid, &cred->lc_mech_token);
+ if (rc) {
+ printerr(0, "Failed to calulate derived key\n");
+ return -1;
+ }
+
+ if (sk_serialize_kctx(skc, ctx_token)) {
+ printerr(0, "Failed to serialize context for kernel\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+struct lgss_mech_type lgss_mech_sk = {
+ .lmt_name = "sk",
+ .lmt_mech_n = LGSS_MECH_SK,
+ .lmt_prepare_cred = lgss_sk_prepare_cred,
+ .lmt_release_cred = lgss_sk_release_cred,
+ .lmt_using_cred = lgss_sk_using_cred,
+ .lmt_validate_cred = lgss_sk_validate_cred,
+};
key_t sem_key;
int sem_id;
} lgss_mutexes[LGSS_MUTEX_MAX] = {
- [LGSS_MUTEX_KRB5] = { "keyring", 0x4292d473, 0 },
+ [LGSS_MUTEX_KRB5] = { "keyring", 0x4292d473, 0 },
};
static int lgss_mutex_get(struct lgss_mutex_s *mutex)
struct lgss_mech_type *lgss_name2mech(const char *mech_name)
{
- if (strcmp(mech_name, "krb5") == 0)
- return &lgss_mech_krb5;
- return NULL;
+ if (strcmp(mech_name, "krb5") == 0)
+ return &lgss_mech_krb5;
+ if (strcmp(mech_name, "gssnull") == 0)
+ return &lgss_mech_null;
+ if (strcmp(mech_name, "sk") == 0)
+ return &lgss_mech_sk;
+ return NULL;
}
int lgss_mech_initialize(struct lgss_mech_type *mech)
gss_buffer_desc *ctx_token);
};
-enum {
- LGSS_ROOT_CRED_ROOT = 0x01,
- LGSS_ROOT_CRED_MDT = 0x02,
- LGSS_ROOT_CRED_OST = 0x04,
-
- LGSS_ROOT_CRED_NR = 3
-};
-
struct lgss_cred {
int lc_uid;
unsigned int lc_root_flags;
LGSS_MECH_SK = 2,
};
+enum {
+ /* sec part flags */
+ LGSS_ROOT_CRED_ROOT = 0x01,
+ LGSS_ROOT_CRED_MDT = 0x02,
+ LGSS_ROOT_CRED_OST = 0x04,
+ /* service type flags */
+ LGSS_SVC_NULL = 0x10,
+ LGSS_SVC_AUTH = 0x20,
+ LGSS_SVC_INTG = 0x40,
+ LGSS_SVC_PRIV = 0x80,
+ /* Number of sec part flags */
+ LGSS_ROOT_CRED_NR = 3,
+};
+
struct lgssd_upcall_data {
uint32_t seq;
uint32_t uid;
char obd[64];
};
-#define GSSD_INTERFACE_VERSION (1)
+#define GSSD_INTERFACE_VERSION GSSD_INTERFACE_VERSION_V2
+#define GSSD_INTERFACE_VERSION_V2 (2)
+#define GSSD_INTERFACE_VERSION_V1 (1)
struct lgssd_ioctl_param {
int version; /* in */
--- /dev/null
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (C) 2015, Trustees of Indiana University
+ *
+ * Author: Jeremy Filizetti <jfilizet@iu.edu>
+ */
+
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <string.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <openssl/dh.h>
+#include <openssl/engine.h>
+#include <openssl/err.h>
+#include <openssl/hmac.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <lnet/nidstr.h>
+
+#include "sk_utils.h"
+#include "write_bytes.h"
+
+static struct sk_crypt_type sk_crypt_types[] = {
+ [SK_CRYPT_AES256_CTR] = {
+ .sct_name = "ctr(aes)",
+ .sct_bytes = 32,
+ },
+};
+
+static struct sk_hmac_type sk_hmac_types[] = {
+ [SK_HMAC_SHA256] = {
+ .sht_name = "hmac(sha256)",
+ .sht_bytes = 32,
+ },
+ [SK_HMAC_SHA512] = {
+ .sht_name = "hmac(sha512)",
+ .sht_bytes = 64,
+ },
+};
+
+#ifdef _NEW_BUILD_
+# include "lgss_utils.h"
+#else
+# include "gss_util.h"
+# include "gss_oids.h"
+# include "err_util.h"
+#endif
+
+#ifdef _ERR_UTIL_H_
+/**
+ * Initializes logging
+ * \param[in] program Program name to output
+ * \param[in] verbose Verbose flag
+ * \param[in] fg Whether or not to run in foreground
+ *
+ */
+void sk_init_logging(char *program, int verbose, int fg)
+{
+ initerr(program, verbose, fg);
+}
+#endif
+
+/**
+ * Loads the key from \a filename and returns the struct sk_keyfile_config.
+ * It should be freed by the caller.
+ *
+ * \param[in] filename Disk or key payload data
+ *
+ * \return sk_keyfile_config sucess
+ * \return NULL failure
+ */
+struct sk_keyfile_config *sk_read_file(char *filename)
+{
+ struct sk_keyfile_config *config;
+ char *ptr;
+ size_t rc;
+ size_t remain;
+ int fd;
+
+ config = malloc(sizeof(*config));
+ if (!config) {
+ printerr(0, "Failed to allocate memory for config\n");
+ return NULL;
+ }
+
+ /* allow standard input override */
+ if (strcmp(filename, "-") == 0)
+ fd = dup(STDIN_FILENO);
+ else
+ fd = open(filename, O_RDONLY);
+
+ if (fd == -1) {
+ printerr(0, "Error opening file %s: %s\n", filename,
+ strerror(errno));
+ goto out_free;
+ }
+
+ ptr = (char *)config;
+ remain = sizeof(*config);
+ while (remain > 0) {
+ rc = read(fd, ptr, remain);
+ if (rc == -1) {
+ if (errno == EINTR)
+ continue;
+ printerr(0, "read() failed on %s: %s\n", filename,
+ strerror(errno));
+ goto out_close;
+ } else if (rc == 0) {
+ printerr(0, "File %s does not have a complete key\n",
+ filename);
+ goto out_close;
+ }
+ ptr += rc;
+ remain -= rc;
+ }
+
+ close(fd);
+ sk_config_disk_to_cpu(config);
+ return config;
+
+out_close:
+ close(fd);
+out_free:
+ free(config);
+ return NULL;
+}
+
+/**
+ * Checks if a key matching \a description is found in the keyring for
+ * logging purposes and then attempts to load \a payload of \a psize into a key
+ * with \a description.
+ *
+ * \param[in] payload Key payload
+ * \param[in] psize Payload size
+ * \param[in] description Description used for key in keyring
+ *
+ * \return 0 sucess
+ * \return -1 failure
+ */
+static key_serial_t sk_load_key(const struct sk_keyfile_config *skc,
+ const char *description)
+{
+ struct sk_keyfile_config payload;
+ key_serial_t key;
+
+ memcpy(&payload, skc, sizeof(*skc));
+
+ /* In the keyring use the disk layout so keyctl pipe can be used */
+ sk_config_cpu_to_disk(&payload);
+
+ /* Check to see if a key is already loaded matching description */
+ key = keyctl_search(KEY_SPEC_USER_KEYRING, "user", description, 0);
+ if (key != -1)
+ printerr(2, "Key %d found in session keyring, replacing\n",
+ key);
+
+ key = add_key("user", description, &payload, sizeof(payload),
+ KEY_SPEC_USER_KEYRING);
+ if (key != -1)
+ printerr(2, "Added key %d with description %s\n", key,
+ description);
+ else
+ printerr(0, "Failed to add key with %s\n", description);
+
+ return key;
+}
+
+/**
+ * Reads the key from \a path, verifies it and loads into the session keyring
+ * using a description determined by the the \a type. Existing keys with the
+ * same description are replaced.
+ *
+ * \param[in] path Path to key file
+ * \param[in] type Type of key to load which determines the description
+ *
+ * \return 0 sucess
+ * \return -1 failure
+ */
+int sk_load_keyfile(char *path, int type)
+{
+ struct sk_keyfile_config *config;
+ char description[SK_DESCRIPTION_SIZE + 1];
+ struct stat buf;
+ int i;
+ int rc;
+ int rc2 = -1;
+
+ rc = stat(path, &buf);
+ if (rc == -1) {
+ printerr(0, "stat() failed for file %s: %s\n", path,
+ strerror(errno));
+ return rc2;
+ }
+
+ config = sk_read_file(path);
+ if (!config)
+ return rc2;
+
+ /* Similar to ssh, require adequate care of key files */
+ if (buf.st_mode & (S_IRGRP | S_IWGRP | S_IWOTH | S_IXOTH)) {
+ printerr(0, "Shared key files must be read/writeable only by "
+ "owner\n");
+ return -1;
+ }
+
+ if (sk_validate_config(config))
+ goto out;
+
+ /* The server side can have multiple key files per file system so
+ * the nodemap name is appended to the key description to uniquely
+ * identify it */
+ if (type & SK_TYPE_MGS) {
+ /* Any key can be an MGS key as long as we are told to use it */
+ rc = snprintf(description, SK_DESCRIPTION_SIZE, "lustre:MGS:%s",
+ config->skc_nodemap);
+ if (rc >= SK_DESCRIPTION_SIZE)
+ goto out;
+ if (sk_load_key(config, description) == -1)
+ goto out;
+ }
+ if (type & SK_TYPE_SERVER) {
+ /* Server keys need to have the file system name in the key */
+ if (!config->skc_fsname) {
+ printerr(0, "Key configuration has no file system "
+ "attribute. Can't load as server type\n");
+ goto out;
+ }
+ rc = snprintf(description, SK_DESCRIPTION_SIZE, "lustre:%s:%s",
+ config->skc_fsname, config->skc_nodemap);
+ if (rc >= SK_DESCRIPTION_SIZE)
+ goto out;
+ if (sk_load_key(config, description) == -1)
+ goto out;
+ }
+ if (type & SK_TYPE_CLIENT) {
+ /* Load client file system key */
+ if (config->skc_fsname) {
+ rc = snprintf(description, SK_DESCRIPTION_SIZE,
+ "lustre:%s", config->skc_fsname);
+ if (rc >= SK_DESCRIPTION_SIZE)
+ goto out;
+ if (sk_load_key(config, description) == -1)
+ goto out;
+ }
+
+ /* Load client MGC keys */
+ for (i = 0; i < MAX_MGSNIDS; i++) {
+ if (config->skc_mgsnids[i] == LNET_NID_ANY)
+ continue;
+ rc = snprintf(description, SK_DESCRIPTION_SIZE,
+ "lustre:MGC%s",
+ libcfs_nid2str(config->skc_mgsnids[i]));
+ if (rc >= SK_DESCRIPTION_SIZE)
+ goto out;
+ if (sk_load_key(config, description) == -1)
+ goto out;
+ }
+ }
+
+ rc2 = 0;
+
+out:
+ free(config);
+ return rc2;
+}
+
+/**
+ * Byte swaps config from cpu format to disk
+ *
+ * \param[in,out] config sk_keyfile_config to swap
+ */
+void sk_config_cpu_to_disk(struct sk_keyfile_config *config)
+{
+ int i;
+
+ if (!config)
+ return;
+
+ config->skc_version = be32_to_cpu(config->skc_version);
+ config->skc_hmac_alg = be16_to_cpu(config->skc_hmac_alg);
+ config->skc_crypt_alg = be16_to_cpu(config->skc_crypt_alg);
+ config->skc_expire = be32_to_cpu(config->skc_expire);
+ config->skc_shared_keylen = be32_to_cpu(config->skc_shared_keylen);
+ config->skc_session_keylen = be32_to_cpu(config->skc_session_keylen);
+
+ for (i = 0; i < MAX_MGSNIDS; i++)
+ config->skc_mgsnids[i] = be64_to_cpu(config->skc_mgsnids[i]);
+
+ return;
+}
+
+/**
+ * Byte swaps config from disk format to cpu
+ *
+ * \param[in,out] config sk_keyfile_config to swap
+ */
+void sk_config_disk_to_cpu(struct sk_keyfile_config *config)
+{
+ int i;
+
+ if (!config)
+ return;
+
+ config->skc_version = cpu_to_be32(config->skc_version);
+ config->skc_hmac_alg = cpu_to_be16(config->skc_hmac_alg);
+ config->skc_crypt_alg = cpu_to_be16(config->skc_crypt_alg);
+ config->skc_expire = cpu_to_be32(config->skc_expire);
+ config->skc_shared_keylen = cpu_to_be32(config->skc_shared_keylen);
+ config->skc_session_keylen = cpu_to_be32(config->skc_session_keylen);
+
+ for (i = 0; i < MAX_MGSNIDS; i++)
+ config->skc_mgsnids[i] = cpu_to_be64(config->skc_mgsnids[i]);
+
+ return;
+}
+
+/**
+ * Verifies the on key payload format is valid
+ *
+ * \param[in] config sk_keyfile_config
+ *
+ * \return -1 failure
+ * \return 0 success
+ */
+int sk_validate_config(const struct sk_keyfile_config *config)
+{
+ int i;
+
+ if (!config) {
+ printerr(0, "Null configuration passed\n");
+ return -1;
+ }
+ if (config->skc_version != SK_CONF_VERSION) {
+ printerr(0, "Invalid version\n");
+ return -1;
+ }
+ if (config->skc_hmac_alg >= SK_HMAC_MAX) {
+ printerr(0, "Invalid HMAC algorithm\n");
+ return -1;
+ }
+ if (config->skc_crypt_alg >= SK_CRYPT_MAX) {
+ printerr(0, "Invalid crypt algorithm\n");
+ return -1;
+ }
+ if (config->skc_expire < 60 || config->skc_expire > INT_MAX) {
+ /* Try to limit key expiration to some reasonable minimum and
+ * also prevent values over INT_MAX because there appears
+ * to be a type conversion issue */
+ printerr(0, "Invalid expiration time should be between %d "
+ "and %d\n", 60, INT_MAX);
+ return -1;
+ }
+ if (config->skc_session_keylen % 8 != 0 ||
+ config->skc_session_keylen > SK_SESSION_MAX_KEYLEN_BYTES * 8) {
+ printerr(0, "Invalid session key length must be a multiple of 8"
+ " and less then %d bits\n",
+ SK_SESSION_MAX_KEYLEN_BYTES * 8);
+ return -1;
+ }
+ if (config->skc_shared_keylen % 8 != 0 ||
+ config->skc_shared_keylen > SK_MAX_KEYLEN_BYTES * 8){
+ printerr(0, "Invalid shared key max length must be a multiple "
+ "of 8 and less then %d bits\n",
+ SK_MAX_KEYLEN_BYTES * 8);
+ return -1;
+ }
+
+ /* Check for terminating nulls on strings */
+ for (i = 0; i < sizeof(config->skc_fsname) &&
+ config->skc_fsname[i] != '\0'; i++)
+ ; /* empty loop */
+ if (i == sizeof(config->skc_fsname)) {
+ printerr(0, "File system name not null terminated\n");
+ return -1;
+ }
+
+ for (i = 0; i < sizeof(config->skc_nodemap) &&
+ config->skc_nodemap[i] != '\0'; i++)
+ ; /* empty loop */
+ if (i == sizeof(config->skc_nodemap)) {
+ printerr(0, "Nodemap name not null terminated\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Hashes \a string and places the hash in \a hash
+ * at \a hash
+ *
+ * \param[in] string Null terminated string to hash
+ * \param[in] hash_alg OpenSSL EVP_MD to use for hash
+ * \param[in,out] hash gss_buffer_desc to hold the result
+ *
+ * \return -1 failure
+ * \return 0 success
+ */
+static int sk_hash_string(const char *string, const EVP_MD *hash_alg,
+ gss_buffer_desc *hash)
+{
+ EVP_MD_CTX *ctx = EVP_MD_CTX_create();
+ size_t len = strlen(string);
+ unsigned int hashlen;
+
+ if (!hash->value || hash->length < EVP_MD_size(hash_alg))
+ goto out_err;
+ if (!EVP_DigestInit_ex(ctx, hash_alg, NULL))
+ goto out_err;
+ if (!EVP_DigestUpdate(ctx, string, len))
+ goto out_err;
+ if (!EVP_DigestFinal_ex(ctx, hash->value, &hashlen))
+ goto out_err;
+
+ EVP_MD_CTX_destroy(ctx);
+ hash->length = hashlen;
+ return 0;
+
+out_err:
+ EVP_MD_CTX_destroy(ctx);
+ return -1;
+}
+
+/**
+ * Hashes \a string and verifies the resulting hash matches the value
+ * in \a current_hash
+ *
+ * \param[in] string Null terminated string to hash
+ * \param[in] hash_alg OpenSSL EVP_MD to use for hash
+ * \param[in,out] current_hash gss_buffer_desc to compare to
+ *
+ * \return gss error failure
+ * \return GSS_S_COMPLETE success
+ */
+uint32_t sk_verify_hash(const char *string, const EVP_MD *hash_alg,
+ const gss_buffer_desc *current_hash)
+{
+ gss_buffer_desc hash;
+ unsigned char hashbuf[EVP_MAX_MD_SIZE];
+
+ hash.value = hashbuf;
+ hash.length = sizeof(hashbuf);
+
+ if (sk_hash_string(string, hash_alg, &hash))
+ return GSS_S_FAILURE;
+ if (current_hash->length != hash.length)
+ return GSS_S_DEFECTIVE_TOKEN;
+ if (memcmp(current_hash->value, hash.value, hash.length))
+ return GSS_S_BAD_SIG;
+
+ return GSS_S_COMPLETE;
+}
+
+static inline int sk_config_has_mgsnid(struct sk_keyfile_config *config,
+ const char *mgsnid)
+{
+ lnet_nid_t nid;
+ int i;
+
+ nid = libcfs_str2nid(mgsnid);
+ if (nid == LNET_NID_ANY)
+ return 0;
+
+ for (i = 0; i < MAX_MGSNIDS; i++)
+ if (config->skc_mgsnids[i] == nid)
+ return 1;
+ return 0;
+}
+
+/**
+ * Create an sk_cred structure populated with initial configuration info and the
+ * key. \a tgt and \a nodemap are used in determining the expected key
+ * description so the key can be found by searching the keyring.
+ * This is done because there is no easy way to pass keys from the mount command
+ * all the way to the request_key call. In addition any keys can be dynamically
+ * added to the keyrings and still found. The keyring that needs to be used
+ * must be the session keyring.
+ *
+ * \param[in] tgt Target file system
+ * \param[in] nodemap Cluster name for the key. This correlates to
+ * the nodemap name and is used by the server side.
+ * For the client this will be NULL.
+ * \param[in] flags Flags for the credentials
+ *
+ * \return sk_cred Allocated struct sk_cred on success
+ * \return NULL failure
+ */
+struct sk_cred *sk_create_cred(const char *tgt, const char *nodemap,
+ const uint32_t flags)
+{
+ struct sk_keyfile_config *config;
+ struct sk_kernel_ctx *kctx;
+ struct sk_cred *skc = NULL;
+ char description[SK_DESCRIPTION_SIZE + 1];
+ char fsname[MTI_NAME_MAXLEN + 1];
+ const char *mgsnid = NULL;
+ char *ptr;
+ long sk_key;
+ int keylen;
+ int len;
+ int rc;
+
+ printerr(2, "Creating credentials for target: %s with nodemap: %s\n",
+ tgt, nodemap);
+
+ memset(description, 0, sizeof(description));
+ memset(fsname, 0, sizeof(fsname));
+
+ /* extract the file system name from target */
+ ptr = index(tgt, '-');
+ if (!ptr) {
+ len = strlen(tgt);
+
+ /* This must be an MGC target */
+ if (strncmp(tgt, "MGC", 3) || len <= 3) {
+ printerr(0, "Invalid target name\n");
+ return NULL;
+ }
+ mgsnid = tgt + 3;
+ } else {
+ len = ptr - tgt;
+ }
+
+ if (len > MTI_NAME_MAXLEN) {
+ printerr(0, "Invalid target name\n");
+ return NULL;
+ }
+ memcpy(fsname, tgt, len);
+
+ if (nodemap) {
+ if (mgsnid)
+ rc = snprintf(description, SK_DESCRIPTION_SIZE,
+ "lustre:MGS:%s", nodemap);
+ else
+ rc = snprintf(description, SK_DESCRIPTION_SIZE,
+ "lustre:%s:%s", fsname, nodemap);
+ } else {
+ rc = snprintf(description, SK_DESCRIPTION_SIZE, "lustre:%s",
+ fsname);
+ }
+
+ if (rc >= SK_DESCRIPTION_SIZE) {
+ printerr(0, "Invalid key description\n");
+ return NULL;
+ }
+
+ /* It may be a good idea to move Lustre keys to the gss_keyring
+ * (lgssc) type so that they expire when Lustre modules are removed.
+ * Unfortunately it can't be done at mount time because the mount
+ * syscall could trigger the Lustre modules to load and until that
+ * point we don't have a lgssc key type.
+ *
+ * TODO: Query the community for a consensus here */
+ printerr(2, "Searching for key with description: %s\n", description);
+ sk_key = keyctl_search(KEY_SPEC_USER_KEYRING, "user",
+ description, 0);
+ if (sk_key == -1) {
+ printerr(1, "No key found for %s\n", description);
+ return NULL;
+ }
+
+ keylen = keyctl_read_alloc(sk_key, (void **)&config);
+ if (keylen == -1) {
+ printerr(0, "keyctl_read() failed for key %ld: %s\n", sk_key,
+ strerror(errno));
+ return NULL;
+ } else if (keylen != sizeof(*config)) {
+ printerr(0, "Unexpected key size: %d returned for key %ld, "
+ "expected %zu bytes\n",
+ keylen, sk_key, sizeof(*config));
+ goto out_err;
+ }
+
+ sk_config_disk_to_cpu(config);
+
+ if (sk_validate_config(config)) {
+ printerr(0, "Invalid key configuration for key: %ld\n", sk_key);
+ goto out_err;
+ }
+
+ if (mgsnid && !sk_config_has_mgsnid(config, mgsnid)) {
+ printerr(0, "Target name does not match key's MGS NIDs\n");
+ goto out_err;
+ }
+
+ if (!mgsnid && strcmp(fsname, config->skc_fsname)) {
+ printerr(0, "Target name does not match key's file system\n");
+ goto out_err;
+ }
+
+ skc = malloc(sizeof(*skc));
+ if (!skc) {
+ printerr(0, "Failed to allocate memory for sk_cred\n");
+ goto out_err;
+ }
+
+ /* this initializes all gss_buffer_desc to empty as well */
+ memset(skc, 0, sizeof(*skc));
+
+ skc->sc_flags = flags;
+ skc->sc_tgt.length = strlen(tgt) + 1;
+ skc->sc_tgt.value = malloc(skc->sc_tgt.length);
+ if (!skc->sc_tgt.value) {
+ printerr(0, "Failed to allocate memory for target\n");
+ goto out_err;
+ }
+ memcpy(skc->sc_tgt.value, tgt, skc->sc_tgt.length);
+
+ skc->sc_nodemap_hash.length = EVP_MD_size(EVP_sha256());
+ skc->sc_nodemap_hash.value = malloc(skc->sc_nodemap_hash.length);
+ if (!skc->sc_nodemap_hash.value) {
+ printerr(0, "Failed to allocate memory for nodemap hash\n");
+ goto out_err;
+ }
+
+ if (sk_hash_string(config->skc_nodemap, EVP_sha256(),
+ &skc->sc_nodemap_hash)) {
+ printerr(0, "Failed to generate hash for nodemap name\n");
+ goto out_err;
+ }
+
+ kctx = &skc->sc_kctx;
+ kctx->skc_version = config->skc_version;
+ kctx->skc_hmac_alg = config->skc_hmac_alg;
+ kctx->skc_crypt_alg = config->skc_crypt_alg;
+ kctx->skc_expire = config->skc_expire;
+
+ /* key payload format is in bits, convert to bytes */
+ skc->sc_session_keylen = config->skc_session_keylen / 8;
+ kctx->skc_shared_key.length = config->skc_shared_keylen / 8;
+ kctx->skc_shared_key.value = malloc(kctx->skc_shared_key.length);
+ if (!kctx->skc_shared_key.value) {
+ printerr(0, "Failed to allocate memory for shared key\n");
+ goto out_err;
+ }
+ memcpy(kctx->skc_shared_key.value, config->skc_shared_key,
+ kctx->skc_shared_key.length);
+
+ free(config);
+
+ return skc;
+
+out_err:
+ if (skc)
+ sk_free_cred(skc);
+
+ free(config);
+ return NULL;
+}
+
+/**
+ * Generates a public key and computes the private key for the DH key exchange.
+ * The parameters must be populated with the p and g from the peer.
+ *
+ * \param[in,out] skc Shared key credentials structure to populate
+ * with DH parameters
+ *
+ * \retval GSS_S_COMPLETE success
+ * \retval GSS_S_FAILURE failure
+ */
+static uint32_t sk_gen_responder_params(struct sk_cred *skc)
+{
+ int rc;
+
+ /* No keys to generate without privacy mode */
+ if ((skc->sc_flags & LGSS_SVC_PRIV) == 0)
+ return GSS_S_COMPLETE;
+
+ skc->sc_params = DH_new();
+ if (!skc->sc_params) {
+ printerr(0, "Failed to allocate DH\n");
+ return GSS_S_FAILURE;
+ }
+
+ /* responder should already have sc_p populated */
+ skc->sc_params->p = BN_bin2bn(skc->sc_p.value, skc->sc_p.length, NULL);
+ if (!skc->sc_params->p) {
+ printerr(0, "Failed to convert binary to BIGNUM\n");
+ return GSS_S_FAILURE;
+ }
+
+ /* and we use a static generator for shared key */
+ skc->sc_params->g = BN_new();
+ if (!skc->sc_params->g) {
+ printerr(0, "Failed to allocate new BIGNUM\n");
+ return GSS_S_FAILURE;
+ }
+ if (BN_set_word(skc->sc_params->g, SK_GENERATOR) != 1) {
+ printerr(0, "Failed to set g value for DH params\n");
+ return GSS_S_FAILURE;
+ }
+
+ /* verify that we have a safe prime and valid generator */
+ if (DH_check(skc->sc_params, &rc) != 1) {
+ printerr(0, "DH_check() failed: %d\n", rc);
+ return GSS_S_FAILURE;
+ } else if (rc) {
+ printerr(0, "DH_check() returned error codes: 0x%x\n", rc);
+ return GSS_S_FAILURE;
+ }
+
+ if (DH_generate_key(skc->sc_params) != 1) {
+ printerr(0, "Failed to generate public DH key: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ return GSS_S_FAILURE;
+ }
+
+ skc->sc_pub_key.length = BN_num_bytes(skc->sc_params->pub_key);
+ skc->sc_pub_key.value = malloc(skc->sc_pub_key.length);
+ if (!skc->sc_pub_key.value) {
+ printerr(0, "Failed to allocate memory for public key\n");
+ return GSS_S_FAILURE;
+ }
+
+ BN_bn2bin(skc->sc_params->pub_key, skc->sc_pub_key.value);
+
+ return GSS_S_COMPLETE;
+}
+
+static void sk_free_parameters(struct sk_cred *skc)
+{
+ if (skc->sc_params)
+ DH_free(skc->sc_params);
+ if (skc->sc_p.value)
+ free(skc->sc_p.value);
+ if (skc->sc_pub_key.value)
+ free(skc->sc_pub_key.value);
+
+ skc->sc_p.value = NULL;
+ skc->sc_p.length = 0;
+ skc->sc_pub_key.value = NULL;
+ skc->sc_pub_key.length = 0;
+}
+
+/**
+ * Generates shared key Diffie Hellman parameters used for the DH key exchange
+ * between host and peer if privacy mode is enabled
+ *
+ * \param[in,out] skc Shared key credentials structure to populate
+ * with DH parameters
+ *
+ * \retval GSS_S_COMPLETE success
+ * \retval GSS_S_FAILURE failure
+ */
+static uint32_t sk_gen_initiator_params(struct sk_cred *skc)
+{
+ gss_buffer_desc *iv = &skc->sc_kctx.skc_iv;
+ int rc;
+
+ /* The credential could be used so free existing parameters */
+ sk_free_parameters(skc);
+
+ /* Pseudo random should be sufficient here because the IV will be used
+ * with a key that is used only once. This also should ensure we have
+ * unqiue tokens that are sent to the remote server which is important
+ * because the token is hashed for the sunrpc cache lookups and a
+ * failure there would cause connection attempts to fail indefinitely
+ * due to the large timeout value on the server side sunrpc cache
+ * (INT_MAX) */
+ iv->length = SK_IV_SIZE;
+ iv->value = malloc(iv->length);
+ if (!iv->value) {
+ printerr(0, "Failed to allocate memory for IV\n");
+ return GSS_S_FAILURE;
+ }
+ memset(iv->value, 0, iv->length);
+ if (RAND_bytes(iv->value, iv->length) != 1) {
+ printerr(0, "Failed to get data for IV\n");
+ return GSS_S_FAILURE;
+ }
+
+ /* Only privacy mode needs the rest of the parameter generation
+ * but we use IV in other modes as well so tokens should be
+ * unique */
+ if ((skc->sc_flags & LGSS_SVC_PRIV) == 0)
+ return GSS_S_COMPLETE;
+
+ skc->sc_params = DH_generate_parameters(skc->sc_session_keylen * 8,
+ SK_GENERATOR, NULL, NULL);
+ if (skc->sc_params == NULL) {
+ printerr(0, "Failed to generate diffie-hellman parameters: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return GSS_S_FAILURE;
+ }
+
+ if (DH_check(skc->sc_params, &rc) != 1) {
+ printerr(0, "DH_check() failed: %d\n", rc);
+ return GSS_S_FAILURE;
+ } else if (rc) {
+ printerr(0, "DH_check() returned error codes: 0x%x\n", rc);
+ return GSS_S_FAILURE;
+ }
+
+ if (DH_generate_key(skc->sc_params) != 1) {
+ printerr(0, "Failed to generate public DH key: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ return GSS_S_FAILURE;
+ }
+
+ skc->sc_p.length = BN_num_bytes(skc->sc_params->p);
+ skc->sc_pub_key.length = BN_num_bytes(skc->sc_params->pub_key);
+ skc->sc_p.value = malloc(skc->sc_p.length);
+ skc->sc_pub_key.value = malloc(skc->sc_pub_key.length);
+ if (!skc->sc_p.value || !skc->sc_pub_key.value) {
+ printerr(0, "Failed to allocate memory for params\n");
+ return GSS_S_FAILURE;
+ }
+
+ BN_bn2bin(skc->sc_params->pub_key, skc->sc_pub_key.value);
+ BN_bn2bin(skc->sc_params->p, skc->sc_p.value);
+
+ return GSS_S_COMPLETE;
+}
+
+/**
+ * Generates or populates the DH parameters depending on whether the system is
+ * the initiator or responder for the connection
+ *
+ * \param[in,out] skc Shared key credentials structure to
+ * populate with DH parameters
+ * \param[in] initiator Boolean whether to initiate parameters
+ *
+ * \retval GSS_S_COMPLETE success
+ * \retval GSS_S_FAILURE failure
+ */
+uint32_t sk_gen_params(struct sk_cred *skc, const bool initiator)
+{
+ if (initiator)
+ return sk_gen_initiator_params(skc);
+
+ return sk_gen_responder_params(skc);
+}
+
+/**
+ * Convert SK hash algorithm into openssl message digest
+ *
+ * \param[in,out] alg SK hash algorithm
+ *
+ * \retval EVP_MD
+ */
+static inline const EVP_MD *sk_hash_to_evp_md(enum sk_hmac_alg alg)
+{
+ switch (alg) {
+ case SK_HMAC_SHA256:
+ return EVP_sha256();
+ case SK_HMAC_SHA512:
+ return EVP_sha512();
+ default:
+ return EVP_md_null();
+ }
+}
+
+/**
+ * Signs (via HMAC) the parameters used only in the key initialization protocol.
+ *
+ * \param[in] key Key to use for HMAC
+ * \param[in] bufs Array of gss_buffer_desc to generate
+ * HMAC for
+ * \param[in] numbufs Number of buffers in array
+ * \param[in] hash_alg OpenSSL EVP_MD to use for hash
+ * \param[in,out] hmac HMAC of buffers is allocated and placed
+ * in this gss_buffer_desc. Caller must
+ * free this.
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int sk_sign_bufs(gss_buffer_desc *key, gss_buffer_desc *bufs, const int numbufs,
+ const EVP_MD *hash_alg, gss_buffer_desc *hmac)
+{
+ HMAC_CTX hctx;
+ unsigned int hashlen = EVP_MD_size(hash_alg);
+ int i;
+ int rc = -1;
+
+ if (hash_alg == EVP_md_null()) {
+ printerr(0, "Invalid hash algorithm\n");
+ return -1;
+ }
+
+ HMAC_CTX_init(&hctx);
+
+ hmac->length = hashlen;
+ hmac->value = malloc(hashlen);
+ if (!hmac->value) {
+ printerr(0, "Failed to allocate memory for HMAC\n");
+ goto out;
+ }
+
+#ifdef HAVE_VOID_OPENSSL_HMAC_FUNCS
+ HMAC_Init_ex(&hctx, key->value, key->length, hash_alg, NULL);
+ for (i = 0; i < numbufs; i++)
+ HMAC_Update(&hctx, bufs[i].value, bufs[i].length);
+ HMAC_Final(&hctx, hmac->value, &hashlen);
+#else
+ if (HMAC_Init_ex(&hctx, key->value, key->length, hash_alg, NULL) != 1) {
+ printerr(0, "Failed to init HMAC\n");
+ goto out;
+ }
+
+ for (i = 0; i < numbufs; i++) {
+ if (HMAC_Update(&hctx, bufs[i].value, bufs[i].length) != 1) {
+ printerr(0, "Failed to update HMAC\n");
+ goto out;
+ }
+ }
+
+ /* The result gets populated in hmac */
+ if (HMAC_Final(&hctx, hmac->value, &hashlen) != 1) {
+ printerr(0, "Failed to finalize HMAC\n");
+ goto out;
+ }
+#endif
+
+ if (hmac->length != hashlen) {
+ printerr(0, "HMAC size does not match expected\n");
+ goto out;
+ }
+
+ rc = 0;
+out:
+ HMAC_CTX_cleanup(&hctx);
+ return rc;
+}
+
+/**
+ * Generates an HMAC for gss_buffer_desc array in \a bufs of \a numbufs
+ * and verifies against \a hmac.
+ *
+ * \param[in] skc Shared key credentials
+ * \param[in] bufs Array of gss_buffer_desc to generate HMAC for
+ * \param[in] numbufs Number of buffers in array
+ * \param[in] hash_alg OpenSSL EVP_MD to use for hash
+ * \param[in] hmac HMAC to verify against
+ *
+ * \retval GSS_S_COMPLETE success (match)
+ * \retval gss error failure
+ */
+uint32_t sk_verify_hmac(struct sk_cred *skc, gss_buffer_desc *bufs,
+ const int numbufs, const EVP_MD *hash_alg,
+ gss_buffer_desc *hmac)
+{
+ gss_buffer_desc bufs_hmac;
+ int rc;
+
+ if (sk_sign_bufs(&skc->sc_kctx.skc_shared_key, bufs, numbufs, hash_alg,
+ &bufs_hmac)) {
+ printerr(0, "Failed to sign buffers to verify HMAC\n");
+ if (bufs_hmac.value)
+ free(bufs_hmac.value);
+ return GSS_S_FAILURE;
+ }
+
+ if (hmac->length != bufs_hmac.length) {
+ printerr(0, "Invalid HMAC size\n");
+ free(bufs_hmac.value);
+ return GSS_S_BAD_SIG;
+ }
+
+ rc = memcmp(hmac->value, bufs_hmac.value, bufs_hmac.length);
+ free(bufs_hmac.value);
+
+ if (rc)
+ return GSS_S_BAD_SIG;
+
+ return GSS_S_COMPLETE;
+}
+
+/**
+ * Cleanup an sk_cred freeing any resources
+ *
+ * \param[in,out] skc Shared key credentials to free
+ */
+void sk_free_cred(struct sk_cred *skc)
+{
+ if (skc->sc_p.value)
+ free(skc->sc_p.value);
+ if (skc->sc_pub_key.value)
+ free(skc->sc_pub_key.value);
+ if (skc->sc_tgt.value)
+ free(skc->sc_tgt.value);
+ if (skc->sc_nodemap_hash.value)
+ free(skc->sc_nodemap_hash.value);
+ if (skc->sc_hmac.value)
+ free(skc->sc_hmac.value);
+
+ /* Overwrite keys and IV before freeing */
+ if (skc->sc_dh_shared_key.value) {
+ memset(skc->sc_dh_shared_key.value, 0,
+ skc->sc_dh_shared_key.length);
+ free(skc->sc_dh_shared_key.value);
+ }
+ if (skc->sc_kctx.skc_shared_key.value) {
+ memset(skc->sc_kctx.skc_shared_key.value, 0,
+ skc->sc_kctx.skc_shared_key.length);
+ free(skc->sc_kctx.skc_shared_key.value);
+ }
+ if (skc->sc_kctx.skc_iv.value) {
+ memset(skc->sc_kctx.skc_iv.value, 0,
+ skc->sc_kctx.skc_iv.length);
+ free(skc->sc_kctx.skc_iv.value);
+ }
+ if (skc->sc_kctx.skc_session_key.value) {
+ memset(skc->sc_kctx.skc_session_key.value, 0,
+ skc->sc_kctx.skc_session_key.length);
+ free(skc->sc_kctx.skc_session_key.value);
+ }
+
+ if (skc->sc_params)
+ DH_free(skc->sc_params);
+
+ free(skc);
+}
+
+/* Populates the sk_cred's session_key using the a Key Derviation Function (KDF)
+ * based on the recommendations in NIST Special Publication SP 800-56B Rev 1
+ * (Sep 2014) Section 5.5.1
+ *
+ * \param[in,out] skc Shared key credentials structure with
+ *
+ * \return -1 failure
+ * \return 0 success
+ */
+int sk_kdf(struct sk_cred *skc, lnet_nid_t client_nid,
+ gss_buffer_desc *key_binding_input)
+{
+ struct sk_kernel_ctx *kctx = &skc->sc_kctx;
+ gss_buffer_desc *session_key = &kctx->skc_session_key;
+ gss_buffer_desc bufs[4];
+ gss_buffer_desc tmp_hash;
+ char *skp;
+ size_t remain;
+ size_t bytes;
+ uint32_t counter;
+ int i;
+ int rc = -1;
+
+ session_key->length = sk_crypt_types[kctx->skc_crypt_alg].sct_bytes;
+ session_key->value = malloc(session_key->length);
+ if (!session_key->value) {
+ printerr(0, "Failed to allocate memory for session key\n");
+ return rc;
+ }
+
+ /* Use the HMAC algorithm provided by in the shared key file to derive
+ * a session key. eg: HMAC(key, msg)
+ * key: the shared key provided in the shared key file
+ * msg is the bytes in the following order:
+ * 1. big_endian(counter)
+ * 2. DH shared key
+ * 3. Clients NIDs
+ * 4. key_binding_input */
+ bufs[0].value = &counter;
+ bufs[0].length = sizeof(counter);
+ bufs[1] = skc->sc_dh_shared_key;
+ bufs[2].value = &client_nid;
+ bufs[2].length = sizeof(client_nid);
+ bufs[3] = *key_binding_input;
+
+ remain = session_key->length;
+ skp = session_key->value;
+ i = 0;
+ while (remain > 0) {
+ counter = cpu_to_be32(i++);
+ rc = sk_sign_bufs(&kctx->skc_shared_key, bufs, 4,
+ sk_hash_to_evp_md(kctx->skc_hmac_alg), &tmp_hash);
+ if (rc) {
+ free(tmp_hash.value);
+ return rc;
+ }
+
+ LASSERT(sk_hmac_types[kctx->skc_hmac_alg].sht_bytes ==
+ tmp_hash.length);
+
+ bytes = (remain < tmp_hash.length) ? remain : tmp_hash.length;
+ memcpy(skp, tmp_hash.value, bytes);
+ free(tmp_hash.value);
+ remain -= bytes;
+ skp += bytes;
+ }
+
+ return 0;
+}
+
+/**
+ * Computes a session key based on the DH parameters from the host and its peer
+ *
+ * \param[in,out] skc Shared key credentials structure with
+ * the session key populated with the
+ * compute key
+ * \param[in] pub_key Public key returned from peer in
+ * gss_buffer_desc
+ * \return gss error failure
+ * \return GSS_S_COMPLETE success
+ */
+uint32_t sk_compute_key(struct sk_cred *skc, const gss_buffer_desc *pub_key)
+{
+ gss_buffer_desc *dh_shared = &skc->sc_dh_shared_key;
+ BIGNUM *remote_pub_key;
+ int status;
+ uint32_t rc = GSS_S_FAILURE;
+
+ /* No keys computed unless privacy mode is in use */
+ if ((skc->sc_flags & LGSS_SVC_PRIV) == 0)
+ return GSS_S_COMPLETE;
+
+ remote_pub_key = BN_bin2bn(pub_key->value, pub_key->length, NULL);
+ if (!remote_pub_key) {
+ printerr(0, "Failed to convert binary to BIGNUM\n");
+ return rc;
+ }
+
+ dh_shared->length = DH_size(skc->sc_params);
+ dh_shared->value = malloc(dh_shared->length);
+ if (!dh_shared->value) {
+ printerr(0, "Failed to allocate memory for computed shared "
+ "secret key\n");
+ goto out_err;
+ }
+
+ /* This compute the shared key from the DHKE */
+ status = DH_compute_key(dh_shared->value, remote_pub_key,
+ skc->sc_params);
+ if (status == -1) {
+ printerr(0, "DH_compute_key() failed: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ goto out_err;
+ } else if (status < dh_shared->length) {
+ printerr(0, "DH_compute_key() returned a short key of %d "
+ "bytes, expected: %zu\n", status, dh_shared->length);
+ rc = GSS_S_DEFECTIVE_TOKEN;
+ goto out_err;
+ }
+
+ rc = GSS_S_COMPLETE;
+
+out_err:
+ BN_free(remote_pub_key);
+ return rc;
+}
+
+/**
+ * Creates a serialized buffer for the kernel in the order of struct
+ * sk_kernel_ctx.
+ *
+ * \param[in,out] skc Shared key credentials structure
+ * \param[in,out] ctx_token Serialized buffer for kernel.
+ * Caller must free this buffer.
+ *
+ * \return 0 success
+ * \return -1 failure
+ */
+int sk_serialize_kctx(struct sk_cred *skc, gss_buffer_desc *ctx_token)
+{
+ struct sk_kernel_ctx *kctx = &skc->sc_kctx;
+ char *p, *end;
+ size_t bufsize;
+
+ bufsize = sizeof(*kctx) + kctx->skc_session_key.length +
+ kctx->skc_iv.length + kctx->skc_shared_key.length;
+
+ ctx_token->value = malloc(bufsize);
+ if (!ctx_token->value)
+ return -1;
+ ctx_token->length = bufsize;
+
+ p = ctx_token->value;
+ end = p + ctx_token->length;
+
+ if (WRITE_BYTES(&p, end, kctx->skc_version))
+ return -1;
+ if (WRITE_BYTES(&p, end, kctx->skc_hmac_alg))
+ return -1;
+ if (WRITE_BYTES(&p, end, kctx->skc_crypt_alg))
+ return -1;
+ if (WRITE_BYTES(&p, end, kctx->skc_expire))
+ return -1;
+ if (write_buffer(&p, end, &kctx->skc_shared_key))
+ return -1;
+ if (write_buffer(&p, end, &kctx->skc_iv))
+ return -1;
+ if (write_buffer(&p, end, &kctx->skc_session_key))
+ return -1;
+
+ printerr(2, "Serialized buffer of %zu bytes for kernel\n", bufsize);
+
+ return 0;
+}
+
+/**
+ * Decodes a netstring \a ns into array of gss_buffer_descs at \a bufs
+ * up to \a numbufs. Memory is allocated for each value and length
+ * will be populated with the length
+ *
+ * \param[in,out] bufs Array of gss_buffer_descs
+ * \param[in,out] numbufs number of gss_buffer_desc in array
+ * \param[in] ns netstring to decode
+ *
+ * \return buffers populated success
+ * \return -1 failure
+ */
+int sk_decode_netstring(gss_buffer_desc *bufs, int numbufs, gss_buffer_desc *ns)
+{
+ char *ptr = ns->value;
+ size_t remain = ns->length;
+ unsigned int size;
+ int digits;
+ int sep;
+ int rc;
+ int i;
+
+ for (i = 0; i < numbufs; i++) {
+ /* read the size of first buffer */
+ rc = sscanf(ptr, "%9u", &size);
+ if (rc < 1)
+ goto out_err;
+ digits = (size) ? ceil(log10(size + 1)) : 1;
+
+ /* sep of current string */
+ sep = size + digits + 2;
+
+ /* check to make sure it's valid */
+ if (remain < sep || ptr[digits] != ':' ||
+ ptr[sep - 1] != ',')
+ goto out_err;
+
+ bufs[i].length = size;
+ if (size == 0) {
+ bufs[i].value = NULL;
+ } else {
+ bufs[i].value = malloc(size);
+ if (!bufs[i].value)
+ goto out_err;
+ memcpy(bufs[i].value, &ptr[digits + 1], size);
+ }
+
+ remain -= sep;
+ ptr += sep;
+ }
+
+ printerr(2, "Decoded netstring of %zu bytes\n", ns->length);
+ return i;
+
+out_err:
+ while (i-- > 0) {
+ if (bufs[i].value)
+ free(bufs[i].value);
+ bufs[i].length = 0;
+ }
+ return -1;
+}
+
+/**
+ * Creates a netstring in a gss_buffer_desc that consists of all
+ * the gss_buffer_desc found in \a bufs. The netstring should be treated
+ * as binary as it can contain null characters.
+ *
+ * \param[in] bufs Array of gss_buffer_desc to use as input
+ * \param[in] numbufs Number of buffers in array
+ * \param[in,out] ns Destination gss_buffer_desc to hold
+ * netstring
+ *
+ * \return -1 failure
+ * \return 0 success
+ */
+int sk_encode_netstring(gss_buffer_desc *bufs, int numbufs,
+ gss_buffer_desc *ns)
+{
+ unsigned char *ptr;
+ int size = 0;
+ int rc;
+ int i;
+
+ /* size of string in decimal, string size, colon, and comma */
+ for (i = 0; i < numbufs; i++) {
+
+ if (bufs[i].length == 0)
+ size += 3;
+ else
+ size += ceil(log10(bufs[i].length + 1)) +
+ bufs[i].length + 2;
+ }
+
+ ns->length = size;
+ ns->value = malloc(ns->length);
+ if (!ns->value) {
+ ns->length = 0;
+ return -1;
+ }
+
+ ptr = ns->value;
+ for (i = 0; i < numbufs; i++) {
+ /* size */
+ rc = snprintf((char *) ptr, size, "%zu:", bufs[i].length);
+ ptr += rc;
+
+ /* contents */
+ memcpy(ptr, bufs[i].value, bufs[i].length);
+ ptr += bufs[i].length;
+
+ /* delimeter */
+ *ptr++ = ',';
+
+ size -= bufs[i].length + rc + 1;
+
+ /* should not happen */
+ if (size < 0)
+ abort();
+ }
+
+ printerr(2, "Encoded netstring of %zu bytes\n", ns->length);
+ return 0;
+}
--- /dev/null
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (C) 2015, Trustees of Indiana University
+ *
+ * Author: Jeremy Filizetti <jfilizet@iu.edu>
+ */
+
+#ifndef SK_UTILS_H
+#define SK_UTILS_H
+
+#include <gssapi/gssapi.h>
+#include <keyutils.h>
+#include <lustre/lustre_idl.h>
+#include <openssl/dh.h>
+#include <openssl/evp.h>
+#include <sys/types.h>
+
+#include "lsupport.h"
+
+/* Some limits and defaults */
+#define SK_CONF_VERSION 1
+#define SK_GENERATOR 2
+#define SK_SESSION_MAX_KEYLEN_BYTES 1024
+#define SK_MAX_KEYLEN_BYTES 128
+#define SK_IV_SIZE 16
+#define MAX_MGSNIDS 16
+
+/* String consisting of "lustre:fsname:nodemap_hash" */
+#define SK_DESCRIPTION_SIZE (9 + MTI_NAME_MAXLEN + LUSTRE_NODEMAP_NAME_LENGTH)
+
+enum sk_key_type {
+ SK_TYPE_INVALID = 0x0,
+ SK_TYPE_CLIENT = 0x1,
+ SK_TYPE_SERVER = 0x2,
+ SK_TYPE_MGS = 0x4,
+};
+
+/* This is the packed structure format of key files that are distributed.
+ * The on disk format should be store in big-endian. */
+struct sk_keyfile_config {
+ /* File format version */
+ uint32_t skc_version;
+ /* HMAC algorithm used for message integrity */
+ uint16_t skc_hmac_alg;
+ /* Crypt algorithm used for privacy mode */
+ uint16_t skc_crypt_alg;
+ /* Number of seconds that a context is valid after it is created from
+ * this keyfile */
+ uint32_t skc_expire;
+ /* Length of shared key in skc_shared_key */
+ uint32_t skc_shared_keylen;
+ /* Minimum length of the session keys using this keyfile */
+ uint32_t skc_session_keylen;
+ /* Array of MGS NIDs to load key's for. This is for the client since
+ * the upcall only knows the target name which is MGC<IP>@<NET>
+ * Only needed when mounting with mgssec */
+ lnet_nid_t skc_mgsnids[MAX_MGSNIDS];
+ /* File system name for this key. It can be unused for MGS only keys */
+ char skc_fsname[MTI_NAME_MAXLEN + 1];
+ /* Nodemap name for this key. Used by the server side to verify the
+ * client is in the correct nodemap */
+ char skc_nodemap[LUSTRE_NODEMAP_NAME_LENGTH + 1];
+ /* Shared key */
+ unsigned char skc_shared_key[SK_MAX_KEYLEN_BYTES];
+} __attribute__((packed));
+
+/* Format passed to the kernel from userspace */
+struct sk_kernel_ctx {
+ uint32_t skc_version;
+ uint16_t skc_hmac_alg;
+ uint16_t skc_crypt_alg;
+ uint32_t skc_expire;
+ gss_buffer_desc skc_shared_key;
+ gss_buffer_desc skc_iv;
+ gss_buffer_desc skc_session_key;
+};
+
+/* Structure used in context initiation to hold all necessary data */
+struct sk_cred {
+ uint32_t sc_session_keylen;
+ uint32_t sc_flags;
+ gss_buffer_desc sc_p;
+ gss_buffer_desc sc_pub_key;
+ gss_buffer_desc sc_tgt;
+ gss_buffer_desc sc_nodemap_hash;
+ gss_buffer_desc sc_hmac;
+ gss_buffer_desc sc_dh_shared_key;
+ struct sk_kernel_ctx sc_kctx;
+ DH *sc_params;
+};
+
+void sk_init_logging(char *program, int verbose, int fg);
+struct sk_keyfile_config *sk_read_file(char *filename);
+int sk_load_keyfile(char *path, int type);
+void sk_config_disk_to_cpu(struct sk_keyfile_config *config);
+void sk_config_cpu_to_disk(struct sk_keyfile_config *config);
+int sk_validate_config(const struct sk_keyfile_config *config);
+uint32_t sk_verify_hash(const char *string, const EVP_MD *hash_alg,
+ const gss_buffer_desc *current_hash);
+struct sk_cred *sk_create_cred(const char *fsname, const char *cluster,
+ const uint32_t flags);
+uint32_t sk_gen_params(struct sk_cred *skc, bool initiator);
+int sk_sign_bufs(gss_buffer_desc *key, gss_buffer_desc *bufs, const int numbufs,
+ const EVP_MD *hash_alg, gss_buffer_desc *hmac);
+uint32_t sk_verify_hmac(struct sk_cred *skc, gss_buffer_desc *bufs,
+ const int numbufs, const EVP_MD *hash_alg,
+ gss_buffer_desc *hmac);
+void sk_free_cred(struct sk_cred *skc);
+int sk_kdf(struct sk_cred *skc, lnet_nid_t client_nid,
+ gss_buffer_desc *key_binding_input);
+uint32_t sk_compute_key(struct sk_cred *skc, const gss_buffer_desc *pub_key);
+int sk_serialize_kctx(struct sk_cred *skc, gss_buffer_desc *ctx_token);
+int sk_decode_netstring(gss_buffer_desc *bufs, int numbufs,
+ gss_buffer_desc *ns);
+int sk_encode_netstring(gss_buffer_desc *bufs, int numbufs,
+ gss_buffer_desc *ns);
+
+#endif /* SK_UTILS_H */
#include "err_util.h"
#include "lsupport.h"
+int null_enabled;
+int krb_enabled;
+int sk_enabled;
+
void
closeall(int min)
{
fprintf(stderr, "-o - Service OSS\n");
fprintf(stderr, "-g - Service MGS\n");
fprintf(stderr, "-k - Enable kerberos support\n");
+ fprintf(stderr, "-s - Enable shared key support\n");
+ fprintf(stderr, "-z - Enable gssnull support\n");
exit(1);
}
int must_srv_mds = 0, must_srv_oss = 0, must_srv_mgs = 0;
char *progname;
- while ((opt = getopt(argc, argv, "fnvmogk")) != -1) {
+ while ((opt = getopt(argc, argv, "fnvmogksz")) != -1) {
switch (opt) {
case 'f':
fg = 1;
case 'h':
usage(stdout, argv[0]);
break;
+ case 's':
+ sk_enabled = 1;
+ break;
+ case 'z':
+ null_enabled = 1;
+ break;
default:
usage(stderr, argv[0]);
break;
void svcgssd_run(void);
int gssd_prepare_creds(int must_srv_mgs, int must_srv_mds, int must_srv_oss);
gss_cred_id_t gssd_select_svc_cred(int lustre_svc);
+const char *gss_OID_mech_name(gss_OID mech);
extern char *mds_local_realm;
extern char *oss_local_realm;
+extern int null_enabled;
+extern int krb_enabled;
+extern int sk_enabled;
#define GSSD_SERVICE_NAME "lustre"
#include "svcgssd.h"
#include "err_util.h"
+#define GSS_RPC_FILE "/proc/net/rpc/auth.sptlrpc.init/channel"
+
/*
* nfs4 in-kernel cache implementation make upcall failed directly
* if there's no listener detected. so here we should keep the init
#include "cacheio.h"
#include "lsupport.h"
#include "gss_oids.h"
+#include "sk_utils.h"
#include <lustre/lustre_idl.h>
-extern const char *gss_OID_mech_name(gss_OID mech);
-
#define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.sptlrpc.context/channel"
#define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.sptlrpc.init/channel"
/* XXXARG: */
int g;
- printerr(2, "sending null reply\n");
-
+ printerr(2, "sending reply\n");
qword_addhex(&bp, &blen, in_handle->value, in_handle->length);
qword_addhex(&bp, &blen, in_token->value, in_token->length);
qword_addint(&bp, &blen, 3600); /* an hour should be sufficient */
qword_addhex(&bp, &blen, out_token->value, out_token->length);
qword_addeol(&bp, &blen);
if (blen <= 0) {
- printerr(0, "WARNING: send_respsonse: message too long\n");
+ printerr(0, "WARNING: send_response: message too long\n");
return -1;
}
g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY);
gss_ctx_id_t internal_ctx_id;
} gss_union_ctx_id_desc, *gss_union_ctx_id_t;
+int handle_sk(struct svc_nego_data *snd)
+{
+ struct sk_cred *skc = NULL;
+ struct svc_cred cred;
+ gss_buffer_desc bufs[7];
+ gss_buffer_desc remote_pub_key = GSS_C_EMPTY_BUFFER;
+ char *target;
+ uint32_t rc = GSS_S_FAILURE;
+ uint32_t flags;
+ int numbufs = 7;
+ int i;
+
+ printerr(3, "Handling sk request\n");
+
+ /* See lgss_sk_using_cred() for client side token
+ * bufs returned are in this order:
+ * bufs[0] - iv
+ * bufs[1] - p
+ * bufs[2] - remote_pub_key
+ * bufs[3] - target
+ * bufs[4] - nodemap_hash
+ * bufs[5] - flags
+ * bufs[6] - hmac */
+ i = sk_decode_netstring(bufs, numbufs, &snd->in_tok);
+ if (i < numbufs) {
+ printerr(0, "Invalid netstring token received from peer\n");
+ rc = GSS_S_DEFECTIVE_TOKEN;
+ goto out_err;
+ }
+
+ /* target must be a null terminated string */
+ i = bufs[3].length - 1;
+ target = bufs[3].value;
+ if (i >= 0 && target[i] != '\0') {
+ printerr(0, "Invalid target from netstring\n");
+ for (i = 0; i < numbufs; i++)
+ free(bufs[i].value);
+ goto out_err;
+ }
+
+ memcpy(&flags, bufs[5].value, sizeof(flags));
+ skc = sk_create_cred(target, snd->nm_name, be32_to_cpu(flags));
+ if (!skc) {
+ printerr(0, "Failed to create sk credentials\n");
+ for (i = 0; i < numbufs; i++)
+ free(bufs[i].value);
+ goto out_err;
+ }
+
+ /* Take control of all the allocated buffers from decoding */
+ skc->sc_kctx.skc_iv = bufs[0];
+ skc->sc_p = bufs[1];
+ remote_pub_key = bufs[2];
+ skc->sc_nodemap_hash = bufs[4];
+ skc->sc_hmac = bufs[6];
+
+ /* Verify that the peer has used a key size greater to or equal
+ * the size specified by the key file */
+ if (skc->sc_flags & LGSS_SVC_PRIV &&
+ skc->sc_p.length < skc->sc_session_keylen) {
+ printerr(0, "Peer DH parameters do not meet the size required "
+ "by keyfile\n");
+ goto out_err;
+ }
+
+ /* Verify HMAC from peer. Ideally this would happen before anything
+ * else but we don't have enough information to lookup key without the
+ * token (fsname and cluster_hash) so it's done shortly after. */
+ rc = sk_verify_hmac(skc, bufs, numbufs - 1, EVP_sha256(),
+ &skc->sc_hmac);
+ free(bufs[3].value);
+ free(bufs[5].value);
+ if (rc != GSS_S_COMPLETE) {
+ printerr(0, "HMAC verification error: 0x%x from peer %s\n",
+ rc, libcfs_nid2str((lnet_nid_t) snd->nid));
+ goto out_err;
+ }
+
+ /* Check that the cluster hash matches the hash of nodemap name */
+ rc = sk_verify_hash(snd->nm_name, EVP_sha256(), &skc->sc_nodemap_hash);
+ if (rc != GSS_S_COMPLETE) {
+ printerr(0, "Cluster hash failed validation: 0x%x\n", rc);
+ goto out_err;
+ }
+
+ rc = sk_gen_params(skc, false);
+ if (rc != GSS_S_COMPLETE) {
+ printerr(0, "Failed to generate DH params for responder\n");
+ goto out_err;
+ }
+ if (sk_compute_key(skc, &remote_pub_key)) {
+ printerr(0, "Failed to compute session key from DH params\n");
+ goto out_err;
+ }
+ if (sk_kdf(skc, snd->nid, &snd->in_tok)) {
+ printerr(0, "Failed to calulate derviced session key\n");
+ goto out_err;
+ }
+ if (sk_serialize_kctx(skc, &snd->ctx_token)) {
+ printerr(0, "Failed to serialize context for kernel\n");
+ goto out_err;
+ }
+
+ /* Server reply only contains the servers public key and HMAC */
+ bufs[0] = skc->sc_pub_key;
+ if (sk_sign_bufs(&skc->sc_kctx.skc_shared_key, bufs, 1, EVP_sha256(),
+ &skc->sc_hmac)) {
+ printerr(0, "Failed to sign parameters\n");
+ goto out_err;
+ }
+ bufs[1] = skc->sc_hmac;
+ if (sk_encode_netstring(bufs, 2, &snd->out_tok)) {
+ printerr(0, "Failed to encode netstring for token\n");
+ goto out_err;
+ }
+
+ printerr(2, "Created netstring of %zd bytes\n", snd->out_tok.length);
+
+ snd->out_handle.length = sizeof(snd->handle_seq);
+ memcpy(snd->out_handle.value, &snd->handle_seq,
+ sizeof(snd->handle_seq));
+ snd->maj_stat = GSS_S_COMPLETE;
+
+ /* fix credentials */
+ memset(&cred, 0, sizeof(cred));
+ cred.cr_mapped_uid = -1;
+
+ if (skc->sc_flags & LGSS_ROOT_CRED_ROOT)
+ cred.cr_usr_root = 1;
+ if (skc->sc_flags & LGSS_ROOT_CRED_MDT)
+ cred.cr_usr_mds = 1;
+ if (skc->sc_flags & LGSS_ROOT_CRED_OST)
+ cred.cr_usr_oss = 1;
+
+ do_svc_downcall(&snd->out_handle, &cred, snd->mech, &snd->ctx_token);
+
+ /* cleanup ctx_token, out_tok is cleaned up in handle_channel_req */
+ free(remote_pub_key.value);
+ free(snd->ctx_token.value);
+ snd->ctx_token.length = 0;
+
+ printerr(3, "sk returning success\n");
+ return 0;
+
+out_err:
+ snd->maj_stat = rc;
+ if (remote_pub_key.value)
+ free(remote_pub_key.value);
+ if (snd->ctx_token.value)
+ free(snd->ctx_token.value);
+ snd->ctx_token.length = 0;
+
+ if (skc)
+ sk_free_cred(skc);
+ printerr(3, "sk returning failure\n");
+ return -1;
+}
+
+int handle_null(struct svc_nego_data *snd)
+{
+ struct svc_cred cred;
+ uint64_t tmp;
+ uint32_t flags;
+
+ /* null just uses the same token as the return token and for
+ * for sending to the kernel. It is a single uint64_t. */
+ if (snd->in_tok.length != sizeof(uint64_t)) {
+ snd->maj_stat = GSS_S_DEFECTIVE_TOKEN;
+ printerr(0, "Invalid token size (%zd) received\n",
+ snd->in_tok.length);
+ return -1;
+ }
+ snd->out_tok.length = snd->in_tok.length;
+ snd->out_tok.value = malloc(snd->out_tok.length);
+ if (!snd->out_tok.value) {
+ snd->maj_stat = GSS_S_FAILURE;
+ printerr(0, "Failed to allocate out_tok\n");
+ return -1;
+ }
+
+ snd->ctx_token.length = snd->in_tok.length;
+ snd->ctx_token.value = malloc(snd->ctx_token.length);
+ if (!snd->ctx_token.value) {
+ snd->maj_stat = GSS_S_FAILURE;
+ printerr(0, "Failed to allocate ctx_token\n");
+ return -1;
+ }
+
+ snd->out_handle.length = sizeof(snd->handle_seq);
+ memcpy(snd->out_handle.value, &snd->handle_seq,
+ sizeof(snd->handle_seq));
+ snd->maj_stat = GSS_S_COMPLETE;
+
+ memcpy(&tmp, snd->in_tok.value, sizeof(tmp));
+ tmp = be64_to_cpu(tmp);
+ flags = (uint32_t)(tmp & 0x00000000ffffffff);
+ memset(&cred, 0, sizeof(cred));
+ cred.cr_mapped_uid = -1;
+
+ if (flags & LGSS_ROOT_CRED_ROOT)
+ cred.cr_usr_root = 1;
+ if (flags & LGSS_ROOT_CRED_MDT)
+ cred.cr_usr_mds = 1;
+ if (flags & LGSS_ROOT_CRED_OST)
+ cred.cr_usr_oss = 1;
+
+ do_svc_downcall(&snd->out_handle, &cred, snd->mech, &snd->ctx_token);
+
+ /* cleanup ctx_token, out_tok is cleaned up in handle_channel_req */
+ free(snd->ctx_token.value);
+ snd->ctx_token.length = 0;
+
+ return 0;
+}
+
static int handle_krb(struct svc_nego_data *snd)
{
u_int32_t ret_flags;
printerr(2, "handling request\n");
if (readline(fileno(f), &lbuf, &lbuflen) != 1) {
- printerr(0, "WARNING: handle_req: failed reading request\n");
+ printerr(0, "WARNING: failed reading request\n");
return -1;
}
}
snd.mech = &krb5oid;
break;
+ case LGSS_MECH_NULL:
+ if (!null_enabled) {
+ printerr(1, "WARNING: Request for gssnull but service "
+ "support not enabled\n");
+ goto ignore;
+ }
+ snd.mech = &nulloid;
+ break;
+ case LGSS_MECH_SK:
+ if (!sk_enabled) {
+ printerr(1, "WARNING: Request for sk but service "
+ "support not enabled\n");
+ goto ignore;
+ }
+ snd.mech = &skoid;
+ break;
default:
printerr(0, "WARNING: invalid mechanism recevied: %d\n",
lustre_mech);
get_len = qword_get(&cp, snd.in_handle.value, sizeof(in_handle_buf));
if (get_len < 0) {
- printerr(0, "WARNING: handle_req: failed parsing request\n");
+ printerr(0, "WARNING: failed parsing request\n");
goto out_err;
}
snd.in_handle.length = (size_t)get_len;
get_len = qword_get(&cp, snd.in_tok.value, sizeof(in_tok_buf));
if (get_len < 0) {
- printerr(0, "WARNING: handle_req: failed parsing request\n");
+ printerr(0, "WARNING: failed parsing request\n");
goto out_err;
}
snd.in_tok.length = (size_t)get_len;
if (snd.in_handle.length != 0) { /* CONTINUE_INIT case */
if (snd.in_handle.length != sizeof(snd.ctx)) {
- printerr(0, "WARNING: handle_req: "
- "input handle has unexpected length %zu\n",
- snd.in_handle.length);
+ printerr(0, "WARNING: input handle has unexpected "
+ "length %zu\n", snd.in_handle.length);
goto out_err;
}
/* in_handle is the context id stored in the out_handle
if (lustre_mech == LGSS_MECH_KRB5)
rc = handle_krb(&snd);
+ else if (lustre_mech == LGSS_MECH_SK)
+ rc = handle_sk(&snd);
+ else if (lustre_mech == LGSS_MECH_NULL)
+ rc = handle_null(&snd);
else
printerr(0, "WARNING: Received or request for"
"subflavor that is not enabled: %d\n", lustre_mech);
ignore:
return 0;
}
-