/* 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); }