/****************************************************************** * Windows Secure Passwords Plugin * * Copyright (C) 2008, C. Shaun Wagner , * Charles Tullock , * Jonathan Blount ******************************************************************* * COMPILING * This plugin saves account passwords encrypted in Secure_Passwords.xml * instead of the plain text accounts.xml file. * * This plugin can be compiled using Cygwin with the default Pidgin makefile. * Set up a Pidgin for Windows Build Environment and place this file * in PIDGIN_ROOT_DIR\libpurple\plugins\ Compile using the command * make -f Makefile.mingw winsecure_password.dll ******************************************************************* * INSTALLATION * Copy winsecure_password.dll to the * libpurple plugins directory: %APPDATA%\.purple\plugins ******************************************************************* * USAGE * Create the accounts that you want to use in the chat program that * uses libpurple. Save the passwords and check the "remember * passwords" checkbox. * In Tools->Windows Secure Passwords, select Save & Encrypt Passwords. * All passwords will be moved from accounts.xml to Secure_Passwords.xml. * * If you create a new account, check the "remember passwords" * checkbox on it and select Encrypt Passwords again. It will * encrypt any accounts that have a password saved in accounts.xml. * * To stop using the pluign, select Forget all encrypted passwords from the menu * The Secure_Passwords.xml will be deleted and you can use whatever storing method * you prefer. ******************************************************************* * LICENSE * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02111-1301, USA. *******************************************************************/ /* Just in case there is an external config header. */ #ifdef HAVE_CONFIG_H # include #endif /* config.h may define PURPLE_PLUGINS; protect the definition here so that we don't get complaints about redefinition when it's not necessary. */ #ifndef PURPLE_PLUGINS # define PURPLE_PLUGINS #endif /* Libpurple is a glib application. */ #include #include /* This will prevent compiler errors in some instances and is better explained in the how-to documents on the wiki */ #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif #endif /* Include the required windows headers */ #include #include #include /* Include the required libpurple headers. */ #include #include #include #include /* XML file to write encrypted passwords to */ #define XML_FILE_NAME "Secure_Passwords.xml" /* Hex table and function for converting bytes */ static TCHAR chHexTable[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; #define TOHEX(a, b) {*b++ = chHexTable[a >> 4];*b++ = chHexTable[a&0xf];} /* Pointers to Windows encryption functions */ typedef gboolean (CALLBACK* LPFNDLL_ENCRYPT)(DATA_BLOB*,LPCWSTR, DATA_BLOB*, PVOID, CRYPTPROTECT_PROMPTSTRUCT*, DWORD, DATA_BLOB*); typedef gboolean (CALLBACK* LPFNDLL_DECRYPT)(DATA_BLOB*, LPWSTR*, DATA_BLOB*, PVOID, CRYPTPROTECT_PROMPTSTRUCT*, DWORD, DATA_BLOB*); /* Set up encryption functions */ HINSTANCE hDLL; LPFNDLL_ENCRYPT fpEncrypt; LPFNDLL_DECRYPT fpDecrypt; /* A handle to our plugin - will be initiated in plugin_load() */ PurplePlugin *secure_windows_password_plugin = NULL; /* Loads Windows library that contains the encryption functions */ static gboolean load_dll() { gboolean failed = FALSE; hDLL = LoadLibrary("crypt32.dll"); if(hDLL != NULL) { fpDecrypt = (LPFNDLL_DECRYPT)GetProcAddress(hDLL, "CryptUnprotectData"); if (!fpDecrypt) { failed = TRUE; } fpEncrypt = (LPFNDLL_ENCRYPT)GetProcAddress(hDLL, "CryptProtectData"); if (!fpEncrypt && !failed) { failed = TRUE; } } else { failed = TRUE; } if(failed) { purple_notify_error(secure_windows_password_plugin, "Windows Secure Passwords", "Password Error", "Failed to load crypt32.dll"); } return failed; } static void free_dll() { if(hDLL!=NULL) { if(FreeLibrary(hDLL)) hDLL = NULL; } } /* Convert a bytearray (ciphertext) into a string of hex number to store as text*/ static void byteToHexString(BYTE* byteArray, DWORD nBytes, char* hexString) { int i; char *temp; temp = hexString; for(i = 0; i < nBytes; ++i) { TOHEX(byteArray[i], temp); } *temp=0; } /* Convert a string of hex into an array of corresponding bytes */ static void hexStringToByte(char *hexStr, BYTE *array) { int size, length, i, dig1 = 0, dig2 = 0; char *str2 = NULL; char ch1, ch2; length = strlen(hexStr); // make sure the input string has an even digit numbers if(length%2 == 1) { str2 = malloc (strlen(hexStr)+1); strcpy(str2, "0"); strncat(str2, hexStr, strlen(hexStr)); hexStr = str2; length++; } size = (length/2); for(i=0; i < size; i++) { ch1 = hexStr[i*2]; ch2 = hexStr[i*2+1]; if(isdigit(ch1)) dig1 = ch1 - '0'; else if(ch1>='A' && ch1<='F') dig1 = ch1 - 'A' + 10; else if(ch1>='a' && ch1<='f') dig1 = ch1 - 'a' + 10; if(isdigit(ch2)) dig2 = ch2 - '0'; else if(ch2>='A' && ch2<='F') dig2 = ch2 - 'A' + 10; else if(ch2>='a' && ch2<='f') dig2 = ch2 - 'a' + 10; array[i] = dig1*16 + dig2; } g_free(str2); } /* Decrypt a password for a specific account */ static gboolean plugin_decrypt_password_cb(DATA_BLOB in, char *decrypted) { gboolean decrypt_ret = FALSE; DATA_BLOB verify; LPWSTR pDecryptDesc = NULL; char* cleartext = NULL; if(hDLL != NULL && fpDecrypt != NULL) { decrypt_ret = (fpDecrypt)( &in, &pDecryptDesc, NULL, // No secondary entropy NULL, NULL, // No prompt 0, &verify); if(decrypt_ret) { cleartext = (CHAR*) malloc (verify.cbData); memcpy(cleartext, verify.pbData, verify.cbData); cleartext[verify.cbData] = '\0'; strncpy(decrypted, cleartext, strlen(cleartext)+1); } else { purple_notify_error(secure_windows_password_plugin, "Windows Secure Passwords", "Password Error", "Could not decrypt password"); } } g_free(cleartext); return decrypt_ret; } static void set_encrypted_account_password(gpointer data, gpointer user_data) { xmlnode *mainNode, *node = NULL, *child = NULL; char *protocol_id = NULL; char *name = NULL; char *password = NULL; PurpleAccount *account; //Decryption variables DATA_BLOB decrypt; BYTE *pInput = NULL; DWORD nBytes = 0; char *decryptedPassword = NULL; account = (PurpleAccount*)data; mainNode = purple_util_read_xml_from_file(XML_FILE_NAME, "accounts"); if (mainNode == NULL) return; //only load accounts not being saved in cleartext if(!purple_account_get_remember_password(account)) { //find the correct account for (node = xmlnode_get_child(mainNode, "account"); node != NULL; node = xmlnode_get_next_twin(node)) { //check username and protocol, then set password child = xmlnode_get_child(node, "protocol"); if (child != NULL) protocol_id = xmlnode_get_data(child); child = xmlnode_get_child(node, "name"); if (child != NULL) name = xmlnode_get_data(child); if ((protocol_id == NULL) || (name == NULL)) { g_free(protocol_id); g_free(name); return; } //check the current file information against the account we're trying to find if(*protocol_id == *purple_account_get_protocol_id(account) && *name == *purple_account_get_username(account)) { // if it matches, set the password child = xmlnode_get_child(node, "password"); if ((child != NULL) && ((password = xmlnode_get_data(child)) != NULL)) { decryptedPassword = malloc (strlen(password)); //Convert ciphertext from hex into byte array nBytes = strlen(password)/2; pInput = (BYTE*) g_malloc (nBytes); hexStringToByte(password, pInput); //Set up the decryption variables decrypt.pbData = pInput; decrypt.cbData = nBytes; //Decrypt the password and set it to the account plugin_decrypt_password_cb(decrypt, decryptedPassword); purple_account_set_password(account, decryptedPassword); g_free(password); g_free(pInput); g_free(decryptedPassword); } break; } } } g_free(name); g_free(protocol_id); xmlnode_free(node); } /* Load all accounts with encrypted passwords */ static void load_encrypted_accounts(void) { GList *accounts = NULL; if(hDLL == NULL) load_dll(); accounts = purple_accounts_get_all(); g_list_foreach(accounts, set_encrypted_account_password, NULL); free_dll(); } /* Encrypt a password for a specific account */ static char* plugin_encrypt_password_cb(const char *cleartext) { //encryption variables gboolean save_ret = FALSE; char *ciphertext = NULL, *decryptedPassword = NULL; BYTE *pInput = NULL, *pEncrypted = NULL; DWORD nBytes = 0; DATA_BLOB in, out; if(hDLL != NULL && fpEncrypt != NULL) { //Use CryptProtectData to encrypt the password nBytes = strlen(cleartext); pInput = (BYTE*) malloc (nBytes); memcpy(pInput, cleartext, nBytes); in.pbData = pInput; in.cbData = nBytes; save_ret = (fpEncrypt)( &in, L"Libpurple Secure Password", NULL, // No secondary entropy NULL, NULL, // No prompt 0, &out); if(save_ret) { //Copy output from encrypt function nBytes = out.cbData; pEncrypted = malloc(nBytes); memcpy(pEncrypted, out.pbData, nBytes); //Convert binary output to a string of hex digits ciphertext = malloc (nBytes*2 + 1); byteToHexString(pEncrypted, nBytes, ciphertext); } else purple_notify_error(secure_windows_password_plugin, "Windows Secure Passwords", "Password Error", "Could not encrypt password"); } free(decryptedPassword); free(pInput); free(pEncrypted); return ciphertext; } /* Save an individual account with encrypted password to a XML node */ static xmlnode * secure_account_to_xmlnode(PurpleAccount *account) { xmlnode *node, *child; const char *encryptedPW, *accountPW; node = xmlnode_new("account"); child = xmlnode_new_child(node, "protocol"); xmlnode_insert_data(child, purple_account_get_protocol_id(account), -1); child = xmlnode_new_child(node, "name"); xmlnode_insert_data(child, purple_account_get_username(account), -1); //Encrypt the password if the account is set to remember password accountPW = purple_account_get_password(account); if ( purple_account_get_remember_password(account) && accountPW != NULL ) { encryptedPW = plugin_encrypt_password_cb(accountPW); child = xmlnode_new_child(node, "password"); xmlnode_insert_data(child, encryptedPW, -1); //Switch off plaintext password saving now that it's saved in the encrypted file purple_account_set_remember_password(account, FALSE); } else //skip accounts that aren't saving the password { return NULL; } return node; } /* Save all encrypted accounts to a XML file */ static xmlnode * secure_accounts_to_xmlnode(void) { xmlnode *node, *child; GList *cur; node = xmlnode_new("account"); xmlnode_set_attrib(node, "version", "1.0"); for(cur = purple_accounts_get_all(); cur != NULL; cur = g_list_next(cur)) { child = secure_account_to_xmlnode(cur->data); if(child != NULL) xmlnode_insert_child(node, child); } return node; } /* Encrypt all current passwords */ static void plugin_encrypt_account_cb(PurplePluginAction * action) { xmlnode *node; char *data; //Load crypt32 library if(hDLL == NULL) load_dll(); //save encrypted accounts to file node = secure_accounts_to_xmlnode(); data = xmlnode_to_formatted_str(node, NULL); purple_util_write_data_to_file(XML_FILE_NAME, data, -1); purple_notify_info(secure_windows_password_plugin, "Windows Secure Passwords", "Success", "All currently saved passwords have been encrypted."); free_dll(); } /* Erase the encrytped password file */ static void plugin_delete_encrypted_file_cb(PurplePluginAction * action) { const char *user_dir = purple_user_dir(); gchar *filename_full; if(user_dir != NULL) { //delete the XML file filename_full = g_build_filename(user_dir, XML_FILE_NAME, NULL); if(g_remove(filename_full) == 0) { purple_notify_info(secure_windows_password_plugin, "Windows Secure Passwords", "Success", "The encrypted password file has been deleted."); } } } /* Register plugin actions */ static GList * plugin_actions (PurplePlugin * plugin, gpointer context) { GList *list = NULL; // The action list. PurplePluginAction *action = NULL; // A action temp pointer. action = purple_plugin_action_new ("Save & Encrypt Passwords", plugin_encrypt_account_cb); list = g_list_append(list, action); action = purple_plugin_action_new ("Forget all encrypted passwords", plugin_delete_encrypted_file_cb); list = g_list_append(list, action); return list; } /* Called when the plugin loads (after plugin_init()) */ static gboolean plugin_load (PurplePlugin * plugin) { secure_windows_password_plugin = plugin; //Load crypt32.dll functions load_dll(); /* Load the passwords for the accounts before they try to connect. */ load_encrypted_accounts(); return TRUE; } /* For specific notes on the meanings of each of these members, consult the C Plugin Howto on the website. */ static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_STANDARD, NULL, 0, NULL, PURPLE_PRIORITY_DEFAULT, "core-winsecure_password", "Windows Secure Passwords", "0.9", "Securely store pidgin passwords.", "This plugin uses the local operating system's security routines to encrypt and store your passwords.", "C. Shaun Wagner , Charles Tullock , Jonathan Blount ", "", plugin_load, NULL, NULL, NULL, NULL, NULL, plugin_actions, /* this tells libpurple the address of the function to call to get the list of plugin actions. */ NULL, NULL, NULL, NULL }; static void init_plugin (PurplePlugin * plugin) { } PURPLE_INIT_PLUGIN (secure_windows_password_plugin, init_plugin, info)