xadessigner.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. from lxml import etree
  2. from datetime import datetime
  3. from OpenSSL import crypto
  4. from hashlib import sha1
  5. from base64 import b64encode
  6. class XAdESSigner:
  7. """
  8. XAdES-BES Enveloped signing of XML files
  9. """
  10. XMLNS_DS = 'http://www.w3.org/2000/09/xmldsig#'
  11. XMLNS_XDS = 'http://uri.etsi.org/01903/v1.1.1#'
  12. SIGNATURE_ALG = 'sha1'
  13. DIGEST_ALG_SPEC = XMLNS_DS + SIGNATURE_ALG
  14. SIGNATURE_ALG_SPEC = XMLNS_DS + 'rsa-' + SIGNATURE_ALG
  15. CANONICAL_ALG_SPEC = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
  16. SIGNEDPROPS_REFERENCE_TYPE = XMLNS_XDS + 'SignedProperties'
  17. def __init__(self, xml_root, xml_reference_type, certificate_file):
  18. """
  19. Initialize object with XML root element, XML reference type URL and certificate file path
  20. :param xml_root: lxml.etree.Element
  21. :param xml_reference_type: str
  22. :param certificate_file: str
  23. """
  24. self.xml_root = xml_root
  25. self.xml_reference_type = xml_reference_type
  26. self.certificate_file = certificate_file
  27. def sign(self, certificate_password):
  28. """
  29. Sign instance's XML and return it
  30. :param certificate_password: str
  31. :return: lxml.etree.Element
  32. """
  33. main_id = self.__prepare_xml()
  34. try:
  35. with open(self.certificate_file, 'rb') as cf:
  36. p12 = crypto.load_pkcs12(cf.read(), certificate_password)
  37. self.__create_signature(main_id, p12.get_privatekey(), p12.get_certificate())
  38. return self.xml_root
  39. except IOError:
  40. raise XaDESSignerException('Problem with accessing certificate file.')
  41. except crypto.Error:
  42. raise XaDESSignerException('Certificate password is wrong.')
  43. def __prepare_xml(self, main_id='data'):
  44. """
  45. Check if XML already contains signature and add appropriate namespaces to root
  46. """
  47. elements_num = len(self.xml_root)
  48. if (elements_num > 1 and
  49. self.xml_root[elements_num - 1].prefix and 'Signature' in self.xml_root[elements_num - 1].tag):
  50. raise XaDESSignerException('XML was already signed.')
  51. nsmap = self.xml_root.nsmap.copy()
  52. nsmap.update(self.__get_namespaces())
  53. xml_root = etree.Element(self.xml_root.tag, nsmap=nsmap)
  54. xml_root[:] = self.xml_root[:]
  55. self.xml_root = xml_root
  56. main_element = self.xml_root[0]
  57. if 'Id' in main_element.keys() and len(main_element.attrib['Id']) > 0:
  58. return main_element.attrib['Id']
  59. main_element.set('Id', main_id)
  60. return main_id
  61. def __create_signature(self, main_id, private_key, certificate):
  62. """
  63. Create signature of all main XML parts along signed properties
  64. :param main_id: str
  65. :param certificate: OpenSSL.crypto.X509
  66. :return: lxml.etree.Element
  67. """
  68. signature_id = 'SignatureId'
  69. ds_object, signedprops_id = self.__create_signature_object(signature_id, certificate)
  70. ds_signature = etree.Element(self.__get_namespaced_tag('ds', 'Signature'))
  71. ds_signature.set('Id', signature_id)
  72. ds_signedinfo = etree.SubElement(ds_signature, self.__get_namespaced_tag('ds', 'SignedInfo'))
  73. ds_canonicalmethod = etree.SubElement(ds_signedinfo, self.__get_namespaced_tag('ds', 'CanonicalizationMethod'))
  74. ds_canonicalmethod.set('Algorithm', self.CANONICAL_ALG_SPEC)
  75. ds_signaturemethod = etree.SubElement(ds_signedinfo, self.__get_namespaced_tag('ds', 'SignatureMethod'))
  76. ds_signaturemethod.set('Algorithm', self.SIGNATURE_ALG_SPEC)
  77. ds_reference_main = etree.SubElement(ds_signedinfo, self.__get_namespaced_tag('ds', 'Reference'))
  78. ds_reference_main.set('Type', self.xml_reference_type)
  79. ds_reference_main.set('URI', '#' + main_id.strip('#'))
  80. ds_refmain_digestmethod = etree.SubElement(ds_reference_main, self.__get_namespaced_tag('ds', 'DigestMethod'))
  81. ds_refmain_digestmethod.set('Algorithm', self.DIGEST_ALG_SPEC)
  82. ds_refmain_digestvalue = etree.SubElement(ds_reference_main, self.__get_namespaced_tag('ds', 'DigestValue'))
  83. ds_reference_signedprops = etree.SubElement(ds_signedinfo, self.__get_namespaced_tag('ds', 'Reference'))
  84. ds_reference_signedprops.set('Type', self.SIGNEDPROPS_REFERENCE_TYPE)
  85. ds_reference_signedprops.set('URI', '#' + signedprops_id.strip('#'))
  86. ds_refsp_digestmethod = etree.SubElement(
  87. ds_reference_signedprops,
  88. self.__get_namespaced_tag('ds', 'DigestMethod')
  89. )
  90. ds_refsp_digestmethod.set('Algorithm', self.DIGEST_ALG_SPEC)
  91. ds_refsp_digestvalue = etree.SubElement(ds_reference_signedprops, self.__get_namespaced_tag('ds', 'DigestValue'))
  92. ds_signaturevalue = etree.SubElement(ds_signature, self.__get_namespaced_tag('ds', 'SignatureValue'))
  93. ds_keyinfo = etree.SubElement(ds_signature, self.__get_namespaced_tag('ds', 'KeyInfo'))
  94. ds_X509data = etree.SubElement(ds_keyinfo, self.__get_namespaced_tag('ds', 'X509Data'))
  95. etree.SubElement(
  96. ds_X509data,
  97. self.__get_namespaced_tag('ds', 'X509Certificate')
  98. ).text = self.__get_certificate_der(certificate)
  99. # Append signature object to signature and signature to root to inherit all used namespaces
  100. ds_signature.append(ds_object)
  101. self.xml_root.append(ds_signature)
  102. # We generate digest values after XML is fully composed
  103. # First we generate digest value for main elements in XML
  104. ds_refmain_digestvalue.text = self.__generate_digest_value(self.xml_root[:-1])
  105. # Then we generate digest for SignedProperties contained inside signature object
  106. ds_refsp_digestvalue.text = self.__generate_digest_value(ds_signature[-1][0][:1])
  107. # Sign XML with private key after we have completed SignedInfo with digests
  108. ds_signaturevalue.text = self.__get_signed_xml(private_key, ds_signature[:1])
  109. return ds_signature
  110. def __get_signed_xml(self, private_key, nodes):
  111. """
  112. Sign canonicalized XML with provided private key, generated from applied nodes
  113. :param private_key: OpenSSL.crypto.PKey
  114. :param nodes: list
  115. :return: str
  116. """
  117. return b64encode(crypto.sign(private_key, self.__get_canonical_xml(nodes), self.SIGNATURE_ALG)).decode()
  118. def __get_certificate_der(self, certificate):
  119. """
  120. Return certificate in DER format encoded in base64
  121. :param certificate: OpenSSL.crypto.X509
  122. :return: str
  123. """
  124. return b64encode(crypto.dump_certificate(crypto.FILETYPE_ASN1, certificate)).decode()
  125. def __get_canonical_xml(self, nodes):
  126. """
  127. Generate canonical XML from applied nodes and replace all unknown namespaces
  128. :param nodes: list
  129. :return: bytearray
  130. """
  131. canonical_xml = b''
  132. for node in nodes:
  133. canonical_xml += etree.tostring(node, method='c14n', with_comments=False)
  134. return canonical_xml.replace(b' xmlns=""', b'')
  135. def __generate_digest_value(self, nodes):
  136. """
  137. Generate digest value for generated canonical XML from applied nodes
  138. :param nodes: list
  139. :return: str
  140. """
  141. return XAdESSigner.base64_sha1_str(self.__get_canonical_xml(nodes))
  142. def __create_signature_object(self, signature_id, certificate):
  143. """
  144. Create signature object which must also be signed as part of XadES signature
  145. :param signature_id: str
  146. :param certificate: OpenSSL.crypto.X509
  147. :return: tuple
  148. """
  149. signedprops_id = 'SignedPropertiesId'
  150. ds_object = etree.Element(self.__get_namespaced_tag('ds', 'Object'))
  151. xds_qualifyingprops = etree.SubElement(ds_object, self.__get_namespaced_tag('xds', 'QualifyingProperties'))
  152. xds_qualifyingprops.set('Target', '#' + signature_id.strip('#'))
  153. xds_signedprops = etree.SubElement(xds_qualifyingprops, self.__get_namespaced_tag('xds', 'SignedProperties'))
  154. xds_signedprops.set('Id', signedprops_id)
  155. xds_signedsigprops = etree.SubElement(
  156. xds_signedprops,
  157. self.__get_namespaced_tag('xds', 'SignedSignatureProperties')
  158. )
  159. etree.SubElement(
  160. xds_signedsigprops,
  161. self.__get_namespaced_tag('xds', 'SigningTime')
  162. ).text = datetime.utcnow().isoformat()[:-3] + 'Z'
  163. xds_signingcert = etree.SubElement(xds_signedsigprops, self.__get_namespaced_tag('xds', 'SigningCertificate'))
  164. xds_cert = etree.SubElement(xds_signingcert, self.__get_namespaced_tag('xds', 'Cert'))
  165. xds_certdigest = etree.SubElement(xds_cert, self.__get_namespaced_tag('xds', 'CertDigest'))
  166. xds_digestmethod = etree.SubElement(xds_certdigest, self.__get_namespaced_tag('xds', 'DigestMethod'))
  167. xds_digestmethod.set('Algorithm', self.DIGEST_ALG_SPEC)
  168. etree.SubElement(
  169. xds_certdigest,
  170. self.__get_namespaced_tag('xds', 'DigestValue')
  171. ).text = self.__get_certificate_digest(certificate)
  172. xds_issuerserial = etree.SubElement(xds_cert, self.__get_namespaced_tag('xds', 'IssuerSerial'))
  173. etree.SubElement(
  174. xds_issuerserial,
  175. self.__get_namespaced_tag('ds', 'X509IssuerName')
  176. ).text = self.__get_certificate_issuer(certificate)
  177. etree.SubElement(
  178. xds_issuerserial,
  179. self.__get_namespaced_tag('ds', 'X509SerialNumber')
  180. ).text = '%d' % certificate.get_serial_number()
  181. xds_signaturepolicyid = etree.SubElement(
  182. xds_signedsigprops,
  183. self.__get_namespaced_tag('xds', 'SignaturePolicyIdentifier')
  184. )
  185. etree.SubElement(xds_signaturepolicyid, self.__get_namespaced_tag('xds', 'SignaturePolicyImplied'))
  186. return ds_object, signedprops_id
  187. def __get_namespaced_tag(self, namespace, tag):
  188. """
  189. Generate tag with prefixed namespace for lxml element name
  190. :param namespace: str
  191. :param tag: str
  192. :return: str
  193. """
  194. namespaces = self.__get_namespaces()
  195. if namespace not in namespaces:
  196. raise XaDESSignerValueError('Namespace unknown.')
  197. return '{%s}%s' % (namespaces[namespace], tag)
  198. def __get_namespaces(self):
  199. """
  200. Wrapper method which returns available namespaces in dictionary form
  201. :return: dict
  202. """
  203. return {
  204. 'ds': self.XMLNS_DS,
  205. 'xds': self.XMLNS_XDS
  206. }
  207. def __get_certificate_digest(self, certificate):
  208. """
  209. Get certificate digest
  210. :param certificate: OpenSSL.crypto.X509
  211. :return: str
  212. """
  213. return XAdESSigner.base64_sha1_str(crypto.dump_certificate(crypto.FILETYPE_ASN1, certificate))
  214. def __get_certificate_issuer(self, certificate):
  215. """
  216. Get certificate issuer - consists of everything known about issuer, separated with commas
  217. :param certificate: OpenSSL.crypto.X509
  218. :return: str
  219. """
  220. return ','.join(m.decode() + '=' + v.decode() for m, v in certificate.get_issuer().get_components())
  221. @staticmethod
  222. def base64_sha1_str(string):
  223. """
  224. Hash string or bytearray to SHA1 and encode it with base64
  225. :param string: str or byte
  226. :return: str
  227. """
  228. try:
  229. string = string.encode()
  230. except AttributeError:
  231. pass
  232. return b64encode(sha1(string).digest()).decode()
  233. class XaDESSignerException(Exception):
  234. pass
  235. class XaDESSignerValueError(Exception):
  236. pass