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