mapcrypto.c 18.6 KB
Newer Older
1
/******************************************************************************
2
 * $Id$
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * 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:
 *
18
 * The above copyright notice and this permission notice shall be included in
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
 * 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.
 ****************************************************************************/

#include <assert.h>
#include <ctype.h>    /* isxdigit() */
#include <stdlib.h>   /* rand() */
#include <time.h>     /* time() */

#include "mapserver.h"

37

38 39 40


/**********************************************************************
41
 * encipher() and decipher() from the Tiny Encryption Algorithm (TEA)
42 43 44
 * website at:
 *   http://www.simonshepherd.supanet.com/tea.htm
 *
45
 * TEA was developed and placed in the public domain by David Wheeler
46 47 48 49 50
 * 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.
51
 *    The TEA algorithm (including new-variant TEA) has been placed
52 53 54 55 56 57 58 59 60 61 62 63 64
 *    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
65 66
 * 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
67 68 69 70
 * Electronic Code Book a little bit easier.
 *
 * TEA can be operated in any of the modes of DES.
 *
71 72 73 74
 * 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
75 76
 * can be made variable if required.
 *
77 78
 * Note this algorithm is optimised for 32-bit CPUs with fast shift
 * capabilities. It can very easily be ported to assembly language
79 80
 * on most CPUs.
 *
81
 * delta is chosen to be the Golden ratio ((5/4)1/2 - 1/2 ~ 0.618034)
82 83
 * 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))
84
 * where EK and DK are encryption and decryption under key K respectively.
85 86 87 88 89 90
 *
 **********************************************************************/

static void encipher(const ms_uint32 *const v, ms_uint32 *const w,
                     const ms_uint32 *const k)
{
91
  register ms_uint32   y=v[0],z=v[1],sum=0,delta=0x9E3779B9,n=32;
92

93 94 95 96 97
  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]);
  }
98

99 100
  w[0]=y;
  w[1]=z;
101 102 103 104 105
}

static void decipher(const ms_uint32 *const v, ms_uint32 *const w,
                     const ms_uint32 *const k)
{
106 107 108 109 110 111 112 113 114 115 116 117
  register ms_uint32       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;
118 119 120 121 122 123 124 125 126 127 128 129
}

/**********************************************************************
 *                          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)
{
130 131 132 133 134 135 136 137
  char *hex = "0123456789ABCDEF";

  while (numbytes-- > 0) {
    *out++ = hex[*in/16];
    *out++ = hex[*in%16];
    in++;
  }
  *out = '\0';
138 139 140 141 142 143 144 145 146 147 148 149 150
}

/**********************************************************************
 *                          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.
 *
151
 * Returns the number of bytes written to out[] which may be different from
152 153 154 155
 * numchars/2 if an error or a '\0' is encountered.
 **********************************************************************/
int msHexDecode(const char *in, unsigned char *out, int numchars)
{
156
  int numbytes_out = 0;
157

158 159
  /* Make sure numchars is even */
  numchars = (numchars/2) * 2;
160

161 162
  if (numchars < 2)
    numchars = -1; /* Will result in this value being ignored in the loop*/
163

164 165 166 167 168
  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++;
169

170 171
    out++;
    numbytes_out++;
172

173 174
    numchars -= 2;
  }
175

176
  return numbytes_out;
177 178 179 180 181 182 183 184 185 186 187 188 189
}


/**********************************************************************
 *                       msGenerateEncryptionKey()
 *
 * Create a new encryption key.
 *
 * The output buffer should be at least MS_ENCRYPTION_KEY_SIZE bytes.
 **********************************************************************/

int msGenerateEncryptionKey(unsigned char *k)
{
190
  int i;
191

192 193
  /* Use current time as seed for rand() */
  srand( (unsigned int) time( NULL ));
194

195 196
  for(i=0; i<MS_ENCRYPTION_KEY_SIZE; i++)
    k[i] = (unsigned char)rand();
197

198
  return MS_SUCCESS;
199 200 201 202 203 204
}

/**********************************************************************
 *                       msReadEncryptionKeyFromFile()
 *
 * Read and decode hex-encoded encryption key from file and returns the
205
 * key in the 'unsigned char k[MS_ENCRYPTION_KEY_SIZE]' buffer that is
206 207 208 209 210 211 212
 * provided by the caller.
 *
 * Returns MS_SUCCESS/MS_FAILURE.
 **********************************************************************/

int msReadEncryptionKeyFromFile(const char *keyfile, unsigned char *k)
{
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
  FILE *fp;
  char szBuf[100];
  int numchars;

  if ((fp = fopen(keyfile, "rt")) == NULL) {
    msSetError(MS_MISCERR, "Cannot open key file.",
               "msReadEncryptionKeyFromFile()");
    return MS_FAILURE;
  }

  numchars = fread(szBuf, sizeof(unsigned char), MS_ENCRYPTION_KEY_SIZE*2, fp);
  fclose(fp);
  szBuf[MS_ENCRYPTION_KEY_SIZE*2] = '\0';

  if (numchars != MS_ENCRYPTION_KEY_SIZE*2) {
    msSetError(MS_MISCERR, "Invalid key file, got %d chars, expected %d.",
               "msReadEncryptionKeyFromFile()",
               numchars, MS_ENCRYPTION_KEY_SIZE*2);
    return MS_FAILURE;
  }

  msHexDecode(szBuf, k, MS_ENCRYPTION_KEY_SIZE*2);

  return MS_SUCCESS;
237 238 239 240 241 242
}

/**********************************************************************
 *                       msLoadEncryptionKey()
 *
 * Load and decode hex-encoded encryption key from file and returns the
243
 * key in the 'unsigned char k[MS_ENCRYPTION_KEY_SIZE]' buffer that is
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
 * provided by the caller.
 *
 * The first time that msLoadEncryptionKey() is called for a given mapObj
 * it will load the encryption key and cache it in mapObj->encryption_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)
{
259
  const char *keyfile;
260

261 262 263 264
  if (map == NULL) {
    msSetError(MS_MISCERR, "NULL MapObj.", "msLoadEncryptionKey()");
    return MS_FAILURE;
  }
265

266 267
  if (map->encryption_key_loaded)
    return MS_SUCCESS;  /* Already loaded */
268

269
  keyfile = msGetConfigOption(map, "MS_ENCRYPTION_KEY");
270

271 272
  if (keyfile == NULL)
    keyfile = getenv("MS_ENCRYPTION_KEY");
273

274 275 276 277 278 279 280 281 282
  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;
  }
283

284
  return MS_SUCCESS;
285 286 287 288 289
}

/**********************************************************************
 *                        msEncryptStringWithKey()
 *
290
 * Encrypts and hex-encodes the contents of string in[] and returns the
291 292 293 294 295 296 297
 * 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)
{
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
  ms_uint32 v[4], w[4];
  const ms_uint32 *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(ms_uint32) == 4);
  k = (const ms_uint32 *) 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;
320 321
        }

322 323 324 325
        v[i] |= *in << (j*8);
        in++;
      }
    }
326

327 328
    if (*in == '\0')
      last_block = MS_TRUE;
329

330 331
    /* Do the actual encryption */
    encipher(v, w, k);
332

333 334 335 336 337 338 339
    /* 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;

  }
340

341 342
  /* Make sure output is 0-terminated */
  *out = '\0';
343 344 345 346 347
}

/**********************************************************************
 *                        msDecryptStringWithKey()
 *
348
 * Hex-decodes and then decrypts the contents of string in[] and returns the
349 350 351 352 353 354 355
 * 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)
{
356 357 358
  ms_uint32 v[4], w[4];
  const ms_uint32 *k;
  int last_block = MS_FALSE;
359

360 361 362 363
  /* Casting the key this way is safe only as long as longs are 4 bytes
   * on this platform */
  assert(sizeof(ms_uint32) == 4);
  k = (const ms_uint32 *) key;
364

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
  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);
386

387 388 389 390 391 392
    /* 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;
393 394
    }

395 396 397 398 399 400
    if (*in == '\0')
      last_block = MS_TRUE;
  }

  /* Make sure output is 0-terminated */
  *out = '\0';
401 402 403 404 405
}

/**********************************************************************
 *                        msDecryptStringTokens()
 *
406
 * Returns a newly allocated string (to be msFree'd by the caller) in
407 408 409 410 411 412 413
 * which all occurences of encrypted strings delimited by {...} have
 * been decrypted.
 *
 **********************************************************************/

char *msDecryptStringTokens(mapObj *map, const char *in)
{
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
  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;
          }
        }
      }
449

450 451 452
      if (valid_token) {
        /* Go ahead and decrypt the token */
        char *pszTmp;
453

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
        /* 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));
        strlcpy(pszTmp, pszStart, (pszEnd-pszStart)+1);

        msDecryptStringWithKey(map->encryption_key, pszTmp, out);

        out += strlen(out);
        in = pszEnd+1;
        free(pszTmp);
      } else {
        /* Not a valid token, just copy the '{' and keep going */
        *out++ = *in++;
      }
    } else {
      /* Just copy any other chars */
      *out++ = *in++;
477
    }
478 479
  }
  *out = '\0';
480

481
  return outbuf;
482 483 484 485 486
}


#ifdef TEST_MAPCRYPTO

487
/* Test for mapcrypto.c functions. To run these tests, use the following
488 489 490
** Makefile directive:

test_mapcrypto: $(LIBMAP_STATIC) mapcrypto.c
491
  $(CC) $(CFLAGS) mapcrypto.c -DTEST_MAPCRYPTO $(EXE_LDFLAGS) -o test_mapcrypto
492 493 494 495 496

**
*/
int main(int argc, char *argv[])
{
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
  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);
590
    }
591 592 593
    msFree(pszBuf);
    msFreeMap(map);
  }
594

595
  return 0;
596 597 598 599
}


#endif /* TEST_MAPCRYPTO */