/****************************************************************************** * * Project: MapServer * Purpose: Encryption functions (see MS-RFC-18) * Author: Daniel Morissette * ****************************************************************************** * Copyright (c) 1996-2006 Regents of the University of Minnesota. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of this Software or works derived from this Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************** * * $Log: mapcrypto.c,v $ * Revision 1.4 2006/06/02 20:34:24 dan * REmoved msDebug() call in msReadEncryptionKeyFromFile() * * Revision 1.3 2006/06/02 18:58:09 dan * Doc update: Buffer passed to msEncryptStringWithKey() has to be * strlen(in)+16+1 bytes and not strlen(in)+8+1 to account for hex encoding of * padding bytes * * Revision 1.2 2006/06/01 20:51:02 dan * Fixed possible buffer overflow in msDecryptStringTokens() and improved * error message if key file could not be read. * * Revision 1.1 2006/06/01 19:56:31 dan * Added ability to encrypt tokens (passwords, etc.) in database connection * strings (MS-RFC-18, bug 1792) * */ #include #include /* isxdigit() */ #include /* rand() */ #include /* time() */ #include "map.h" MS_CVSID("$Id: mapcrypto.c,v 1.4 2006/06/02 20:34:24 dan Exp $") /********************************************************************** * encipher() and decipher() from the Tiny Encryption Algorithm (TEA) * website at: * http://www.simonshepherd.supanet.com/tea.htm * * TEA was developed and placed in the public domain by David Wheeler * and Roger Needham at the Computer Laboratory of Cambridge University. * * The source below came with the following public domain notice: * * "Please feel free to use any of this code in your applications. * The TEA algorithm (including new-variant TEA) has been placed * in the public domain, as have my assembly language implementations." * * ... and the following usage notes: * * All the routines have the general form * * void encipher(const unsigned long *const v,unsigned long *const w, * const unsigned long * const k); * * void decipher(const unsigned long *const v,unsigned long *const w, * const unsigned long * const k); * * TEA takes 64 bits of data in v[0] and v[1], and 128 bits of key in * k[0] - k[3]. The result is returned in w[0] and w[1]. Returning the * result separately makes implementation of cipher modes other than * Electronic Code Book a little bit easier. * * TEA can be operated in any of the modes of DES. * * n is the number of iterations. 32 is ample, 16 is sufficient, as few * as eight should be OK for most applications, especially ones where * the data age quickly (real-time video, for example). The algorithm * achieves good dispersion after six iterations. The iteration count * can be made variable if required. * * Note this algorithm is optimised for 32-bit CPUs with fast shift * capabilities. It can very easily be ported to assembly language * on most CPUs. * * delta is chosen to be the Golden ratio ((5/4)1/2 - 1/2 ~ 0.618034) * multiplied by 232. On entry to decipher(), sum is set to be delta * n. * Which way round you call the functions is arbitrary: DK(EK(P)) = EK(DK(P)) * where EK and DK are encryption and decryption under key K respectively. * **********************************************************************/ static void encipher(const unsigned long *const v,unsigned long *const w, const unsigned long *const k) { register unsigned long y=v[0],z=v[1],sum=0,delta=0x9E3779B9,n=32; while(n-->0) { y += ((z << 4 ^ z >> 5) + z) ^ (sum + k[sum&3]); sum += delta; z += ((y << 4 ^ y >> 5) + y) ^ (sum + k[sum>>11 & 3]); } w[0]=y; w[1]=z; } static void decipher(const unsigned long *const v,unsigned long *const w, const unsigned long *const k) { register unsigned long y=v[0],z=v[1],sum=0xC6EF3720, delta=0x9E3779B9,n=32; /* sum = delta<<5, in general sum = delta * n */ while(n-->0) { z -= ((y << 4 ^ y >> 5) + y) ^ (sum + k[sum>>11 & 3]); sum -= delta; y -= ((z << 4 ^ z >> 5) + z) ^ (sum + k[sum&3]); } w[0]=y; w[1]=z; } /********************************************************************** * msHexEncode() * * Hex-encode numbytes from in[] and return the result in out[]. * * out[] should be preallocated by the caller to be at least 2*numbytes+1 * (+1 for the terminating '\0') **********************************************************************/ void msHexEncode(const unsigned char *in, char *out, int numbytes) { char *hex = "0123456789ABCDEF"; while (numbytes-- > 0) { *out++ = hex[*in/16]; *out++ = hex[*in%16]; in++; } *out = '\0'; } /********************************************************************** * msHexDecode() * * Hex-decode numchars from in[] and return the result in out[]. * * If numchars > 0 then only up to this number of chars from in[] are * processed, otherwise the full in[] string up to the '\0' is processed. * * out[] should be preallocated by the caller to be large enough to hold * the resulting bytes. * * Returns the number of bytes written to out[] which may be different from * numchars/2 if an error or a '\0' is encountered. **********************************************************************/ int msHexDecode(const char *in, unsigned char *out, int numchars) { int numbytes_out = 0; /* Make sure numchars is even */ numchars = (numchars/2) * 2; if (numchars < 2) numchars = -1; /* Will result in this value being ignored in the loop*/ while (*in != '\0' && *(in+1) != '\0' && numchars != 0) { *out = 0x10 * (*in >= 'A' ? ((*in & 0xdf) - 'A')+10 : (*in - '0')); in++; *out += (*in >= 'A' ? ((*in & 0xdf) - 'A')+10 : (*in - '0')); in++; out++; numbytes_out++; numchars -= 2; } return numbytes_out; } /********************************************************************** * msGenerateEncryptionKey() * * Create a new encryption key. * * The output buffer should be at least MS_ENCRYPTION_KEY_SIZE bytes. **********************************************************************/ int msGenerateEncryptionKey(unsigned char *k) { int i; /* Use current time as seed for rand() */ srand( (unsigned int) time( NULL )); for(i=0; iencryption_key. * If the key is already set in the mapObj then it does nothing and returns. * * The location of the encryption key can be specified in two ways, * either by setting the environment variable MS_ENCRYPTION_KEY or using * a CONFIG directive: * CONFIG MS_ENCRYPTION_KEY "/path/to/mykey.txt" * Returns MS_SUCCESS/MS_FAILURE. **********************************************************************/ static int msLoadEncryptionKey(mapObj *map) { const char *keyfile; if (map == NULL) { msSetError(MS_MISCERR, "NULL MapObj.", "msLoadEncryptionKey()"); return MS_FAILURE; } if (map->encryption_key_loaded) return MS_SUCCESS; /* Already loaded */ keyfile = msGetConfigOption(map, "MS_ENCRYPTION_KEY"); if (keyfile == NULL) keyfile = getenv("MS_ENCRYPTION_KEY"); if (keyfile && msReadEncryptionKeyFromFile(keyfile,map->encryption_key) == MS_SUCCESS) { map->encryption_key_loaded = MS_TRUE; } else { msSetError(MS_MISCERR, "Failed reading encryption key. Make sure " "MS_ENCRYPTION_KEY is set and points to a valid key file.", "msLoadEncryptionKey()"); return MS_FAILURE; } return MS_SUCCESS; } /********************************************************************** * msEncryptStringWithKey() * * Encrypts and hex-encodes the contents of string in[] and returns the * result in out[] which should have been pre-allocated by the caller * to be at least twice the size of in[] + 16+1 bytes (for padding + '\0'). * **********************************************************************/ void msEncryptStringWithKey(const unsigned char *key, const char *in, char *out) { unsigned long v[4], w[4]; const unsigned long *k; int last_block = MS_FALSE; /* Casting the key this way is safe only as long as longs are 4 bytes * on this platform */ assert(sizeof(unsigned long) == 4); k = (const unsigned long *) key; while(!last_block) { int i, j; /* encipher() takes v[2] (64 bits) as input. * Copy bytes from in[] to the v[2] input array (pair of 4 bytes) * v[] is padded with zeros if string doesn't align with 8 bytes */ v[0] = 0; v[1] = 0; for(i=0; !last_block && i<2; i++) { for(j=0; j<4; j++) { if (*in == '\0') { last_block = MS_TRUE; break; } v[i] |= *in << (j*8); in++; } } if (*in == '\0') last_block = MS_TRUE; /* Do the actual encryption */ encipher(v, w, k); /* Append hex-encoded bytes to output, 4 bytes at a time */ msHexEncode((unsigned char *)w, out, 4); out += 8; msHexEncode((unsigned char *)(w+1), out, 4); out += 8; } /* Make sure output is 0-terminated */ *out = '\0'; } /********************************************************************** * msDecryptStringWithKey() * * Hex-decodes and then decrypts the contents of string in[] and returns the * result in out[] which should have been pre-allocated by the caller * to be at least half the size of in[]. * **********************************************************************/ void msDecryptStringWithKey(const unsigned char *key, const char *in, char *out) { unsigned long v[4], w[4]; const unsigned long *k; int last_block = MS_FALSE; /* Casting the key this way is safe only as long as longs are 4 bytes * on this platform */ assert(sizeof(unsigned long) == 4); k = (const unsigned long *) key; while(!last_block) { int i; /* decipher() takes v[2] (64 bits) as input. * Copy bytes from in[] to the v[2] input array (pair of 4 bytes) * v[] is padded with zeros if string doesn't align with 8 bytes */ v[0] = 0; v[1] = 0; if (msHexDecode(in, (unsigned char *)v, 8) != 4) last_block = MS_TRUE; else { in += 8; if (msHexDecode(in, (unsigned char *)(v+1), 8) != 4) last_block = MS_TRUE; else in += 8; } /* Do the actual decryption */ decipher(v, w, k); /* Copy the results to out[] */ for(i=0; i<2; i++) { *out++ = (w[i] & 0x000000ff); *out++ = (w[i] & 0x0000ff00) >> 8; *out++ = (w[i] & 0x00ff0000) >> 16; *out++ = (w[i] & 0xff000000) >> 24; } if (*in == '\0') last_block = MS_TRUE; } /* Make sure output is 0-terminated */ *out = '\0'; } /********************************************************************** * msDecryptStringTokens() * * Returns a newly allocated string (to be msFree'd by the caller) in * which all occurences of encrypted strings delimited by {...} have * been decrypted. * **********************************************************************/ char *msDecryptStringTokens(mapObj *map, const char *in) { char *outbuf, *out; if (map == NULL) { msSetError(MS_MISCERR, "NULL MapObj.", "msLoadEncryptionKey()"); return NULL; } /* Start with a copy of the string. Decryption can only result in * a string with the same or shorter length */ if ((outbuf = (char *)malloc((strlen(in)+1)*sizeof(char))) == NULL) { msSetError(MS_MEMERR, NULL, "msDecryptStringTokens()"); return NULL; } out = outbuf; while(*in != '\0') { if (*in == '{') { /* Possibly beginning of a token, look for closing bracket ** and make sure all chars in between are valid hex encoding chars */ const char *pszStart, *pszEnd; int valid_token = MS_FALSE; pszStart = in+1; if ( (pszEnd = strchr(pszStart, '}')) != NULL && pszEnd - pszStart > 1) { const char *pszTmp; valid_token = MS_TRUE; for(pszTmp = pszStart; pszTmp < pszEnd; pszTmp++) { if (!isxdigit(*pszTmp)) { valid_token = MS_FALSE; break; } } } if (valid_token) { /* Go ahead and decrypt the token */ char *pszTmp; /* Make sure encryption key is loaded. We do this here instead * of at the beginning of the function to avoid loading the * key unless ready necessary. This is a very cheap call if * the key is already loaded */ if (msLoadEncryptionKey(map) != MS_SUCCESS) return NULL; pszTmp = (char*)malloc( (pszEnd-pszStart+1)*sizeof(char)); strncpy(pszTmp, pszStart, pszEnd-pszStart); pszTmp[pszEnd-pszStart] = '\0'; msDecryptStringWithKey(map->encryption_key, pszTmp, out); out += strlen(out); in = pszEnd+1; } else { /* Not a valid token, just copy the '{' and keep going */ *out++ = *in++; } } else { /* Just copy any other chars */ *out++ = *in++; } } *out = '\0'; return outbuf; } #ifdef TEST_MAPCRYPTO /* Test for mapcrypto.c functions. To run these tests, use the following ** Makefile directive: test_mapcrypto: $(LIBMAP_STATIC) mapcrypto.c $(CC) $(CFLAGS) mapcrypto.c -DTEST_MAPCRYPTO $(EXE_LDFLAGS) -o test_mapcrypto ** */ int main(int argc, char *argv[]) { const unsigned char bytes_in[] = {0x12, 0x34, 0xff, 0x00, 0x44, 0x22}; unsigned char bytes_out[8], encryption_key[MS_ENCRYPTION_KEY_SIZE*2+1]; char string_buf[256], string_buf2[256]; int numbytes = 0; /* ** Test msHexEncode() */ msHexEncode(bytes_in, string_buf, 6); printf("msHexEncode returned '%s'\n", string_buf); /* ** Test msHexDecode() */ memset(bytes_out, 0, 8); numbytes = msHexDecode(string_buf, bytes_out, -1); printf("msHexDecode(%s, -1) = %d, bytes_out = %x, %x, %x, %x, %x, %x, %x, %x\n", string_buf, numbytes, bytes_out[0], bytes_out[1], bytes_out[2], bytes_out[3], bytes_out[4], bytes_out[5], bytes_out[6], bytes_out[7]); memset(bytes_out, 0, 8); numbytes = msHexDecode(string_buf, bytes_out, 4); printf("msHexDecode(%s, 4) = %d, bytes_out = %x, %x, %x, %x, %x, %x, %x, %x\n", string_buf, numbytes, bytes_out[0], bytes_out[1], bytes_out[2], bytes_out[3], bytes_out[4], bytes_out[5], bytes_out[6], bytes_out[7]); memset(bytes_out, 0, 8); numbytes = msHexDecode(string_buf, bytes_out, 20); printf("msHexDecode(%s, 20) = %d, bytes_out = %x, %x, %x, %x, %x, %x, %x, %x\n", string_buf, numbytes, bytes_out[0], bytes_out[1], bytes_out[2], bytes_out[3], bytes_out[4], bytes_out[5], bytes_out[6], bytes_out[7]); /* ** Test loading encryption key */ if (msReadEncryptionKeyFromFile("/tmp/test.key", encryption_key) != MS_SUCCESS) { printf("msReadEncryptionKeyFromFile() = MS_FAILURE\n"); printf("Aborting tests!\n"); msWriteError(stderr); return -1; } else { msHexEncode(encryption_key, string_buf, MS_ENCRYPTION_KEY_SIZE); printf("msReadEncryptionKeyFromFile() returned '%s'\n", string_buf); } /* ** Test Encryption/Decryption */ /* First with an 8 bytes input string (test boundaries) */ msEncryptStringWithKey(encryption_key, "test1234", string_buf); printf("msEncryptStringWithKey('test1234') returned '%s'\n", string_buf); msDecryptStringWithKey(encryption_key, string_buf, string_buf2); printf("msDecryptStringWithKey('%s') returned '%s'\n", string_buf, string_buf2); /* Next with an 1 byte input string */ msEncryptStringWithKey(encryption_key, "t", string_buf); printf("msEncryptStringWithKey('t') returned '%s'\n", string_buf); msDecryptStringWithKey(encryption_key, string_buf, string_buf2); printf("msDecryptStringWithKey('%s') returned '%s'\n", string_buf, string_buf2); /* Next with an 12 bytes input string */ msEncryptStringWithKey(encryption_key, "test123456", string_buf); printf("msEncryptStringWithKey('test123456') returned '%s'\n", string_buf); msDecryptStringWithKey(encryption_key, string_buf, string_buf2); printf("msDecryptStringWithKey('%s') returned '%s'\n", string_buf, string_buf2); /* ** Test decryption with tokens */ { char *pszBuf; mapObj *map; //map = msNewMapObj(); map = msLoadMap("/tmp/test.map", NULL); sprintf(string_buf2, "string with a {%s} encrypted token", string_buf); pszBuf = msDecryptStringTokens(map, string_buf2); if (pszBuf == NULL) { printf("msDecryptStringTokens() failed.\n"); printf("Aborting tests!\n"); msWriteError(stderr); return -1; } else { printf("msDecryptStringTokens('%s') returned '%s'\n", string_buf2, pszBuf); } msFree(pszBuf); msFreeMap(map); } return 0; } #endif /* TEST_MAPCRYPTO */