fis-gtm/sr_unix/gtmcrypt_pk_ref.c

383 lines
14 KiB
C

/****************************************************************
* *
* Copyright 2009, 2010 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 <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <gpgme.h> /* gpgme functions */
#include <gpg-error.h> /* gcry*_err_t */
#include <dlfcn.h>
#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_pk_ref.h"
static char *gtm_passwd;
static char *gtm_passwd_env;
int can_prompt_passwd;
gpgme_ctx_t pk_crypt_ctx;
extern char err_string[ERR_STRLEN];
/* Take a masked/unmasked passwd and convert it to the other form by doing an XOR operation.
* MASKING:
* The original gtm_passwd value is XOR'ed with the inode number of mumps executable and the
* the value of the env variable $USER (which contains the username of the current user logged in).
* This information is then converted to the hex form for easy viewing and set into the environment
* UNMASKING:
* The gtm_passwd set in the environment is un hexed (after masking, the passwd is set in the hex
* form). This gtm_passwd value is then XOR'ed with the inode number of the executable and the
* env variable $USER (which contains the username of the current user). This information is then
* stored in the gtm_passwd variable for future access. Note the environment variable ($gtm_passwd) still
* contains only the masked password and hence safety of the passwd is still guarenteed.
Note that always, if the username and(or) the inode number is lesser than the length of the un hexed gtm_passwd,
the position of their values are left and right justified with respect to the gtm_passwd value. A typical example
is shown below.
* G T M P A S S W D */
/* ^ ^ ^ ^ ^ ^ ^ ^ ^ */
/* U S E R 0 0 0 0 0 */
/* ^ ^ ^ ^ ^ ^ ^ ^ ^ */
/* 0 0 0 0 I N O D E */
/* ----------------- */
int gc_pk_mask_unmask_passwd(char *in, char *out, int len)
{
char *ptr;
char tmp[GTM_PASSPHRASE_MAX], inode[GTM_PASSPHRASE_MAX], user[GTM_PASSPHRASE_MAX], mumps_ex[GTM_PATH_MAX];
int passwd_len, ilen, status, i;
struct stat stat_info;
passwd_len = len < GTM_PASSPHRASE_MAX ? len : GTM_PASSPHRASE_MAX;
memset(inode, 0, passwd_len);
memset(user, 0, passwd_len);
memset(mumps_ex, 0, GTM_PATH_MAX);
GC_GETENV(ptr, "USER", status);
if (GC_SUCCESS == status)
{
strncpy(user, ptr, passwd_len);
GC_GETENV(ptr, "gtm_dist", status);
if (GC_SUCCESS == status)
{
sprintf(mumps_ex, "%s/%s", ptr, "mumps");
if (0 == stat(mumps_ex, &stat_info))
{
sprintf(tmp, "%ld", (long) stat_info.st_ino);
ilen = (int)strlen(tmp);
if (ilen < passwd_len)
strncpy(inode + (passwd_len - ilen), tmp, ilen);
else
strncpy(inode, tmp, passwd_len);
} else
{
sprintf(err_string, "Cannot find MUMPS executable in %s", ptr);
return GC_FAILURE;
}
for (i = 0; i < passwd_len; i++)
out[i] = in[i] ^ inode[i] ^ user[i];
return GC_SUCCESS;
} else
GC_ENV_UNSET_ERROR("gtm_dist");
}
GC_ENV_UNSET_ERROR("USER");
return GC_FAILURE;
}
int gc_pk_mask_unmask_passwd_interlude(int nparm, gtm_string_t *in, gtm_string_t *out, int len)
{
out->length=len;
return gc_pk_mask_unmask_passwd(in->address, out->address, len);
}
void gc_pk_scrub_passwd()
{
/* Nullify the key strings, so that any generated cores will not contain the unencrypted keys */
memset(gtm_passwd, 0, strlen(gtm_passwd));
/* Free gtm_passwd and gtm_passwd_env variables */
if (NULL != gtm_passwd)
GC_FREE(gtm_passwd);
if (NULL != gtm_passwd_env)
GC_FREE(gtm_passwd_env);
/* Finally release the gpgme context */
if (NULL != pk_crypt_ctx)
gpgme_release(pk_crypt_ctx);
}
/* Loads the GTMCI variable with the path of the gtmcrypt.tab which will be placed in gtm_dist folder at build time.
* Here we assume that the tab file be in $gtm_dist/plugin/gtmcrypt
*/
void gc_pk_crypt_load_gtmci_env()
{
const char *gtm_dist_value;
const char *gtmcrypt_tab_file = "gtmcrypt.tab"; /* Name of the tab file */
static char gtmcrypt_tab_path[TAB_NAME_MAX]; /* Needs to be in scope always */
gtm_dist_value = getenv("gtm_dist");
assert(NULL != gtm_dist_value);
assert(0 != strlen(gtm_dist_value));
sprintf(gtmcrypt_tab_path, "%s/%s/%s", gtm_dist_value, "plugin/gtmcrypt", gtmcrypt_tab_file);
setenv(GTMCI, gtmcrypt_tab_path, TRUE);
}
/* The following function checks if gtm_passwd is already set. If gtm_passwd is not set in the env, it's a serious
* error condition. We return back immediately. If it's set to empty string, we prompt for passwd immediately. The
* current implementation of password prompting is done via a mumps call-in to %GETPASS.
*/
xc_status_t gc_pk_crypt_prompt_passwd_if_needed(int prompt_passwd)
{
/* Name of the mumps password routine that will be called. */
const char *password_routine = "getpass";
/* Points to the value that was held in GTMCI prior to modification. */
char *save_gtmci, tgtm_passwd[GTM_PASSPHRASE_MAX];
char *lgtm_passwd;
int status, len;
gtm_int_t pass_len = GTM_PASSPHRASE_MAX;
can_prompt_passwd = prompt_passwd;
GC_GETENV(lgtm_passwd, GTM_PASSWD, status);
/* This is an error condition. We have hit a encrypted database but the env doesn't have gtm_passwd set. */
if (0 != status)
{
GC_ENV_UNSET_ERROR(GTM_PASSWD);
return GC_FAILURE;
}
/* If the masked password in the environment is same as we have in memory then it means that the password
* has not been changed and so the actual value in the gtm_passwd is still good to use. */
if (NULL != gtm_passwd_env && (0 == strcmp(gtm_passwd_env, lgtm_passwd)))
return GC_SUCCESS;
/* If the password is set to an appropriate value, then we know for sure it's in it's masked form. So, we unmask it
* and set it in the global variable and return to the caller. */
if (0 < (len = (int)strlen(lgtm_passwd)))
{
if (gtm_passwd)
GC_FREE(gtm_passwd);
GC_MALLOC(gtm_passwd, len / 2 + 1, char);
memset(gtm_passwd, 0, len / 2 + 1);
GC_UNHEX(lgtm_passwd, gtm_passwd, len);
status = gc_pk_mask_unmask_passwd(gtm_passwd, gtm_passwd, len / 2);
if (GC_SUCCESS == status)
{
/* Now that we have unmasked the gtm_passwd in the environment
* store the masked version in gtm_passwd_env so that future
* calls to this function can make use of this and return early
* if we find no change between the one in the environment and
* the one in the memory */
if (NULL != gtm_passwd_env)
GC_FREE(gtm_passwd_env);
GC_MALLOC(gtm_passwd_env, strlen(lgtm_passwd) + 1, char);
strcpy(gtm_passwd_env, lgtm_passwd);
}
return status;
} else if (!prompt_passwd)
{
/* If we are here, it means that the caller of the plugin library was not MUMPS (may be MUPIP, DSE and LKE).
* For the utility programs, we expect the password to be set in the environment to an appropriate masked
* form. If not, it's an error and we return the appropriate error message. */
strcpy(err_string, PASSWD_EMPTY);
return GC_FAILURE;
}
/* Only if the gtm_passwd is set to empty string, we prompt the user for password */
GC_MALLOC(gtm_passwd, GTM_PASSPHRASE_MAX, char);
memset(gtm_passwd, 0, GTM_PASSPHRASE_MAX);
save_gtmci = getenv(GTMCI);
gc_pk_crypt_load_gtmci_env();
status = gtm_ci_fptr(password_routine, gtm_passwd, pass_len);
if (0 != status)
{
gtm_zstatus_fptr(err_string, ERR_STRLEN);
return GC_FAILURE;
}
/* Restore the GTMCI variable */
if (NULL != save_gtmci) /* To make sure we don't set an environment variable as NULL */
setenv(GTMCI, save_gtmci, 1);
/* After applying a minimal encryption, we set it to the environment variable */
GC_MALLOC(lgtm_passwd, strlen(gtm_passwd) * 2 + 1, char);
gc_pk_mask_unmask_passwd(gtm_passwd, tgtm_passwd, (int)strlen(gtm_passwd));
GC_HEX(tgtm_passwd, lgtm_passwd, strlen(gtm_passwd) * 2);
setenv("gtm_passwd", lgtm_passwd, TRUE); /* Note that we currently do not free 'gtm_passwd', even if it was
* allocated above, as it needs to be in the env buffer
*/
return GC_SUCCESS;
}
/* This function is called whenever gpg needs the passphrase with which the secret key is encrypted. In this case, the passphrase
* is obtained from the ENVIRONMENT VARIABLE - $gtm_passwd or by invoking the mumps engine during the "gtmcrypt_init()".
* In either ways, it's guaranteed that when this function is called, the passphrase is already set in the global variable.
* In either ways, it's guaranteed that when this function is called, the passphrase is already set in the global variable.
*/
int gc_pk_crypt_passphrase_callback(void *opaque, const char *uid_hint,
const char *passphrase_info, int last_was_bad,
int fd)
{
assert(0 != fd);
assert(NULL != gtm_passwd);
/* This is just being cautious. We would have thrown the appropriate error message
* if gtm_passwd have been zero length'ed one.
*/
assert(0 != strlen(gtm_passwd));
write(fd, gtm_passwd, strlen(gtm_passwd));
write(fd, "\n", 1);
return 0;
}
/* Given the structure that holds the plain data, this function reads through the structure and retrieves the plain text. We
* also return the number of bytes actually read from the structure.
*/
int gc_pk_crypt_retrieve_plain_text(gpgme_data_t plain_data, char *plain_text)
{
int ret;
assert(NULL != plain_text);
/* Clear the temporary buffer */
memset(plain_text, 0, GTM_KEY_MAX);
gpgme_data_seek(plain_data, 0, SEEK_SET);
ret = (int)gpgme_data_read(plain_data, plain_text, GTM_KEY_MAX);
return ret;
}
/* This is currently necessary to work around what seems to be a gpgme issue in not clearing the plaintext keys
* from the C stack (shows up in a core dump otherwise). When gpgme is fixed, this code can be removed.
* The size of lclarray (8K) is determined purely from experimentation on all platforms.
*/
int gc_pk_scrub_plaintext_keys_from_c_stack()
{
char lclarray[8192];
memset(lclarray, 0, SIZEOF(lclarray));
return 0;
}
/* This function tries to decrypt the cipher file (the file containing the symmetric key with which the database is encrypted).
* It's assumed that the context is initialized and is set with the appropriate passphrase callback. The cipher_file
* should contain the fully qualified path of the encrypted database key file. Also, plain text is supposed to be allocated with
* sufficient space to hold the decrypted text.
*/
gpgme_error_t gc_pk_get_decrypted_key(const char *cipher_file, char *plain_text, int *plain_text_length)
{
gpgme_error_t err;
gpgme_data_t cipher_data = NULL, plain_data = NULL;
xc_status_t ret_status;
gpg_err_code_t ecode;
char null_buffer[GTM_KEY_MAX];
assert(NULL != cipher_file);
assert(NULL != plain_text);
assert(NULL != pk_crypt_ctx);
assert(0 != strlen(cipher_file));
/* Convert the cipher content in the cipher file into
* in-memory content. This in-memory content is stored
* in gpgme_data_t structure. */
err = gpgme_data_new_from_file(&cipher_data, cipher_file, 1);
if (!err)
{
err = gpgme_data_new(&plain_data);
if (!err)
{ /* Try decrypting the cipher content with the context.
* The decrypted content will also be stored in gpgme_data_t structure.
*/
err = gpgme_op_decrypt(pk_crypt_ctx, cipher_data, plain_data);
if (!err) /* Once decrypted, the plain text has to be obtained from the plain_data structure. */
*plain_text_length = gc_pk_crypt_retrieve_plain_text(plain_data, plain_text);
gc_pk_scrub_plaintext_keys_from_c_stack();
}
}
ecode = gpgme_err_code(err);
if (0 != ecode)
{
switch(ecode)
{
case GPG_ERR_BAD_PASSPHRASE:
snprintf(err_string, ERR_STRLEN, "%s", "Incorrect password");
break;
case GPG_ERR_ENOENT:
snprintf(err_string, ERR_STRLEN, "encryption key file %s not found", cipher_file);
break;
default:
snprintf(err_string, ERR_STRLEN, "%s", gpgme_strerror(err));
break;
}
}
if (NULL != plain_data)
{ /* scrub plaintext data before releasing it */
assert(GTM_KEY_MAX == SIZEOF(null_buffer));
memset(null_buffer, 0, GTM_KEY_MAX);
gpgme_data_write(plain_data, null_buffer, GTM_KEY_MAX);
gpgme_data_release(plain_data);
}
if (NULL != cipher_data)
gpgme_data_release(cipher_data);
return ecode;
}
int gc_pk_gpghome_has_permissions()
{
char filename[GTM_PATH_MAX], *tmp_ptr = NULL;
int gnupghome_set, status, fd;
/* See if GNUPGHOME is set in the environment */
GC_GETENV(tmp_ptr, GNUPGHOME, status);
if (GC_SUCCESS != status)
{
gnupghome_set = FALSE;
GC_GETENV(tmp_ptr, "HOME", status);
if (GC_SUCCESS != status)
{
GC_ENV_UNSET_ERROR("HOME");
return GC_FAILURE;
}
/* If GNUPGHOME is not set, we choose the filename as $HOME/.gnupg */
snprintf(filename, GTM_PATH_MAX, "%s/%s", tmp_ptr, DOT_GNUPG);
} else
{
gnupghome_set = TRUE;
/* If GNUPGHOME is set, then we choose the path pointed by GNUPGHOME as the
* directory containing the public keys and private keys whose permissions we are
* interested in. */
strcpy(filename, tmp_ptr);
}
/* At this point, we are sure that the filename is pointing to the appropriate directory containing the public/private
* keys. If not, then we had encountered an error and would have returned back to the caller. */
if (-1 != (fd = open(filename, O_RDONLY)))
{
close(fd);
return GC_SUCCESS;
}
/* If we don't have appropriate read permissions then we report the error accordingly. */
if (EACCES == errno)
{
if (gnupghome_set)
snprintf(err_string, ERR_STRLEN, "%s", "No read permissions on $GNUPGHOME");
else
snprintf(err_string, ERR_STRLEN, "%s", "No read permissions on $HOME/.gnupg");
}
close(fd);
return GC_FAILURE;
}