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