diff --git a/README.md b/README.md index 2f652c7..392e423 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ bash -c "$(curl -sSL https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/ ```powershell Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; $scriptContent = (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/install.ps1'); & ([scriptblock]::create($scriptContent)) ``` +The Windows installer will attempt to install Git automatically if it is not already available. *Install the beta branch:* ```powershell Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; $scriptContent = (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/install.ps1'); & ([scriptblock]::create($scriptContent)) -Branch beta diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 9c2c4a8..f2f3f1d 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -32,7 +32,19 @@ function Write-Error { # 1. Check for prerequisites Write-Info "Installing SeedPass from branch: '$Branch'" Write-Info "Checking for prerequisites..." -if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Write-Error "Git is not installed. Please install it from https://git-scm.com/ and ensure it's in your PATH." } +if (-not (Get-Command git -ErrorAction SilentlyContinue)) { + Write-Warning "Git is not installed. Attempting to install..." + if (Get-Command winget -ErrorAction SilentlyContinue) { + try { winget install --id Git.Git -e --source winget -h } catch { Write-Error "Failed to install Git via winget. Error: $_" } + } elseif (Get-Command choco -ErrorAction SilentlyContinue) { + try { choco install git -y } catch { Write-Error "Failed to install Git via Chocolatey. Error: $_" } + } elseif (Get-Command scoop -ErrorAction SilentlyContinue) { + try { scoop install git } catch { Write-Error "Failed to install Git via Scoop. Error: $_" } + } else { + Write-Error "Git is not installed. Please install it from https://git-scm.com/ and ensure it's in your PATH." + } + if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Write-Error "Git installation failed or git not found in PATH after installation." } +} $pythonExe = Get-Command python -ErrorAction SilentlyContinue if (-not $pythonExe) { Write-Error "Python 3 is not installed or not in your PATH. Please install it from https://www.python.org/" } diff --git a/scripts/install.sh b/scripts/install.sh index 48a72fd..5cea79c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -57,7 +57,21 @@ main() { # 2. Check for prerequisites print_info "Checking for prerequisites (git, python3, pip)..." - if ! command -v git &> /dev/null; then print_error "Git is not installed. Please install it."; fi + if ! command -v git &> /dev/null; then + print_warning "Git is not installed. Attempting to install..." + if [ "$OS_NAME" = "Linux" ]; then + if command -v apt-get &> /dev/null; then sudo apt-get update && sudo apt-get install -y git; + elif command -v dnf &> /dev/null; then sudo dnf install -y git; + elif command -v pacman &> /dev/null; then sudo pacman -Syu --noconfirm git; + else print_error "Git is not installed and automatic installation is not supported on this system."; fi + elif [ "$OS_NAME" = "Darwin" ]; then + if command -v brew &> /dev/null; then brew install git; + else print_error "Git is not installed and Homebrew was not found. Please install Git manually."; fi + else + print_error "Git is not installed. Please install it." + fi + if ! command -v git &> /dev/null; then print_error "Git installation failed or git not found in PATH."; fi + fi if ! command -v python3 &> /dev/null; then print_error "Python 3 is not installed. Please install it."; fi if ! python3 -m ensurepip --default-pip &> /dev/null && ! command -v pip3 &> /dev/null; then print_error "pip for Python 3 is not available. Please install it."; fi if ! python3 -c "import venv" &> /dev/null; then diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 3ca8fb5..65776e8 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -14,6 +14,7 @@ import json import logging import getpass import os +import hashlib from typing import Optional import shutil import time @@ -2267,7 +2268,8 @@ class PasswordManager: # Schema version and checksum status stats["schema_version"] = data.get("schema_version") - current_checksum = json_checksum(data) + json_content = json.dumps(data, indent=4) + current_checksum = hashlib.sha256(json_content.encode("utf-8")).hexdigest() chk_path = self.entry_manager.checksum_file if chk_path.exists(): stored = chk_path.read_text().strip() diff --git a/src/tests/test_nostr_entry.py b/src/tests/test_nostr_entry.py index 5e8102b..cfedc8f 100644 --- a/src/tests/test_nostr_entry.py +++ b/src/tests/test_nostr_entry.py @@ -4,6 +4,8 @@ from tempfile import TemporaryDirectory from helpers import create_vault, TEST_SEED, TEST_PASSWORD +from nostr.coincurve_keys import Keys + sys.path.append(str(Path(__file__).resolve().parents[1])) from password_manager.entry_management import EntryManager @@ -36,3 +38,11 @@ def test_nostr_key_determinism(): assert nsec1 == nsec2 assert npub1.startswith("npub") assert nsec1.startswith("nsec") + + priv_hex = Keys.bech32_to_hex(nsec1) + derived = Keys(priv_k=priv_hex) + encoded_npub = Keys.hex_to_bech32(derived.public_key_hex(), "npub") + assert encoded_npub == npub1 + + data = enc_mgr.load_json_data(entry_mgr.index_file) + assert data["entries"][str(idx)]["label"] == "main" diff --git a/src/tests/test_pgp_entry.py b/src/tests/test_pgp_entry.py index b68db84..ee33b60 100644 --- a/src/tests/test_pgp_entry.py +++ b/src/tests/test_pgp_entry.py @@ -25,3 +25,15 @@ def test_pgp_key_determinism(): assert fp1 == fp2 assert key1 == key2 + + # parse returned armored key and verify fingerprint + from pgpy import PGPKey + + parsed_key, _ = PGPKey.from_blob(key1) + assert parsed_key.fingerprint == fp1 + + # ensure the index file stores key_type and user_id + data = enc_mgr.load_json_data(entry_mgr.index_file) + entry = data["entries"][str(idx)] + assert entry["key_type"] == "ed25519" + assert entry["user_id"] == "Test" diff --git a/src/tests/test_seed_entry.py b/src/tests/test_seed_entry.py index 332240b..4d75c5b 100644 --- a/src/tests/test_seed_entry.py +++ b/src/tests/test_seed_entry.py @@ -3,6 +3,7 @@ from pathlib import Path from tempfile import TemporaryDirectory from helpers import create_vault, TEST_SEED, TEST_PASSWORD +from mnemonic import Mnemonic sys.path.append(str(Path(__file__).resolve().parents[1])) @@ -30,6 +31,9 @@ def test_seed_phrase_determinism(): phrase24_a = entry_mgr.get_seed_phrase(idx_24, TEST_SEED) phrase24_b = entry_mgr.get_seed_phrase(idx_24, TEST_SEED) + entry12 = entry_mgr.retrieve_entry(idx_12) + entry24 = entry_mgr.retrieve_entry(idx_24) + seed_bytes = Bip39SeedGenerator(TEST_SEED).Generate() bip85 = BIP85(seed_bytes) expected12 = derive_seed_phrase(bip85, idx_12, 12) @@ -39,3 +43,7 @@ def test_seed_phrase_determinism(): assert phrase24_a == phrase24_b == expected24 assert len(phrase12_a.split()) == 12 assert len(phrase24_a.split()) == 24 + assert Mnemonic("english").check(phrase12_a) + assert Mnemonic("english").check(phrase24_a) + assert entry12.get("words") == 12 + assert entry24.get("words") == 24 diff --git a/src/tests/test_ssh_entry_valid.py b/src/tests/test_ssh_entry_valid.py new file mode 100644 index 0000000..42372ee --- /dev/null +++ b/src/tests/test_ssh_entry_valid.py @@ -0,0 +1,40 @@ +import sys +from pathlib import Path +from tempfile import TemporaryDirectory + +from helpers import create_vault, TEST_SEED, TEST_PASSWORD + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +from password_manager.entry_management import EntryManager +from password_manager.backup import BackupManager +from password_manager.vault import Vault +from password_manager.config_manager import ConfigManager +from cryptography.hazmat.primitives import serialization + + +def test_ssh_private_key_corresponds_to_public(): + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) + cfg_mgr = ConfigManager(vault, tmp_path) + backup_mgr = BackupManager(tmp_path, cfg_mgr) + entry_mgr = EntryManager(vault, backup_mgr) + + idx = entry_mgr.add_ssh_key(TEST_SEED) + priv_pem, pub_pem = entry_mgr.get_ssh_key_pair(idx, TEST_SEED) + + priv_key = serialization.load_pem_private_key( + priv_pem.encode(), + password=None, + ) + derived_pub_pem = ( + priv_key.public_key() + .public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + .decode() + ) + + assert derived_pub_pem == pub_pem