From: David Howells Date: Fri, 9 Feb 2018 10:32:08 +0000 (+0000) Subject: Initial commit X-Git-Tag: v0.3~23 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=2003f6665bc42f3cbd561d3fac77016612cff048;p=users%2Fdhowells%2Fkafs-client.git Initial commit --- 2003f6665bc42f3cbd561d3fac77016612cff048 diff --git a/afs.mount b/afs.mount new file mode 100644 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 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 [] +.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 +This is the name of the cell with which the ticket is intended to be used. +.IP +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 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 [] + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 []\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 index 0000000..80dea2f --- /dev/null +++ b/redhat/kafs-client.spec @@ -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 0.1-1 +- Initial commit