/*
* 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 <string.h> /* For memset / memcpy / strlen */
#include <unistd.h> /* For open */
#include <stdlib.h> /* For rand */
#include <fcntl.h> /* For O_RDONLY */
#include <constants.h>
#include <metadata.h> /* For putord / getord */
#include <debug.h>
#include <md5.h>
#include <misc.h>
/*
* 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);
}
syntax highlighted by Code2HTML, v. 0.9.1