420 lines
13 KiB
C
420 lines
13 KiB
C
/****************************************************************
|
|
* *
|
|
* Copyright 2009 Fidelity Information Services, Inc *
|
|
* *
|
|
* This source code contains the intellectual property *
|
|
* of its copyright holder(s), and is made available *
|
|
* under a license. If you do not know the terms of *
|
|
* the license, please stop and do not read further. *
|
|
* *
|
|
****************************************************************/
|
|
|
|
#define _FILE_OFFSET_BITS 64 /* Needed to compile gpgme client progs also with large file support */
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <gpgme.h> /* gpgme functions */
|
|
#include <gpg-error.h> /* gcry*_err_t */
|
|
#include "gtmxc_types.h" /* xc_string, xc_status_t and other callin interfaces xc_fileid */
|
|
#include "gtmcrypt_interface.h" /* Function prototypes for gtmcrypt*.* functions */
|
|
#include "gtmcrypt_ref.h"
|
|
#include "gtmcrypt_dbk_ref.h"
|
|
#include "gtmcrypt_pk_ref.h"
|
|
#include "gtmcrypt_sym_ref.h"
|
|
|
|
int num_entries;
|
|
db_key_map *db_map_root;
|
|
db_key_map **fast_lookup_entry;
|
|
extern char err_string[ERR_STRLEN];
|
|
extern int can_prompt_passwd;
|
|
|
|
/* Cleanup the db key entries and also remove the plain text passwd stored there */
|
|
void gc_dbk_scrub_entries()
|
|
{
|
|
db_key_map *temp, *temp1;
|
|
|
|
temp = GC_DBK_GET_FIRST_ENTRY();
|
|
/* Walk through the linked list and free each member of the structure.*/
|
|
while (NULL != temp)
|
|
{
|
|
# ifdef USE_GCRYPT
|
|
if (temp->encr_key_handle)
|
|
gcry_cipher_close(temp->encr_key_handle);
|
|
if (temp->decr_key_handle)
|
|
gcry_cipher_close(temp->decr_key_handle);
|
|
# endif
|
|
temp1 = GC_DBK_GET_NEXT_ENTRY(temp);
|
|
GC_FREE_DB_KEY_MAP(temp); /* Note, this will memset the key_string to 0 before free'ing */
|
|
temp = temp1;
|
|
}
|
|
if (NULL != fast_lookup_entry)
|
|
GC_FREE(fast_lookup_entry);
|
|
}
|
|
|
|
/* Find out whether the db key file is modified since last time */
|
|
xc_status_t gc_dbk_is_db_key_file_modified()
|
|
{
|
|
struct stat stat_info;
|
|
char *gtm_dbkeys;
|
|
int status;
|
|
static time_t last_modified = 0;
|
|
|
|
GC_GETENV(gtm_dbkeys, GTM_DBKEYS, status);
|
|
if (GC_FAILURE == status)
|
|
{
|
|
GC_ENV_UNSET_ERROR(GTM_DBKEYS);
|
|
return GC_FAILURE;
|
|
}
|
|
if (0 != stat(gtm_dbkeys, &stat_info) || (last_modified != stat_info.st_mtime))
|
|
{
|
|
last_modified = stat_info.st_mtime;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Given a xc_fileid, containing a unique description of the dat file, the function searches for it's
|
|
* entry in the linked list. On unsuccessful search, returns NULL.
|
|
*/
|
|
db_key_map* gc_dbk_get_entry_by_fileid(xc_fileid_ptr_t fileid)
|
|
{
|
|
db_key_map *cur = db_map_root;
|
|
|
|
while (NULL != cur)
|
|
{
|
|
if (!cur->fileid_dirty && (!cur->sym_key_dirty) && (gtm_is_file_identical_fptr(fileid, (cur->fileid))))
|
|
break;
|
|
cur = (db_key_map *)cur->next;
|
|
}
|
|
return cur;
|
|
}
|
|
|
|
/* Given a hash of the symmetric key, the function searches for the entry in the linked list that matches with the
|
|
* given hash
|
|
*/
|
|
db_key_map* gc_dbk_get_entry_by_hash(xc_string_t *hash)
|
|
{
|
|
db_key_map *cur = db_map_root;
|
|
|
|
assert(hash);
|
|
assert(hash->length);
|
|
|
|
while (NULL != cur)
|
|
{
|
|
if (hash->length == cur->hash.length && (0 == memcmp(hash->address, cur->hash.address, hash->length)))
|
|
break;
|
|
cur = (db_key_map *)cur->next;
|
|
}
|
|
return cur;
|
|
}
|
|
|
|
dbkeyfile_line_type gc_dbk_get_line_info (char *buf, char *data)
|
|
{
|
|
dbkeyfile_line_type line_type = ERROR_LINE_INFO;
|
|
|
|
if (!memcmp(buf, DAT_LINE_INDICATOR, DAT_LINE_INDICATOR_SIZE))
|
|
{
|
|
strcpy(data, &buf[DAT_LINE_INDICATOR_SIZE]); /* The rest of the line is a file name */
|
|
if ('\n' == data[strlen(data) - 1]) data[strlen(data) - 1] = '\0';
|
|
line_type = DAT_LINE_INFO;
|
|
} else if (!memcmp(buf, KEY_LINE_INDICATOR, KEY_LINE_INDICATOR_SIZE))
|
|
{
|
|
strcpy(data, &buf[KEY_LINE_INDICATOR_SIZE]); /* The rest of the line is a file name */
|
|
if ('\n' == data[strlen(data) - 1]) data[strlen(data) - 1] = '\0';
|
|
line_type = KEY_LINE_INFO;
|
|
}
|
|
return line_type;
|
|
}
|
|
|
|
xc_status_t gc_dbk_load_gtm_dbkeys(FILE **gtm_dbkeys)
|
|
{
|
|
char *ptr, dbkeys_filename[GTM_PATH_MAX];
|
|
int status;
|
|
FILE *dbkeys_fp;
|
|
struct stat stat_buf;
|
|
|
|
GC_GETENV(ptr, GTM_DBKEYS, status);
|
|
if (GC_SUCCESS == status)
|
|
{
|
|
if (0 == strlen(ptr))
|
|
{
|
|
snprintf(err_string, ERR_STRLEN, "%s", "Environment variable gtm_dbkeys set to empty string");
|
|
return GC_FAILURE;
|
|
}
|
|
if (0 == stat(ptr, &stat_buf)) /* See if the environment variable points to a proper path */
|
|
{
|
|
if (S_ISDIR(stat_buf.st_mode)) /* if directory */
|
|
snprintf(dbkeys_filename, GTM_PATH_MAX, "%s/%s", ptr, DOT_GTM_DBKEYS);
|
|
else if (S_ISREG(stat_buf.st_mode)) /* if file */
|
|
snprintf(dbkeys_filename, GTM_PATH_MAX, "%s", ptr);
|
|
else
|
|
{
|
|
snprintf(err_string, ERR_STRLEN, "Unknown file type : %s", ptr);
|
|
return GC_FAILURE;
|
|
}
|
|
} else /* error if env variable present but couldn't stat */
|
|
{
|
|
snprintf(err_string, ERR_STRLEN, "Cannot find DB keys file - %s", ptr);
|
|
return GC_FAILURE;
|
|
}
|
|
} else /* if env variable is undefined, then look for $HOME/.gtm_dbkeys */
|
|
{
|
|
GC_GETENV(ptr, "HOME", status);
|
|
snprintf(dbkeys_filename, GTM_PATH_MAX, "%s/%s", ptr, DOT_GTM_DBKEYS);
|
|
if (0 != stat(dbkeys_filename, &stat_buf))
|
|
{
|
|
snprintf(err_string,
|
|
ERR_STRLEN,
|
|
"Environment variable gtm_dbkeys undefined. Cannot find %s/.gtm_dbkeys",
|
|
ptr);
|
|
return GC_FAILURE;
|
|
}
|
|
}
|
|
/* At this point we would have at least one form of the gtm_dbkeys in dbkeys_filename */
|
|
status = GC_SUCCESS;
|
|
if (NULL != (dbkeys_fp = fopen(dbkeys_filename, "r")))
|
|
*gtm_dbkeys = dbkeys_fp;
|
|
else
|
|
{
|
|
snprintf(err_string, ERR_STRLEN, "Cannot open DB keys file - %s", dbkeys_filename);
|
|
status = GC_FAILURE;
|
|
}
|
|
return status;
|
|
}
|
|
/* Initialize the linked list with minimal things. For each pair of entries in the db key file, load the
|
|
* file names into the linked list and validate the format of the entries. Returns error if the format is
|
|
* not the one that's expected. This is a fatal error and program will not continue on encountering this
|
|
* error. Another fatal error is the 'gtm_dbkeys' env variable not set
|
|
*/
|
|
xc_status_t gc_dbk_load_entries_from_file()
|
|
{
|
|
FILE *dbkeys_fp = NULL;
|
|
db_key_map *node = NULL;
|
|
int current_state;
|
|
int start = TRUE, count = 0, status, all_done = FALSE;
|
|
int looking_for_dat_entry = 1, looking_for_key_entry = 2;
|
|
int line_no = 0;
|
|
char *prefix = "Error parsing database key file";
|
|
char buf[GTM_PATH_MAX], data[GTM_PATH_MAX];
|
|
dbkeyfile_line_type line_type;
|
|
/* Check for $gtm_dbkeys */
|
|
if (0 != gc_dbk_load_gtm_dbkeys(&dbkeys_fp))
|
|
return GC_FAILURE;
|
|
|
|
/* Read the file and parse the contents and fill a mapping table */
|
|
/* Note the format of this dbkeys will be like this -
|
|
dat <db file1 path>
|
|
key <key file1 name>
|
|
dat <db file2 path>
|
|
key <key file2 name>
|
|
*/
|
|
/* To start with we are looking for a dat entry */
|
|
current_state = looking_for_dat_entry;
|
|
GC_DBK_SET_FIRST_ENTRY(NULL);
|
|
while (!feof(dbkeys_fp))
|
|
{
|
|
|
|
memset(buf, 0, GTM_PATH_MAX);
|
|
memset(data, 0, GTM_PATH_MAX);
|
|
|
|
/* Skip past empty lines */
|
|
while (1)
|
|
{
|
|
if (!fgets(buf, GTM_PATH_MAX, dbkeys_fp))
|
|
{
|
|
/* If EOF is reached but the line_no din't move beyond 0, it means we have no entries
|
|
* in the db key file. */
|
|
if (0 == line_no)
|
|
{
|
|
fclose(dbkeys_fp);
|
|
snprintf(err_string,
|
|
ERR_STRLEN,
|
|
"%s. %s",
|
|
prefix,
|
|
"No entries found in DB keys file.");
|
|
return GC_FAILURE;
|
|
}
|
|
/* At the end if we are looking for a key entry, then the last dat entry is unmatched*/
|
|
if (current_state == looking_for_key_entry)
|
|
{
|
|
fclose(dbkeys_fp);
|
|
snprintf(err_string,
|
|
ERR_STRLEN,
|
|
"%s. No matching KEY entry found for DAT entry at line: %d",
|
|
prefix,
|
|
line_no);
|
|
return GC_FAILURE;
|
|
}
|
|
all_done = TRUE;
|
|
break;
|
|
}
|
|
if (buf[0] != '\0' && (buf[0] != '\n')) /* Non-Empty line */
|
|
{
|
|
buf[strlen(buf) - 1] = '\0';
|
|
break;
|
|
}
|
|
else
|
|
line_no++;
|
|
}
|
|
if (all_done) break;
|
|
/* Figure out what kind of line are we going to deal with. */
|
|
line_type = gc_dbk_get_line_info(buf, data);
|
|
switch(line_type)
|
|
{
|
|
case DAT_LINE_INFO:
|
|
line_no++;
|
|
/* We should have seen a key before seeing the next dat file */
|
|
if (current_state == looking_for_key_entry && (FALSE == start))
|
|
{
|
|
fclose(dbkeys_fp);
|
|
snprintf(err_string,
|
|
ERR_STRLEN,
|
|
"%s. At line %d: No matching KEY entry found for %s",
|
|
prefix,
|
|
line_no,
|
|
buf);
|
|
return GC_FAILURE;
|
|
}
|
|
/* Now that we have seen a dat file, we will now be looking for a key entry */
|
|
current_state = looking_for_key_entry;
|
|
start = FALSE;
|
|
GC_NEW_DB_KEYMAP(node);
|
|
GC_COPY_TO_XC_STRING(&node->db_name, data, strlen(data));
|
|
node->next = (struct db_key_map*) db_map_root;
|
|
GC_DBK_SET_FIRST_ENTRY(node);
|
|
break;
|
|
|
|
case KEY_LINE_INFO:
|
|
line_no++;
|
|
/* We should have seen a dat file before seeing a key file */
|
|
if (!node && (current_state == looking_for_dat_entry))
|
|
{
|
|
fclose(dbkeys_fp);
|
|
snprintf(err_string,
|
|
ERR_STRLEN,
|
|
"%s. At line %d: No matching DAT entry found for %s",
|
|
prefix,
|
|
line_no,
|
|
buf);
|
|
return GC_FAILURE;
|
|
}
|
|
/* Now that we have seen a key file, we will now be looking for a dat entry */
|
|
current_state = looking_for_dat_entry;
|
|
num_entries++;
|
|
GC_COPY_TO_XC_STRING(&node->key_filename, data, strlen(data));
|
|
break;
|
|
|
|
default:
|
|
line_no++;
|
|
fclose(dbkeys_fp);
|
|
snprintf(err_string,
|
|
ERR_STRLEN,
|
|
"%s. At line %d: %s does not start with 'dat '/'key '",
|
|
prefix,
|
|
line_no,
|
|
buf);
|
|
return GC_FAILURE;
|
|
}
|
|
}
|
|
GC_MALLOC(fast_lookup_entry, (SIZEOF(fast_lookup_entry) * num_entries), db_key_map*);
|
|
node = GC_DBK_GET_FIRST_ENTRY();
|
|
while (NULL != node)
|
|
{
|
|
node->index = count;
|
|
fast_lookup_entry[count] = node;
|
|
count++;
|
|
node = GC_DBK_GET_NEXT_ENTRY(node);
|
|
}
|
|
assert(count == num_entries);
|
|
fclose(dbkeys_fp);
|
|
return GC_SUCCESS;
|
|
}
|
|
|
|
xc_status_t gc_dbk_fill_sym_key_and_hash(xc_fileid_ptr_t req_fileid, char *req_hash)
|
|
{
|
|
db_key_map *cur;
|
|
int status, concerns_current_file;
|
|
xc_fileid_ptr_t db_fileid;
|
|
|
|
cur = GC_DBK_GET_FIRST_ENTRY();
|
|
while (NULL != cur)
|
|
{
|
|
db_fileid = NULL;
|
|
if (TRUE == cur->fileid_dirty)
|
|
{
|
|
if (TRUE == gtm_filename_to_id_fptr(&(cur->db_name), &db_fileid))
|
|
{
|
|
cur->fileid_dirty = FALSE;
|
|
cur->fileid = db_fileid;
|
|
}
|
|
}
|
|
if (TRUE == cur->sym_key_dirty) /* Need to fill sym key value */
|
|
{
|
|
/* Before decrypting the key, let's see if the gtm_passwd in the environment has changed since
|
|
* the last time we read from the environment. This way if the user had originally entered a wrong
|
|
* password and if he is in MUMPS and changes the password through a external call then we should
|
|
* be using the new password rather than the old one which might still be hanging in the environment. */
|
|
gc_pk_crypt_prompt_passwd_if_needed(can_prompt_passwd);
|
|
GC_PK_GET_DECRYPTED_KEY(cur->key_string, status);
|
|
|
|
/* If we failed because of a gtm_passwd being wrong we wouldn't want to continue any further although it
|
|
* might not concern for the current file. */
|
|
if (GPG_ERR_BAD_PASSPHRASE == status)
|
|
return GC_FAILURE;
|
|
|
|
concerns_current_file = (NULL != req_fileid && (gtm_is_file_identical_fptr(cur->fileid, req_fileid)));
|
|
/* Eventhough we may have an encountered error in the above decryption, we report only when it is concerned
|
|
* with the current dat file being used. For other files, we silently ignore the error, with a hope that by
|
|
* the time the database file is accessed, db key file would have been updated appropriately by the user.
|
|
*/
|
|
if (0 != status && concerns_current_file)
|
|
return GC_FAILURE;
|
|
|
|
/* It could be possible that the decryption din't return any error but plain_text_length happens to
|
|
* be zero. So, we verify it and return error in case the length is zero. Again we make sure that we return
|
|
* the error only when it concerned with the current dat file.
|
|
*/
|
|
if (0 == cur->key_string.length && concerns_current_file)
|
|
{
|
|
snprintf(err_string, ERR_STRLEN, "Symmetric key %s found to be empty", cur->key_filename.address);
|
|
return GC_FAILURE;
|
|
}
|
|
|
|
/* If we fall through here, it means that we have encountered an error for a database which is not of
|
|
* concern at this moment. So, we continue with the next database. */
|
|
if (0 != status)
|
|
{
|
|
cur = GC_DBK_GET_NEXT_ENTRY(cur);
|
|
continue;
|
|
}
|
|
|
|
/* If everything is fine, compute the hash for the key */
|
|
GC_PK_COMPUTE_HASH(cur->hash, cur->key_string);
|
|
GC_SYM_CREATE_HANDLES(cur);
|
|
cur->sym_key_dirty = FALSE;
|
|
|
|
/* If we have found a matching entry for the hash/fileid that we requested for, return immediately with
|
|
* GC_SUCCESS */
|
|
if (concerns_current_file
|
|
|| (NULL != req_hash && (0 == memcmp(cur->hash.address, req_hash, GTMCRYPT_HASH_LEN))))
|
|
return GC_SUCCESS;
|
|
}
|
|
cur = GC_DBK_GET_NEXT_ENTRY(cur);
|
|
}
|
|
return GC_SUCCESS;
|
|
}
|
|
|
|
void gc_dbk_get_hash(db_key_map *entry, xc_string_t *hash)
|
|
{
|
|
/*Make sure the reference block that is being passed is already allocated */
|
|
assert(hash->address);
|
|
assert(NULL != entry);
|
|
memcpy(hash->address, entry->hash.address, GTMCRYPT_HASH_LEN);
|
|
hash->length = GTMCRYPT_HASH_LEN;
|
|
}
|