pkcs7.py 5.2 KB

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