/*
**==============================================================================
**
** Open Management Infrastructure (OMI)
**
** Copyright (c) Microsoft Corporation
**
** Licensed under the Apache License, Version 2.0 (the "License"); you may not
** use this file except in compliance with the License. You may obtain a copy
** of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
** WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
** MERCHANTABLITY OR NON-INFRINGEMENT.
**
** See the Apache 2 License for the specific language governing permissions
** and limitations under the License.
**
**==============================================================================
*/
#include "credcache.h"
#include "log.h"
#include
#if defined (CONFIG_POSIX)
# include
# include
/*
**==============================================================================
**
** Local definitions:
**
**==============================================================================
*/
/* how long entry in cache is valid */
#define CRED_CACHE_TIME_TO_KEEP_USEC (((MI_Uint64)120) * 1000000)
/* since pre-defined structures are used, user name is limited in size */
#define CRED_USER_NAME_MAX_LEN 32
/* Max hash length (sha512) */
#define CRED_HASH_MAX_LEN 64
/* Salt size */
#define CRED_SALT_SIZE 16
/* Max entries in cache (number of simultaneously identified users) */
#define CRED_ITEMS_MAX 4
typedef struct _CredItem
{
char user[CRED_USER_NAME_MAX_LEN];
unsigned char hash[CRED_HASH_MAX_LEN];
MI_Uint64 timestamp;
} CredItem;
static unsigned char s_salt[CRED_SALT_SIZE];
static CredItem s_cache[CRED_ITEMS_MAX];
static int s_init;
static int s_initAttempted;
static const EVP_MD* s_md;
static MI_Uint64 s_expirationTime_us = CRED_CACHE_TIME_TO_KEEP_USEC;
static int _Init()
{
/* Avoid mulitple attempts to init if it failed */
if (s_initAttempted)
return -1;
s_initAttempted = 1;
/* Initialize salt */
if (0 == RAND_load_file("/dev/urandom", 1024))
{
LOGW_CHAR(("failed to load /dev/urandom"));
return -1;
}
if (0 == RAND_bytes(s_salt, sizeof(s_salt)))
{
LOGW_CHAR(("failed to init salt"));
return -1;
}
/* Find digest */
OpenSSL_add_all_digests();
/* Find digest, starting with strongest */
if (!(s_md = EVP_get_digestbyname("sha512")) &&
!(s_md = EVP_get_digestbyname("sha384")) &&
!(s_md = EVP_get_digestbyname("sha256")) &&
!(s_md = EVP_get_digestbyname("sha224")) &&
!(s_md = EVP_get_digestbyname("sha1")))
{
LOGW_CHAR(("no digest available"));
return -1;
}
s_init = 1;
return 0;
}
/* Calculates hash:
uses 3 parts:
- user name
- pwd
- salt
this way hash values are unique per user, per startup sequence
*/
static void _Hash(
const char* data1,
int size1,
const char* data2,
int size2,
unsigned char hash[CRED_HASH_MAX_LEN])
{
EVP_MD_CTX ctx;
unsigned int hashSize = CRED_HASH_MAX_LEN;
EVP_DigestInit(&ctx, s_md);
EVP_DigestUpdate(&ctx, data1, size1);
EVP_DigestUpdate(&ctx, data2, size2);
EVP_DigestUpdate(&ctx, s_salt, sizeof(s_salt));
EVP_DigestFinal(&ctx, hash, &hashSize);
}
/* Find position to add/update user:
if user is already in cache, it returns this position,
otherwise if empty item available - retunrs it,
otherwise retunrs oldest element */
static int _FindUserEmptyOldest(
const char* user)
{
int posEmpty = -1, posOldest = 0, pos;
MI_Uint64 timestampOldest = s_cache[0].timestamp;
for (pos = 0; pos < MI_COUNT(s_cache); pos++)
{
/* Did we find user? */
if (strcmp(user,s_cache[pos].user) == 0)
return pos;
/* Is it empty? */
if (0 == s_cache[pos].user[0])
{
posEmpty = pos;
}
else if (-1 == posEmpty)
{
/* Is it oldest with no epmty? */
if (timestampOldest > s_cache[pos].timestamp)
{
timestampOldest = s_cache[pos].timestamp;
posOldest = pos;
}
}
}
if (-1 != posEmpty)
return posEmpty;
return posOldest;
}
/* Find position with given user:
Returns:
user posiiton if found; -1 otherwise
*/
static int _Find(
const char* user)
{
int pos;
for (pos = 0; pos < MI_COUNT(s_cache); pos++)
{
/* Did we find user? */
if (strcmp(user,s_cache[pos].user) == 0)
return pos;
}
return -1;
}
/*
Adds user name and password into cache
*/
void CredCache_PutUser(const char* user, const char* password)
{
int pos;
int userLen;
if (!s_init && 0 != _Init())
{
return;
}
/* Check if user name is too long for cache */
userLen = strlen(user);
if (userLen >= CRED_USER_NAME_MAX_LEN)
return;
/* find position for user */
pos = _FindUserEmptyOldest(user);
/* timestamp */
if (MI_RESULT_OK != Time_Now(&s_cache[pos].timestamp))
return;
/* user name */
strcpy(s_cache[pos].user, user);
/* hash */
_Hash(user, userLen, password, strlen(password), s_cache[pos].hash);
}
/*
Checks if user credentials matches the one in cache
Returns:
'0' if user account matches entry in cache
'-1' if user is not in cache, if password does not match or
record expired
*/
int CredCache_CheckUser(const char* user, const char* password)
{
int pos;
unsigned char hash[CRED_HASH_MAX_LEN];
MI_Uint64 now;
/* 'no' if not initialized */
if (!s_init)
return -1;
/* Does user exisit in cache */
if (-1 == (pos = _Find(user)))
return -1;
/* Is it expired? */
if (MI_RESULT_OK != Time_Now(&now))
return -1;
if (s_cache[pos].timestamp + s_expirationTime_us < now)
return -1;
/* Hash matches? */
memset(hash, 0, sizeof(hash));
_Hash(user, strlen(user), password, strlen(password), hash);
assert(pos < MI_COUNT(s_cache));
if (0 != memcmp(hash, s_cache[pos].hash, sizeof(hash)))
return -1;
/* Credentials are valid */
return 0;
}
/* Unit-test support - updating expiration timeout */
void CredCache_SetExpirationTimeout(MI_Uint64 expirationTimeUS)
{
if (expirationTimeUS)
s_expirationTime_us = expirationTimeUS;
else
s_expirationTime_us = CRED_CACHE_TIME_TO_KEEP_USEC;
}
/* Unit-test support mostly - clear all cached items */
void CredCache_Clean()
{
memset(s_cache, 0, sizeof(s_cache));
}
/*
Generates crypto-suitable random data
Parameters:
buf - bufer for random data
size - number of bytes to generate
Returns:
0 - success
-1 - failed
*/
int CredCache_GenerateRandom(
char* buf,
size_t size)
{
/* Initialize if needed */
if (!s_init && 0 != _Init())
{
return -1;
}
if (0 == RAND_bytes((unsigned char*)buf, size))
{
LOGW_CHAR(("failed to produce random data"));
return -1;
}
return 0;
}
#endif /* defined(CONFIG_POSIX) */