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