Easy CA management
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
6.1 KiB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import os.path
  5. import sqlite3
  6. import subprocess
  7. from paths import *
  8. class SignRequest(object):
  9. def __init__(self, req_id):
  10. self.req_id = req_id
  11. def get_name(self):
  12. raise NotImplementedError()
  13. def get_fields(self):
  14. raise NotImplementedError()
  15. class UserSSHRequest(SignRequest, object):
  16. def __init__(self, req_id, user_name, root_requested, key_data):
  17. super(UserSSHRequest, self).__init__(req_id)
  18. self.user_name = user_name
  19. self.root_requested = root_requested
  20. self.key_data = key_data
  21. def get_name(self):
  22. return "User: %s [R:%d]" % (self.user_name, int(self.root_requested))
  23. def get_fields(self):
  24. return [
  25. ("User name", self.user_name),
  26. ("Root access requested", 'yes' if self.root_requested else 'no')
  27. ]
  28. def __str__(self):
  29. return ("%s %s" % (self.req_id, self.user_name))
  30. class HostSSLRequest(SignRequest, object):
  31. def __init__(self, req_id, host_name, key_data):
  32. super(HostSSLRequest, self).__init__(req_id)
  33. self.host_name = host_name
  34. self.key_data = key_data
  35. def get_name(self):
  36. return "Hostname: %s" % self.host_name
  37. def get_fields(self):
  38. return [
  39. ("Hostname", self.host_name)
  40. ]
  41. def __str__(self):
  42. return ("%s %s" % (self.req_id, self.host_name))
  43. class HostSSHRequest(SignRequest, object):
  44. def __init__(self, req_id, host_name, key_data):
  45. super(HostSSHRequest, self).__init__(req_id)
  46. self.host_name = host_name
  47. self.key_data = key_data
  48. def get_name(self):
  49. return "Hostname: %s" % self.host_name
  50. def get_fields(self):
  51. return [
  52. ("Hostname", self.host_name)
  53. ]
  54. def __str__(self):
  55. return ("%s %s" % (self.req_id, self.host_name))
  56. class Authority(object):
  57. ca_type = None
  58. def __init__(self, ca_id, name, path):
  59. self.ca_id = ca_id
  60. self.name = name
  61. self.path = path
  62. def generate(self):
  63. raise NotImplementedError()
  64. def sign(self, request):
  65. raise NotImplementedError()
  66. class SSHAuthority(Authority):
  67. ca_type = 'ssh'
  68. key_algorithm = 'ed25519'
  69. user_validity = '+52w'
  70. host_validity = '+52w'
  71. def generate(self):
  72. if os.path.exists(self.path):
  73. raise ValueError("A CA with the same id and type already exists")
  74. subprocess.check_output(['ssh-keygen',
  75. '-f', self.path,
  76. '-t', self.key_algorithm,
  77. '-C', self.name])
  78. with open(self.path + '.serial', 'w') as stream:
  79. stream.write(str(0))
  80. def sign(self, request):
  81. assert type(request) in [UserSSHRequest, HostSSHRequest]
  82. pub_key_path = os.path.join(OUTPUT_PATH, request.req_id + '.pub')
  83. cert_path = os.path.join(OUTPUT_PATH, request.req_id + '-cert.pub')
  84. with open(self.path + '.serial', 'r') as stream:
  85. next_serial = int(stream.read())
  86. with open(self.path + '.serial', 'w') as stream:
  87. stream.write(str(next_serial + 1))
  88. with open(pub_key_path, 'w') as stream:
  89. stream.write(request.key_data)
  90. ca_private_key = self.path
  91. if type(request) == UserSSHRequest:
  92. login_names = [request.user_name]
  93. if request.root_requested:
  94. login_names.append('root')
  95. subprocess.check_output(['ssh-keygen',
  96. '-s', ca_private_key,
  97. '-I', 'user_%s' % request.user_name,
  98. '-n', ','.join(login_names),
  99. '-V', self.user_validity,
  100. '-z', str(next_serial),
  101. pub_key_path])
  102. elif type(request) == HostSSHRequest:
  103. subprocess.check_output(['ssh-keygen',
  104. '-s', ca_private_key,
  105. '-I', 'host_%s' % request.host_name.replace('.', '_'),
  106. '-h',
  107. '-n', request.host_name,
  108. '-V', self.host_validity,
  109. '-z', str(next_serial),
  110. pub_key_path])
  111. return cert_path
  112. class SSLAuthority(Authority):
  113. ca_type = 'ssl'
  114. ca_key_algorithm = 'des3'
  115. key_length = '4096'
  116. key_algorithm = 'sha256'
  117. ca_validity = '365'
  118. cert_validity = '365'
  119. def generate(self):
  120. if os.path.exists(self.path):
  121. raise ValueError("A CA with the same id and type already exists")
  122. subprocess.check_output(['openssl',
  123. 'genrsa',
  124. '-%s'%self.ca_key_algorithm,
  125. '-out', '%s'%(self.path),
  126. self.key_length])
  127. subprocess.check_output(['openssl',
  128. 'req',
  129. '-new',
  130. '-x509',
  131. '-days', self.ca_validity,
  132. '-key', self.path,
  133. # '-extensions', 'v3_ca'
  134. '-out', "%s.pub"%self.path,
  135. # '-config', "%s.conf"%self.path
  136. ])
  137. with open(self.path + '.serial', 'w') as stream:
  138. stream.write(str(0))
  139. def sign(self, request):
  140. OUTPUT_PATH
  141. assert type(request) in [HostSSLRequest]
  142. pub_key_path = os.path.join(OUTPUT_PATH, request.req_id + '.pub')
  143. cert_path = os.path.join(OUTPUT_PATH, request.req_id + '-cert.pub')
  144. with open(self.path + '.serial', 'r') as stream:
  145. next_serial = int(stream.read())
  146. with open(self.path + '.serial', 'w') as stream:
  147. stream.write(str(next_serial + 1))
  148. with open(pub_key_path, 'w') as stream:
  149. stream.write(request.key_data)
  150. ca_private_key = self.path
  151. # openssl x509 -req -days 360 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt # print()
  152. subprocess.check_output(['openssl',
  153. 'x509',
  154. '-req',
  155. '-days', self.ca_validity,
  156. '-in', pub_key_path,
  157. '-CA', "%s.pub"%self.path,
  158. '-CAkey', self.path,
  159. '-CAcreateserial',
  160. '-out', cert_path,
  161. '-%s'%self.key_algorithm])
  162. return cert_path