Compare commits

..

No commits in common. "9140cedad05c27a496ace56d72c8bfc0b79f098b" and "0b1a98fb73035c1600cccdfb714bebc05873ecc8" have entirely different histories.

10 changed files with 26 additions and 111 deletions

5
.gitignore vendored
View file

@ -1,4 +1,3 @@
*.pyc
*.egg-info/
build/
dist/
/venv/

View file

@ -1,5 +1,6 @@
TODO
----------------------------------------
more UT
setup.py
make client cert gen configurable

View file

@ -43,5 +43,4 @@ def main():
save_cert_stash(cert_stash, cert_stash_path)
if __name__ == '__main__':
main()

View file

@ -301,7 +301,7 @@ def _handle_cert_required(
The result of `open_gemini_url` with the client certificate provided.
"""
identities = load_identities(get_identities_list_path())
if identities is None:
if not identities:
browser.set_status_error("Can't load identities.")
return None
browser.identities = identities
@ -352,9 +352,8 @@ def create_identity(browser: Browser, url: str):
return None
browser.set_status("Generating certificate…")
gen_command = browser.config["generate_client_cert_command"]
try:
mangled_name = create_certificate(url, common_name, gen_command)
mangled_name = create_certificate(url, common_name)
except ClientCertificateException as exc:
browser.set_status_error(exc.message)
return None

View file

@ -2,7 +2,7 @@
import json
import logging
from pathlib import Path
import os.path
DEFAULT_CONFIG = {
@ -16,25 +16,13 @@ DEFAULT_CONFIG = {
"external_command_default": ["xdg-open"],
"home": "bebop:welcome",
"render_mode": "fancy",
"generate_client_cert_command": [
"openssl", "req",
"-newkey", "rsa:4096",
"-nodes",
"-keyform", "PEM",
"-keyout", "{key_path}",
"-x509",
"-days", "28140", # https://www.youtube.com/watch?v=F9L4q-0Pi4E
"-outform", "PEM",
"-out", "{cert_path}",
"-subj", "/CN={common_name}",
],
}
RENDER_MODES = ("fancy", "dumb")
def load_config(config_path: Path):
if not config_path.is_file():
def load_config(config_path):
if not os.path.isfile(config_path):
create_default_config(config_path)
return DEFAULT_CONFIG
@ -42,11 +30,9 @@ def load_config(config_path: Path):
with open(config_path, "rt") as config_file:
config = json.load(config_file)
except OSError as exc:
abs_path = config_path.absolute()
logging.error(f"Could not read config file {abs_path}: {exc}")
logging.error(f"Could not read config file {config_path}: {exc}")
except ValueError as exc:
abs_path = config_path.absolute()
logging.error(f"Could not parse config file {abs_path}: {exc}")
logging.error(f"Could not parse config file {config_path}: {exc}")
else:
# Fill missing values with defaults.
for key, value in DEFAULT_CONFIG.items():
@ -56,14 +42,7 @@ def load_config(config_path: Path):
return DEFAULT_CONFIG
def create_default_config(config_path: Path):
config_dir = config_path.parent
if not config_dir.is_dir():
try:
config_dir.mkdir(parents=True)
except OSError as exc:
logging.error(f"Could not create config dir {config_dir}: {exc}")
return
def create_default_config(config_path):
try:
with open(config_path, "wt") as config_file:
json.dump(DEFAULT_CONFIG, config_file, indent=2)

View file

@ -68,7 +68,6 @@ Here are the available options:
* external_command_default (see note 1): default command to open files.
* home (string): home page.
* render_mode (string): default render mode to use ("fancy" or "dumb").
* generate_client_cert_command (see note 3): command to generate a client cert.
Notes:
@ -76,8 +75,6 @@ Notes:
2: the external_commands dict maps MIME types to commands just as above. For example, if you want to open video files with VLC and audio files in Clementine, you can use the following dict: `{"audio": ["clementine"], "video": ["vlc"]}`. For now only "main" MIME types are supported, i.e. you cannot specify precise types like "audio/flac", just "audio".
3: the generate_client_cert_command uses the same format as other commands (specified in note 1 above), with the exception that if the strings "{cert_path}", "{key_path}" or "{common_name}" are present in any string for the list, they will be replaced respectively by the certificate output path, the key output path and the CN to use.
Your current configuration is:
"""

View file

@ -83,23 +83,24 @@ def get_cert_and_key(cert_id: str):
return directory / f"{cert_id}.crt", directory / f"{cert_id}.key"
def create_certificate(url: str, common_name: str, gen_command: list):
def create_certificate(url: str, common_name: str):
"""Create a secure self-signed certificate using system's OpenSSL."""
identities_path = get_identities_path()
mangled_name = get_mangled_name(url, common_name)
cert_path = identities_path / f"{mangled_name}.crt"
key_path = identities_path / f"{mangled_name}.key"
command = []
for part in gen_command:
if "{key_path}" in part:
part = part.format(key_path=str(key_path))
if "{cert_path}" in part:
part = part.format(cert_path=str(cert_path))
if "{common_name}" in part:
part = part.format(common_name=common_name)
command.append(part)
command = [
"openssl", "req",
"-newkey", "rsa:4096",
"-nodes",
"-keyform", "PEM",
"-keyout", str(key_path),
"-x509",
"-days", "28140", # https://www.youtube.com/watch?v=F9L4q-0Pi4E
"-outform", "PEM",
"-out", str(cert_path),
"-subj", f"/CN={common_name}",
]
try:
subprocess.check_call(
command,

View file

@ -1,6 +1,5 @@
"""Gemini protocol implementation."""
import logging
import re
import socket
import ssl
@ -158,15 +157,7 @@ class Request:
# Setup TLS.
context = Request.get_ssl_context()
if self.identity:
try:
context.load_cert_chain(*self.identity)
except FileNotFoundError as exc:
sock.close()
self.state = Request.STATE_CONNECTION_FAILED
self.error = "Could not load identity files."
logging.error(f"Failed to load identity files {self.identity}")
return False
try:
self.ssock = context.wrap_socket(sock, server_hostname=hostname)
except OSError as exc:

View file

@ -1,29 +0,0 @@
import unittest
from ..mime import MimeType, DEFAULT_CHARSET, DEFAULT_MIME_TYPE
class TestMime(unittest.TestCase):
def test_from_str(self):
self.assertIsNone(MimeType.from_str(""))
self.assertIsNone(MimeType.from_str("dumb"))
self.assertIsNone(MimeType.from_str("dumb;dumber"))
self.assertIsNone(MimeType.from_str("123456"))
mime = MimeType.from_str("a/b")
self.assertEqual(mime.main_type, "a")
self.assertEqual(mime.sub_type, "b")
self.assertEqual(mime.parameters, {})
mime = MimeType.from_str("text/gemini")
self.assertEqual(mime.main_type, "text")
self.assertEqual(mime.sub_type, "gemini")
self.assertEqual(mime.parameters, {})
mime = MimeType.from_str("text/gemini;lang=en")
self.assertEqual(mime.main_type, "text")
self.assertEqual(mime.sub_type, "gemini")
self.assertEqual(mime.parameters, {"lang": "en"})
mime = MimeType.from_str("text/gemini ;lang=en")
self.assertEqual(mime.parameters, {"lang": "en"})

View file

@ -1,22 +0,0 @@
[metadata]
name = bebop-browser
version = 0.0.1
description = Terminal browser for Gemini
long_description = file: README.md
license = GPLv3
author = dece
author-email = shgck@pistache.land
home-page = https://git.dece.space/Dece/Bebop
classifiers =
Environment :: Console
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
[options]
packages = bebop, bebop.browser
python_requires = >= 3.7
setup_requires = setuptools >= 38.3.0
[options.entry_points]
console_scripts =
bebop = bebop.__main__:main