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.

205 lines
6.0 KiB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import os.path
  5. import pickle
  6. import subprocess
  7. from paths import *
  8. from certificate import Certificate
  9. from request import UserSSHRequest, HostSSHRequest, HostSSLRequest
  10. __doc__= """
  11. Module of classes to handle certificate requests
  12. """
  13. class Authority(object):
  14. request_allowed = []
  15. def __init__(self, ca_id, ca_dir = MANAGER_PATH):
  16. self.ca_id = ca_id
  17. self.ca_dir = ca_dir
  18. def __bool__(self):
  19. return os.path.exists(self.path)
  20. @property
  21. def path(self):
  22. return os.path.join(self.ca_dir, self.ca_id)
  23. def generate(self):
  24. """
  25. Leave a dump of this class in the MANAGER_PATH
  26. """
  27. pickled_path = os.path.join(MANAGER_PATH, 'pickled_cas', self.ca_id)
  28. with open(pickled_path, 'wb') as out:
  29. pickle.dump(self, out)
  30. def sign(self, request):
  31. raise NotImplementedError()
  32. def __repr__(self):
  33. return ( "%s %s" % ( self.__class__.__name__, self.ca_id ) )
  34. class SSHAuthority(Authority):
  35. request_allowed = [ UserSSHRequest, HostSSHRequest, ]
  36. key_algorithm = 'ed25519'
  37. user_validity = '+52w'
  38. host_validity = '+52w'
  39. def __init__(self, ca_id):
  40. ssh_ca_dir = os.path.join(MANAGER_PATH, 'ssh_cas')
  41. super(SSHAuthority, self).__init__(ca_id, ssh_ca_dir)
  42. def __bool__(self):
  43. """
  44. For a SSH Authority we only need a private-public key couple,
  45. moreover we request to have the next serial number
  46. """
  47. keys_couple_exist = os.path.exists(self.path) and os.path.exists(self.path + '.pub')
  48. serial_exist = os.path.exists(self.path + '.serial')
  49. return keys_couple_exist and serial_exist
  50. def generate(self):
  51. """
  52. Generate a SSHAuthority if the files associated
  53. do not exists
  54. """
  55. # check if the public key exists
  56. if not self:
  57. # let ssh-keygen do its job
  58. subprocess.check_output(['ssh-keygen',
  59. '-f', self.path,
  60. '-t', self.key_algorithm,
  61. '-C', self.name])
  62. # write the serial file with a value of
  63. # 0 for first certificate
  64. with open(self.path + '.serial', 'w') as stream:
  65. stream.write(str(0))
  66. super(SSHAuthority, self).generate()
  67. else:
  68. raise ValueError('A CA with the same id and type already exists')
  69. def sign(self, request):
  70. assert type(request) in self.request_allowed
  71. pub_key_path = request.destination
  72. cert_path = Certificate(request.req_id).path
  73. with open(self.path + '.serial', 'r') as stream:
  74. next_serial = int(stream.read())
  75. with open(self.path + '.serial', 'w') as stream:
  76. stream.write(str(next_serial + 1))
  77. with open(request.destination, 'w') as stream:
  78. stream.write(request.key_data)
  79. ca_private_key = self.path
  80. if type(request) == UserSSHRequest:
  81. login_names = [ request.user_name, ]
  82. if request.root_requested:
  83. login_names.append('root')
  84. subprocess.check_output(['ssh-keygen',
  85. '-s', ca_private_key,
  86. '-I', 'user_%s' % request.user_name,
  87. '-n', ','.join(login_names),
  88. '-V', self.user_validity,
  89. '-z', str(next_serial),
  90. pub_key_path])
  91. elif type(request) == HostSSHRequest:
  92. subprocess.check_output(['ssh-keygen',
  93. '-s', ca_private_key,
  94. '-I', 'host_%s' % request.host_name.replace('.', '_'),
  95. '-h',
  96. '-n', request.host_name,
  97. '-V', self.host_validity,
  98. '-z', str(next_serial),
  99. pub_key_path])
  100. return cert_path
  101. class SSLAuthority(Authority):
  102. request_allowed = [ HostSSLRequest, ]
  103. ca_key_algorithm = 'des3'
  104. key_length = '4096'
  105. key_algorithm = 'sha256'
  106. ca_validity = '365'
  107. cert_validity = '365'
  108. def __init__(self, ca_id):
  109. ssl_ca_dir = os.path.join(MANAGER_PATH, 'ssl_ca')
  110. super(SSLAuthority, self).__init__(ca_id, ssl_ca_dir)
  111. def generate(self):
  112. if os.path.exists(self.path):
  113. raise ValueError("A CA with the same id and type already exists")
  114. subprocess.check_output(['openssl',
  115. 'genrsa',
  116. '-%s'%self.ca_key_algorithm,
  117. '-out', '%s'%(self.path),
  118. self.key_length])
  119. subprocess.check_output(['openssl',
  120. 'req',
  121. '-new',
  122. '-x509',
  123. '-days', self.ca_validity,
  124. '-key', self.path,
  125. # '-extensions', 'v3_ca'
  126. '-out', "%s.pub"%self.path,
  127. # '-config', "%s.conf"%self.path
  128. ])
  129. with open(self.path + '.serial', 'w') as stream:
  130. stream.write(str(0))
  131. def sign(self, request):
  132. OUTPUT_PATH
  133. assert type(request) in self.request_allowed
  134. pub_key_path = os.path.join(OUTPUT_PATH, request.req_id + '.pub')
  135. cert_path = os.path.join(OUTPUT_PATH, request.req_id + '-cert.pub')
  136. with open(self.path + '.serial', 'r') as stream:
  137. next_serial = int(stream.read())
  138. with open(self.path + '.serial', 'w') as stream:
  139. stream.write(str(next_serial + 1))
  140. with open(pub_key_path, 'w') as stream:
  141. stream.write(request.key_data)
  142. ca_private_key = self.path
  143. # openssl x509 -req -days 360 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt # print()
  144. subprocess.check_output(['openssl',
  145. 'x509',
  146. '-req',
  147. '-days', self.ca_validity,
  148. '-in', pub_key_path,
  149. '-CA', "%s.pub"%self.path,
  150. '-CAkey', self.path,
  151. '-CAcreateserial',
  152. '-out', cert_path,
  153. '-%s'%self.key_algorithm])
  154. return cert_path