/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The 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 #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 SInt32 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 char *dufr_id = "$dufr$"; // Doesn't allocate any memory. The size of the result buffer should be nbytes void MD5Encode(char *pw, 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; char *sp, *ep; unsigned char final[kHashLen]; int sl, pl, i; MD5_CTX ctx, ctx1; UInt32 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, (unsigned char *)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, (unsigned char *)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, (unsigned char *)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); }