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.

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