/ etc / tor_client_key.py
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))