/* main.c * Copyright (C) 2001, 2002, 2003, 2007, 2008 Free Software Foundation, Inc. * * Author: Timo Schulz * * This file is part of OpenCDK. * * The OpenCDK library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA * */ #ifdef HAVE_CONFIG_H # include #endif #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef _WIN32 #include #endif #include "opencdk.h" #include "main.h" #include "packet.h" /* Set a default cipher algorithm and a digest algorithm. Even if AES and SHA-256 are not 'MUST' in the latest OpenPGP draft, AES seems to be a good choice. */ #define DEFAULT_CIPHER_ALGO GCRY_CIPHER_AES #define DEFAULT_DIGEST_ALGO GCRY_MD_SHA256 /* The site of the secure memory which is allocated in gcrypt. */ #define SECMEM_SIZE 16384 /* Hooks to custom memory allocation functions. */ static void *(*alloc_func) (size_t n) = gcry_xmalloc; static void *(*alloc_secure_func) (size_t n) = gcry_malloc_secure; static void *(*realloc_func) (void *p, size_t n) = gcry_realloc; static void *(*calloc_func) (size_t m, size_t n) = gcry_calloc; static void (*free_func) (void *) = gcry_free; static int malloc_hooks = 0; static int secmem_init = 0; /* Global settings for the logging. */ static cdk_log_fnc_t log_handler = NULL; static void *log_handler_value = NULL; static int log_level = CDK_LOG_NONE; /** * cdk_strerror: * @ec: the error number * * Return an error text for the given id. **/ const char* cdk_strerror (int ec) { static char buf[20]; switch (ec) { case CDK_EOF: return "End Of File"; case CDK_Success: return "No error"; case CDK_General_Error: return "General error"; case CDK_File_Error: return strerror (errno); case CDK_Bad_Sig: return "Bad signature"; case CDK_Inv_Packet: return "Invalid packet"; case CDK_Inv_Algo: return "Invalid algorithm"; case CDK_Not_Implemented: return "This is not implemented yet"; case CDK_Armor_Error: return "ASCII armor error"; case CDK_Armor_CRC_Error: return "ASCII armored damaged (CRC error)"; case CDK_MPI_Error: return "Invalid or missformed MPI"; case CDK_Inv_Value: return "Invalid parameter or value"; case CDK_Error_No_Key: return "No key available or not found"; case CDK_Chksum_Error: return "Check for key does not match"; case CDK_Time_Conflict: return "Time conflict"; case CDK_Zlib_Error: return "ZLIB error"; case CDK_Weak_Key: return "Weak key was detected"; case CDK_Out_Of_Core: return "Out of core!!"; case CDK_Wrong_Seckey: return "Wrong secret key"; case CDK_Wrong_Format: return "Data has wrong format"; case CDK_Bad_MDC: return "Manipulated MDC detected"; case CDK_Inv_Mode: return "Invalid mode"; case CDK_Error_No_Keyring: return "No keyring available"; case CDK_Inv_Packet_Ver: return "Invalid version for packet"; case CDK_Too_Short: return "Buffer or object is too short"; case CDK_Unusable_Key: return "Unusable public key"; case CDK_No_Data: return "No data"; case CDK_No_Passphrase: return "No passphrase supplied"; case CDK_Network_Error: return "A network error occurred"; default: sprintf (buf, "ec=%d", ec); return buf; } return NULL; } static void out_of_core (size_t n) { fprintf (stderr, "\n ** fatal error: out of memory (%d bytes) **\n", n); } /** * cdk_set_malloc_hooks: * @new_alloc_func: malloc replacement * @new_alloc_secure_func: secure malloc replacement * @new_realloc_func: realloc replacement * @new_calloc_func: calloc replacement * @new_free_func: free replacement * * Set private memory hooks for the library. */ void cdk_set_malloc_hooks (void *(*new_alloc_func) (size_t n), void *(*new_alloc_secure_func) (size_t n), void *(*new_realloc_func) (void *p, size_t n), void *(*new_calloc_func) (size_t m, size_t n), void (*new_free_func) (void *)) { alloc_func = new_alloc_func; alloc_secure_func = new_alloc_secure_func; realloc_func = new_realloc_func; calloc_func = new_calloc_func; free_func = new_free_func; malloc_hooks = 1; } /** * cdk_malloc_hook_initialized: * * Return if the malloc hooks are already initialized. **/ int cdk_malloc_hook_initialized (void) { return malloc_hooks; } void* cdk_malloc (size_t size) { void *p = alloc_func (size); if (!p) out_of_core (size); return p; } /** * cdk_calloc: * @n: amount of elements * @m: size of one element * * Safe wrapper around the c-function calloc. **/ void* cdk_calloc (size_t n, size_t m) { void * p = calloc_func (n, m); if (!p) out_of_core (m); return p; } /* Things which need to be done after the secure memory initialisation. */ static void _secmem_finish (void) { gcry_control (GCRYCTL_DROP_PRIVS); } /* Initialize the secure memory. */ static void _secmem_init (size_t size) { if (secmem_init == 1) return; if (size >= SECMEM_SIZE) size = SECMEM_SIZE; /* Check if no other library has already initialized gcrypt. */ if (!gcry_control (GCRYCTL_ANY_INITIALIZATION_P)) { _cdk_log_debug ("init: libgcrypt initialize.\n"); gcry_control (GCRYCTL_INIT_SECMEM, size, 0); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL, 0); secmem_init = 1; } } /* Things which needs to be done to deinit the secure memory. */ static void _secmem_end (void) { gcry_control (GCRYCTL_TERM_SECMEM); secmem_init = 0; } /* The Windows system needs to startup the Winsock interface first before we can use any socket related function. */ #ifdef _WIN32 static void init_sockets (void) { static int initialized = 0; WSADATA wsdata; if (initialized) return; if (WSAStartup (0x202, &wsdata)) _cdk_log_debug ("winsock init failed.\n"); initialized = 1; } static void deinit_sockets (void) { WSACleanup (); } #else void init_sockets (void) {} void deinit_sockets (void) {} #endif /** * cdk_lib_startup: * * Prepare the internal structures of the library. * This function should be called before any other CDK function. */ void cdk_lib_startup (void) { _secmem_init (SECMEM_SIZE); _secmem_finish (); init_sockets (); } /** * cdk_lib_shutdown: * * Shutdown the library and free all internal and globally used * memory and structures. This function should be called in the * exit handler of the calling program. */ void cdk_lib_shutdown (void) { deinit_sockets (); _secmem_end (); } /** * cdk_salloc: * @size: how much bytes should be allocated. * @clear: shall the buffer cleared after the allocation? * * Allocated the requested amount of bytes in 'secure' memory. */ void* cdk_salloc (size_t size, int clear) { void *p; if (!secmem_init) _secmem_init (SECMEM_SIZE); p = alloc_secure_func (size); if (!p) out_of_core (size); if (clear) memset (p, 0, size); return p; } void * cdk_realloc (void *ptr, size_t size) { void * p = realloc_func (ptr, size); if (!p) out_of_core (size); return p; } char * cdk_strdup (const char * ptr) { char * p = cdk_malloc (strlen (ptr) + 1); if (p) strcpy (p, ptr); return p; } void cdk_free (void * ptr) { if (ptr) free_func (ptr); } /* Internal logging routine. */ static void _cdk_logv (int level, const char *fmt, va_list arg_ptr) { if (log_handler) log_handler (log_handler_value, level, fmt, arg_ptr); else { if (level == CDK_LOG_NONE) return; if (level == CDK_LOG_DEBUG) fputs ("DBG: ", stderr); vfprintf (stderr, fmt, arg_ptr); } } /** * cdk_set_log_handler: * @logfnc: the function pointer * @opaque: a private values for the function * * Set a custom handler for logging. **/ void cdk_set_log_handler (cdk_log_fnc_t logfnc, void *opaque) { log_handler = logfnc; log_handler_value = opaque; } /** * cdk_set_log_level: * @lvl: the level * * Set the verbosity level. **/ void cdk_set_log_level (int level) { log_level = level; } /* Return the current log level of the lib. */ int _cdk_get_log_level (void) { return log_level; } void _cdk_log_info (const char *fmt, ...) { va_list arg; if (log_level == CDK_LOG_NONE) return; va_start (arg, fmt); _cdk_logv (CDK_LOG_INFO, fmt, arg); va_end (arg); } void _cdk_log_debug (const char *fmt, ...) { va_list arg; if (log_level < CDK_LOG_DEBUG) return; va_start (arg, fmt); _cdk_logv (CDK_LOG_DEBUG, fmt, arg); va_end (arg); } /* Use the passphrase callback in the handle HD or return NULL if there is no valid callback. */ char* _cdk_passphrase_get (cdk_ctx_t hd, const char *prompt) { if (!hd || !hd->passphrase_cb) return NULL; return hd->passphrase_cb (hd->passphrase_cb_value, prompt); } static void handle_set_cipher (cdk_ctx_t hd, int cipher) { if (!hd) return; if (gcry_cipher_test_algo (cipher)) cipher = DEFAULT_CIPHER_ALGO; hd->cipher_algo = cipher; } static void handle_set_digest (cdk_ctx_t hd, int digest) { if (!hd) return; if (gcry_md_test_algo (digest)) digest = DEFAULT_DIGEST_ALGO; hd->digest_algo = digest; } static void handle_set_s2k (cdk_ctx_t hd, int mode, int digest, int cipher) { if (!hd) return; if (gcry_cipher_test_algo (cipher)) cipher = DEFAULT_CIPHER_ALGO; if (gcry_md_test_algo (digest)) digest = DEFAULT_DIGEST_ALGO; if (mode != CDK_S2K_SIMPLE && mode != CDK_S2K_SALTED && mode != CDK_S2K_ITERSALTED) mode = CDK_S2K_ITERSALTED; hd->_s2k.mode = mode; hd->_s2k.digest_algo = digest; } static void handle_set_compress (cdk_ctx_t hd, int algo, int level) { if (!hd) return; if (algo < 0 || algo > 2) algo = 0; hd->compress.algo = algo; if (!algo) hd->opt.compress = 0; else { if (level > 0 && level < 10) hd->compress.level = level; else hd->compress.level = 6; } } /** * cdk_handle_control: * @hd: session handle * @action: flag which indicates whether put or get is requested * @cmd: command id * * Perform various control operations for the current session. **/ int cdk_handle_control (cdk_ctx_t hd, int action, int cmd, ...) { va_list arg_ptr; int set = action == CDK_CTLF_SET, val = 0; if (!hd) return -1; if (action != CDK_CTLF_SET && action != CDK_CTLF_GET) return -1; va_start (arg_ptr, cmd); switch( cmd ) { case CDK_CTL_ARMOR: if (set) hd->opt.armor = va_arg( arg_ptr, int ); else val = hd->opt.armor; break; case CDK_CTL_CIPHER: if (set) handle_set_cipher (hd, va_arg (arg_ptr, int)); else val = hd->cipher_algo; break; case CDK_CTL_DIGEST: if (set) handle_set_digest( hd, va_arg( arg_ptr, int ) ); else val = hd->digest_algo; break; case CDK_CTL_OVERWRITE: if (set) hd->opt.overwrite = va_arg (arg_ptr, int); else val = hd->opt.overwrite; break; case CDK_CTL_COMPRESS: if (set) { int algo = va_arg (arg_ptr, int); int level = va_arg (arg_ptr, int); handle_set_compress (hd, algo, level); } else val = hd->compress.algo; break; case CDK_CTL_S2K: if( set ) { int mode = va_arg( arg_ptr, int ); int digest = va_arg( arg_ptr, int ); int cipher = va_arg( arg_ptr, int ); handle_set_s2k( hd, mode, digest, cipher ); } else val = hd->_s2k.mode; break; case CDK_CTL_FORCE_DIGEST: if (set) hd->opt.force_digest = va_arg (arg_ptr, int); else val = hd->opt.force_digest; break; case CDK_CTL_BLOCKMODE_ON: if( set ) hd->opt.blockmode = va_arg( arg_ptr, int ); else val = hd->opt.blockmode; break; default: val = -1; break; } va_end (arg_ptr); return val; } /** * cdk_handle_new: * @r_ctx: context to store the handle * * create a new session handle. **/ cdk_error_t cdk_handle_new (cdk_ctx_t *r_ctx) { cdk_ctx_t c; if (!r_ctx) return CDK_Inv_Value; c = cdk_calloc (1, sizeof *c); if (!c) return CDK_Out_Of_Core; /* For S2K use the iterated and salted mode and use the default digest and cipher algorithms. Because the MDC feature will be used, the default cipher should use a blocksize of 128 bits. */ c->_s2k.mode = CDK_S2K_ITERSALTED; c->_s2k.digest_algo = DEFAULT_DIGEST_ALGO; c->opt.mdc = 1; c->opt.compress = 1; c->opt.armor = 0; c->opt.textmode = 0; c->digest_algo = DEFAULT_DIGEST_ALGO; c->cipher_algo = DEFAULT_CIPHER_ALGO; c->compress.algo = CDK_COMPRESS_ZIP; c->compress.level = 6; *r_ctx = c; return 0; } /** * cdk_handle_set_keyring: * @hd: session handle * @type: public=0 or secret=1 keyring type * @kringname: file name of the keyring which shall be used. * * Convenient function to set the keyring for the current session. */ cdk_error_t cdk_handle_set_keyring (cdk_ctx_t hd, int type, const char *kringname) { cdk_keydb_hd_t db; cdk_error_t err; err = cdk_keydb_new_from_file (&db, type, kringname); if (err) return err; if (!type) hd->db.pub = db; else hd->db.sec = db; hd->db.close_db = 1; return 0; } /** * cdk_handle_set_keydb: * @hd: session handle * @db: the database handle * * set the key database handle. * the function automatically detects whether this is a public or * secret keyring and the right handle is set. **/ void cdk_handle_set_keydb (cdk_ctx_t hd, cdk_keydb_hd_t db) { if (!hd) return; if (_cdk_keydb_is_secret (db)) hd->db.sec = db; else hd->db.pub = db; } /** * cdk_handle_get_keydb: * @hd: session handle * @type: type of the keyring * * Return the keydb handle from the session handle. * The caller should not free these handles. **/ cdk_keydb_hd_t cdk_handle_get_keydb (cdk_ctx_t hd, int type) { if (!hd) return NULL; if (type == CDK_DBTYPE_PK_KEYRING) return hd->db.pub; else if (type == CDK_DBTYPE_SK_KEYRING) return hd->db.sec; return NULL; } /** * cdk_handle_set_passphrase_cb: * @hd: session handle * @cb: callback function * @cb_value: the opaque value for the cb function * * set the passphrase callback. **/ void cdk_handle_set_passphrase_cb (cdk_ctx_t hd, char *(*cb) (void *opa, const char *prompt), void * cb_value) { if (!hd) return; hd->passphrase_cb = cb; hd->passphrase_cb_value = cb_value; } /** * cdk_handle_verify_get_result: * @hd: the session handle * * Return the verify result for the current session. * Do not free the pointer. **/ cdk_verify_result_t cdk_handle_verify_get_result (cdk_ctx_t hd) { return hd->result.verify; } /** * cdk_handle_free: * @hd: the handle * * Release the main handle. **/ void cdk_handle_free (cdk_ctx_t hd) { if (!hd) return; _cdk_result_verify_free (hd->result.verify); /* If cdk_handle_set_keyring() were used, we need to free the key db handles here because the handles are not controlled by the user. */ if (hd->db.close_db) { if (hd->db.pub) cdk_keydb_free (hd->db.pub); if (hd->db.sec) cdk_keydb_free (hd->db.sec); hd->db.pub = hd->db.sec = NULL; } cdk_free (hd->dek); cdk_free (hd); }