]> www.wagner.pp.ru Git - oss/ctypescrypto.git/blob - ctypescrypto/cms.py
Added explicit check for CMS functions in libcrypto
[oss/ctypescrypto.git] / ctypescrypto / cms.py
1 """
2 Implements operations with CMS EnvelopedData and SignedData messages
3
4 Contains function CMS() which parses CMS message and creates either
5 EnvelopedData or SignedData objects (EncryptedData and CompressedData
6 can be easily added, because OpenSSL contain nessesary function)
7
8 Each of these objects contains create() static method which is used to
9 create it from raw data and neccessary certificates.
10
11
12 """
13 from ctypes import c_int, c_void_p, c_char_p, c_int, c_uint, c_size_t, POINTER
14 from ctypescrypto.exception import LibCryptoError
15 from ctypescrypto import libcrypto
16 from ctypescrypto.bio import Membio
17 from ctypescrypto.oid import Oid
18 from ctypescrypto.x509 import StackOfX509
19
20 # Check for neccesary functionality in libcrypto
21 # LibreSSL fails this check
22
23 if not hasattr(libcrypto,"CMS_decrypt"):
24     raise OSError("libcrypto lacks CMS functionality. Try using different libcrypto")
25
26 class CMSError(LibCryptoError):
27     """
28     Exception which is raised when error occurs
29     """
30     pass
31
32 class Flags:
33     """
34     Constants for flags passed to the CMS methods.
35     Can be OR-ed together
36     """
37     TEXT = 1
38     NOCERTS = 2
39     NO_CONTENT_VERIFY = 4
40     NO_ATTR_VERIFY = 8
41     NO_SIGS = NO_CONTENT_VERIFY | NO_ATTR_VERIFY
42     NOINTERN = 0x10
43     NO_SIGNER_CERT_VERIFY = 0x20
44     NO_VERIFY = 0x20
45     DETACHED = 0x40
46     BINARY = 0x80
47     NOATTR = 0x100
48     NOSMIMECAP = 0x200
49     NOOLDMIMETYPE = 0x400
50     CRLFEOL = 0x800
51     STREAM = 0x1000
52     NOCRL = 0x2000
53     PARTIAL = 0x4000
54     REUSE_DIGEST = 0x8000
55     USE_KEYID = 0x10000
56     DEBUG_DECRYPT = 0x20000
57
58 def CMS(data, format="PEM"):
59     """
60     Parses CMS data and returns either SignedData or EnvelopedData
61     object
62     """
63     bio = Membio(data)
64     if format == "PEM":
65         ptr = libcrypto.PEM_read_bio_CMS(bio.bio, None, None, None)
66     else:
67         ptr = libcrypto.d2i_CMS_bio(bio.bio, None)
68     if ptr is None:
69         raise CMSError("Error parsing CMS data")
70     typeoid = Oid(libcrypto.OBJ_obj2nid(libcrypto.CMS_get0_type(ptr)))
71     if typeoid.shortname() == "pkcs7-signedData":
72         return SignedData(ptr)
73     elif typeoid.shortname() == "pkcs7-envelopedData":
74         return EnvelopedData(ptr)
75     elif typeoid.shortname() == "pkcs7-encryptedData":
76         return EncryptedData(ptr)
77     else:
78         raise NotImplementedError("cannot handle "+typeoid.shortname())
79
80 class CMSBase(object):
81     """
82     Common ancessor for all CMS types.
83     Implements serializatio/deserialization
84     """
85     def __init__(self, ptr=None):
86         self.ptr = ptr
87     def __str__(self):
88         """
89         Serialize in DER format
90         """
91         bio = Membio()
92         if not libcrypto.i2d_CMS_bio(bio.bio, self.ptr):
93             raise CMSError("writing CMS to DER")
94         return str(bio)
95
96     def pem(self):
97         """
98         Serialize in PEM format
99         """
100         bio = Membio()
101         if not libcrypto.PEM_write_bio_CMS(bio.bio, self.ptr):
102             raise CMSError("writing CMS to PEM")
103         return str(bio)
104
105
106 #pylint: disable=R0921
107 class SignedData(CMSBase):
108     """
109     Represents signed message (signeddata CMS type)
110     """
111     @staticmethod
112     def create(data, cert, pkey, flags=Flags.BINARY, certs=None):
113         """
114             Creates SignedData message by signing data with pkey and
115             certificate.
116
117             @param data - data to sign
118             @param cert - signer's certificate
119             @param pkey - pkey object with private key to sign
120             @param flags - OReed combination of Flags constants
121             @param certs - list of X509 objects to include into CMS
122         """
123         if not pkey.cansign:
124             raise ValueError("Specified keypair has no private part")
125         if cert.pubkey != pkey:
126             raise ValueError("Certificate doesn't match public key")
127         bio = Membio(data)
128         if certs is not None and len(certs) > 0:
129             certstack = StackOfX509(certs).ptr
130         else:
131             certstack = None
132         ptr = libcrypto.CMS_sign(cert.cert, pkey.key, certstack, bio.bio, flags)
133         if ptr is None:
134             raise CMSError("signing message")
135         return SignedData(ptr)
136     def sign(self, cert, pkey, digest_type=None, data=None, flags=Flags.BINARY):
137         """
138             Adds another signer to already signed message
139             @param cert - signer's certificate
140             @param pkey - signer's private key
141             @param digest_type - message digest to use as DigestType object
142                 (if None - default for key would be used)
143             @param data - data to sign (if detached and
144                     Flags.REUSE_DIGEST is not specified)
145             @param flags - ORed combination of Flags consants
146         """
147         if not pkey.cansign:
148             raise ValueError("Specified keypair has no private part")
149         if cert.pubkey != pkey:
150             raise ValueError("Certificate doesn't match public key")
151         if libcrypto.CMS_add1_signer(self.ptr, cert.cert, pkey.key,
152                                           digest_type.digest, flags) is None:
153             raise CMSError("adding signer")
154         if flags & Flags.REUSE_DIGEST == 0:
155             if data is not None:
156                 bio = Membio(data)
157                 biodata = bio.bio
158             else:
159                 biodata = None
160             res = libcrypto.CMS_final(self.ptr, biodata, None, flags)
161             if res <= 0:
162                 raise CMSError("Cannot finalize CMS")
163     def verify(self, store, flags, data=None, certs=None):
164         """
165         Verifies signature under CMS message using trusted cert store
166
167         @param store -  X509Store object with trusted certs
168         @param flags - OR-ed combination of flag consants
169         @param data - message data, if messge has detached signature
170         param certs - list of certificates to use during verification
171                 If Flags.NOINTERN is specified, these are only
172                 sertificates to search for signing certificates
173         @returns True if signature valid, False otherwise
174         """
175         bio = None
176         if data != None:
177             bio_obj = Membio(data)
178             bio = bio_obj.bio
179         if certs is not None and len(certs) > 0:
180             certstack = StackOfX509(certs)
181         else:
182             certstack = None
183         res = libcrypto.CMS_verify(self.ptr, certstack, store.store, bio,
184                                    None, flags)
185         return res > 0
186
187     @property
188     def signers(self):
189         """
190         Return list of signer's certificates
191         """
192         signerlist = libcrypto.CMS_get0_signers(self.ptr)
193         if signerlist is None:
194             raise CMSError("Cannot get signers")
195         return StackOfX509(ptr=signerlist, disposable=False)
196
197     @property
198     def data(self):
199         """
200         Returns signed data if present in the message
201         """
202         bio = Membio()
203         if not libcrypto.CMS_verify(self.ptr, None, None, None, bio.bio,
204                                     Flags.NO_VERIFY):
205             raise CMSError("extract data")
206         return str(bio)
207
208     def addcert(self, cert):
209         """
210         Adds a certificate (probably intermediate CA) to the SignedData
211         structure
212         """
213         if libcrypto.CMS_add1_cert(self.ptr, cert.cert) <= 0:
214             raise CMSError("Cannot add cert")
215     def addcrl(self, crl):
216         """
217         Adds a CRL to the signed data structure
218         """
219         raise NotImplementedError
220     @property
221     def certs(self):
222         """
223         List of the certificates contained in the structure
224         """
225         certstack = libcrypto.CMS_get1_certs(self.ptr)
226         if certstack is None:
227             raise CMSError("getting certs")
228         return StackOfX509(ptr=certstack, disposable=True)
229     @property
230     def crls(self):
231         """
232         List of the CRLs contained in the structure
233         """
234         raise NotImplementedError
235
236 class EnvelopedData(CMSBase):
237     """
238     Represents EnvelopedData CMS, i.e. message encrypted with session
239     keys, encrypted with recipient's public keys
240     """
241     @staticmethod
242     def create(recipients, data, cipher, flags=0):
243         """
244         Creates and encrypts message
245         @param recipients - list of X509 objects
246         @param data - contents of the message
247         @param cipher - CipherType object
248         @param flags - flag
249         """
250         recp = StackOfX509(recipients)
251         bio = Membio(data)
252         cms_ptr = libcrypto.CMS_encrypt(recp.ptr, bio.bio, cipher.cipher,
253                                         flags)
254         if cms_ptr is None:
255             raise CMSError("encrypt EnvelopedData")
256         return EnvelopedData(cms_ptr)
257
258     def decrypt(self, pkey, cert, flags=0):
259         """
260         Decrypts message
261         @param pkey - private key to decrypt
262         @param cert - certificate of this private key (to find
263             neccessary RecipientInfo
264         @param flags - flags
265         @returns - decrypted data
266         """
267         if not pkey.cansign:
268             raise ValueError("Specified keypair has no private part")
269         if pkey != cert.pubkey:
270             raise ValueError("Certificate doesn't match private key")
271         bio = Membio()
272         res = libcrypto.CMS_decrypt(self.ptr, pkey.key, cert.cert, None,
273                                     bio.bio, flags)
274         if res <= 0:
275             raise CMSError("decrypting CMS")
276         return str(bio)
277
278 class EncryptedData(CMSBase):
279     """
280     Represents encrypted data CMS structure, i.e. encrypted
281     with symmetric key, shared by sender and recepient.
282     """
283     @staticmethod
284     def create(data, cipher, key, flags=0):
285         """
286         Creates an EncryptedData message.
287         @param data data to encrypt
288         @param cipher cipher.CipherType object represening required
289                 cipher type
290         @param key - byte array used as simmetic key
291         @param flags - OR-ed combination of Flags constant
292         """
293         bio = Membio(data)
294         ptr = libcrypto.CMS_EncryptedData_encrypt(bio.bio, cipher.cipher,
295                                                   key, len(key), flags)
296         if ptr is None:
297             raise CMSError("encrypt data")
298         return EncryptedData(ptr)
299
300     def decrypt(self, key, flags=0):
301         """
302         Decrypts encrypted data message
303         @param key - symmetic key to decrypt
304         @param flags - OR-ed combination of Flags constant
305         """
306         bio = Membio()
307         if libcrypto.CMS_EncryptedData_decrypt(self.ptr, key, len(key), None,
308                                                bio.bio, flags) <= 0:
309             raise CMSError("decrypt data")
310         return str(bio)
311
312 __all__ = ['CMS', 'CMSError', 'Flags', 'SignedData', 'EnvelopedData',
313            'EncryptedData']
314
315 libcrypto.CMS_get0_type.restype = c_void_p
316 libcrypto.CMS_get0_type.argtypes = (c_void_p,)
317 libcrypto.CMS_add1_cert.restype = c_int
318 libcrypto.CMS_add1_cert.argtypes = (c_void_p, c_void_p)
319 libcrypto.CMS_decrypt.restype = c_int
320 libcrypto.CMS_decrypt.argtypes = (c_void_p, c_void_p, c_void_p,
321                                   c_void_p, c_void_p, c_uint)
322 libcrypto.CMS_encrypt.restype = c_void_p
323 libcrypto.CMS_encrypt.argtypes = (c_void_p, c_void_p, c_void_p, c_uint)
324 libcrypto.CMS_EncryptedData_decrypt.restype = c_int
325 libcrypto.CMS_EncryptedData_decrypt.argtypes = (c_void_p, c_char_p, c_size_t,
326                                                 c_void_p, c_void_p, c_uint)
327 libcrypto.CMS_EncryptedData_encrypt.restype = c_void_p
328 libcrypto.CMS_EncryptedData_encrypt.argtypes = (c_void_p, c_void_p, c_char_p,
329                                                 c_size_t, c_uint)
330 libcrypto.CMS_final.restype = c_int
331 libcrypto.CMS_final.argtypes = (c_void_p, c_void_p, c_void_p, c_uint)
332 libcrypto.CMS_get0_signers.restype = c_void_p
333 libcrypto.CMS_get0_signers.argtypes = (c_void_p, )
334 libcrypto.CMS_get1_certs.restype = c_void_p
335 libcrypto.CMS_get1_certs.argtypes = (c_void_p, )
336 libcrypto.CMS_sign.restype = c_void_p
337 libcrypto.CMS_sign.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p, c_uint)
338 libcrypto.CMS_add1_signer.restype = c_void_p
339 libcrypto.CMS_add1_signer.argtypes = (c_void_p, c_void_p, c_void_p,
340                                            c_void_p, c_uint)
341 libcrypto.CMS_verify.restype = c_int
342 libcrypto.CMS_verify.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p,
343                                  c_void_p, c_int)
344 libcrypto.d2i_CMS_bio.restype = c_void_p
345 libcrypto.d2i_CMS_bio.argtypes = (c_void_p, POINTER(c_void_p))
346 libcrypto.i2d_CMS_bio.restype = c_int
347 libcrypto.i2d_CMS_bio.argtypes = (c_void_p, c_void_p)
348 libcrypto.PEM_read_bio_CMS.restype = c_void_p
349 libcrypto.PEM_read_bio_CMS.argtypes = (c_void_p, POINTER(c_void_p),
350                                        c_void_p, c_void_p)
351 libcrypto.PEM_write_bio_CMS.restype = c_int
352 libcrypto.PEM_write_bio_CMS.argtypes = (c_void_p, c_void_p)