/* * MISC - Miscellaneous utility functions * * Author: * Emile van Bergen, emile@evbergen.xs4all.nl * * Permission to redistribute an original or modified version of this program * in source, intermediate or object code form is hereby granted exclusively * under the terms of the GNU General Public License, version 2. Please see the * file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html. * * History: * 2001/06/25 - EvB - Moved hex and memrchr here from lang_vm.c * 2002/04/07 - EvB - Added encryption/decryption functions as used in * RFC 2868 (tunneling attrs) and RFC 2548 (MS VSAs) * 2002/02/23 - EvB - Added encrypt_attr_pap * 2005/06/01 - EvB - Added hmac_md5, moved get_random_data here from radclient */ char misc_id[] = "MISC - Copyright (C) 2001 Emile van Bergen."; /* * INCLUDES & DEFINES */ #include /* For memset / memcpy / strlen */ #include /* For open */ #include /* For rand */ #include /* For O_RDONLY */ #include #include /* For putord / getord */ #include #include #include /* * FUNCTIONS */ char *memrchr(char *s, int c, ssize_t len) { char *ret; if (len && s) { for(ret = s + len - 1; ret >= s; ret--) if (*ret == c) return ret; } return 0; } void hex(char *buf, const char *src, ssize_t len) { static char *hextbl = "0123456789abcdef"; for( ; len > 0; len--) { *buf++ = hextbl[(*src >> 4) & 0xf]; *buf++ = hextbl[*src++ & 0xf]; } } /* Get reasonably good random data */ #define RANDOM_DEV "/dev/urandom" void get_random_data(char *p, ssize_t len) { #ifdef RANDOM_DEV int fd; ssize_t n; if ((fd = open(RANDOM_DEV, O_RDONLY)) != -1 && (n = read(fd, p, len)) == len && close(fd) != -1) return; msg(F_MISC, L_NOTICE, "Warning: No data from " RANDOM_DEV ", using rand()\n"); #endif while(len-- > 0) *p++ = (char)(256.0 * rand() / (RAND_MAX + 1.0)); } /* * Encrypt/decrypt string attributes, User-Password (PAP) style (RFC2865) */ /* These functions allow encryption/decryption in place, so it's possible to set dst to src and dstlen pointing to srclen. Note that dstlen in no way limits the number of bytes output; in case of encryption that's always srclen rounded up to an even multiple of 16, and in case of decryption it's srclen or less, srclen - 15 minimum. */ void encrypt_attr_pap(char *src, META_ORD srclen, char *dst, META_ORD *dstlen, char *sec, META_ORD seclen, char *auth, META_ORD authlen) { char *i, *o, *p, md5buf[16], *m; int l, chunks, n, retlen; md5_state_t md5ctx; /* Use request authenticator after secret to hash first chunk */ p = auth; l = authlen; /* Initialise */ i = src; o = dst; retlen = 0; /* Process full chunks at beginning, if any */ for(chunks = srclen >> 4; chunks > 0; chunks--) { md5_init(&md5ctx); md5_append(&md5ctx, sec, seclen); md5_append(&md5ctx, p, l); md5_finish(&md5ctx, md5buf); p = o; l = 16; for(m = md5buf, n = l; n > 0; n--) *o++ = *i++ ^ *m++; retlen += l; } /* Process partial chunk at end, if any */ if (srclen & 15) { md5_init(&md5ctx); md5_append(&md5ctx, sec, seclen); md5_append(&md5ctx, p, l); md5_finish(&md5ctx, md5buf); for(m = md5buf, n = srclen & 15; n > 0; n--) *o++ = *i++ ^ *m++; memcpy(o, m, 16 - (srclen & 15)); /* 0 ^ m = m after all */ retlen += 16; } *dstlen = retlen; } void decrypt_attr_pap(char *src, META_ORD srclen, char *dst, META_ORD *dstlen, char *sec, META_ORD seclen, char *auth, META_ORD authlen) { char *i, *o, md5buf[16], *m; int n, retlen; md5_state_t md5ctx; /* Initialise. Round down source length to multiple of 16 and * exit if not at least one chunk. */ srclen &= ~0xf; if (!srclen) { *dstlen = 0; return; } i = src + srclen; o = dst + srclen; retlen = 0; /* Process chunks beyond first one, if any, using md5(sec . prev) * Everything happens in reverse to allow decrypting in place */ while (i >= src + 32) { md5_init(&md5ctx); md5_append(&md5ctx, sec, seclen); md5_append(&md5ctx, i - 32, 16); md5_finish(&md5ctx, md5buf); for(n = 16, m = md5buf + n; n > 0; n--) *--o = *--i ^ *--m; retlen += 16; } /* Process first chunk using md5(sec . auth) */ md5_init(&md5ctx); md5_append(&md5ctx, sec, seclen); md5_append(&md5ctx, auth, authlen); md5_finish(&md5ctx, md5buf); for(n = 16, m = md5buf + n; n > 0; n--) *--o = *--i ^ *--m; retlen += 16; *dstlen = retlen; } /* * Encrypt/decrypt string attributes, style 1 (RFC2868) * * See RFC 2868, section 3.5 for details. Currently probably indeed * only useful for Tunnel-Password, but why make it a special case. * It's optimized a little for speed, but it could probably be better. * * Both operations are done in place; for encryption, there must be * enough *extra* room for 16 - (cleartextlen + 1) % 16 + 2 bytes. * To be safe, you could simply have 18 bytes extra at all times. * The maximum cleartext length is limited to 256 bytes, so 274 bytes * is the maximum that can ever get output. */ #define CLEAR_STRING_LEN 256 /* The RFC says it is */ #define AUTHENTICATOR_LEN 16 /* The RFC says it is */ #define MD5_LEN 16 /* The algorithm specifies it */ #define SALT_LEN 2 /* The RFC says it is */ void encrypt_attr_style_1(char *text, META_ORD *len, char *secret,char *reqauth) { char clear_buf[CLEAR_STRING_LEN]; char work_buf[C_MAX_SECRSIZE + AUTHENTICATOR_LEN + SALT_LEN]; char digest[MD5_LEN]; char *i,*o; unsigned short salt; int clear_len; int work_len; int secret_len; int n; /* Create the string we'll actually be processing by copying up to 255 bytes of original cleartext, padding it with zeroes to a multiple of 16 bytes and inserting a length octet in front. */ /* Limit length */ clear_len = *len; if (clear_len > CLEAR_STRING_LEN - 1) clear_len = CLEAR_STRING_LEN - 1; /* Write the 'limited original' length byte and copy the buffer */ *clear_buf = clear_len; memcpy(clear_buf + 1, text, clear_len); /* From now on, the length byte is included with the byte count */ clear_len++; /* Pad the string to a multiple of 1 chunk */ if (clear_len % MD5_LEN) { memset(clear_buf+clear_len, 0, MD5_LEN - (clear_len % MD5_LEN)); } /* Define input and number of chunks to process */ i = clear_buf; clear_len = (clear_len + (MD5_LEN - 1)) / MD5_LEN; /* Define output and its starting length */ o = text; *len = sizeof(salt); /* * Fill in salt. Must be unique per attribute that uses it in the same * packet, and the most significant bit must be set - see RFC 2868. * We use the memory adddress of the text xor-ed with the longword * from the request authenticator. */ salt = ( ((long)text ^ *(long *)reqauth) & 0xffff ) | 0x8000; putord(o, sizeof(salt), salt); o += sizeof(salt); /* Create a first working buffer to calc the MD5 hash over */ secret_len = strlen(secret); /* already limited by read_clients */ memcpy(work_buf, secret, secret_len); memcpy(work_buf + secret_len, reqauth, AUTHENTICATOR_LEN); putord(work_buf + secret_len + AUTHENTICATOR_LEN, sizeof(salt), salt); work_len = secret_len + AUTHENTICATOR_LEN + sizeof(salt); for( ; clear_len; clear_len--) { /* Get the digest */ md5(digest, work_buf, work_len); /* Xor the clear text to get the output chunk and next buffer */ for(n = 0; n < MD5_LEN; n++) { *(work_buf + secret_len + n) = *o++ = *i++ ^ digest[n]; } /* This is the size of the next working buffer */ work_len = secret_len + MD5_LEN; /* Increment the output length */ *len += MD5_LEN; } } int decrypt_attr_style_1(char *text, META_ORD *len, char *secret, char *reqauth) { char work_buf[C_MAX_SECRSIZE + AUTHENTICATOR_LEN + SALT_LEN]; char digest[MD5_LEN]; char *i,*o; unsigned short salt; int chunks; int secret_len; int n; /* Check the length; the salt and at least one chunk must be present, and partial chunks are not allowed. */ if (((*len - SALT_LEN) < MD5_LEN) || ((*len - SALT_LEN) % MD5_LEN)) { msg(F_MISC, L_ERR, "decrypt_attr_style_1: bogus cyphertext " "length %d\n", *len); return -1; } /* Define input */ i = text; chunks = ((*len - SALT_LEN) + (MD5_LEN - 1)) / MD5_LEN; /* Define output - it's done in place, overwriting the salt! */ o = text; /* Get the salt from the input and check it */ salt = getord(i, sizeof(salt)); i += sizeof(salt); if (!(salt & 0x8000)) { msg(F_MISC, L_ERR, "decrypt_attr_style_1: bogus salt 0x%04x\n", salt); return -1; } /* Create a first working buffer to calculate the MD5 hash over */ secret_len = strlen(secret); /* already limited by read_clients */ memcpy(work_buf, secret, secret_len); memcpy(work_buf + secret_len, reqauth, AUTHENTICATOR_LEN); putord(work_buf + secret_len + AUTHENTICATOR_LEN, sizeof(salt), salt); /* Calculate the digest */ md5(digest, work_buf, secret_len+AUTHENTICATOR_LEN+sizeof(salt)); /* Process the first byte: the cleartext length */ *(work_buf + secret_len + 0) = *i; *len = *i++ ^ digest[0]; if ((*len > CLEAR_STRING_LEN) || (*len > MD5_LEN * chunks) || (*len < MD5_LEN * (chunks - 1))) { msg(F_MISC, L_ERR, "decrypt_attr_style_1: bogus decrypted " "length %d\n", *len); return -1; } /* Process the rest of the first chunk */ for(n = 1; n < MD5_LEN; n++) { *(work_buf + secret_len + n) = *i; *o++ = *i++ ^ digest[n]; } /* One full chunk is already done. */ chunks--; /* Loop over the rest of the chunks */ for( ; chunks > 0; chunks--) { /* Get the digest */ md5(digest, work_buf, secret_len + MD5_LEN); /* Fill the work buffer with the next cyphertext chunk and xor the it with the digest to get the output segment */ for(n = 0; n < MD5_LEN; n++) { *(work_buf + secret_len + n) = *i; *o++ = *i++ ^ digest[n]; } } return 0; } /* Compute HMAC using MD5 as a hash */ void hmac_md5(char *out, char *in, META_ORD inl, char *key, META_ORD keyl) { static unsigned char ipad[64], opad[64], inner[16]; unsigned char *ip = ipad, *op = opad; md5_state_t mds; if (keyl > 64) { md5_init(&mds); md5_append(&mds, key, keyl); md5_finish(&mds, inner); key = inner; keyl = 16; } memset(ipad, 0x36, 64); memset(opad, 0x5c, 64); while(keyl--) { *ip++ ^= *key; *op++ ^= *key++; } md5_init(&mds); md5_append(&mds, ipad, 64); md5_append(&mds, (unsigned char *)in, inl); md5_finish(&mds, inner); md5_init(&mds); md5_append(&mds, opad, 64); md5_append(&mds, inner, 16); md5_finish(&mds, out); }