fis-gtm/sr_unix/gtmcrypt_dbk_ref.c

373 lines
12 KiB
C

/****************************************************************
* *
* Copyright 2009, 2012 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> /* BYPASSOK -- Plugin doesn't have access to gtm_* header files */
#include <string.h> /* BYPASSOK -- see above */
#include <unistd.h> /* BYPASSOK -- see above */
#include <stdlib.h> /* BYPASSOK -- see above */
#include <sys/stat.h> /* BYPASSOK -- see above */
#include <ctype.h>
#include <assert.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_sym_ref.h"
#include "gtmcrypt_dbk_ref.h"
#include "gtmcrypt_pk_ref.h"
#define NEWLINE 0x0A
GBLDEF int num_entries;
GBLREF int can_prompt_passwd;
GBLDEF gtm_dbkeys_tbl *tbl_head;
GBLDEF gtm_dbkeys_tbl **fast_lookup_entry;
/* Free up the linked list of database-symmetric key association AFTER scrubbing off the contents of the raw symmetric key and
* its corresponding hash
*/
void gc_dbk_scrub_entries()
{
gtm_dbkeys_tbl *cur, *temp;
cur = tbl_head;
while (NULL != cur)
{
# ifdef USE_GCRYPT
if (!cur->symmetric_key_dirty)
{
if (cur->encr_key_handle)
gcry_cipher_close(cur->encr_key_handle);
if (cur->decr_key_handle)
gcry_cipher_close(cur->decr_key_handle);
}
# endif
temp = cur->next;
GC_FREE_TBL_ENTRY(cur); /* Note, this will memset the symmetric_key to 0 before free'ing */
cur = temp;
}
if (NULL != fast_lookup_entry)
GC_FREE(fast_lookup_entry);
num_entries = 0;
}
/* 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.
*/
gtm_dbkeys_tbl* gc_dbk_get_entry_by_fileid(xc_fileid_ptr_t fileid)
{
gtm_dbkeys_tbl *cur = tbl_head;
while (NULL != cur)
{
if (!cur->fileid_dirty && (!cur->symmetric_key_dirty) && (gtm_is_file_identical_fptr(fileid, cur->fileid)))
break;
cur = (gtm_dbkeys_tbl *)cur->next;
}
return cur;
}
/* Given a hash, the function returns the entry in the linked list that matches with the given hash. Otherwise, NULL is returned */
gtm_dbkeys_tbl* gc_dbk_get_entry_by_hash(xc_string_t *hash)
{
gtm_dbkeys_tbl *cur = tbl_head;
assert(hash && (hash->length == GTMCRYPT_HASH_LEN));
while (NULL != cur)
{
if ((hash->length == GTMCRYPT_HASH_LEN) && (0 == memcmp(hash->address, cur->symmetric_key_hash, GTMCRYPT_HASH_LEN)))
break;
cur = cur->next;
}
return cur;
}
xc_status_t gc_dbk_fill_gtm_dbkeys_fname(char *fname)
{
char *ptr;
int status;
struct stat stat_buf;
if (ptr = getenv(GTM_DBKEYS))
{
if (0 == STRLEN(ptr))
{
UPDATE_ERROR_STRING(ENV_EMPTY_ERROR, GTM_DBKEYS);
return GC_FAILURE;
} else 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(fname, GTM_PATH_MAX, "%s/%s", ptr, DOT_GTM_DBKEYS);
} else if (S_ISREG(stat_buf.st_mode)) /* if file */
{
SNPRINTF(fname, GTM_PATH_MAX, "%s", ptr);
} else
{
UPDATE_ERROR_STRING("%s is neither a directory nor a regular file", ptr);
return GC_FAILURE;
}
} else if (ENOENT == errno)
{ /* File doesn't exist */
UPDATE_ERROR_STRING("Cannot find DB keys file - %s", ptr);
return GC_FAILURE;
} else
{ /* Some other error */
UPDATE_ERROR_STRING("Cannot find DB keys file - %s. %s", ptr, strerror(errno));
return GC_FAILURE;
}
} else if (ptr = getenv(HOME))
{
SNPRINTF(fname, GTM_PATH_MAX, "%s/%s", ptr, DOT_GTM_DBKEYS);
} else
{
UPDATE_ERROR_STRING("Neither $"GTM_DBKEYS "nor $"HOME " is defined");
return GC_FAILURE;
}
return GC_SUCCESS;
}
/* 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 *gtm_dbkeys_fp;
int current_state, count, status, space_cnt, line_no = 0, line_type, filename_len;
int looking_for_dat_entry = 1, looking_for_key_entry = 2, buflen, save_errno;
const char *prefix = "Error parsing database key file";
char buf[LINE_MAX], gtm_dbkeys_fname[GTM_PATH_MAX];
struct stat stat_info;
static time_t last_modified = 0;
gtm_dbkeys_tbl *node = NULL;
if (GC_SUCCESS != gc_dbk_fill_gtm_dbkeys_fname(&gtm_dbkeys_fname[0]))
return GC_FAILURE;
if (0 != stat(gtm_dbkeys_fname, &stat_info))
{
save_errno = errno;
if (ENOENT == save_errno)
{
UPDATE_ERROR_STRING("Cannot find DB keys file - %s", gtm_dbkeys_fname);
} else
UPDATE_ERROR_STRING("Cannot find DB keys file - %s. %s", gtm_dbkeys_fname, strerror(save_errno));
return GC_FAILURE;
}
if (last_modified == stat_info.st_mtime)
return GC_SUCCESS;/* Nothing changed since we last read it. So, return success */
last_modified = stat_info.st_mtime;
if (NULL == (gtm_dbkeys_fp = fopen(gtm_dbkeys_fname, "r")))
{
save_errno = errno;
UPDATE_ERROR_STRING("Cannot open DB keys file - %s. %s", gtm_dbkeys_fname, strerror(save_errno));
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>
*/
current_state = looking_for_dat_entry; /* To start with we are looking for a database entry */
if (tbl_head)
{
gc_dbk_scrub_entries(); /* free up the existing linked list as we are about to create a fresh one */
tbl_head = NULL;
}
while (!feof(gtm_dbkeys_fp))
{
if (NULL == fgets(buf, LINE_MAX, gtm_dbkeys_fp))
break;
line_no++;
buflen = STRLEN(buf);
if (NEWLINE != buf[buflen - 1])
{ /* last character in the read buffer is not a newline implying that the line contains more than
* LINE_MAX characters.
*/
fclose(gtm_dbkeys_fp);
UPDATE_ERROR_STRING("%s. Entry at line: %d longer than %ld characters", prefix, line_no, LINE_MAX);
return GC_FAILURE;
}
buf[buflen - 1] = '\0'; /* strip off the newline at the end */
space_cnt = 0;
while (isspace(buf[space_cnt])) /* BYPASSOK -- don't have access to gtm_ctype.h */
space_cnt++; /* go past any whitespace characters */
assert(space_cnt <= (buflen - 1));
if ((0 == space_cnt) && ('\0' != buf[0]))
{
if (0 == memcmp(buf, DATABASE_LINE_INDICATOR, DATABASE_LINE_INDICATOR_SIZE))
{
filename_len = buflen - DATABASE_LINE_INDICATOR_SIZE;
line_type = DATABASE_LINE_INFO;
}
else if (0 == memcmp(buf, SYMMETRIC_KEY_LINE_INDICATOR, SYMMETRIC_KEY_LINE_INDICATOR_SIZE))
{
filename_len = buflen - SYMMETRIC_KEY_LINE_INDICATOR_SIZE;
line_type = SYMMETRIC_KEY_LINE_INFO;
}
else
line_type = -1;
} else if (space_cnt < (buflen - 1))
line_type = -1; /* line doesn't consist entirely of spaces (but only has leading spaces) */
else
continue; /* skip this line as it consists entirely of spaces -- blank line */
switch(line_type)
{
case DATABASE_LINE_INFO:
if (current_state == looking_for_key_entry)
{
fclose(gtm_dbkeys_fp);
UPDATE_ERROR_STRING("%s. At line %d: Found DAT entry, expecting KEY entry", prefix,
line_no);
return GC_FAILURE;
}
GC_ALLOCATE_TBL_ENTRY(node);
memcpy(node->database_fn, &buf[DATABASE_LINE_INDICATOR_SIZE], filename_len + 1);
assert('\0' == node->database_fn[filename_len]);
node->database_fn_len = filename_len;
node->next = tbl_head;
tbl_head = node;
current_state = looking_for_key_entry;
break;
case SYMMETRIC_KEY_LINE_INFO:
if (current_state == looking_for_dat_entry)
{
fclose(gtm_dbkeys_fp);
UPDATE_ERROR_STRING("%s. At line %d: Found KEY entry, expecting DAT entry", prefix,
line_no);
return GC_FAILURE;
}
assert(NULL != node);
memcpy(node->symmetric_key_fn, &buf[SYMMETRIC_KEY_LINE_INDICATOR_SIZE], filename_len + 1);
assert('\0' == node->symmetric_key_fn[filename_len]);
num_entries++; /* one set of entries processed */
current_state = looking_for_dat_entry;
break;
default:
fclose(gtm_dbkeys_fp);
UPDATE_ERROR_STRING("%s. At line %d: %s does not start with 'dat '/'key '", prefix, line_no, buf);
return GC_FAILURE;
}
}
if (!feof(gtm_dbkeys_fp))
{
save_errno = errno;
UPDATE_ERROR_STRING("Error while reading from database key file. %s", strerror(save_errno));
return GC_FAILURE;
} else if (0 == line_no)
{ /* EOF reached, but did not go past the first line -- no entries in database key file */
fclose(gtm_dbkeys_fp);
UPDATE_ERROR_STRING("%s. No entries found in DB keys file.", prefix);
return GC_FAILURE;
} else if (current_state == looking_for_key_entry)
{ /* last database file entry has no matching symmetric key file entry */
fclose(gtm_dbkeys_fp);
UPDATE_ERROR_STRING("%s. No matching KEY entry found for DAT entry at line: %d", prefix, line_no);
return GC_FAILURE;
}
GC_MALLOC(fast_lookup_entry, (SIZEOF(fast_lookup_entry) * num_entries), gtm_dbkeys_tbl*);
node = tbl_head;
count = 0;
while (NULL != node)
{
node->index = count;
fast_lookup_entry[count] = node;
count++;
node = node->next;
}
assert(count == num_entries);
fclose(gtm_dbkeys_fp);
return GC_SUCCESS;
}
xc_status_t gc_dbk_fill_sym_key_and_hash(xc_fileid_ptr_t req_fileid, char *req_hash)
{
gtm_dbkeys_tbl *cur;
int status, concerns_current_file, skip_entry, plain_text_length;
xc_fileid_ptr_t db_fileid;
xc_string_t filename;
cur = tbl_head;
while (NULL != cur)
{
db_fileid = NULL;
if (cur->fileid_dirty)
{
filename.length = cur->database_fn_len;
filename.address = cur->database_fn;
if (TRUE == gtm_filename_to_id_fptr(&filename, &db_fileid))
{
cur->fileid_dirty = FALSE;
cur->fileid = db_fileid;
}
}
if (cur->symmetric_key_dirty) /* Need to fill sym key value */
{
skip_entry = FALSE;
/* 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/she is in MUMPS and changes the password through an 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);
status = gc_pk_get_decrypted_key(cur->symmetric_key_fn, cur->symmetric_key, &plain_text_length);
concerns_current_file = (NULL != req_fileid && (gtm_is_file_identical_fptr(cur->fileid, req_fileid)));
if (0 != status)
{
/* If we failed because of wrong we password OR we are processing an entry that concerns the file
* for which we are called for, don't continue any further
*/
if ((GPG_ERR_BAD_PASSPHRASE == status) || concerns_current_file)
return GC_FAILURE;
skip_entry = TRUE;
} else if (0 == plain_text_length)
{ /* It's possible that the decryption didn't encounter error but the plain text length is 0 */
if (concerns_current_file)
{
UPDATE_ERROR_STRING("Symmetric key %s found to be empty", cur->symmetric_key_fn);
return GC_FAILURE;
}
skip_entry = TRUE;
}
if (!skip_entry)
{ /* Everything is fine, compute the hash for the key */
GC_PK_COMPUTE_HASH(cur->symmetric_key_hash, cur->symmetric_key);
GC_SYM_CREATE_HANDLES(cur);
cur->symmetric_key_dirty = FALSE;
if (concerns_current_file
|| (NULL != req_hash && (0 == memcmp(cur->symmetric_key_hash, req_hash, GTMCRYPT_HASH_LEN))))
{ /* Processed the entry for which the function was called or found a matching hash. Return */
return GC_SUCCESS;
}
}
}
cur = cur->next;
}
return GC_SUCCESS;
}
void gc_dbk_get_hash(gtm_dbkeys_tbl *entry, xc_string_t *hash)
{
assert(hash->address);
assert(NULL != entry);
memcpy(hash->address, entry->symmetric_key_hash, GTMCRYPT_HASH_LEN);
hash->length = GTMCRYPT_HASH_LEN;
}