FreeCalypso > hg > tcs211-fcmodem
diff g23m/condat/com/src/comlib/sec_drv.c @ 0:509db1a7b7b8
initial import: leo2moko-r1
author | Space Falcon <falcon@ivan.Harhan.ORG> |
---|---|
date | Mon, 01 Jun 2015 03:24:05 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/g23m/condat/com/src/comlib/sec_drv.c Mon Jun 01 03:24:05 2015 +0000 @@ -0,0 +1,747 @@ +#include "general.h" +#include "typedefs.h" +#include "vsi.h" +#include <stdlib.h> +#include <string.h> +#include "sec_drv.h" +#include "sec_drv_prim.h" + +#ifdef FIRSTBOOT_ENABLED +#include "sec_firstboot.h" +#endif + + +#define UNLOCKED(status) ((status==SEC_DRV_CAT_STAT_Unlocked)||(status==SEC_DRV_CAT_STAT_PermUnlocked)) + + +/** + * Resets the phone. + */ +static void reset(void) +{ + UINT16 i; + + TRACE("** RESET **"); + // Setup watchdog timer and let it timeout to make a reset + *(volatile UINT16*) 0xfffff804 = 0xFFFF; // Timer to watchdog + *(volatile UINT16*) 0xfffff800 = 0x0080; // Start timer + // Apparently works it only if we read this register? + i = *(volatile UINT16*) 0xfffff802; + *(volatile UINT16*) 0xfffff802 = 0x0001; // Load timer +} + + +/** + * Check whether or not we have valid lock data in the flash-area + * Run first_boot if we've got data, but its not initialized. + * + * @param pNumCategories Pointer to UINT8 where the number of + * categories will be stored. + * @return SEC_DRV_RET_Ok if theres valid data in the secure area. + */ +static T_SEC_DRV_RETURN check_hdr(UINT8 *pNumCategories) +{ + T_SEC_DRV_RETURN result = SEC_DRV_RET_Ok; + T_SEC_DRV_GLOBAL_CONF hdr; + + TRACE("check_hdr"); + if (sec_prim_get_global_conf(&hdr)) + { + switch (hdr.firstboot_pattern) + { + case SEC_PATTERN_INITIALIZED: + /* Secure area seems to be initialized OK */ + result = SEC_DRV_RET_Ok; + if (pNumCategories) + { + T_SEC_DRV_CONFIGURATION conf; + sec_prim_get_configuration(&conf); + *pNumCategories = conf.NumCategories; + } + TRACE("check_hdr - OK"); + break; + case SEC_PATTERN_UNINITIALIZED: + /* We've got new data in the secure area that needs to be processed */ +#ifdef FIRSTBOOT_ENABLED + first_boot(); +#endif + result = SEC_DRV_RET_NotPresent; + TRACE("check_hdr - uninitialized"); + reset(); + break; + default: + /* Secure area seems to contain void data! */ + result = SEC_DRV_RET_NotPresent; + if (pNumCategories) *pNumCategories = 0; + TRACE("check_hdr - void data"); + break; + } + } + else + { + result = SEC_DRV_RET_NotPresent; + TRACE("check_hdr - void!!!"); + } + return result; +} + + +/** + * Check if a counter is exceeded. + * + * @param pCount Pointer to current value - max value must follow immediately before! + * @return TRUE if the failure counter has been exceeded + */ +static BOOL Counter_Exceeded(UINT8 *pCount) +{ + pCount--; + return ((pCount[0] != 0xFF) && (pCount[1] >= pCount[0])); +} + + +/** + * Increment a counter if needed. + * + * @param pCount Pointer to the counter + */ +static void Counter_Increment(UINT8 *pCount) +{ + if ((*pCount) < 0xFF) + { + (*pCount)++; + } +} + + +/** + * Check whether or not a given key-len is ok according to its category. + * + * @param rec_num The record number to check the key_length against. + * @param key_len The key length to check. + * @return TRUE if the key_len is within specifications for the given + * record (category) - otherwise FALSE. + */ +static BOOL check_key_len(int rec_num, UINT8 key_len) +{ + if (key_len > SEC_DRV_KEY_MAX_LEN) return FALSE; + if ((rec_num == SEC_DRV_CAT_NUM_SIM) && (key_len < 6)) return FALSE; + if ((rec_num != SEC_DRV_CAT_NUM_SIM) && (key_len < 8)) return FALSE; + return TRUE; +} + + +/** + * Transform an unlock key into a special lock key - for use by the + * non-ETSI lock algorithm. + * + * @param pKey Pointer to a key to transform. This also serves as the output + * buffer. Must be '\0' terminated if shorter than SEC_DRV_KEY_MAX_LEN + */ +void calculate_spec_lock_key(char *pKey) +{ + int len=0; + int index; + + while((len<SEC_DRV_KEY_MAX_LEN) && (pKey[len])) len++; + len = len & 0xFE; + for(index=0; index<len; index+=2) + { + char ch; + ch = pKey[index]; + pKey[index] = pKey[index+1]; + pKey[index+1] = ch; + } +} + + +/** + * Compare 2 keys up to key_len characters, given the length constraints implied + * by the category number and check_key_len(). + * + * @param rec_num The category number to use when validating key_len. + * @param pRefKey Pointer to the key to use as reference (origin). + * @param pKey Pointer to the key to compare against pRefKey. + * @param key_len The maximum number of chars to use in the compare or 0 (to use all). + * @return SEC_DRV_RET_KeyWrong if the length constraints are violated, SEC_DRV_RET_Ok + * if the keys match, otherwise SEC_DRV_RET_KeyMismatch. + */ +static T_SEC_DRV_RETURN compare_keys(int rec_num, const char *pRefKey, const char *pKey, UINT8 key_len) +{ + if (pRefKey[0] == '\0') + { + TRACE("compare_keys - old key not present"); + return SEC_DRV_RET_Ok; + } + + if (key_len == 0) + { + /* compare using full length of the keys -> + to '\0' termination or max SEC_DRV_KEY_MAX_LEN chars... + whichever comes first */ + key_len = SEC_DRV_KEY_MAX_LEN; + } + /* compare using the specified number of chars */ + if (!check_key_len(rec_num, key_len) || (pKey==0L) || (pKey[0]=='\0')) return SEC_DRV_RET_KeyWrong; + return (strncmp(pRefKey, pKey, key_len)==0)? SEC_DRV_RET_Ok : SEC_DRV_RET_KeyMismatch; +} + + +/** + * Try to set a new key on a specific category. For this to happen, the new key must + * meet the length constraints implied by the category, and the current category key + * must match the given oldkey within the key_len chars (again, remember length + * constraints). + * + * @param rec_num The category number for which to set the key. + * @param pOldKey Key to compare against current category key. The 2 must match + * for the new key to be set. + * @param pNewKey Key to set the category to, if possible. + * @param key_len Length to use when comparing keys, see check_key_len(). + * @return SEC_DRV_RET_Ok if the key could be set. + */ +static T_SEC_DRV_RETURN set_key(T_SEC_DRV_CONFIGURATION *pConf, int rec_num, const char *pOldKey, const char *pNewKey, UINT8 key_len) +{ + T_SEC_DRV_RETURN result; + T_SEC_DRV_KEY key; + if (sec_prim_get_key(rec_num, &key)) + { + result = compare_keys(rec_num, (char *)key.digit, pOldKey, key_len); + if (result == SEC_DRV_RET_Ok) + { + int len = strlen(pNewKey); + if (check_key_len(rec_num, len)) + { + len++; /* convert len into a size! */ + if (len > SEC_DRV_KEY_MAX_LEN) len = SEC_DRV_KEY_MAX_LEN; + memcpy(key.digit, pNewKey, len); + sec_prim_set_key(rec_num, &key); + } + else + { + /* key must be within specified length according to category */ + result = SEC_DRV_RET_KeyWrong; + } + } + else + { + Counter_Increment(&pConf->FC_Current); + sec_prim_set_configuration(pConf); + } + } + else + { + result = SEC_DRV_RET_NotPresent; + } + return result; +} + + +/** + * Update all dependant categories. Scanning through a categories dependants, + * set their status and key if needed. + * + * @param pCatHdr Pointer to the category header for the parent category. + * @param pCatKey Pointer to the key for the parent category. + */ +static void update_dependants(const T_SEC_DRV_CAT_HDR *pCatHdr, const T_SEC_DRV_KEY *pCatKey, UINT16 dependMask) +{ + int dependentCat = 0; + int dependentBit = 1; + + for (; dependentCat<(sizeof(pCatHdr->Dependency)*8); dependentCat++) + { + if (pCatHdr->Dependency & dependentBit & dependMask) + { + { + /* set key on dependant iff not already done, and parent has a key */ + T_SEC_DRV_KEY dependentKey; + if (sec_prim_get_key(dependentCat, &dependentKey)) + { + if (pCatKey->digit[0] != '\0' && dependentKey.digit[0] == '\0') + { + sec_prim_set_key(dependentCat, pCatKey); + } + } + } + { + /* update status on dependant */ + T_SEC_DRV_CAT_HDR dependentHdr; + if (sec_prim_get_cat_header(dependentCat, &dependentHdr)) + { + if (dependentHdr.Status != SEC_DRV_CAT_STAT_PermUnlocked) + { + dependentHdr.Status = pCatHdr->Status; + sec_prim_set_cat_header(dependentCat, &dependentHdr); + } + } + } + } + dependentBit = dependentBit<<1; + } +} + + +/** + * Get the MEPD configuration. + * + * @param ppConfiguration Pointer to pointer where the configuration is stored. + * @return SEC_DRV_RET_Ok if the configuration could be read. + */ +T_SEC_DRV_RETURN sec_get_CFG(T_SEC_DRV_CONFIGURATION **ppConfiguration) +{ + T_SEC_DRV_RETURN result = check_hdr(0L); + if (result == SEC_DRV_RET_Ok) + { + *ppConfiguration = (T_SEC_DRV_CONFIGURATION *)M_ALLOC(sizeof(T_SEC_DRV_CONFIGURATION)); + sec_prim_get_configuration(*ppConfiguration); + } + else + { + *ppConfiguration = 0L; + } + return result; +} + + +/** + * Compare a key against the one stored for a given category. + * + * @param rec_num The category number for which to check the key. + * @param pKey The key to compare. + * @param key_len The length of the keys to use in the compare. Subject to constraints. + * @return SEC_DRV_RET_Ok if the keys match + */ +T_SEC_DRV_RETURN sec_cmp_KEY (UINT8 rec_num, const char *pKey, UINT8 key_len) +{ + T_SEC_DRV_KEY refKey; + T_SEC_DRV_CONFIGURATION conf; + UINT8 numCategories=0; + T_SEC_DRV_RETURN result = check_hdr(&numCategories); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + return result; + } + + sec_prim_get_configuration(&conf); + if (Counter_Exceeded(&conf.FC_Current)) + { + result = SEC_DRV_RET_FCExeeded; + } + else if (conf.Flags & SEC_DRV_HDR_FLAG_LAM_Unlock) + { + result = SEC_DRV_RET_Unknown; + } + else if (sec_prim_get_key(rec_num, &refKey)) + { + result = compare_keys(rec_num, (char *)refKey.digit, pKey, key_len); + } + else + { + /* requested data outside configured categories! */ + result = SEC_DRV_RET_NotPresent; + } + return result; +} + + +/** + * Try to set a key for a given category. The category must be unlocked prior + * to this attempt. + * + * @param rec_num Category number whoose key should be set. + * @param pOldKey pointer to current key of the category (or rather what the + * client believes to be the current key). + * @param pNewKey Pointer to the key that should be set. + * @param key_len length to use during key-comparision. + * @return SEC_DRV_RET_Ok if the key could be set. + */ +T_SEC_DRV_RETURN sec_set_KEY (UINT8 rec_num, const char *pOldKey, const char *pNewKey, UINT8 key_len) +{ + T_SEC_DRV_CONFIGURATION conf; + UINT8 numCategories=0; + T_SEC_DRV_CAT_HDR catHdr; + T_SEC_DRV_RETURN result = check_hdr(&numCategories); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + return result; + } + + sec_prim_get_configuration(&conf); + if (Counter_Exceeded(&conf.FC_Current)) + { + result = SEC_DRV_RET_FCExeeded; + } + else if (sec_prim_get_cat_header(rec_num, &catHdr)) + { + if (UNLOCKED(catHdr.Status) && !(conf.Flags & SEC_DRV_HDR_FLAG_LAM_Unlock)) + { + result = set_key(&conf, rec_num, pOldKey, pNewKey, key_len); + } + else + { + /* category must be unlocked prior to setting a key */ + result = SEC_DRV_RET_Unknown; + } + } + else + { + /* requested data outside configured categories! */ + result = SEC_DRV_RET_NotPresent; + } + return result; +} + + +/** + * Set the key for tha failure counter. + * + * @param pOldKey The current failure count key. Must match that stored in the + * secure area for the new key to be set. + * @param pNewKey The new key to set for the failure counter. + * @return SEC_DRV_RET_Ok if the key could be set. + */ +T_SEC_DRV_RETURN sec_set_FC_KEY (const char *pOldKey, const char *pNewKey) +{ + T_SEC_DRV_CONFIGURATION conf; + T_SEC_DRV_RETURN result = check_hdr(0L); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + return result; + } + + sec_prim_get_configuration(&conf); + if (Counter_Exceeded(&conf.FC_Current)) + { + result = SEC_DRV_RET_FCExeeded; + } + else + { + result = set_key(&conf, -1, pOldKey, pNewKey, 0); + } + return result; +} + + +/** + * Get the record data (both header and body) for a given category. + * + * @param rec_num The category number to get the data for. + * @param ppCategory Pointer to where the result pointer should be stored. + * @return SEC_DRV_RET_Ok if the record data could be read. + */ +T_SEC_DRV_RETURN sec_get_REC (UINT8 rec_num, T_SEC_DRV_CATEGORY **ppCategory) +{ + UINT8 numCategories=0; + T_SEC_DRV_RETURN result = check_hdr(&numCategories); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + *ppCategory = 0L; + return result; + } + + if (rec_num < numCategories) + { + *ppCategory = (T_SEC_DRV_CATEGORY *)M_ALLOC(sizeof(T_SEC_DRV_CATEGORY)); + sec_prim_get_cat_header(rec_num, &(*ppCategory)->Header); + if ((*ppCategory)->Header.DataLen) + { + (*ppCategory)->pBody = M_ALLOC((*ppCategory)->Header.DataLen); + sec_prim_get_cat_body(rec_num, (*ppCategory)->pBody, (*ppCategory)->Header.DataLen); + } + else + { + (*ppCategory)->pBody = 0L; + } + result = SEC_DRV_RET_Ok; + } + else + { + /* requested data outside configured categories! */ + *ppCategory = 0L; + result = SEC_DRV_RET_NotPresent; + } + return result; +} + + +/** + * Set the body part of a category's data. The data can only be set if the + * category is unlocked. + * + * @param rec_num Category number whose body part should be set. + * @param pBody Pointer to the body data that should be stored. + * @return SEC_DRV_RET_Ok if the data could be stored. + */ +T_SEC_DRV_RETURN sec_set_REC (UINT8 rec_num, const T_SEC_DRV_CATEGORY *pCategory) +{ + T_SEC_DRV_CAT_HDR header; + UINT8 numCategories=0; + T_SEC_DRV_RETURN result = check_hdr(&numCategories); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + return result; + } + + if (sec_prim_get_cat_header(rec_num, &header)) + { + if (header.Status == SEC_DRV_CAT_STAT_Unlocked || + header.Status == SEC_DRV_CAT_STAT_PermUnlocked) + { + sec_prim_set_cat_body(rec_num, pCategory->pBody, pCategory->Header.DataLen); + result = SEC_DRV_RET_Ok; + } + else + { + /* Category must be unlocked in order to write record data */ + result = SEC_DRV_RET_Unknown; + } + } + else + { + /* requested data outside configured categories! */ + result = SEC_DRV_RET_NotPresent; + } + return result; +} + + +/** + * Try to unlock a category. Failure count must not have been exeeded, NAM unlock + * must not be set and the category must not be linklocked. Also the given key + * must match the stored key for the category before it is unlocked. + * + * @param rec_num Category number that should be unlocked. + * @param pKey Key that should match the stored category key. + * @param key_len The maximum number of chars to use in the compare or 0 (to use all). + * @return SEC_DRV_RET_Ok if the category could be unlocked. + */ +T_SEC_DRV_RETURN sec_rec_Unlock (UINT8 rec_num, T_SEC_DRV_UNLOCK_TYPE unlockType, const char *pKey, UINT8 key_len, UINT16 dependMask) +{ + T_SEC_DRV_CONFIGURATION conf; + T_SEC_DRV_CAT_HDR catHdr; + UINT8 numCategories=0; + T_SEC_DRV_RETURN result = check_hdr(&numCategories); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + return result; + } + + sec_prim_get_configuration(&conf); + if (Counter_Exceeded(&conf.FC_Current)) + { + result = SEC_DRV_RET_FCExeeded; + } + else if (sec_prim_get_cat_header(rec_num, &catHdr)) + { + if (UNLOCKED(catHdr.Status) || + (conf.Flags & SEC_DRV_HDR_FLAG_LAM_Unlock) || + (catHdr.Flags & SEC_DRV_CAT_FLAG_LinkLocked)) + { + /* NAM or LinkLock failure */ + result = SEC_DRV_RET_Unknown; + } + else + { + T_SEC_DRV_KEY catKey; + sec_prim_get_key(rec_num, &catKey); + result = compare_keys(rec_num, (char *)catKey.digit, pKey, key_len); + if (result == SEC_DRV_RET_Ok) + { + /* update status */ + catHdr.Status = (unlockType == TEMPORARY_UNLOCK)? + SEC_DRV_CAT_STAT_Unlocked : + SEC_DRV_CAT_STAT_PermUnlocked; + sec_prim_set_cat_header(rec_num, &catHdr); + /* update dependants */ + update_dependants(&catHdr, &catKey, dependMask); + /* reset failure counter */ + conf.FC_Current = 0; + sec_prim_set_configuration(&conf); + } + else + { + /* Failure comparing keys! - + update failure counter */ + Counter_Increment(&conf.FC_Current); + sec_prim_set_configuration(&conf); + } + } + } + else + { + /* requested data outside configured categories! */ + result = SEC_DRV_RET_NotPresent; + } + return result; +} + + +static T_SEC_DRV_RETURN lock_record( + UINT8 rec_num, + T_SEC_DRV_CAT_HDR *catHdr, + UINT16 dependMask) +{ + T_SEC_DRV_KEY catKey; + catHdr->Status = SEC_DRV_CAT_STAT_Locked; + sec_prim_get_key(rec_num, &catKey); + sec_prim_set_cat_header(rec_num, catHdr); + update_dependants(catHdr, &catKey, dependMask); + return SEC_DRV_RET_Ok; +} + + +/** + * Try to lock a category. Actual algorith depends on flags set for the individual + * category. + * + * @param rec_num Category number that should be locked. + * @param pKey Key that should match (or be set in) the stored category key. + * @param key_len The maximum number of chars to use in the compare or 0 (to use all). + * @return SEC_DRV_RET_Ok if the category could be locked. + */ +T_SEC_DRV_RETURN sec_rec_Lock (UINT8 rec_num, const char *pKey, UINT8 key_len, UINT16 dependMask) +{ + T_SEC_DRV_CAT_HDR catHdr; + UINT8 numCategories=0; + T_SEC_DRV_RETURN result = check_hdr(&numCategories); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + return result; + } + + if (sec_prim_get_cat_header(rec_num, &catHdr)) + { + if (catHdr.Status != SEC_DRV_CAT_STAT_Unlocked) + { + /* Status must be unlocked before locking! */ + result = SEC_DRV_RET_Unknown; + } + else + { + if (rec_num == SEC_DRV_CAT_NUM_AP) + { + result = lock_record(rec_num, &catHdr, dependMask); + } + else + { + T_SEC_DRV_CONFIGURATION conf; + sec_prim_get_configuration(&conf); + if (conf.Flags & SEC_DRV_HDR_FLAG_ETSI_Flag) + { + /* ETSI Mode - set the category key */ + int len = strlen(pKey); + if (check_key_len(rec_num, len)) + { + T_SEC_DRV_KEY key; + memset(key.digit, 0, SEC_DRV_KEY_MAX_LEN); + memcpy(key.digit, pKey, len); + sec_prim_set_key(rec_num, &key); + result = lock_record(rec_num, &catHdr, dependMask); + } + else + { + /* Key has wrong length! */ + result = SEC_DRV_RET_KeyWrong; + } + } + else if (conf.Flags & SEC_DRV_HDR_FLAG_LAM_Unlock) + { + /* Non-ETSI, but LAM_unlock is set */ + result = SEC_DRV_RET_Unknown; + } + else + { + /* Non-ETSI mode */ + T_SEC_DRV_KEY key; + sec_prim_get_key(rec_num, &key); + if (conf.Flags & SEC_DRV_HDR_FLAG_Spec_Lock_Key) + { + /* Special lock key enabled */ + calculate_spec_lock_key((char *)&key.digit[0]); + } + result = compare_keys(rec_num, (char *)key.digit, pKey, key_len); + if (result == SEC_DRV_RET_Ok) + { + result = lock_record(rec_num, &catHdr, dependMask); + } + } + } + } + } + else + { + /* requested data outside configured categories! */ + result = SEC_DRV_RET_NotPresent; + } + return result; +} + + +/** + * reset the failure counter. The correct key must of course be given. + * + * @param pKey The key used to try to reset the failure counter. + * @key_len Length of the key to use. + * @return SEC_DRV_RET_Ok if the failure counter could be reset. + */ +T_SEC_DRV_RETURN sec_FC_Reset (const char *pKey, UINT8 key_len) +{ + T_SEC_DRV_KEY refKey; + T_SEC_DRV_CONFIGURATION conf; + T_SEC_DRV_RETURN result = check_hdr(0L); + if (result != SEC_DRV_RET_Ok) + { + /* data not present in secure area! */ + return result; + } + + sec_prim_get_configuration(&conf); + if (Counter_Exceeded(&conf.FC_Reset_Fail_Current) || + Counter_Exceeded(&conf.FC_Reset_Success_Current)) + { + result = SEC_DRV_RET_FCExeeded; + } + else + { + sec_prim_get_key(-1, &refKey); + result = compare_keys(-1, (char *)refKey.digit, pKey, key_len); + if (result == SEC_DRV_RET_Ok) + { + Counter_Increment(&conf.FC_Reset_Success_Current); + conf.FC_Current = 0; + } + else + { + Counter_Increment(&conf.FC_Reset_Fail_Current); + } + sec_prim_set_configuration(&conf); + } + return result; +} + + +T_SEC_DRV_RETURN sec_FC_Increment(void) +{ + T_SEC_DRV_RETURN result = SEC_DRV_RET_Ok; + T_SEC_DRV_CONFIGURATION conf; + + sec_prim_get_configuration(&conf); + if (Counter_Exceeded(&conf.FC_Current)) + { + result = SEC_DRV_RET_FCExeeded; + } + else + { + Counter_Increment(&conf.FC_Current); + sec_prim_set_configuration(&conf); + } + return result; +} +