pkcs7.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. import typing
  5. from enum import Enum
  6. from cryptography import x509
  7. from cryptography.hazmat.backends import _get_backend
  8. from cryptography.hazmat.primitives import hashes, serialization
  9. from cryptography.hazmat.primitives.asymmetric import ec, rsa
  10. from cryptography.utils import _check_byteslike
  11. def load_pem_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]:
  12. backend = _get_backend(None)
  13. return backend.load_pem_pkcs7_certificates(data)
  14. def load_der_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]:
  15. backend = _get_backend(None)
  16. return backend.load_der_pkcs7_certificates(data)
  17. _ALLOWED_PKCS7_HASH_TYPES = typing.Union[
  18. hashes.SHA1,
  19. hashes.SHA224,
  20. hashes.SHA256,
  21. hashes.SHA384,
  22. hashes.SHA512,
  23. ]
  24. _ALLOWED_PRIVATE_KEY_TYPES = typing.Union[
  25. rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey
  26. ]
  27. class PKCS7Options(Enum):
  28. Text = "Add text/plain MIME type"
  29. Binary = "Don't translate input data into canonical MIME format"
  30. DetachedSignature = "Don't embed data in the PKCS7 structure"
  31. NoCapabilities = "Don't embed SMIME capabilities"
  32. NoAttributes = "Don't embed authenticatedAttributes"
  33. NoCerts = "Don't embed signer certificate"
  34. class PKCS7SignatureBuilder(object):
  35. def __init__(self, data=None, signers=[], additional_certs=[]):
  36. self._data = data
  37. self._signers = signers
  38. self._additional_certs = additional_certs
  39. def set_data(self, data: bytes) -> "PKCS7SignatureBuilder":
  40. _check_byteslike("data", data)
  41. if self._data is not None:
  42. raise ValueError("data may only be set once")
  43. return PKCS7SignatureBuilder(data, self._signers)
  44. def add_signer(
  45. self,
  46. certificate: x509.Certificate,
  47. private_key: _ALLOWED_PRIVATE_KEY_TYPES,
  48. hash_algorithm: _ALLOWED_PKCS7_HASH_TYPES,
  49. ) -> "PKCS7SignatureBuilder":
  50. if not isinstance(
  51. hash_algorithm,
  52. (
  53. hashes.SHA1,
  54. hashes.SHA224,
  55. hashes.SHA256,
  56. hashes.SHA384,
  57. hashes.SHA512,
  58. ),
  59. ):
  60. raise TypeError(
  61. "hash_algorithm must be one of hashes.SHA1, SHA224, "
  62. "SHA256, SHA384, or SHA512"
  63. )
  64. if not isinstance(certificate, x509.Certificate):
  65. raise TypeError("certificate must be a x509.Certificate")
  66. if not isinstance(
  67. private_key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey)
  68. ):
  69. raise TypeError("Only RSA & EC keys are supported at this time.")
  70. return PKCS7SignatureBuilder(
  71. self._data,
  72. self._signers + [(certificate, private_key, hash_algorithm)],
  73. )
  74. def add_certificate(
  75. self, certificate: x509.Certificate
  76. ) -> "PKCS7SignatureBuilder":
  77. if not isinstance(certificate, x509.Certificate):
  78. raise TypeError("certificate must be a x509.Certificate")
  79. return PKCS7SignatureBuilder(
  80. self._data, self._signers, self._additional_certs + [certificate]
  81. )
  82. def sign(
  83. self,
  84. encoding: serialization.Encoding,
  85. options: typing.Iterable[PKCS7Options],
  86. backend=None,
  87. ) -> bytes:
  88. if len(self._signers) == 0:
  89. raise ValueError("Must have at least one signer")
  90. if self._data is None:
  91. raise ValueError("You must add data to sign")
  92. options = list(options)
  93. if not all(isinstance(x, PKCS7Options) for x in options):
  94. raise ValueError("options must be from the PKCS7Options enum")
  95. if encoding not in (
  96. serialization.Encoding.PEM,
  97. serialization.Encoding.DER,
  98. serialization.Encoding.SMIME,
  99. ):
  100. raise ValueError(
  101. "Must be PEM, DER, or SMIME from the Encoding enum"
  102. )
  103. # Text is a meaningless option unless it is accompanied by
  104. # DetachedSignature
  105. if (
  106. PKCS7Options.Text in options
  107. and PKCS7Options.DetachedSignature not in options
  108. ):
  109. raise ValueError(
  110. "When passing the Text option you must also pass "
  111. "DetachedSignature"
  112. )
  113. if PKCS7Options.Text in options and encoding in (
  114. serialization.Encoding.DER,
  115. serialization.Encoding.PEM,
  116. ):
  117. raise ValueError(
  118. "The Text option is only available for SMIME serialization"
  119. )
  120. # No attributes implies no capabilities so we'll error if you try to
  121. # pass both.
  122. if (
  123. PKCS7Options.NoAttributes in options
  124. and PKCS7Options.NoCapabilities in options
  125. ):
  126. raise ValueError(
  127. "NoAttributes is a superset of NoCapabilities. Do not pass "
  128. "both values."
  129. )
  130. backend = _get_backend(backend)
  131. return backend.pkcs7_sign(self, encoding, options)