]> www.infradead.org Git - users/dhowells/kafs-client.git/commitdiff
Initial commit
authorDavid Howells <dhowells@redhat.com>
Fri, 9 Feb 2018 10:32:08 +0000 (10:32 +0000)
committerDavid Howells <dhowells@redhat.com>
Fri, 9 Feb 2018 10:32:08 +0000 (10:32 +0000)
afs.mount [new file with mode: 0644]
aklog.1 [new file with mode: 0644]
aklog.c [new file with mode: 0644]
redhat/kafs-client.spec [new file with mode: 0644]

diff --git a/afs.mount b/afs.mount
new file mode 100644 (file)
index 0000000..207f3a7
--- /dev/null
+++ b/afs.mount
@@ -0,0 +1,12 @@
+[Unit]
+Description=AFS Dynamic Root mount
+ConditionPathExists=/afs
+
+[Mount]
+What=none
+Where=/afs
+Type=afs
+Options=_netdev,dyn
+
+[Install]
+WantedBy=remote-fs.target
diff --git a/aklog.1 b/aklog.1
new file mode 100644 (file)
index 0000000..e6187e9
--- /dev/null
+++ b/aklog.1
@@ -0,0 +1,35 @@
+.\"
+.\" Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+.\" Written by David Howells (dhowells@redhat.com)
+.\"
+.\" This program is free software; you can redistribute it and/or
+.\" modify it under the terms of the GNU General Public License
+.\" as published by the Free Software Foundation; either version
+.\" 2 of the License, or (at your option) any later version.
+.\"
+.TH AKLOG 1 "9 Feb 2018" Linux "AFS Kerberos authentication"
+.SH NAME
+aklog \- AFS Kerberos authentication tool
+.SH SYNOPSIS
+\fBaklog\fR <cell> [<realm>]
+.P
+.B
+*** NOTE THE ABOVE IS PROVISIONAL AND IS LIKELY TO CHANGE ***
+.R
+.SH DESCRIPTION
+This program is used to get an authentication ticket from Kerberos that can be
+used by the kAFS filesystem to perform authenticated and encrypted accesses to
+the server.  Without this only unencrypted anonymous accesses can be made.
+.P
+Before calling this, the \fBkinit\fR program or similar should be invoked to
+authenticate with the appropriate Kerberos server.
+.SH ARGUMENTS
+.IP <cell>
+This is the name of the cell with which the ticket is intended to be used.
+.IP <realm>
+This is the name of the Kerberos realm from which the ticket will be obtained.
+.SH ERRORS
+.SH SEE ALSO
+.ad l
+.nh
+.BR kinit (1)
diff --git a/aklog.c b/aklog.c
new file mode 100644 (file)
index 0000000..8e4cd47
--- /dev/null
+++ b/aklog.c
@@ -0,0 +1,335 @@
+/* aklog.c: description
+ *
+ * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * Based on code:
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ * Copyright (C) 2008 Chaskiel Grundman. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Kerberos-5 strong enctype support for rxkad:
+ *     https://tools.ietf.org/html/draft-kaduk-afs3-rxkad-k5-kdf-00
+ *
+ * Invoke as: aklog-k5 <cell> [<realm>]
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <keyutils.h>
+#include <krb5/krb5.h>
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/des.h>
+#include <openssl/md5.h>
+#include <openssl/err.h>
+
+struct rxrpc_key_sec2_v1 {
+        uint32_t        kver;                   /* key payload interface version */
+        uint16_t        security_index;         /* RxRPC header security index */
+        uint16_t        ticket_length;          /* length of ticket[] */
+        uint32_t        expiry;                 /* time at which expires */
+        uint32_t        kvno;                   /* key version number */
+        uint8_t         session_key[8];         /* DES session key */
+        uint8_t         ticket[0];              /* the encrypted ticket */
+};
+
+#define RXKAD_TKT_TYPE_KERBEROS_V5              256
+#define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0)
+#define OSZERROR(X, Y) do { if ((long)(X) == 0) { perror(Y); exit(1); } } while(0)
+#define KRBERROR(X, Y) do { if ((X) != 0) { const char *msg = krb5_get_error_message(k5_ctx, (X)); fprintf(stderr, "%s: %s\n", (Y), msg); krb5_free_error_message(k5_ctx, msg); exit(1); } } while(0)
+
+/*
+ * Report an error from the crypto lib.
+ */
+static void crypto_error(const char *msg)
+{
+       const char *file;
+       char buf[120];
+       int e, line;
+
+       if (ERR_peek_error() == 0)
+               return;
+       fprintf(stderr, "aklog: %s:\n", msg);
+
+       while ((e = ERR_get_error_line(&file, &line))) {
+               ERR_error_string(e, buf);
+               fprintf(stderr, "- SSL %s: %s:%d\n", buf, file, line);
+       }
+
+       exit(1);
+}
+
+/*
+ * Strip Triple-DES parity bits from a block.
+ *
+ * Discard the parity bits and converts an 8-octet block to a 7-octet block.
+ *
+ * See [afs3-rxkad-k5-kdf-00 §4.2] and [RFC3961 §6.3.1].
+ *
+ * [These docs number the bits weirdly.  Bit '8' appears to be the LSB of the
+ * first octet, and 1 the MSB].
+ */
+static void des3_strip_parity_bits(void *random, const void *key)
+{
+       const unsigned char *k = key;
+       unsigned char *r = random, lsbs;
+       int i;
+
+       lsbs = k[7] >> 1;
+       for (i = 0; i < 7; i++) {
+               r[i] = (k[i] & 0xfe) | (lsbs & 0x1);
+               lsbs >>= 1;
+       }
+}
+
+/*
+ * Reverse the Triple-DES random-to-key operation, converting three 64-bit DES
+ * keys to 56-bit random strings and concatenate to give a 168-bit random
+ * string that can then be fed to the KDF.
+ */
+static unsigned int des3_key_to_random(void *random, const void *key, unsigned int len)
+{
+       unsigned int new_len = 0;
+
+       while (len > 8) {
+               des3_strip_parity_bits(random, key);
+               key += 8;
+               random += 7;
+               len -= 8;
+               new_len += 7;
+       }
+
+       return new_len;
+}
+
+/*
+ * The data to pass into the key derivation function.
+ */
+struct kdf_data {
+       unsigned char i_2;
+       unsigned char Label[6];
+       unsigned char L_2[4];
+} __attribute__((packed));
+
+static const struct kdf_data rxkad_kdf_data = {
+       .Label  = "rxkad",              /* Including NUL separator */
+       .L_2    = { 0, 0, 0, 64 },      /* BE integer */
+};
+
+/*
+ * Derive a 64-bit key we can pass to rxkad from the ticket data.  The ticket
+ * data is used as the key for the HMAC-MD5 algorithm, which is used as the
+ * PRF.  We then iterate over a series of constructed source strings, passing
+ * each one through the PRF until we get an MD5 output that we can cut down and
+ * use as a substitute for the DES session key that isn't too weak.
+ *
+ * [afs3-rxkad-k5-kdf-00 §4.3]
+ */
+static void key_derivation_function(krb5_creds *creds, uint8_t *session_key)
+{
+       unsigned int i, len;
+       union {
+               unsigned char md5[MD5_DIGEST_LENGTH];
+               DES_cblock des;
+       } buf;
+       const EVP_MD *algo = EVP_md5(); /* We use HMAC-MD5 */
+
+       struct kdf_data kdf_data = rxkad_kdf_data;
+       
+       for (i = 1; i <= 255; i++) {
+               /* K(i) = PRF(Ks, [i]_2 || Label || 0x00 || [L]_2) */
+               kdf_data.i_2 = i;
+               len = sizeof(buf.md5);
+               if (!HMAC(algo,
+                         creds->keyblock.contents, creds->keyblock.length,
+                         (unsigned char *)&kdf_data, sizeof(kdf_data),
+                         buf.md5, &len))
+                           crypto_error("HMAC");
+
+               if (len < sizeof(buf.des)) {
+                       fprintf(stderr, "aklog: HMAC returned short result\n");
+                       exit(1);
+               }
+
+               /* Overlay the DES parity. */
+               DES_set_odd_parity(&buf.des);
+               if (!DES_is_weak_key(&buf.des))
+                       goto success;
+       }
+
+       fprintf(stderr, "aklog: Unable to derive strong DES key\n");
+       exit(1);
+
+success:
+       memcpy(session_key, buf.des, sizeof(buf.des));
+}
+
+/*
+ * Extract or derive the session key.
+ */
+static void derive_key(krb5_creds *creds, uint8_t *session_key)
+{
+       unsigned int length = creds->keyblock.length;
+
+       switch (creds->keyblock.enctype) {
+       case ENCTYPE_NULL:              goto not_supported;
+       case ENCTYPE_DES_CBC_CRC:       goto just_copy;
+       case ENCTYPE_DES_CBC_MD4:       goto just_copy;
+       case ENCTYPE_DES_CBC_MD5:       goto just_copy;
+       case ENCTYPE_DES_CBC_RAW:       goto deprecated;
+       case ENCTYPE_DES3_CBC_SHA:      goto des3_discard_parity; /* des3-cbc-md5 */
+       case ENCTYPE_DES3_CBC_RAW:      goto deprecated;
+       case 7:                         goto des3_discard_parity; /* des3-cbc-sha1 */
+       case ENCTYPE_DES_HMAC_SHA1:     goto deprecated;
+       case ENCTYPE_DSA_SHA1_CMS:      goto not_supported;
+       case ENCTYPE_MD5_RSA_CMS:       goto not_supported;
+       case ENCTYPE_SHA1_RSA_CMS:      goto not_supported;
+       case ENCTYPE_RC2_CBC_ENV:       goto not_supported;
+       case ENCTYPE_RSA_ENV:           goto not_supported;
+       case ENCTYPE_RSA_ES_OAEP_ENV:   goto not_supported;
+       case ENCTYPE_DES3_CBC_ENV:      goto not_supported;
+       case ENCTYPE_DES3_CBC_SHA1:     goto des3_discard_parity; /* des3-cbc-sha1-kd */
+       default:
+               if (length < 7)
+                       goto key_too_short;
+               if (creds->keyblock.enctype < 0)
+                       goto not_supported;
+               goto derive_key;
+       }
+
+       /* Strip the parity bits for 3DES then do KDF [afs3-rxkad-k5-kdf-00 §4.2]. */
+des3_discard_parity:
+       if (length & 7) {
+               fprintf(stderr, "aklog: 3DES session key not multiple of 8 octets.\n");
+               exit(1);
+       }
+       creds->keyblock.length = des3_key_to_random(creds->keyblock.contents,
+                                                   creds->keyblock.contents,
+                                                   length);
+       goto derive_key;
+
+       /* Do KDF [afs3-rxkad-k5-kdf-00 §4.3]. */
+derive_key:
+       key_derivation_function(creds, session_key);
+       return;
+
+       /* Use as-is for single-DES [afs3-rxkad-k5-kdf-00 §4.1]. */
+just_copy:
+       if (length != 8) {
+               fprintf(stderr, "aklog: DES session key not 8 octets.\n");
+               exit(1);
+       }
+
+       memcpy(session_key, creds->keyblock.contents, length);
+       return;
+
+deprecated:
+       fprintf(stderr, "aklog: Ticket contains deprecated enc type (%d)\n",
+               creds->keyblock.enctype);
+       exit(1);
+
+not_supported:
+       fprintf(stderr, "aklog: Ticket contains unsupported enc type (%d)\n",
+               creds->keyblock.enctype);
+       exit(1);
+key_too_short:
+       fprintf(stderr, "aklog: Ticket contains short key block (%u)\n", length);
+       exit(1);
+}
+
+int main(int argc, char **argv)
+{
+       char *cell, *realm, *princ, *desc, *p;
+       int ret;
+       size_t plen;
+       struct rxrpc_key_sec2_v1 *payload;
+       krb5_error_code kresult;
+       krb5_context k5_ctx;
+       krb5_ccache cc;
+       krb5_creds search_cred, *creds;
+
+       if (argc < 2 || argc > 3) {
+               fprintf(stderr, "Usage: aklog <cell> [<realm>]\n");
+               exit(1);
+       }
+
+       cell = argv[1];
+       if (argc == 3) {
+               realm = strdup(argv[3]);
+               OSZERROR(realm, "strdup");
+       } else {
+               realm = strdup(cell);
+               OSZERROR(realm, "strdup");
+               for (p = realm; *p; p++)
+                       *p = toupper(*p);
+       }
+
+       for (p = cell; *p; p++)
+               *p = tolower(*p);
+       
+       ret = asprintf(&princ, "afs/%s@%s", cell, realm);
+       OSERROR(ret, "asprintf");
+       ret = asprintf(&desc, "afs@%s", cell);
+       OSERROR(ret, "asprintf");
+
+       printf("CELL %s\n", cell);
+       printf("PRINC %s\n", princ);
+
+       kresult = krb5_init_context(&k5_ctx);
+       if (kresult) { fprintf(stderr, "krb5_init_context failed\n"); exit(1); }
+
+       kresult = krb5_cc_default(k5_ctx, &cc);
+       KRBERROR(kresult, "Getting credential cache");
+
+       memset(&search_cred, 0, sizeof(krb5_creds));
+
+       kresult = krb5_cc_get_principal(k5_ctx, cc, &search_cred.client);
+       KRBERROR(kresult, "Getting client principal");
+
+       kresult = krb5_parse_name(k5_ctx, princ, &search_cred.server);
+       KRBERROR(kresult, "Parsing server principal name");
+
+       //search_cred.keyblock.enctype = ENCTYPE_DES_CBC_CRC;
+
+       kresult = krb5_get_credentials(k5_ctx, 0, cc, &search_cred, &creds);
+       KRBERROR(kresult, "Getting tickets");
+
+       plen = sizeof(*payload) + creds->ticket.length;
+       payload = calloc(1, plen + 4);
+       if (!payload) {
+               perror("calloc");
+               exit(1);
+       }
+
+       printf("plen=%zu tklen=%u rk=%zu\n",
+              plen, creds->ticket.length, sizeof(*payload));
+
+       /* use version 1 of the key data interface */
+       payload->kver           = 1;
+       payload->security_index = 2;
+       payload->ticket_length  = creds->ticket.length;
+       payload->expiry         = creds->times.endtime;
+       payload->kvno           = RXKAD_TKT_TYPE_KERBEROS_V5;
+
+       derive_key(creds, payload->session_key);
+       memcpy(payload->ticket, creds->ticket.data, creds->ticket.length);
+
+       ret = add_key("rxrpc", desc, payload, plen, KEY_SPEC_SESSION_KEYRING);
+       OSERROR(ret, "add_key");
+
+       krb5_free_creds(k5_ctx, creds);
+       krb5_free_cred_contents(k5_ctx, &search_cred);
+       krb5_cc_close(k5_ctx, cc);
+       krb5_free_context(k5_ctx);
+       exit(0);
+}
diff --git a/redhat/kafs-client.spec b/redhat/kafs-client.spec
new file mode 100644 (file)
index 0000000..80dea2f
--- /dev/null
@@ -0,0 +1,65 @@
+# % define buildid .local
+
+Name:          kafs-client
+Version:       0.1
+Release:       1%{?dist}%{?buildid}
+Summary:       kAFS basic tools and /afs dynamic root
+License:       GPLv2+
+URL:           https://www.infradead.org/~dhowells/kafs/
+Source0:       https://www.infradead.org/~dhowells/kafs/kafs-client-%{version}.tar.bz2
+
+BuildRequires: systemd-units
+Requires(post): systemd-units
+Requires(preun): systemd-units
+Requires(postun): systemd-units
+Requires: selinux-policy-base >= 3.7.19-5
+
+%define _hardened_build 1
+
+%description
+Provide basic AFS-compatible tools for kAFS and mount the dynamic root
+on /afs.
+
+%global docdir %{_docdir}/kafs-client
+
+%prep
+%setup -q
+
+%build
+make all \
+       ETCDIR=%{_sysconfdir} \
+       BINDIR=%{_bindir} \
+       MANDIR=%{_mandir} \
+       CFLAGS="-Wall -Werror $RPM_OPT_FLAGS $RPM_LD_FLAGS $ARCH_OPT_FLAGS"
+
+%install
+mkdir -p %{buildroot}%{_bindir}
+mkdir -p %{buildroot}%{_unitdir}
+mkdir -p %{buildroot}%{_mandir}/man1
+
+make DESTDIR=%{buildroot} install \
+       ETCDIR=%{_sysconfdir} \
+       SBINDIR=%{_bindir} \
+       MANDIR=%{_mandir} \
+       CFLAGS="-Wall $RPM_OPT_FLAGS -Werror"
+
+install -m 644 afs.mount %{buildroot}%{_unitdir}/afs.mount
+
+%post
+%systemd_post afs.mount
+
+%preun
+%systemd_preun afs.mount
+
+%postun
+%systemd_postun_with_restart afs.mount
+
+%files
+%doc README
+%{_bindir}/*
+%{_unitdir}/*
+%{_mandir}/*/*
+
+%changelog
+* Fri Feb 9 2018 David Howells <dhowells@redhat.com> 0.1-1
+- Initial commit