From 659f43a01cfdfd860808c3699cc760b6bd0c5e45 Mon Sep 17 00:00:00 2001 From: Dmytro Hladkykh Date: Mon, 24 Aug 2026 18:35:43 +0300 Subject: [PATCH 1/2] server: create the key file readable by its owner only --- .github/workflows/python-app.yml | 2 ++ README.md | 15 ++++++++++++++- server.py | 13 ++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 35b292d..158ae76 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -45,6 +45,7 @@ jobs: - name: Test with filesystem backend run: | cp test_keys/* ../ + chmod 600 ../server_private_key.key cd .. python -m unittest -v env: @@ -54,6 +55,7 @@ jobs: - name: Test with Redis backend run: | cp test_keys/* ../ + chmod 600 ../server_private_key.key cd .. python -m unittest -v env: diff --git a/README.md b/README.md index 38778f9..3a9c53e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,19 @@ Green. `PYTHONPATH=.. python -m blind_pin_server.generateserverkey` +## Securing the server key + +Anyone who can read `server_private_key.key` can impersonate this server, so it is created +mode `0600` and should stay that way. + +In the container it is read by `www-data`, uid 33: + +`sudo chown 33:33 server_private_key.key` + +`sudo chmod 400 server_private_key.key` + +Mount it read-only (`:ro`, as below) so the container can't alter it. + ## Build the docker image `docker build -f Dockerfile . -t dockerized_pinserver` @@ -28,4 +41,4 @@ Green. ## Run the docker image (requires the previous steps) -`docker run -v $PWD/server_private_key.key:/server_private_key.key -v $PWD/pinsdir:/pins -p 8096:8096 dockerized_pinserver` +`docker run -v $PWD/server_private_key.key:/server_private_key.key:ro -v $PWD/pinsdir:/pins -p 8096:8096 dockerized_pinserver` diff --git a/server.py b/server.py index bb1a0a6..a98c6da 100644 --- a/server.py +++ b/server.py @@ -1,6 +1,8 @@ import time from hmac import compare_digest import os +import stat +import sys from .lib import decrypt, encrypt, E_ECDH from wallycore import ec_private_key_verify, ec_sig_from_bytes, sha256, \ hmac_sha256, EC_FLAG_ECDSA, ec_private_key_bip341_tweak, ec_public_key_from_private_key @@ -20,7 +22,11 @@ def generate_server_key_pair(cls): private_key, public_key = cls.generate_ec_key_pair() - with open(cls.STATIC_SERVER_PRIVATE_KEY_FILE, 'wb') as f: + # O_EXCL: fail if it exists, S_IRUSR|S_IWUSR: chmod 600 + fd = os.open(cls.STATIC_SERVER_PRIVATE_KEY_FILE, + os.O_WRONLY | os.O_CREAT | os.O_EXCL, + stat.S_IRUSR | stat.S_IWUSR) + with os.fdopen(fd, 'wb') as f: f.write(private_key) with open(cls.STATIC_SERVER_PUBLIC_KEY_FILE, 'wb') as f: @@ -32,6 +38,11 @@ def generate_server_key_pair(cls): @classmethod def load_private_key(cls): if not cls.STATIC_SERVER_PRIVATE_KEY: + mode = stat.S_IMODE(os.stat(cls.STATIC_SERVER_PRIVATE_KEY_FILE).st_mode) + if mode & (stat.S_IRWXG | stat.S_IRWXO): + print(f'WARNING: {cls.STATIC_SERVER_PRIVATE_KEY_FILE} is mode {mode:04o}, ' + 'readable by other users', file=sys.stderr) + with open(cls.STATIC_SERVER_PRIVATE_KEY_FILE, 'rb') as f: cls.STATIC_SERVER_PRIVATE_KEY = f.read() ec_private_key_verify(cls.STATIC_SERVER_PRIVATE_KEY) From 62b920ef50b8e035d26916d9ed77b42903664993 Mon Sep 17 00:00:00 2001 From: Dmytro Hladkykh Date: Tue, 25 Aug 2026 11:45:02 +0300 Subject: [PATCH 2/2] server: refuse to start if the key is readable by others --- README.md | 8 ++++---- server.py | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3a9c53e..5b0d247 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ Green. ## Securing the server key Anyone who can read `server_private_key.key` can impersonate this server, so it is created -mode `0600` and should stay that way. +mode `0600` and the server refuses to start if it is readable by anyone else. -In the container it is read by `www-data`, uid 33: +In the container it is read by `www-data`: -`sudo chown 33:33 server_private_key.key` +`sudo chown www-data:www-data server_private_key.key` -`sudo chmod 400 server_private_key.key` +`sudo chmod 600 server_private_key.key` Mount it read-only (`:ro`, as below) so the container can't alter it. diff --git a/server.py b/server.py index a98c6da..a83dbd5 100644 --- a/server.py +++ b/server.py @@ -2,7 +2,6 @@ from hmac import compare_digest import os import stat -import sys from .lib import decrypt, encrypt, E_ECDH from wallycore import ec_private_key_verify, ec_sig_from_bytes, sha256, \ hmac_sha256, EC_FLAG_ECDSA, ec_private_key_bip341_tweak, ec_public_key_from_private_key @@ -40,8 +39,8 @@ def load_private_key(cls): if not cls.STATIC_SERVER_PRIVATE_KEY: mode = stat.S_IMODE(os.stat(cls.STATIC_SERVER_PRIVATE_KEY_FILE).st_mode) if mode & (stat.S_IRWXG | stat.S_IRWXO): - print(f'WARNING: {cls.STATIC_SERVER_PRIVATE_KEY_FILE} is mode {mode:04o}, ' - 'readable by other users', file=sys.stderr) + raise Exception(f'{cls.STATIC_SERVER_PRIVATE_KEY_FILE} is mode {mode:04o}, ' + 'must not be readable by other users') with open(cls.STATIC_SERVER_PRIVATE_KEY_FILE, 'rb') as f: cls.STATIC_SERVER_PRIVATE_KEY = f.read()