keys.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. """Store and retrieve wheel signing / verifying keys.
  2. Given a scope (a package name, + meaning "all packages", or - meaning
  3. "no packages"), return a list of verifying keys that are trusted for that
  4. scope.
  5. Given a package name, return a list of (scope, key) suggested keys to sign
  6. that package (only the verifying keys; the private signing key is stored
  7. elsewhere).
  8. Keys here are represented as urlsafe_b64encoded strings with no padding.
  9. Tentative command line interface:
  10. # list trusts
  11. wheel trust
  12. # trust a particular key for all
  13. wheel trust + key
  14. # trust key for beaglevote
  15. wheel trust beaglevote key
  16. # stop trusting a key for all
  17. wheel untrust + key
  18. # generate a key pair
  19. wheel keygen
  20. # import a signing key from a file
  21. wheel import keyfile
  22. # export a signing key
  23. wheel export key
  24. """
  25. import json
  26. import os.path
  27. from wheel.util import native, load_config_paths, save_config_path
  28. class WheelKeys(object):
  29. SCHEMA = 1
  30. CONFIG_NAME = 'wheel.json'
  31. def __init__(self):
  32. self.data = {'signers':[], 'verifiers':[]}
  33. def load(self):
  34. # XXX JSON is not a great database
  35. for path in load_config_paths('wheel'):
  36. conf = os.path.join(native(path), self.CONFIG_NAME)
  37. if os.path.exists(conf):
  38. with open(conf, 'r') as infile:
  39. self.data = json.load(infile)
  40. for x in ('signers', 'verifiers'):
  41. if not x in self.data:
  42. self.data[x] = []
  43. if 'schema' not in self.data:
  44. self.data['schema'] = self.SCHEMA
  45. elif self.data['schema'] != self.SCHEMA:
  46. raise ValueError(
  47. "Bad wheel.json version {0}, expected {1}".format(
  48. self.data['schema'], self.SCHEMA))
  49. break
  50. return self
  51. def save(self):
  52. # Try not to call this a very long time after load()
  53. path = save_config_path('wheel')
  54. conf = os.path.join(native(path), self.CONFIG_NAME)
  55. with open(conf, 'w+') as out:
  56. json.dump(self.data, out, indent=2)
  57. return self
  58. def trust(self, scope, vk):
  59. """Start trusting a particular key for given scope."""
  60. self.data['verifiers'].append({'scope':scope, 'vk':vk})
  61. return self
  62. def untrust(self, scope, vk):
  63. """Stop trusting a particular key for given scope."""
  64. self.data['verifiers'].remove({'scope':scope, 'vk':vk})
  65. return self
  66. def trusted(self, scope=None):
  67. """Return list of [(scope, trusted key), ...] for given scope."""
  68. trust = [(x['scope'], x['vk']) for x in self.data['verifiers'] if x['scope'] in (scope, '+')]
  69. trust.sort(key=lambda x: x[0])
  70. trust.reverse()
  71. return trust
  72. def signers(self, scope):
  73. """Return list of signing key(s)."""
  74. sign = [(x['scope'], x['vk']) for x in self.data['signers'] if x['scope'] in (scope, '+')]
  75. sign.sort(key=lambda x: x[0])
  76. sign.reverse()
  77. return sign
  78. def add_signer(self, scope, vk):
  79. """Remember verifying key vk as being valid for signing in scope."""
  80. self.data['signers'].append({'scope':scope, 'vk':vk})