tor_client_key.py
1 from base64 import b32encode 2 from pathlib import Path 3 from subprocess import run, PIPE 4 5 6 class PKey: 7 key_type = "x25519" 8 genpkey_args = ("-algorithm", key_type) 9 10 def __init__(self, path=None): 11 if path is None: 12 proc = run( 13 ["openssl", "genpkey", *self.genpkey_args, "-outform", "PEM"], 14 check=True, 15 stdout=PIPE, 16 ) 17 self._private = proc.stdout 18 else: 19 self._private = Path(path).read_bytes() 20 21 def write_private_key(self, path): 22 Path(path).write_bytes(self._private) 23 24 def get_private_key(self, form="PEM") -> bytes: 25 match form: 26 case "DER": 27 proc = run( 28 ["openssl", "pkey", "-outform", form], 29 check=True, 30 input=self._private, 31 stdout=PIPE, 32 ) 33 return proc.stdout 34 case "PEM": 35 return self._private 36 case _: 37 raise ValueError(form) 38 39 def get_public_key(self, form="PEM") -> bytes: 40 match form: 41 case "DER": 42 proc = run( 43 ["openssl", "pkey", "-pubout", "-outform", form], 44 check=True, 45 input=self._private, 46 stdout=PIPE, 47 ) 48 return proc.stdout 49 case "PEM": 50 proc = run( 51 ["openssl", "pkey", "-pubout"], 52 check=True, 53 input=self._private, 54 stdout=PIPE, 55 ) 56 return proc.stdout 57 case _: 58 raise ValueError(form) 59 60 def __str__(self): 61 return b32encode(self.get_public_key("DER")).decode() 62 63 64 def make_server_auth_entry(key: PKey): 65 pubkey = b32encode(key.get_public_key("DER")).decode() 66 return ":".join( 67 ( 68 "descriptor", 69 "x25519", 70 pubkey.rstrip("="), 71 ) 72 ) 73 74 75 def make_client_auth_entry(site, key: PKey | Path | None): 76 if not isinstance(key, PKey): 77 path = key 78 key = PKey(path=path) 79 pubkey, _, _ = site.partition(".") 80 return ":".join( 81 ( 82 pubkey, 83 "descriptor", 84 key.key_type, 85 b32encode(key.get_private_key("DER")).decode().rstrip("="), 86 ) 87 ) 88 89 90 if __name__ == "__main__": 91 import sys 92 93 match sys.argv[1]: 94 case "add-key": 95 (site,) = sys.argv[2:] 96 name = site.replace(".", "_") 97 path = Path("/var/lib/tor/onion_auth") / f"{name}.auth_private" 98 assert not path.exists() 99 key = PKey() 100 print("Creating", path) 101 path.write_text(make_client_auth_entry(site, key)) 102 print("\nGreetings,\n") 103 print(f"Please add this key to {site},\n") 104 print(make_server_auth_entry(key))