/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
	File:		md5digest.cpp

	Contains:   Implements the function declared in md5digest.h
*/

#include "md5.h"
#include "md5digest.h"
#include "StrPtrLen.h"
#include <string.h>
#include "OSMemory.h"

static StrPtrLen sColon(":", 1);
static StrPtrLen sMD5Sess("md5-sess", 8);
static StrPtrLen sQopAuth("auth", 4);
static StrPtrLen sQopAuthInt("auth-int", 8);

// allocates memory for hashStr->Ptr
void HashToString(unsigned char aHash[kHashLen], StrPtrLen* hashStr){
	UInt16 i;
	UInt8 hexDigit;
	// Allocating memory
	char* str = NEW char[kHashHexLen+1];
	str[kHashHexLen] = 0;
		
	for(i = 0; i < kHashLen; i++) {
		hexDigit = (aHash[i] >> 4) & 0xF;
		str[i*2] = (hexDigit <= 9) ? (hexDigit + '0') : (hexDigit + 'a' - 10);
		hexDigit = aHash[i] & 0xF;
		str[i*2 + 1] = (hexDigit <= 9) ? (hexDigit + '0') : (hexDigit + 'a' - 10);
	}
	
	hashStr->Ptr = str;
	hashStr->Len = kHashHexLen;
}

// allocates memory for hashA1Hex16Bit->Ptr
void CalcMD5HA1( StrPtrLen* userName, 
			  	 StrPtrLen* realm,
			     StrPtrLen* userPassword, 
			     StrPtrLen* hashA1Hex16Bit
			   ) 
{
	// parameters must be valid pointers 
	// It is ok if parameter->Ptr is NULL as long as parameter->Len is 0
	Assert(userName);
	Assert(realm);
	Assert(userPassword);
	Assert(hashA1Hex16Bit);
	Assert(hashA1Hex16Bit->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL.
	
	MD5_CTX context;
	unsigned char* aHash = NEW unsigned char[kHashLen];
	
	// Calculate H(A1) for MD5
	// where A1 for algorithm = "md5" or if nothing is specified is
	// 				A1 	= userName:realm:userPassword
	MD5_Init(&context);
	MD5_Update(&context, (unsigned char *)userName->Ptr, userName->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)realm->Ptr, realm->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)userPassword->Ptr, userPassword->Len);
	MD5_Final(aHash, &context);
	hashA1Hex16Bit->Ptr = (char *)aHash;
	hashA1Hex16Bit->Len = kHashLen;
}

// allocates memory to hA1->Ptr
void CalcHA1( StrPtrLen* algorithm, 
			  StrPtrLen* userName, 
			  StrPtrLen* realm,
			  StrPtrLen* userPassword, 
			  StrPtrLen* nonce, 
			  StrPtrLen* cNonce,
			  StrPtrLen* hA1
			) 
{
	// parameters must be valid pointers 
	// It is ok if parameter->Ptr is NULL as long as parameter->Len is 0
	Assert(algorithm);
	Assert(userName);
	Assert(realm);
	Assert(userPassword);
	Assert(nonce);
	Assert(cNonce);
	Assert(hA1);
	Assert(hA1->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL.
	
	MD5_CTX context;
	unsigned char aHash[kHashLen];
	
	// Calculate H(A1)
	// where A1 for algorithm = "md5" or if nothing is specified is
	// 				A1 	= userName:realm:userPassword
	// and for algorithm = "md5-sess" is
	// 				A1	= H(userName:realm:userPassword):nonce:cnonce
	MD5_Init(&context);
	MD5_Update(&context, (unsigned char *)userName->Ptr, userName->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)realm->Ptr, realm->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)userPassword->Ptr, userPassword->Len);
	MD5_Final(aHash, &context);
	if(algorithm->Equal(sMD5Sess)) {
		MD5_Init(&context);
		MD5_Update(&context, aHash, kHashLen);
		MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
		MD5_Update(&context, (unsigned char *)nonce->Ptr, nonce->Len);
		MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
		MD5_Update(&context, (unsigned char *)cNonce->Ptr, cNonce->Len);
		MD5_Final(aHash, &context);
	}
	HashToString(aHash, hA1);
}

// allocates memory to hA1->Ptr
void CalcHA1Md5Sess(StrPtrLen* hashA1Hex16Bit, StrPtrLen* nonce, StrPtrLen* cNonce, StrPtrLen* hA1)
{
	// parameters must be valid pointers 
	// It is ok if parameter->Ptr is NULL as long as parameter->Len is 0
	Assert(hashA1Hex16Bit);
	Assert(hashA1Hex16Bit->Len == kHashLen);
	Assert(nonce);
	Assert(cNonce);
	Assert(hA1);
	Assert(hA1->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL.
	
	MD5_CTX context;
	unsigned char aHash[kHashLen];
	
	MD5_Init(&context);
	MD5_Update(&context, (unsigned char *)hashA1Hex16Bit->Ptr, kHashLen);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)nonce->Ptr, nonce->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)cNonce->Ptr, cNonce->Len);
	MD5_Final(aHash, &context);
	
	// allocates memory to hA1->Ptr
	HashToString(aHash, hA1);
}

// allocates memory for requestDigest->Ptr				
void CalcRequestDigest(	StrPtrLen* hA1, 
						StrPtrLen* nonce, 
						StrPtrLen* nonceCount, 
						StrPtrLen* cNonce,
						StrPtrLen* qop,
						StrPtrLen* method, 
						StrPtrLen* digestUri, 
						StrPtrLen* hEntity, 
						StrPtrLen* requestDigest
					  )
{
	// parameters must be valid pointers 
	// It is ok if parameter->Ptr is NULL as long as parameter->Len is 0
	Assert(hA1);
	Assert(nonce);
	Assert(nonceCount);
	Assert(cNonce);
	Assert(qop);
	Assert(method);
	Assert(digestUri);
	Assert(hEntity);
	Assert(requestDigest);
	Assert(requestDigest->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL.

	unsigned char aHash[kHashLen], requestHash[kHashLen];
	StrPtrLen hA2;
	MD5_CTX context;
	
	
	// H(data) = MD5(data)
	// and KD(secret, data) = H(concat(secret, ":", data))
	
	// Calculate H(A2)
	// where A2 for qop="auth" or no qop is 
	//				A2	= method:digestUri
	// and for qop = "auth-int" is
	//				A2 = method:digestUri:H(entity-body)
	MD5_Init(&context);
	MD5_Update(&context, (unsigned char *)method->Ptr, method->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)digestUri->Ptr, digestUri->Len);
	if(qop->Equal(sQopAuthInt)) {
		MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
		MD5_Update(&context, (unsigned char *)hEntity->Ptr, hEntity->Len);
	}
	MD5_Final(aHash, &context);
	
	// HashToString allocates memory for hA2...delete it after request-digest is created
	HashToString(aHash, &hA2);
	// Calculate request-digest
	// where request-digest for qop="auth" or qop="auth-int" is
	// 			request-digest	= KD( H(A1), nonce:nonceCount:cNonce:qop:H(A2) )
	// and if qop directive isn't present is
	//			request-digest = KD( H(A1), nonce:H(A2) )
	MD5_Init(&context);
	MD5_Update(&context, (unsigned char *)hA1->Ptr, hA1->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	MD5_Update(&context, (unsigned char *)nonce->Ptr, nonce->Len);
	MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	if(qop->Ptr != NULL) {
		MD5_Update(&context, (unsigned char *)nonceCount->Ptr, nonceCount->Len);
		MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
		MD5_Update(&context, (unsigned char *)cNonce->Ptr, cNonce->Len);
		MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
		MD5_Update(&context, (unsigned char *)qop->Ptr, qop->Len);
		MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len);
	}
	MD5_Update(&context, (unsigned char *)hA2.Ptr, hA2.Len);
	MD5_Final(requestHash, &context);
	HashToString(requestHash, requestDigest);
	
	// Deleting memory allocated for hA2
	delete [] hA2.Ptr;
}



/* From local_passwd.c (C) Regents of Univ. of California blah blah */
static unsigned char itoa64[] =	/* 0 ... 63 => ascii - 64 */
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

void to64(register char *s, register long v, register int n)
{
    while (--n >= 0) {
	*s++ = itoa64[v & 0x3f];
	v >>= 6;
    }
}

/*
 * Define the Magic String prefix that identifies a password as being
 * hashed using our algorithm.
 */
static const char *dufr_id = "$dufr$";

// Doesn't allocate any memory. The size of the result buffer should be nbytes
void MD5Encode(const char *pw, const char *salt, char *result, int nbytes)
{
  /*
   * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL,
   * plus 4 for the '$' separators, plus the password hash itself.
   * Let's leave a goodly amount of leeway.
   */
  
  char passwd[120], *p;
  const char *sp, *ep;
  unsigned char final[kHashLen];
  int sl, pl, i;
  MD5_CTX ctx, ctx1;
  unsigned long l;
 
  /*
   * Refine the salt first.  It's possible we were given an already-hashed
   * string as the salt argument, so extract the actual salt value from it
   * if so.  Otherwise just use the string up to the first '$' as the salt.
   */
  sp = salt;

  //If it starts with the magic string, then skip that.
  if (!strncmp(sp, dufr_id, strlen(dufr_id))) 
  {
    sp += strlen(dufr_id);
  }

  //It stops at the first '$' or 8 chars, whichever comes first
  for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) 
  {
    continue;
  }

  //Get the length of the true salt
  sl = ep - sp;
  
  //'Time to make the doughnuts..'
  MD5_Init(&ctx);

  //The password first, since that is what is most unknown
  MD5_Update(&ctx, (unsigned char *)pw, strlen(pw));

  //Then our magic string
  MD5_Update(&ctx, (unsigned char *)dufr_id, strlen(dufr_id));

  //Then the raw salt
  MD5_Update(&ctx, (unsigned char *)sp, sl);

  //Then just as many characters of the MD5(pw, salt, pw)
  MD5_Init(&ctx1);
  MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw));
  MD5_Update(&ctx1, (unsigned char *)sp, sl);
  MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw));
  MD5_Final(final, &ctx1);
  for (pl = strlen(pw); pl > 0; pl -= kHashLen)
  {
    MD5_Update(&ctx, final,(pl > kHashLen) ? kHashLen : pl);
  }

  //Don't leave anything around in vm they could use.
  memset(final, 0, sizeof(final));

  //Then something really weird...
  for (i = strlen(pw); i != 0; i >>= 1)
  {
    if (i & 1) {
      MD5_Update(&ctx, final, 1);
    }
    else {
      MD5_Update(&ctx, (unsigned char *)pw, 1);
    }
  }

  /*
   * Now make the output string.  We know our limitations, so we
   * can use the string routines without bounds checking.
   */
  strcpy(passwd, dufr_id);
  strncat(passwd, sp, sl);
  strcat(passwd, "$");

  MD5_Final(final, &ctx);

  /*
   * And now, just to make sure things don't run too fast..
   * On a 60 Mhz Pentium this takes 34 msec, so you would
   * need 30 seconds to build a 1000 entry dictionary...
   */
  for (i = 0; i < 1000; i++)
  {
    MD5_Init(&ctx1);
    if (i & 1) {
      MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw));
    }
    else {
      MD5_Update(&ctx1, final, kHashLen);
    }
    if (i % 3) {
      MD5_Update(&ctx1, (unsigned char *)sp, sl);
    }

    if (i % 7) {
      MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw));
    }

    if (i & 1) {
      MD5_Update(&ctx1, final, kHashLen);
    }
    else {
      MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw));
    }
    MD5_Final(final,&ctx1);
  }

  p = passwd + strlen(passwd);

  l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p, l, 4); p += 4;
  l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p, l, 4); p += 4;
  l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p, l, 4); p += 4;
  l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p, l, 4); p += 4;
  l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p, l, 4); p += 4;
  l =                    final[11]                ; to64(p, l, 2); p += 2;
  *p = '\0';
  
  //Don't leave anything around in vm they could use.
  memset(final, 0, sizeof(final));

  strncpy(result, passwd, nbytes - 1);
}
