diff --git a/docs/README.md b/docs/README.md index 29c68cc7e..324801e77 100755 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,9 @@ [![Build Status](https://scrutinizer-ci.com/g/nilsteampassnet/TeamPass/badges/build.png?b=master)](https://scrutinizer-ci.com/g/nilsteampassnet/TeamPass/build-status/master) [![Code Intelligence Status](https://scrutinizer-ci.com/g/nilsteampassnet/TeamPass/badges/code-intelligence.svg?b=master)](https://scrutinizer-ci.com/code-intelligence) -> 💪 Work in progress - 📡 [Old documentation](https://teampass.readthedocs.io/en/latest/) is still available. +> 💪 Work in progress +> +> 📡 Old documentation is still available at [teampass.readthedocs.io](https://teampass.readthedocs.io/en/latest/). ## Features diff --git a/docs/_media/tp3_keys_1.png b/docs/_media/tp3_keys_1.png new file mode 100644 index 000000000..ae79571a7 Binary files /dev/null and b/docs/_media/tp3_keys_1.png differ diff --git a/docs/_media/tp3_keys_2.png b/docs/_media/tp3_keys_2.png new file mode 100644 index 000000000..747ecd682 Binary files /dev/null and b/docs/_media/tp3_keys_2.png differ diff --git a/docs/_media/tp3_keys_3.png b/docs/_media/tp3_keys_3.png new file mode 100644 index 000000000..5055fdf34 Binary files /dev/null and b/docs/_media/tp3_keys_3.png differ diff --git a/docs/_sidebar.md b/docs/_sidebar.md index aa15b0890..9e1395777 100755 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -8,6 +8,7 @@ - [Extra](install/extra-settings.md) - **Features** - [Authentication](features/authentication.md) +- [Keys](features/keys.md) - [Roles](features/roles.md) - **Manage** - [Settings](manage/settings.md) diff --git a/docs/features/keys.md b/docs/features/keys.md new file mode 100644 index 000000000..e851532c3 --- /dev/null +++ b/docs/features/keys.md @@ -0,0 +1,32 @@ + + +> 🚧 Under construction + +## Generalities + +In Teampass, all encrypted elements (such as passwords and encrypted fields) have a unique key for each user. +This key is encrypted with his/hers login password. +Such a process ensures a high level of security for all data stored in the database through Teampass. + +💡 [Read more](../install/encryption.md) about this encryption process. + +## Regenerate your keys (as a User) + +For any reason, if you notice that while browsing Teampass's objects, all related passwords are empty then it might be a corruption of your private key is corrupted. +Could be after several login password changes. + +For regenerated all your keys, just follow the next instructions. + +1. Select entry `Generate new keys` in personal menu + ![1](../_media/tp3_keys_1.png) + +2. Ensure that the form contains your login password + ![1](../_media/tp3_keys_2.png) + +3. Click `Confirm` button + +4. Once started, the process will run in background during several minutes. You can still use Teampass but all the passwords will be blank. +On top of screen, an orange box will show you the process progress. Once finished, you will have your passwords back. + ![1](../_media/tp3_keys_3.png) + +> 💡 During this process, you can change page and even leave Teampass. \ No newline at end of file diff --git a/docs/features/roles.md b/docs/features/roles.md index 2e32818ae..cee2bc345 100644 --- a/docs/features/roles.md +++ b/docs/features/roles.md @@ -1,7 +1,7 @@ -> STILL UNDER CONSTRUCTION +> 🚧 Under construction ## Generalities diff --git a/docs/install/encryption.md b/docs/install/encryption.md index 5c25851cd..22dd2118c 100755 --- a/docs/install/encryption.md +++ b/docs/install/encryption.md @@ -16,10 +16,10 @@ User credentails are stored encrypted in the database. The encryption is perform Teampass encrypts sensitive data and especially password part of any defined item. The encryption relies on public and private keys each user has. When a user is added, his keys are generated following the next process. -![Generating user keys](./_media/tp3_encrypt_user.png) +![Generating user keys](../_media/tp3_encrypt_user.png) Each encrypted element (password, custom fields) has one shared key by user. This key can only be decrypted with one user Password and Private key. -![Element encryption](./_media/tp3_encrypt_item.png) +![Element encryption](../_media/tp3_encrypt_item.png) When a user has to visualize an encrypted element, his password and private key is mandatory -![encryption model](./_media/tp3_decrypt_item.png) \ No newline at end of file +![encryption model](../_media/tp3_decrypt_item.png) \ No newline at end of file diff --git a/pages/items.js.php b/pages/items.js.php index bfde54d72..9f9a2de49 100755 --- a/pages/items.js.php +++ b/pages/items.js.php @@ -4582,7 +4582,7 @@ function(teampassItem) { $('#card-item-field-' + field.id) .removeClass('hidden') .children(".card-item-field-value") - .text(field.value); + .html(field.value); } // Item edit form $('#form-item-field-' + field.id) diff --git a/pages/search.js.php b/pages/search.js.php index f4c1beb32..e68ed1748 100755 --- a/pages/search.js.php +++ b/pages/search.js.php @@ -26,7 +26,7 @@ * @see https://www.teampass.net */ - $var = []; +$var = []; $var['hidden_asterisk'] = ''; ?> @@ -74,7 +74,7 @@ "url": "/includes/language/datatables..txt" }, "columns": [{ - "width": "10%", + "width": "70px", class: "details-control", defaultContent: "" }, diff --git a/pages/search.php b/pages/search.php index 76e51a171..50fb88563 100755 --- a/pages/search.php +++ b/pages/search.php @@ -100,7 +100,7 @@
- +
diff --git a/sources/find.queries.php b/sources/find.queries.php index 4b6215f86..b9e5b56ed 100755 --- a/sources/find.queries.php +++ b/sources/find.queries.php @@ -78,11 +78,11 @@ DB::$ssl = DB_SSL; DB::$connect_options = DB_CONNECT_OPTIONS; //Columns name -$aColumns = ['id', 'label', 'login', 'description', 'tags', 'id_tree', 'folder', 'login', 'url']; +$aColumns = ['c.id', 'c.label', 'c.login', 'c.description', 'c.tags', 'c.id_tree', 'c.folder', 'c.login', 'c.url', 'ci.data'];// $aSortTypes = ['ASC', 'DESC']; //init SQL variables $sOrder = $sLimit = $sWhere = ''; -$sWhere = 'id_tree IN %ls_idtree'; +$sWhere = 'c.id_tree IN %ls_idtree'; //limit search to the visible folders if (isset($_GET['limited']) === false @@ -190,6 +190,7 @@ '6' => $search_criteria, '7' => $search_criteria, '8' => $search_criteria, + '9' => $search_criteria, 'pf' => $arrayPf, ]; } @@ -207,6 +208,7 @@ '6' => $search_criteria, '7' => $search_criteria, '8' => $search_criteria, + '9' => $search_criteria, 'pf' => $arrayPf, ]; } @@ -216,27 +218,42 @@ if (empty($sWhere) === false) { $sWhere .= ' AND '; } - $sWhere = 'WHERE ' . $sWhere . 'id_tree NOT IN %ls_pf '; + $sWhere = 'WHERE ' . $sWhere . 'c.id_tree NOT IN %ls_pf '; } else { $sWhere = 'WHERE ' . $sWhere; } +// Do queries DB::query( - 'SELECT id FROM ' . prefixTable('cache') . " + "SELECT c.id + FROM " . prefixTable('cache') . " AS c + LEFT JOIN " . prefixTable('categories_items') . " AS ci ON (ci.item_id = c.id) ${sWhere} ${sOrder}", $crit ); $iTotal = DB::count(); $rows = DB::query( - 'SELECT id, label, description, tags, id_tree, perso, restricted_to, login, folder, author, renewal_period, url, timestamp - FROM ' . prefixTable('cache') . " + "SELECT c.*, ci.data + FROM " . prefixTable('cache') . " AS c + LEFT JOIN " . prefixTable('categories_items') . " AS ci ON (ci.item_id = c.id) ${sWhere} ${sOrder} ${sLimit}", $crit ); +/* +// Search in fields +$rows_fields = DB::query( + 'SELECT item_id, data + FROM ' . prefixTable('categories_items') . " + WHERE encryption_type = 'not_set' AND data LIKE %ss_search + ${sOrder} + ${sLimit}", + $search_criteria +);*/ + /* * Output */ @@ -388,7 +405,7 @@ } elseif (isset($_GET['type']) && ($_GET['type'] === 'search_for_items' || $_GET['type'] === 'search_for_items_with_tags')) { include_once 'main.functions.php'; include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . $_SESSION['user']['user_language'] . '.php'; - + $arr_data = []; foreach ($rows as $record) { $displayItem = false; diff --git a/sources/items.queries.php b/sources/items.queries.php index 8d086acb0..8fbe1b245 100755 --- a/sources/items.queries.php +++ b/sources/items.queries.php @@ -212,11 +212,10 @@ FILTER_SANITIZE_FULL_SPECIAL_CHARS ); $post_email = filter_var(htmlspecialchars_decode($dataReceived['email']), FILTER_SANITIZE_EMAIL); - $post_fields = filter_var( + $post_fields = filter_var_array( $dataReceived['fields'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ); - $post_fields = $post_fields !== false ? json_decode($post_fields) : ''; $inputData['folderId'] = filter_var($dataReceived['folder'], FILTER_SANITIZE_NUMBER_INT); $post_folder_is_personal = filter_var($dataReceived['folder_is_personal'], FILTER_SANITIZE_NUMBER_INT); $inputData['label'] = filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS); @@ -465,89 +464,56 @@ $SETTINGS ); - /* - // Prepare shareKey for users - if ((int) $post_folder_is_personal === 1 && isset($post_folder_is_personal) === true) { - // If this is a personal object - DB::insert( - prefixTable('sharekeys_items'), - array( - 'object_id' => $newID, - 'user_id' => $_SESSION['user_id'], - 'share_key' => encryptUserObjectKey($cryptedStuff['objectKey'], $_SESSION['user']['public_key']), - ) - ); - } else { - // This is a public object - $users = DB::query( - 'SELECT id, public_key - FROM '.prefixTable('users').' - WHERE id NOT IN ("'.OTV_USER_ID.'","'.SSH_USER_ID.'","'.API_USER_ID.'") - AND public_key != ""' - ); - foreach ($users as $user) { - // Insert in DB the new object key for this item by user - DB::insert( - prefixTable('sharekeys_items'), - array( - 'object_id' => $newID, - 'user_id' => (int) $user['id'], - 'share_key' => encryptUserObjectKey($cryptedStuff['objectKey'], $user['public_key']), - ) - ); - } - } - */ - // update fields if ( isset($SETTINGS['item_extra_fields']) === true && (int) $SETTINGS['item_extra_fields'] === 1 ) { - foreach (explode('_|_', $post_fields) as $field) { - $field_data = explode('~~', $field); - if (count($field_data) > 1 && empty($field_data[1]) === false) { + foreach ($post_fields as $field) { + if (empty($field['value']) === false) { // should we encrypt the data $dataTmp = DB::queryFirstRow( 'SELECT encrypted_data FROM ' . prefixTable('categories') . ' WHERE id = %i', - $field_data[0] + $field['id'] ); // Should we encrypt the data if ((int) $dataTmp['encrypted_data'] === 1) { - $cryptedStuff = doDataEncryption($field_data[1]); - // Create sharekeys for users - storeUsersShareKey( - prefixTable('sharekeys_fields'), - (int) $post_folder_is_personal, - (int) $inputData['folderId'], - (int) $newId, - $cryptedStuff['objectKey'], - $SETTINGS - ); + $cryptedStuff = doDataEncryption($field['value']); - // update value + // Store value DB::insert( prefixTable('categories_items'), array( 'item_id' => $newID, - 'field_id' => $field_data[0], + 'field_id' => $field['id'], 'data' => $cryptedStuff['encrypted'], 'data_iv' => '', 'encryption_type' => TP_ENCRYPTION_NAME, ) ); + $newBojectId = DB::insertId(); + + // Store key + storeUsersShareKey( + prefixTable('sharekeys_fields'), + (int) $post_folder_is_personal, + (int) $inputData['folderId'], + (int) $newBojectId, + $cryptedStuff['objectKey'], + $SETTINGS + ); } else { // update value DB::insert( prefixTable('categories_items'), array( 'item_id' => $newID, - 'field_id' => $field_data[0], - 'data' => $field_data[1], + 'field_id' => $field['id'], + 'data' => $field['value'], 'data_iv' => '', 'encryption_type' => 'not_set', ) @@ -2706,7 +2672,8 @@ WHERE user_id = %i AND object_id = %i', $_SESSION['user_id'], $row['id'] - );//db::debugmode(false); + ); + //db::debugmode(false); $fieldText = []; if (DB::count() === 0) { // Not encrypted diff --git a/sources/main.functions.php b/sources/main.functions.php index 0e98f0583..9ce3e5eea 100755 --- a/sources/main.functions.php +++ b/sources/main.functions.php @@ -3914,7 +3914,8 @@ function handleUserKeys( string $encryptionKey = '', bool $deleteExistingKeys = false, bool $sendEmailToUser = true, - bool $encryptWithUserPassword = false + bool $encryptWithUserPassword = false, + int $nbItemsToTreat ): string { @@ -3968,7 +3969,7 @@ function handleUserKeys( 'task' => json_encode([ 'step' => 'step0', 'index' => 0, - 'nb' => empty($nbItemsToTreat) === false ? $nbItemsToTreat : NUMBER_ITEMS_IN_BATCH, + 'nb' => $nbItemsToTreat, ]), ) ); @@ -3981,7 +3982,7 @@ function handleUserKeys( 'task' => json_encode([ 'step' => 'step1', 'index' => 0, - 'nb' => empty($nbItemsToTreat) === false ? $nbItemsToTreat : NUMBER_ITEMS_IN_BATCH, + 'nb' => $nbItemsToTreat, ]), ) ); @@ -3994,7 +3995,7 @@ function handleUserKeys( 'task' => json_encode([ 'step' => 'step2', 'index' => 0, - 'nb' => empty($nbItemsToTreat) === false ? $nbItemsToTreat : NUMBER_ITEMS_IN_BATCH, + 'nb' => $nbItemsToTreat, ]), ) ); @@ -4007,7 +4008,7 @@ function handleUserKeys( 'task' => json_encode([ 'step' => 'step3', 'index' => 0, - 'nb' => empty($nbItemsToTreat) === false ? $nbItemsToTreat : NUMBER_ITEMS_IN_BATCH, + 'nb' => $nbItemsToTreat, ]), ) ); @@ -4020,7 +4021,7 @@ function handleUserKeys( 'task' => json_encode([ 'step' => 'step4', 'index' => 0, - 'nb' => empty($nbItemsToTreat) === false ? $nbItemsToTreat : NUMBER_ITEMS_IN_BATCH, + 'nb' => $nbItemsToTreat, ]), ) ); @@ -4033,7 +4034,7 @@ function handleUserKeys( 'task' => json_encode([ 'step' => 'step5', 'index' => 0, - 'nb' => empty($nbItemsToTreat) === false ? $nbItemsToTreat : NUMBER_ITEMS_IN_BATCH, + 'nb' => $nbItemsToTreat, ]), ) ); @@ -4046,7 +4047,7 @@ function handleUserKeys( 'task' => json_encode([ 'step' => 'step6', 'index' => 0, - 'nb' => empty($nbItemsToTreat) === false ? $nbItemsToTreat : NUMBER_ITEMS_IN_BATCH, + 'nb' => $nbItemsToTreat, ]), ) ); diff --git a/sources/main.queries.php b/sources/main.queries.php index aec9f7d43..3b4444114 100755 --- a/sources/main.queries.php +++ b/sources/main.queries.php @@ -516,7 +516,7 @@ function keyHandler(string $post_type, /*php8 array|null|string */$dataReceived, (bool) filter_var($dataReceived['delete_existing_keys'], FILTER_VALIDATE_BOOLEAN), (bool) filter_var($dataReceived['send_email_to_user'], FILTER_VALIDATE_BOOLEAN), (bool) filter_var($dataReceived['encrypt_with_user_pwd'], FILTER_VALIDATE_BOOLEAN), - (int) isset($SETTINGS['maximum_number_of_items_to_treat']) === true ? $SETTINGS['maximum_number_of_items_to_treat'] : '', + (int) isset($SETTINGS['maximum_number_of_items_to_treat']) === true ? $SETTINGS['maximum_number_of_items_to_treat'] : NUMBER_ITEMS_IN_BATCH, ); /*