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