Compare commits

...

12 Commits

Author SHA1 Message Date
bdb8f0aac9 fix
Some checks failed
BlockStorage/repertory/pipeline/head There was a failure building this commit
2025-03-20 13:49:11 -05:00
29fb70149c encrypt/decrypt secret data 2025-03-20 12:59:23 -05:00
4b45e71193 encrypt/decrypt secret data 2025-03-20 12:54:43 -05:00
0101e92d97 fix 2025-03-20 11:39:35 -05:00
ba5bde24e1 fix 2025-03-20 09:33:10 -05:00
40d71223ae remove passwords from api calls 2025-03-20 09:12:50 -05:00
e73fb02101 remove passwords from api calls 2025-03-20 08:55:10 -05:00
7f300b33c8 remove passwords from api calls 2025-03-20 08:51:50 -05:00
c4c509790d remove passwords from api calls 2025-03-20 08:32:20 -05:00
1ac64c9d82 remove passwords from api calls 2025-03-20 08:09:45 -05:00
c037fd5657 remove passwords from api calls 2025-03-20 08:08:43 -05:00
9b9929e69d remove passwords from api calls 2025-03-20 08:05:00 -05:00
9 changed files with 345 additions and 15 deletions

View File

@ -12,6 +12,7 @@
### Changes from v2.0.4-rc
* Continue documentation updates
* Removed passwords and secret key values from API calls
* Renamed setting `ApiAuth` to `ApiPassword`
* Require `--name,-na` option for encryption provider

View File

@ -314,6 +314,11 @@ provider_type_from_string(std::string_view type,
[[nodiscard]] auto provider_type_to_string(provider_type type) -> std::string;
void clean_json_config(provider_type prov, nlohmann::json &data);
[[nodiscard]] auto clean_json_value(std::string_view name,
std::string_view data) -> std::string;
#if defined(_WIN32)
struct open_file_data final {
PVOID directory_buffer{nullptr};

View File

@ -39,6 +39,7 @@ server::server(app_config &config) : config_(config) {}
void server::handle_get_config(const httplib::Request & /*req*/,
httplib::Response &res) {
auto data = config_.get_json();
clean_json_config(config_.get_provider_type(), data);
res.set_content(data.dump(), "application/json");
res.status = http_error_codes::ok;
}
@ -46,7 +47,10 @@ void server::handle_get_config(const httplib::Request & /*req*/,
void server::handle_get_config_value_by_name(const httplib::Request &req,
httplib::Response &res) {
auto name = req.get_param_value("name");
auto data = json({{"value", config_.get_value_by_name(name)}});
auto data = json({{
"value",
clean_json_value(name, config_.get_value_by_name(name)),
}});
res.set_content(data.dump(), "application/json");
res.status = http_error_codes::ok;
}
@ -56,7 +60,10 @@ void server::handle_set_config_value_by_name(const httplib::Request &req,
auto name = req.get_param_value("name");
auto value = req.get_param_value("value");
json data = {{"value", config_.set_value_by_name(name, value)}};
json data = {{
"value",
clean_json_value(name, config_.set_value_by_name(name, value)),
}};
res.set_content(data.dump(), "application/json");
res.status = http_error_codes::ok;
}

View File

@ -26,6 +26,51 @@
#include "utils/string.hpp"
namespace repertory {
void clean_json_config(provider_type prov, nlohmann::json &data) {
data[JSON_API_PASSWORD] = "";
switch (prov) {
case provider_type::encrypt:
data[JSON_ENCRYPT_CONFIG][JSON_ENCRYPTION_TOKEN] = "";
data[JSON_REMOTE_MOUNT][JSON_ENCRYPTION_TOKEN] = "";
break;
case provider_type::remote:
data[JSON_REMOTE_CONFIG][JSON_ENCRYPTION_TOKEN] = "";
break;
case provider_type::s3:
data[JSON_REMOTE_MOUNT][JSON_ENCRYPTION_TOKEN] = "";
data[JSON_S3_CONFIG][JSON_ENCRYPTION_TOKEN] = "";
data[JSON_S3_CONFIG][JSON_SECRET_KEY] = "";
break;
case provider_type::sia:
data[JSON_REMOTE_MOUNT][JSON_ENCRYPTION_TOKEN] = "";
data[JSON_HOST_CONFIG][JSON_API_PASSWORD] = "";
break;
default:
return;
}
}
auto clean_json_value(std::string_view name, std::string_view data)
-> std::string {
if (name ==
fmt::format("{}.{}", JSON_ENCRYPT_CONFIG, JSON_ENCRYPTION_TOKEN) ||
name == fmt::format("{}.{}", JSON_HOST_CONFIG, JSON_API_PASSWORD) ||
name == fmt::format("{}.{}", JSON_REMOTE_CONFIG, JSON_ENCRYPTION_TOKEN) ||
name == fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN) ||
name == fmt::format("{}.{}", JSON_S3_CONFIG, JSON_ENCRYPTION_TOKEN) ||
name == fmt::format("{}.{}", JSON_S3_CONFIG, JSON_SECRET_KEY) ||
name == JSON_API_PASSWORD) {
return "";
}
return std::string{data};
}
auto database_type_from_string(std::string type, database_type default_type)
-> database_type {
type = utils::string::to_lower(utils::string::trim(type));

View File

@ -29,10 +29,33 @@
#include "utils/common.hpp"
#include "utils/error_utils.hpp"
#include "utils/file.hpp"
#include "utils/hash.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
namespace {
[[nodiscard]] auto decrypt(std::string_view data, std::string_view password)
-> std::string {
auto decoded = macaron::Base64::Decode(data);
repertory::data_buffer buffer(decoded.size());
auto key = repertory::utils::encryption::create_hash_blake2b_256(password);
std::uint64_t size{};
crypto_aead_xchacha20poly1305_ietf_decrypt(
reinterpret_cast<unsigned char *>(buffer.data()), &size, nullptr,
reinterpret_cast<const unsigned char *>(
&decoded.at(crypto_aead_xchacha20poly1305_IETF_NPUBBYTES)),
decoded.size() - crypto_aead_xchacha20poly1305_IETF_NPUBBYTES,
reinterpret_cast<const unsigned char *>(REPERTORY.data()), 9U,
reinterpret_cast<const unsigned char *>(decoded.data()),
reinterpret_cast<const unsigned char *>(key.data()));
return std::string(
buffer.begin(),
std::next(buffer.begin(), static_cast<std::int64_t>(size)));
}
[[nodiscard]] constexpr auto is_restricted(std::string_view data) -> bool {
constexpr std::string_view invalid_chars = "&;|><$()`{}!*?";
return data.find_first_of(invalid_chars) != std::string_view::npos;
@ -218,6 +241,8 @@ void handlers::handle_get_mount(auto &&req, auto &&res) const {
lines.erase(lines.begin());
auto result = nlohmann::json::parse(utils::string::join(lines, '\n'));
clean_json_config(prov, result);
res.set_content(result.dump(), "application/json");
res.status = http_error_codes::ok;
}
@ -320,6 +345,7 @@ void handlers::handle_get_mount_status(auto &&req, auto &&res) const {
void handlers::handle_get_settings(auto &&res) const {
auto settings = config_->to_json();
settings[JSON_API_PASSWORD] = "";
settings.erase(JSON_MOUNT_LOCATIONS);
res.set_content(settings.dump(), "application/json");
res.status = http_error_codes::ok;
@ -383,6 +409,7 @@ void handlers::handle_post_mount(auto &&req, auto &&res) const {
void handlers::handle_put_set_value_by_name(auto &&req, auto &&res) const {
auto name = req.get_param_value("name");
auto prov = provider_type_from_string(req.get_param_value("type"));
if (not data_directory_exists(prov, name)) {
@ -391,8 +418,19 @@ void handlers::handle_put_set_value_by_name(auto &&req, auto &&res) const {
}
auto key = req.get_param_value("key");
auto last_key{key};
auto value = req.get_param_value("value");
auto parts = utils::string::split(key, '.', false);
if (parts.size() > 1U) {
last_key = parts.at(parts.size() - 1U);
}
if (last_key == JSON_API_PASSWORD || last_key == JSON_ENCRYPTION_TOKEN ||
last_key == JSON_SECRET_KEY) {
value = decrypt(value, config_->get_api_password());
}
set_key_value(prov, name, key, value);
res.status = http_error_codes::ok;
@ -402,7 +440,9 @@ void handlers::handle_put_settings(auto &&req, auto &&res) const {
nlohmann::json data = nlohmann::json::parse(req.get_param_value("data"));
if (data.contains(JSON_API_PASSWORD)) {
config_->set_api_password(data.at(JSON_API_PASSWORD).get<std::string>());
auto password = decrypt(data.at(JSON_API_PASSWORD).get<std::string>(),
config_->get_api_password());
config_->set_api_password(password);
}
if (data.contains(JSON_API_PORT)) {

View File

@ -0,0 +1,198 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test_common.hpp"
#include "app_config.hpp"
#include "types/repertory.hpp"
namespace repertory {
TEST(clean_json_test, can_clean_values) {
auto result = clean_json_value(JSON_API_PASSWORD, "moose");
EXPECT_TRUE(result.empty());
result = clean_json_value(
fmt::format("{}.{}", JSON_ENCRYPT_CONFIG, JSON_ENCRYPTION_TOKEN),
"moose");
EXPECT_TRUE(result.empty());
result = clean_json_value(
fmt::format("{}.{}", JSON_HOST_CONFIG, JSON_API_PASSWORD), "moose");
EXPECT_TRUE(result.empty());
result = clean_json_value(
fmt::format("{}.{}", JSON_REMOTE_CONFIG, JSON_ENCRYPTION_TOKEN), "moose");
EXPECT_TRUE(result.empty());
result = clean_json_value(
fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose");
EXPECT_TRUE(result.empty());
result = clean_json_value(
fmt::format("{}.{}", JSON_S3_CONFIG, JSON_ENCRYPTION_TOKEN), "moose");
EXPECT_TRUE(result.empty());
result = clean_json_value(
fmt::format("{}.{}", JSON_S3_CONFIG, JSON_SECRET_KEY), "moose");
EXPECT_TRUE(result.empty());
}
TEST(clean_json_test, can_clean_encrypt_config) {
auto dir =
utils::path::combine(test::get_test_output_dir(), {
"clean_json_test",
"encrypt",
});
app_config cfg(provider_type::encrypt, dir);
cfg.set_value_by_name(JSON_API_PASSWORD, "moose");
cfg.set_value_by_name(
fmt::format("{}.{}", JSON_ENCRYPT_CONFIG, JSON_ENCRYPTION_TOKEN),
"moose");
cfg.set_value_by_name(
fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose");
auto data = cfg.get_json();
EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_FALSE(data.at(JSON_ENCRYPT_CONFIG)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
EXPECT_FALSE(data.at(JSON_REMOTE_MOUNT)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
clean_json_config(cfg.get_provider_type(), data);
EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_TRUE(data.at(JSON_ENCRYPT_CONFIG)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
EXPECT_TRUE(data.at(JSON_REMOTE_MOUNT)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
}
TEST(clean_json_test, can_clean_remote_config) {
auto dir =
utils::path::combine(test::get_test_output_dir(), {
"clean_json_test",
"remote",
});
app_config cfg(provider_type::remote, dir);
cfg.set_value_by_name(JSON_API_PASSWORD, "moose");
cfg.set_value_by_name(
fmt::format("{}.{}", JSON_REMOTE_CONFIG, JSON_ENCRYPTION_TOKEN), "moose");
auto data = cfg.get_json();
EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_FALSE(data.at(JSON_REMOTE_CONFIG)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
clean_json_config(cfg.get_provider_type(), data);
EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_TRUE(data.at(JSON_REMOTE_CONFIG)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
}
TEST(clean_json_test, can_clean_s3_config) {
auto dir =
utils::path::combine(test::get_test_output_dir(), {
"clean_json_test",
"s3",
});
app_config cfg(provider_type::s3, dir);
cfg.set_value_by_name(JSON_API_PASSWORD, "moose");
cfg.set_value_by_name(
fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose");
cfg.set_value_by_name(
fmt::format("{}.{}", JSON_S3_CONFIG, JSON_ENCRYPTION_TOKEN), "moose");
cfg.set_value_by_name(fmt::format("{}.{}", JSON_S3_CONFIG, JSON_SECRET_KEY),
"moose");
auto data = cfg.get_json();
EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_FALSE(data.at(JSON_REMOTE_MOUNT)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
EXPECT_FALSE(data.at(JSON_S3_CONFIG)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
EXPECT_FALSE(
data.at(JSON_S3_CONFIG).at(JSON_SECRET_KEY).get<std::string>().empty());
clean_json_config(cfg.get_provider_type(), data);
EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_TRUE(data.at(JSON_REMOTE_MOUNT)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
EXPECT_TRUE(data.at(JSON_S3_CONFIG)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
EXPECT_TRUE(
data.at(JSON_S3_CONFIG).at(JSON_SECRET_KEY).get<std::string>().empty());
}
TEST(clean_json_test, can_clean_sia_config) {
auto dir =
utils::path::combine(test::get_test_output_dir(), {
"clean_json_test",
"sia",
});
app_config cfg(provider_type::sia, dir);
cfg.set_value_by_name(JSON_API_PASSWORD, "moose");
cfg.set_value_by_name(
fmt::format("{}.{}", JSON_HOST_CONFIG, JSON_API_PASSWORD), "moose");
cfg.set_value_by_name(
fmt::format("{}.{}", JSON_REMOTE_MOUNT, JSON_ENCRYPTION_TOKEN), "moose");
auto data = cfg.get_json();
EXPECT_FALSE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_FALSE(data.at(JSON_HOST_CONFIG)
.at(JSON_API_PASSWORD)
.get<std::string>()
.empty());
EXPECT_FALSE(data.at(JSON_REMOTE_MOUNT)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
clean_json_config(cfg.get_provider_type(), data);
EXPECT_TRUE(data.at(JSON_API_PASSWORD).get<std::string>().empty());
EXPECT_TRUE(data.at(JSON_HOST_CONFIG)
.at(JSON_API_PASSWORD)
.get<std::string>()
.empty());
EXPECT_TRUE(data.at(JSON_REMOTE_MOUNT)
.at(JSON_ENCRYPTION_TOKEN)
.get<std::string>()
.empty());
}
} // namespace repertory

View File

@ -1,7 +1,10 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:repertory/constants.dart' as constants;
import 'package:sodium_libs/sodium_libs.dart';
typedef Validator = bool Function(String);
@ -224,13 +227,19 @@ bool validateSettings(
}
Map<String, dynamic> convertAllToString(Map<String, dynamic> settings) {
final password = 'test';
settings.forEach((key, value) {
if (value is Map<String, dynamic>) {
convertAllToString(value);
return;
}
if (value is String) {
if (key == 'ApiPassword' ||
key == 'EncryptionToken' ||
key == 'SecretKey') {
value = encryptValue(value, password);
} else if (value is String) {
return;
}
@ -239,3 +248,29 @@ Map<String, dynamic> convertAllToString(Map<String, dynamic> settings) {
return settings;
}
String encryptValue(String value, String password) {
final sodium = constants.sodium;
if (sodium == null) {
return value;
}
final keyHash = sodium.crypto.genericHash(
outLen: sodium.crypto.aeadXChaCha20Poly1305IETF.keyBytes,
message: Uint8List.fromList(password.toCharArray()),
);
debugPrint("key: ${base64Encode(keyHash)}");
final crypto = sodium.crypto.aeadXChaCha20Poly1305IETF;
final nonce = sodium.secureRandom(crypto.nonceBytes).extractBytes();
debugPrint("nonce: ${base64Encode(nonce)}");
final data = crypto.encrypt(
additionalData: Uint8List.fromList('repertory'.toCharArray()),
key: SecureKey.fromList(sodium, keyHash),
message: Uint8List.fromList(value.toCharArray()),
nonce: nonce,
);
return base64Encode(Uint8List.fromList([...nonce, ...data]));
}

View File

@ -1,4 +1,4 @@
import 'dart:convert' show jsonDecode;
import 'dart:convert' show jsonDecode, jsonEncode;
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
@ -28,6 +28,7 @@ class _EditSettingsScreenState extends State<EditSettingsScreen> {
}
return UISettingsWidget(
origSettings: jsonDecode(jsonEncode(snapshot.requireData)),
settings: snapshot.requireData,
showAdvanced: false,
);
@ -56,7 +57,6 @@ class _EditSettingsScreenState extends State<EditSettingsScreen> {
return {};
}
// UISettingsWidget(settings: {}, showAdvanced: false),
@override
void setState(VoidCallback fn) {
if (!mounted) {

View File

@ -16,8 +16,10 @@ import 'package:settings_ui/settings_ui.dart';
class UISettingsWidget extends StatefulWidget {
final bool showAdvanced;
final Map<String, dynamic> settings;
final Map<String, dynamic> origSettings;
const UISettingsWidget({
super.key,
required this.origSettings,
required this.settings,
required this.showAdvanced,
});
@ -27,8 +29,6 @@ class UISettingsWidget extends StatefulWidget {
}
class _UISettingsWidgetState extends State<UISettingsWidget> {
late Map<String, dynamic> _origSettings;
@override
Widget build(BuildContext context) {
List<SettingsTile> commonSettings = [];
@ -100,7 +100,12 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
@override
void dispose() {
if (!DeepCollectionEquality().equals(widget.settings, _origSettings)) {
debugPrint('current: ${jsonEncode(widget.settings)}');
debugPrint('orig: ${jsonEncode(widget.origSettings)}');
if (!DeepCollectionEquality().equals(
widget.settings,
widget.origSettings,
)) {
http
.put(
Uri.parse(
@ -118,12 +123,6 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
super.dispose();
}
@override
void initState() {
_origSettings = jsonDecode(jsonEncode(widget.settings));
super.initState();
}
@override
void setState(VoidCallback fn) {
if (!mounted) {