mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2025-01-06 04:07:16 +03:00
tools: Implement and test tools/legacy_keyring.py
(#13947)
* Add `-l` option to `install.sh` and use it for linux tests on CI * Implement and test `tools/legacy_keyring.py` * Update install.sh Co-authored-by: Jeff <paninaro@gmail.com> Co-authored-by: Jeff <paninaro@gmail.com>
This commit is contained in:
parent
306b318bcd
commit
b916275540
6
.github/actions/install/action.yml
vendored
6
.github/actions/install/action.yml
vendored
@ -11,6 +11,10 @@ inputs:
|
||||
description: "Install development dependencies."
|
||||
required: false
|
||||
default: ""
|
||||
legacy_keyring:
|
||||
description: "Install legacy keyring dependencies."
|
||||
required: false
|
||||
default: ""
|
||||
automated:
|
||||
description: "Automated install, no questions."
|
||||
required: false
|
||||
@ -29,7 +33,7 @@ runs:
|
||||
env:
|
||||
INSTALL_PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
run: |
|
||||
${{ inputs.command-prefix }} sh install.sh ${{ inputs.development && '-d' || '' }} ${{ inputs.automated == 'true' && '-a' || '' }}
|
||||
${{ inputs.command-prefix }} sh install.sh ${{ inputs.development && '-d' || '' }} ${{ inputs.legacy_keyring && '-l' || '' }} ${{ inputs.automated == 'true' && '-a' || '' }}
|
||||
|
||||
- name: Run install script (Windows)
|
||||
if: runner.os == 'windows'
|
||||
|
1
.github/workflows/benchmarks.yml
vendored
1
.github/workflows/benchmarks.yml
vendored
@ -71,6 +71,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
development: true
|
||||
legacy_keyring: true
|
||||
|
||||
- uses: chia-network/actions/activate-venv@main
|
||||
|
||||
|
1
.github/workflows/test-single.yml
vendored
1
.github/workflows/test-single.yml
vendored
@ -197,6 +197,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python.install_sh }}
|
||||
development: true
|
||||
legacy_keyring: ${{ matrix.configuration.legacy_keyring_required }}
|
||||
|
||||
- uses: chia-network/actions/activate-venv@main
|
||||
|
||||
|
@ -3,10 +3,11 @@
|
||||
set -o errexit
|
||||
|
||||
USAGE_TEXT="\
|
||||
Usage: $0 [-adsph]
|
||||
Usage: $0 [-adlsph]
|
||||
|
||||
-a automated install, no questions
|
||||
-d install development dependencies
|
||||
-l install legacy keyring dependencies (linux only)
|
||||
-s skip python package installation and just do pip install
|
||||
-p additional plotters installation
|
||||
-h display this help and exit
|
||||
@ -21,7 +22,7 @@ EXTRAS=
|
||||
SKIP_PACKAGE_INSTALL=
|
||||
PLOTTER_INSTALL=
|
||||
|
||||
while getopts adsph flag
|
||||
while getopts adlsph flag
|
||||
do
|
||||
case "${flag}" in
|
||||
# automated
|
||||
@ -31,6 +32,8 @@ do
|
||||
# simple install
|
||||
s) SKIP_PACKAGE_INSTALL=1;;
|
||||
p) PLOTTER_INSTALL=1;;
|
||||
# legacy keyring
|
||||
l) EXTRAS=${EXTRAS}legacy_keyring,;;
|
||||
h) usage; exit 0;;
|
||||
*) echo; usage; exit 1;;
|
||||
esac
|
||||
|
5
setup.py
5
setup.py
@ -67,6 +67,10 @@ dev_dependencies = [
|
||||
"types-setuptools",
|
||||
]
|
||||
|
||||
legacy_keyring_dependencies = [
|
||||
"keyrings.cryptfile==1.3.9",
|
||||
]
|
||||
|
||||
kwargs = dict(
|
||||
name="chia-blockchain",
|
||||
author="Mariano Sorgente",
|
||||
@ -80,6 +84,7 @@ kwargs = dict(
|
||||
extras_require=dict(
|
||||
dev=dev_dependencies,
|
||||
upnp=upnp_dependencies,
|
||||
legacy_keyring=legacy_keyring_dependencies,
|
||||
),
|
||||
packages=[
|
||||
"build_scripts",
|
||||
|
@ -116,6 +116,7 @@ for path in test_paths:
|
||||
"install_timelord": conf["install_timelord"],
|
||||
"test_files": paths_for_cli,
|
||||
"name": ".".join(path.relative_to(root_path).with_suffix("").parts),
|
||||
"legacy_keyring_required": conf.get("legacy_keyring_required", False),
|
||||
}
|
||||
for_matrix = dict(sorted(for_matrix.items()))
|
||||
configuration.append(for_matrix)
|
||||
|
@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
parallel = True
|
||||
legacy_keyring_required = sys.platform == "linux"
|
||||
|
82
tests/tools/test_legacy_keyring.py
Normal file
82
tests/tools/test_legacy_keyring.py
Normal file
@ -0,0 +1,82 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner, Result
|
||||
|
||||
try:
|
||||
from keyrings.cryptfile.cryptfile import CryptFileKeyring
|
||||
except ImportError:
|
||||
if sys.platform == "linux":
|
||||
raise
|
||||
|
||||
from tools.legacy_keyring import create_legacy_keyring, generate_and_add, get_keys, legacy_keyring
|
||||
|
||||
|
||||
def show() -> Result:
|
||||
return CliRunner().invoke(legacy_keyring, ["show"])
|
||||
|
||||
|
||||
def clear(input_str: str) -> Result:
|
||||
return CliRunner().invoke(legacy_keyring, ["clear"], input=f"{input_str}\n")
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32" or sys.platform == "darwin", reason="Tests the linux legacy keyring format")
|
||||
def test_legacy_keyring_format(tmp_dir: Path) -> None:
|
||||
keyring = CryptFileKeyring()
|
||||
keyring.keyring_key = "your keyring password"
|
||||
# Create the legacy keyring file with the old format
|
||||
keyring.file_path = tmp_dir / "keyring"
|
||||
keyring.filename = keyring.file_path.name
|
||||
keyring_data = """
|
||||
[chia_2Duser_2Dchia_2D1_2E8]
|
||||
wallet_2duser_2dchia_2d1_2e8_2d0 =
|
||||
eyJzYWx0IjogIi9NY3J3UG9iQjdiclpQMGRHclZiU1E9PSIsICJkYXRhIjogIjBnMEROUzRDSGdJ
|
||||
NU4yVEFYUVVhaExFY2RzN0NFR05rNnpKSmNLcWY5VmdOb2h6SkdxcUlOZzNKaTBEa3NIOGh3aHlM
|
||||
cG1GeFZVYWRcbmRtMTVWMDlsU3I1b3dNZDZHY3JGQTJHckZtZGszUmFmY0ZicmhlMmlRMjMzRW1P
|
||||
c28zQUxNbG5CcGtWTlR0cHZYYjlzbEp4VE5yVVVcbm8xUE0wNytTa1lJTHVzcmlNUStkUjBIQkxZ
|
||||
WXF3VjBUVndETHVKZmdtNWdyd1hrUkdkUjdvU0VyVTJUcnRnPT0iLCAibWFjIjogInA4MWJFTXhJ
|
||||
ay83bm1iMDMxR0NpZnc9PSIsICJub25jZSI6ICJzcUhoTUhOMkZQeTQxR3U4em40MXhBPT0ifQ==
|
||||
wallet_2duser_2dchia_2d1_2e8_2d1 =
|
||||
eyJzYWx0IjogIjNhWkFCQXBCcXUxdzI5WHpJcXBzS3c9PSIsICJkYXRhIjogImZwU05ZYk5WMmJM
|
||||
Vms5MjB6cGYzdzYrK2ZMc2w4b3Y4OU9uTWdHNlo4OXhzenRoc0tFZjdieHVKVGRyT3JmYmtBUmgv
|
||||
TzhzY3R1R2ZcblR1REVIOHJHNVA3RGpOWWQ3dFhxd2xabkg1VTVnV2VCNzZPaXdmVDQxQytxWlVX
|
||||
RXQ5L1dnMTQybHdqMy8vR2pJZ0w2d2Q0QXQyWjBcbmtQQVNOMnVnVmZpa0RiZGFaN21oeFRxNnRK
|
||||
TEszQWtLU3VPVmJyWEplbjZ2OGhXcGNMVU1HN3RIZENWNU5nPT0iLCAibWFjIjogIitPS3h1ZjZQ
|
||||
RzArdTA2Z2Qzb2dSNGc9PSIsICJub25jZSI6ICIxdWR2N1JIajhWaER2UWpVSjRJLzZnPT0ifQ==
|
||||
"""
|
||||
with open(str(keyring.file_path), "w") as keyring_file:
|
||||
keyring_file.write(keyring_data)
|
||||
# Make sure the loaded keys match the file content
|
||||
keys = get_keys(keyring)
|
||||
assert len(keys) == 2
|
||||
assert keys[0].fingerprint == 1925978301
|
||||
assert keys[1].fingerprint == 2990446712
|
||||
|
||||
|
||||
def test_legacy_keyring_cli() -> None:
|
||||
keyring = create_legacy_keyring()
|
||||
result = show()
|
||||
assert result.exit_code == 1
|
||||
assert "No keys found in the legacy keyring." in result.output
|
||||
keys = []
|
||||
for i in range(5):
|
||||
keys.append(generate_and_add(keyring))
|
||||
result = show()
|
||||
assert result.exit_code == 0
|
||||
for key in keys:
|
||||
assert key.mnemonic_str() in result.output
|
||||
|
||||
# Should abort if the prompt gets a `n`
|
||||
result = clear("n")
|
||||
assert result.exit_code == 1
|
||||
assert "Aborted" in result.output
|
||||
|
||||
# And succeed if the prompt gets a `y`
|
||||
result = clear("y")
|
||||
assert result.exit_code == 0
|
||||
for key in keys:
|
||||
assert key.mnemonic_str() in result.output
|
||||
assert f"{len(keys)} keys removed" in result.output
|
153
tools/legacy_keyring.py
Normal file
153
tools/legacy_keyring.py
Normal file
@ -0,0 +1,153 @@
|
||||
"""
|
||||
Provides a helper to access the legacy keyring which was supported up to version 1.6.1 of chia-blockchain. To use this
|
||||
helper it's required to install the `legacy_keyring` extra dependency which can be done via the install-option `-l`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Callable, List, Union, cast
|
||||
|
||||
import click
|
||||
from blspy import G1Element
|
||||
from keyring.backends.macOS import Keyring as MacKeyring
|
||||
from keyring.backends.Windows import WinVaultKeyring as WinKeyring
|
||||
|
||||
try:
|
||||
from keyrings.cryptfile.cryptfile import CryptFileKeyring
|
||||
except ImportError:
|
||||
if sys.platform == "linux":
|
||||
sys.exit("Use `install.sh -l` to install the legacy_keyring dependency.")
|
||||
CryptFileKeyring = None
|
||||
|
||||
|
||||
from chia.util.errors import KeychainUserNotFound
|
||||
from chia.util.keychain import KeyData, KeyDataSecrets, get_private_key_user
|
||||
from chia.util.misc import prompt_yes_no
|
||||
|
||||
LegacyKeyring = Union[MacKeyring, WinKeyring, CryptFileKeyring]
|
||||
|
||||
|
||||
CURRENT_KEY_VERSION = "1.8"
|
||||
DEFAULT_USER = f"user-chia-{CURRENT_KEY_VERSION}" # e.g. user-chia-1.8
|
||||
DEFAULT_SERVICE = f"chia-{DEFAULT_USER}" # e.g. chia-user-chia-1.8
|
||||
MAX_KEYS = 100
|
||||
|
||||
|
||||
# casting to compensate for a combination of mypy and keyring issues
|
||||
# https://github.com/python/mypy/issues/9025
|
||||
# https://github.com/jaraco/keyring/issues/437
|
||||
def create_legacy_keyring() -> LegacyKeyring:
|
||||
if sys.platform == "darwin":
|
||||
return cast(Callable[[], LegacyKeyring], MacKeyring)()
|
||||
elif sys.platform == "win32" or sys.platform == "cygwin":
|
||||
return cast(Callable[[], LegacyKeyring], WinKeyring)()
|
||||
elif sys.platform == "linux":
|
||||
keyring: CryptFileKeyring = CryptFileKeyring()
|
||||
keyring.keyring_key = "your keyring password"
|
||||
return keyring
|
||||
raise click.ClickException(f"platform '{sys.platform}' not supported.")
|
||||
|
||||
|
||||
def generate_and_add(keyring: LegacyKeyring) -> KeyData:
|
||||
key = KeyData.generate()
|
||||
index = 0
|
||||
while True:
|
||||
try:
|
||||
get_key_data(keyring, index)
|
||||
index += 1
|
||||
except KeychainUserNotFound:
|
||||
keyring.set_password(
|
||||
DEFAULT_SERVICE,
|
||||
get_private_key_user(DEFAULT_USER, index),
|
||||
bytes(key.public_key).hex() + key.entropy.hex(),
|
||||
)
|
||||
return key
|
||||
|
||||
|
||||
def get_key_data(keyring: LegacyKeyring, index: int) -> KeyData:
|
||||
user = get_private_key_user(DEFAULT_USER, index)
|
||||
read_str = keyring.get_password(DEFAULT_SERVICE, user)
|
||||
if read_str is None or len(read_str) == 0:
|
||||
raise KeychainUserNotFound(DEFAULT_SERVICE, user)
|
||||
str_bytes = bytes.fromhex(read_str)
|
||||
|
||||
public_key = G1Element.from_bytes(str_bytes[: G1Element.SIZE])
|
||||
fingerprint = public_key.get_fingerprint()
|
||||
entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32]
|
||||
|
||||
return KeyData(
|
||||
fingerprint=fingerprint,
|
||||
public_key=public_key,
|
||||
label=None,
|
||||
secrets=KeyDataSecrets.from_entropy(entropy),
|
||||
)
|
||||
|
||||
|
||||
def get_keys(keyring: LegacyKeyring) -> List[KeyData]:
|
||||
keys: List[KeyData] = []
|
||||
for index in range(MAX_KEYS + 1):
|
||||
try:
|
||||
keys.append(get_key_data(keyring, index))
|
||||
except KeychainUserNotFound:
|
||||
pass
|
||||
return keys
|
||||
|
||||
|
||||
def print_key(key: KeyData) -> None:
|
||||
print(f"fingerprint: {key.fingerprint}, mnemonic: {key.mnemonic_str()}")
|
||||
|
||||
|
||||
def print_keys(keyring: LegacyKeyring) -> None:
|
||||
keys = get_keys(keyring)
|
||||
|
||||
if len(keys) == 0:
|
||||
raise click.ClickException("No keys found in the legacy keyring.")
|
||||
|
||||
for key in keys:
|
||||
print_key(key)
|
||||
|
||||
|
||||
def remove_keys(keyring: LegacyKeyring) -> None:
|
||||
removed = 0
|
||||
for index in range(MAX_KEYS + 1):
|
||||
try:
|
||||
keyring.delete_password(DEFAULT_SERVICE, get_private_key_user(DEFAULT_USER, index))
|
||||
removed += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"{removed} key{'s' if removed != 1 else ''} removed.")
|
||||
|
||||
|
||||
@click.group(help="Manage the keys in the legacy keyring.")
|
||||
def legacy_keyring() -> None:
|
||||
pass
|
||||
|
||||
|
||||
@legacy_keyring.command(help="Generate and add a random key (for testing)", hidden=True)
|
||||
def generate() -> None:
|
||||
keyring = create_legacy_keyring()
|
||||
key = generate_and_add(keyring)
|
||||
print_key(key)
|
||||
|
||||
|
||||
@legacy_keyring.command(help="Show all available keys")
|
||||
def show() -> None:
|
||||
print_keys(create_legacy_keyring())
|
||||
|
||||
|
||||
@legacy_keyring.command(help="Remove all keys")
|
||||
def clear() -> None:
|
||||
keyring = create_legacy_keyring()
|
||||
|
||||
print_keys(keyring)
|
||||
|
||||
if not prompt_yes_no("\nDo you really want to remove all the keys from the legacy keyring? This can't be undone."):
|
||||
raise click.ClickException("Aborted!")
|
||||
|
||||
remove_keys(keyring)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
legacy_keyring()
|
Loading…
Reference in New Issue
Block a user