Skip to content

Commit 67ea6e4

Browse files
committed
fix(core): use monocypher lib for password hashing
1 parent ed40dfc commit 67ea6e4

27 files changed

Lines changed: 707 additions & 149 deletions

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@
1616
[submodule "third_party/rapidxml"]
1717
path = third_party/rapidxml
1818
url = https://salsa.debian.org/debian/rapidxml.git
19+
[submodule "third_party/monocypher"]
20+
path = third_party/monocypher
21+
url = https://github.com/LoupVaillant/Monocypher.git

CMakeLists.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ if(NOT KEEN_PBR_RELEASE MATCHES "^[0-9]+$")
4141
message(FATAL_ERROR "KEEN_PBR_RELEASE must contain only digits")
4242
endif()
4343

44-
project(keen-pbr VERSION ${KEEN_PBR_VERSION} LANGUAGES CXX)
44+
project(keen-pbr VERSION ${KEEN_PBR_VERSION} LANGUAGES C CXX)
4545

4646
include(GNUInstallDirs)
4747

@@ -214,6 +214,15 @@ set(FMT_FUZZ OFF CACHE INTERNAL "")
214214
set(FMT_CUDA_TEST OFF CACHE INTERNAL "")
215215
add_subdirectory(third_party/fmt)
216216

217+
# Monocypher (pinned git submodule, compiled into the final executable)
218+
add_library(monocypher STATIC third_party/monocypher/src/monocypher.c)
219+
target_include_directories(monocypher PUBLIC third_party/monocypher/src)
220+
set_target_properties(monocypher PROPERTIES
221+
C_STANDARD 99
222+
C_STANDARD_REQUIRED YES
223+
C_EXTENSIONS NO
224+
)
225+
217226
# cpp-httplib (bundled as git submodule, only if WITH_API — no TLS needed)
218227
if(WITH_API)
219228
set(HTTPLIB_USE_OPENSSL_IF_AVAILABLE OFF CACHE INTERNAL "")
@@ -391,6 +400,7 @@ target_link_libraries(keen-pbr PRIVATE
391400
Threads::Threads
392401
fmt::fmt-header-only
393402
keen_pbr_libunwind
403+
monocypher
394404
nlohmann_json::nlohmann_json
395405
)
396406
keen_pbr_apply_project_options(keen-pbr)

build_scripts/generate_api_types.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ process.stdin.on('end', () => {
1919
const spec = JSON.parse(d);
2020
const schemas = spec.components.schemas;
2121
22+
// password_hash is daemon-internal configuration state. Keep it out of the
23+
// public OpenAPI contract (and therefore out of browser-generated models),
24+
// while retaining it in the C++ configuration representation.
25+
schemas.AuthenticationConfig.properties.password_hash = { type: 'string' };
26+
2227
// Remove OpenAPI-specific extensions that QuickType does not understand
2328
function clean(obj) {
2429
if (typeof obj !== 'object' || obj === null) return;

docs/content/docs/rest-api.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The REST API is available when:
2121
"listen": "0.0.0.0:12121",
2222
"authentication": {
2323
"enabled": true,
24-
"password_hash": "pbkdf2-sha256$200000$..."
24+
"password_hash": "argon2id$v=19$m=19456,t=2,p=1$..."
2525
},
2626
"cors": {
2727
"allowed_origins": ["https://panel.example.com"]
@@ -42,6 +42,12 @@ The command updates the file atomically and reminds
4242
you to restart the keen-pbr service. A restart is required for authentication
4343
and CORS changes to affect the running HTTP server.
4444

45+
Password verifiers use Monocypher's Argon2id implementation. Verifiers from
46+
older PBKDF2-based releases are not accepted and must be regenerated.
47+
The config API never returns or accepts the stored verifier. The WebUI reads
48+
only `GET /api/auth/password` state and sends a new clear-text password only to
49+
the write-only `POST /api/auth/password` operation; hashing happens in the daemon.
50+
4551
Authenticated API clients may use Basic authentication with username `admin`
4652
or a Bearer token returned by `POST /api/auth/login`. Only one Bearer session is
4753
valid at a time; a successful login immediately invalidates the previous UI

docs/content/docs/rest-api.ru.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,46 @@ REST API доступен, если:
1818
{
1919
"api": {
2020
"enabled": true,
21-
"listen": "0.0.0.0:12121"
21+
"listen": "0.0.0.0:12121",
22+
"authentication": {
23+
"enabled": true,
24+
"password_hash": "argon2id$v=19$m=19456,t=2,p=1$..."
25+
},
26+
"cors": {
27+
"allowed_origins": ["https://panel.example.com"]
28+
}
2229
}
2330
}
2431
```
2532

33+
Интерактивно сгенерировать хеш пароля:
34+
35+
```bash
36+
keen-pbr hash-password
37+
```
38+
39+
Чтобы включить аутентификацию и сразу записать новый хеш в выбранный
40+
`config.json`, выполните `keen-pbr --config /path/to/config.json hash-password --update`.
41+
Команда атомарно обновит файл и напомнит о необходимости перезапуска службы.
42+
Изменения аутентификации и CORS применяются только после перезапуска keen-pbr.
43+
44+
Для хеширования пароля используется реализация Argon2id из Monocypher. Хеши
45+
PBKDF2 из предыдущих версий не поддерживаются — их необходимо создать заново.
46+
API конфигурации никогда не возвращает и не принимает сохранённый хеш. Веб-интерфейс
47+
получает только состояние через `GET /api/auth/password`, а новый пароль в открытом
48+
виде отправляет только в `POST /api/auth/password`; хеширование выполняет демон.
49+
50+
Клиенты API могут использовать Basic-аутентификацию с именем пользователя
51+
`admin` или Bearer-токен, полученный через `POST /api/auth/login`. Одновременно
52+
действует только одна Bearer-сессия: новый успешный вход немедленно завершает
53+
предыдущую сессию веб-интерфейса. Сессия истекает через 24 часа и теряется при
54+
перезапуске демона.
55+
56+
Когда аутентификация отключена, межсайтовые запросы браузера запрещены. Когда
57+
она включена, разрешены точно указанные источники, а также источники расширений
58+
Chrome и Firefox. При входе и Basic-аутентификации пароль передаётся API,
59+
поэтому используйте доверенную сеть или HTTPS reverse proxy.
60+
2661
По умолчанию API прослушивает `0.0.0.0:12121`. Все эндпоинты обслуживаются на настроенном адресе `api.listen`.
2762

2863
---

docs/openapi.yaml

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,37 @@ paths:
5555
"204":
5656
description: Session revoked
5757

58+
/api/auth/password:
59+
get:
60+
summary: Get administrator password state
61+
operationId: getAuthPasswordStatus
62+
responses:
63+
"200":
64+
description: Password state
65+
content:
66+
application/json:
67+
schema:
68+
$ref: "#/components/schemas/AuthPasswordStatus"
69+
post:
70+
summary: Set the administrator password
71+
description: Accepts the clear-text password and stores only its verifier in the staged server configuration.
72+
operationId: postAuthPassword
73+
requestBody:
74+
required: true
75+
content:
76+
application/json:
77+
schema:
78+
$ref: "#/components/schemas/AuthPasswordRequest"
79+
responses:
80+
"200":
81+
description: Password updated in the staged configuration
82+
content:
83+
application/json:
84+
schema:
85+
$ref: "#/components/schemas/AuthPasswordStatus"
86+
"400":
87+
description: Invalid or empty password
88+
5889
/api/health/service:
5990
get:
6091
summary: Service health and outbound status
@@ -588,9 +619,6 @@ components:
588619
enabled:
589620
type: boolean
590621
default: false
591-
password_hash:
592-
type: string
593-
description: PBKDF2-SHA256 password verifier generated by `keen-pbr hash-password`.
594622

595623
CorsConfig:
596624
type: object
@@ -627,6 +655,21 @@ components:
627655
type: integer
628656
format: int64
629657

658+
AuthPasswordRequest:
659+
type: object
660+
required: [password]
661+
properties:
662+
password:
663+
type: string
664+
minLength: 1
665+
666+
AuthPasswordStatus:
667+
type: object
668+
required: [password_set]
669+
properties:
670+
password_set:
671+
type: boolean
672+
630673
RetryConfig:
631674
type: object
632675
properties:

0 commit comments

Comments
 (0)