#include <stdlib.h>
#include <syslog.h>
#include <sasl/saslutil.h>
#include <sys/param.h>
#include <DirectoryService/DirServices.h>
#include <DirectoryService/DirServicesUtils.h>
#include <PasswordServer/PSUtilitiesDefs.h>
#include <DirectoryServiceCore/pps.h>
#define NONCE_SIZE_PPS (30)
#define NONCE64_SIZE ((NONCE_SIZE_PPS + 3) * 4 / 3)
#define NONCE_LONG_COUNT ((NONCE_SIZE_PPS / sizeof(long)) + 1)
#define kSaltSize (4)
#define kSaltedHashLen (kSaltSize + CC_SHA1_DIGEST_LENGTH)
#define kKeyUserName "username="
#define kKeyUserID "userid="
#define kKeyChallenge "challenge="
#define kKeyHashType "hashtype="
#define kKeySalt "salt="
#define kKeyResponse "response="
#define kKeyPeerChallenge "peerchallenge="
#define kKeyNonce "nonce="
#define SASL_SEC_PPS_FLAGS (SASL_SEC_NOPLAINTEXT | SASL_SEC_NOANONYMOUS | SASL_SEC_MUTUAL_AUTH)
#define ALIGN_TO_BLOCKSIZE(size, blocksize) ((size) + ((blocksize) - ((size) % (blocksize))))
#define SETERROR(A,args...) syslog(LOG_ALERT, (A), ##args)
static int
server_step_1_parse_input(const char *inClientData, char **outUserName);
static char *
server_step_1_format(ServerAuthDataBlockPtr inAuthData);
static int
server_step_2_parse_input(const char *inClientData, ServerAuthDataBlockPtr inOutAuthData);
static char *
server_step_2_format(ServerAuthDataBlockPtr inAuthData);
#pragma mark -
#pragma mark UTILS
#pragma mark -
static int
create_nonce(char *nonce, size_t maxnonce)
{
int result = 0;
unsigned int idx = 0;
unsigned int len = 0;
union {
long randomLongs[NONCE_LONG_COUNT];
char randomData[NONCE_SIZE_PPS + 1];
} u;
char nonce64[NONCE64_SIZE];
srandomdev();
for (idx = 0; idx < NONCE_LONG_COUNT; idx++)
u.randomLongs[idx] = random();
result = sasl_encode64(u.randomData, NONCE_SIZE_PPS, nonce64, sizeof(nonce64), &len);
if ( result == SASL_OK )
strlcpy( nonce, nonce64, maxnonce );
return result;
}
#pragma mark -
#pragma mark SERVER
#pragma mark -
void
server_step_0_set_hash(
const unsigned char *inSaltedSHA1Hash,
ServerAuthDataBlockPtr inOutAuthData)
{
if (inSaltedSHA1Hash != NULL)
{
inOutAuthData->c.saltLen = kSaltSize;
inOutAuthData->c.salt = (unsigned char *) malloc(inOutAuthData->c.saltLen);
memcpy(inOutAuthData->c.salt, inSaltedSHA1Hash, inOutAuthData->c.saltLen);
inOutAuthData->saltedHashLen = inOutAuthData->c.saltLen + CC_SHA1_DIGEST_LENGTH;
inOutAuthData->saltedHash = (unsigned char *) malloc(inOutAuthData->saltedHashLen);
memcpy(inOutAuthData->saltedHash, inSaltedSHA1Hash, inOutAuthData->saltedHashLen);
}
}
int
server_step_1(
const char *inClientData,
ServerAuthDataBlockPtr inOutAuthData)
{
CCCryptorStatus status = kCCSuccess;
size_t len = 0;
int result = SASL_FAIL;
size_t dataMoved = 0;
unsigned char ivec[kCCBlockSizeAES128] = {0};
CC_MD5_CTX ctx;
result = server_step_1_parse_input(inClientData, &inOutAuthData->userName);
if (result != SASL_OK)
return result;
CC_MD5_Init(&ctx);
CC_MD5_Update(&ctx, inOutAuthData->saltedHash, inOutAuthData->saltedHashLen);
CC_MD5_Final(inOutAuthData->saltedHashHash, &ctx);
inOutAuthData->c.serverChallenge = (char *)calloc(1, NONCE64_SIZE + kCCBlockSizeAES128 + 1);
result = create_nonce(inOutAuthData->c.serverChallenge, NONCE64_SIZE);
if (result != SASL_OK)
return result;
len = strlen(inOutAuthData->c.serverChallenge) + 1;
inOutAuthData->c.encryptedChallengeLen = ALIGN_TO_BLOCKSIZE(len, kCCBlockSizeAES128);
inOutAuthData->encryptedChallenge = (unsigned char *)malloc(inOutAuthData->c.encryptedChallengeLen);
memcpy(ivec, inOutAuthData->saltedHash, kSaltSize);
memcpy(ivec + kSaltSize, inOutAuthData->saltedHashHash, 12);
status = CCCrypt(
kCCEncrypt, kCCAlgorithmAES128, 0,
inOutAuthData->saltedHashHash, kCCKeySizeAES128,
ivec,
(const unsigned char *)inOutAuthData->c.serverChallenge,
inOutAuthData->c.encryptedChallengeLen,
inOutAuthData->encryptedChallenge,
inOutAuthData->c.encryptedChallengeLen,
&dataMoved );
inOutAuthData->c.hashType = 1;
return SASL_OK;
}
static int
server_step_1_parse_input(const char *inClientData, char **outUserName)
{
const char *tptr;
const char *endPtr;
char bufStr[512];
size_t uname_len;
unsigned long binlen;
if (inClientData == NULL || outUserName == NULL)
return SASL_FAIL;
tptr = strstr(inClientData, kKeyUserID);
if (tptr != NULL)
{
tptr += sizeof(kKeyUserID) - 1;
endPtr = strchr(tptr, ';');
if (endPtr == NULL)
strlcpy(bufStr, tptr, sizeof(bufStr));
else {
strlcpy(bufStr, tptr, MIN((size_t)(endPtr - tptr + 1), sizeof(bufStr)));
}
*outUserName = strdup( bufStr );
}
else
{
tptr = strstr(inClientData, kKeyUserName);
if (tptr == NULL)
return SASL_FAIL;
tptr += sizeof(kKeyUserName) - 1;
endPtr = strchr(tptr, ';');
if (endPtr == NULL)
strlcpy(bufStr, tptr, sizeof(bufStr));
else {
strlcpy(bufStr, tptr, MIN((size_t)(endPtr - tptr + 1), sizeof(bufStr)));
}
*outUserName = (char *) malloc( (uname_len = strlen(bufStr) * 3 / 4 + 4) );
Convert64ToBinary(bufStr, *outUserName, uname_len, &binlen);
(*outUserName)[binlen] = '\0';
}
return SASL_OK;
}
static char *
server_step_1_format(ServerAuthDataBlockPtr inAuthData)
{
char saltStr[128] = {0};
char encryptedChallengeStr[512];
char buffStr[512];
unsigned int len64;
int len;
int result;
result = sasl_encode64((const char *)inAuthData->c.salt,
inAuthData->c.saltLen,
saltStr, sizeof(saltStr), &len64);
result = sasl_encode64((const char *)inAuthData->encryptedChallenge,
inAuthData->c.encryptedChallengeLen,
encryptedChallengeStr, sizeof(encryptedChallengeStr), &len64);
len = snprintf(buffStr, sizeof(buffStr),
"salt=%s;challenge=%s;hashtype=%d",
saltStr, encryptedChallengeStr, inAuthData->c.hashType);
if (len >= (int)sizeof(buffStr))
return NULL;
return strdup(buffStr);
}
int
server_step_2(const char *inClientData, ServerAuthDataBlockPtr inOutAuthData)
{
int result;
unsigned char ivec[kCCBlockSizeAES128] = {0};
char nonceStr[256] = {0};
CC_SHA256_CTX shaCtx;
unsigned char calculated_response[CC_SHA256_DIGEST_LENGTH];
result = server_step_2_parse_input(inClientData, inOutAuthData);
if (result != SASL_OK)
return result;
CC_SHA256_Init(&shaCtx);
CC_SHA256_Update(&shaCtx, inOutAuthData->c.serverChallenge, strlen(inOutAuthData->c.serverChallenge));
CC_SHA256_Update(&shaCtx, inOutAuthData->c.peerChallenge, strlen(inOutAuthData->c.peerChallenge));
CC_SHA256_Update(&shaCtx, inOutAuthData->saltedHash, kSaltedHashLen);
CC_SHA256_Final(calculated_response, &shaCtx);
if (memcmp(calculated_response, inOutAuthData->c.response, CC_SHA256_DIGEST_LENGTH) != 0)
return SASL_BADAUTH;
sprintf(nonceStr, "%lu", inOutAuthData->c.nonce + 1);
inOutAuthData->c.encryptedNonceLen = sizeof(inOutAuthData->encryptedNonce);
result = AES_set_encrypt_key(inOutAuthData->c.sessionKey, 128, &inOutAuthData->c.sessionEncryptKey);
AES_cbc_encrypt(
(const unsigned char *)nonceStr,
(unsigned char *)inOutAuthData->encryptedNonce,
inOutAuthData->c.encryptedNonceLen,
&inOutAuthData->c.sessionEncryptKey,
ivec,
AES_ENCRYPT);
return SASL_OK;
}
static int
server_step_2_parse_input(const char *inClientData, ServerAuthDataBlockPtr inOutAuthData)
{
const char *tptr;
const char *endPtr;
char response64Str[256] = {0};
char peerchal64Str[512] = {0};
unsigned char encPeerChal[256] = {0};
unsigned long encPeerChalLen = 0;
char nonce64Str[256] = {0};
unsigned long len;
unsigned char encNonce[256];
unsigned char decNonce[256];
unsigned long encNonceLen = 0;
unsigned char ivec[kCCBlockSizeAES128] = {0};
int result;
AES_KEY aesEncryptKey;
CC_MD5_CTX ctx;
tptr = strstr(inClientData, kKeyResponse);
if (tptr == NULL)
return SASL_FAIL;
tptr += sizeof(kKeyResponse) - 1;
endPtr = strchr(tptr, ';');
if (endPtr == NULL)
return SASL_FAIL;
len = endPtr - tptr + 1;
strlcpy(response64Str, tptr, len);
Convert64ToBinary(
response64Str,
(char *)inOutAuthData->c.response,
sizeof(inOutAuthData->c.response),
&len);
tptr = strstr(inClientData, kKeyPeerChallenge);
if (tptr == NULL)
return SASL_FAIL;
tptr += sizeof(kKeyPeerChallenge) - 1;
endPtr = strchr(tptr, ';');
if (endPtr == NULL)
return SASL_FAIL;
len = endPtr - tptr + 1;
strlcpy(peerchal64Str, tptr, len);
Convert64ToBinary(peerchal64Str, (char *)encPeerChal, sizeof(encPeerChal), &encPeerChalLen);
inOutAuthData->c.peerChallenge = (char *)malloc(256);
memcpy(ivec, inOutAuthData->saltedHash, kSaltSize);
memcpy(ivec + kSaltSize, inOutAuthData->saltedHashHash, 12);
result = AES_set_decrypt_key(inOutAuthData->saltedHashHash, CC_MD5_DIGEST_LENGTH * 8, &aesEncryptKey);
AES_cbc_encrypt(
encPeerChal,
(unsigned char *)inOutAuthData->c.peerChallenge,
encPeerChalLen,
&aesEncryptKey,
ivec,
AES_DECRYPT);
CC_MD5_Init(&ctx);
CC_MD5_Update(&ctx, inOutAuthData->c.serverChallenge, strlen(inOutAuthData->c.serverChallenge));
CC_MD5_Update(&ctx, inOutAuthData->c.peerChallenge, strlen(inOutAuthData->c.peerChallenge));
CC_MD5_Update(&ctx, inOutAuthData->saltedHashHash, CC_MD5_DIGEST_LENGTH);
CC_MD5_Final(inOutAuthData->c.sessionKey, &ctx);
tptr = strstr(inClientData, kKeyNonce);
if (tptr == NULL)
return SASL_FAIL;
tptr += sizeof(kKeyNonce) - 1;
endPtr = strchr(tptr, ';');
if (endPtr == NULL)
strlcpy(nonce64Str, tptr, sizeof(nonce64Str));
else
strlcpy(nonce64Str, tptr, MIN((size_t)(endPtr - tptr + 1), sizeof(nonce64Str)));
Convert64ToBinary(nonce64Str, (char *)encNonce, sizeof(encNonce), &encNonceLen);
bzero(ivec, sizeof(ivec));
result = AES_set_decrypt_key(inOutAuthData->c.sessionKey, 128, &aesEncryptKey);
AES_cbc_encrypt(
(const unsigned char *)encNonce,
decNonce,
encNonceLen,
&aesEncryptKey,
ivec,
AES_DECRYPT);
sscanf((char *)decNonce, "%lu", &inOutAuthData->c.nonce);
return SASL_OK;
}
static char *
server_step_2_format(ServerAuthDataBlockPtr inAuthData)
{
char encryptedNonceStr[128] = {0};
char buffStr[512];
unsigned int len64;
int len;
int result;
result = sasl_encode64(inAuthData->encryptedNonce,
inAuthData->c.encryptedNonceLen,
encryptedNonceStr, sizeof(encryptedNonceStr), &len64);
if (result != SASL_OK)
return NULL;
len = snprintf(buffStr, sizeof(buffStr), "nonce=%s", encryptedNonceStr);
if (len >= (int)sizeof(buffStr))
return NULL;
return strdup(buffStr);
}
int
pps_server_mech_step(
ServerAuthDataBlockPtr contextPtr,
const char *clientin,
unsigned clientinlen,
const char **serverout,
unsigned *serveroutlen )
{
int result = 0;
switch ( contextPtr->c.step )
{
case 0:
result = server_step_1(clientin, contextPtr);
if (result != SASL_OK)
return result;
if ((contextPtr->c.outPtr = server_step_1_format(contextPtr)) == NULL)
return SASL_FAIL;
*serverout = contextPtr->c.outPtr;
*serveroutlen = strlen(*serverout);
contextPtr->c.step++;
break;
case 1:
result = server_step_2(clientin, contextPtr);
if (result != SASL_OK)
return result;
if (contextPtr->c.outPtr != NULL)
free(contextPtr->c.outPtr);
if ((contextPtr->c.outPtr = server_step_2_format(contextPtr)) == NULL)
return SASL_FAIL;
*serverout = contextPtr->c.outPtr;
*serveroutlen = strlen(*serverout);
contextPtr->c.step++;
break;
default:
return SASL_FAIL;
}
return (contextPtr->c.step == 2) ? SASL_OK : SASL_CONTINUE;
}
void
pps_server_mech_dispose(ServerAuthDataBlockPtr contextPtr)
{
if ( contextPtr != NULL )
{
if ( contextPtr->userName )
free( contextPtr->userName );
if ( contextPtr->c.outPtr )
free( contextPtr->c.outPtr );
if ( contextPtr->c.salt )
free( contextPtr->c.salt );
if ( contextPtr->saltedHash )
free( contextPtr->saltedHash );
if ( contextPtr->c.serverChallenge )
free( contextPtr->c.serverChallenge );
if ( contextPtr->encryptedChallenge )
free( contextPtr->encryptedChallenge );
if ( contextPtr->c.peerChallenge )
free( contextPtr->c.peerChallenge );
if (contextPtr->dsRef != 0)
{
if (contextPtr->nodeRef != 0)
dsCloseDirNode(contextPtr->nodeRef);
if (contextPtr->authBuff)
dsDataBufferDeAllocate(contextPtr->dsRef, contextPtr->authBuff);
if (contextPtr->authStepBuff)
dsDataBufferDeAllocate(contextPtr->dsRef, contextPtr->authStepBuff);
if (contextPtr->typeBuff)
dsDataNodeDeAllocate(contextPtr->dsRef, contextPtr->typeBuff);
dsCloseDirService(contextPtr->dsRef);
}
free(contextPtr);
}
}