]> www.wagner.pp.ru Git - oss/ctypescrypto.git/blob - ctypescrypto/cms.py
CMS verification implemented
[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):
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                 """
108                 if not pkey.cansign:
109                         raise ValueError("Specified keypair has no private part")
110                 if cert.pubkey!=pkey:
111                         raise ValueError("Certificate doesn't match public key")
112                 b=Membio(data)
113                 ptr=libcrypto.CMS_sign(cert.cert,pkey.ptr,None,b.bio,flags)
114                 if ptr is None:
115                         raise CMSError("signing message")
116                 return SignedData(ptr)
117         def sign(self,cert,pkey,md=None,data=None,flags=Flags.BINARY):
118                 """
119                         Adds another signer to already signed message
120                         @param cert - signer's certificate
121                         @param pkey - signer's private key
122                         @param md - message digest to use as DigestType object 
123                                 (if None - default for key would be used)
124                         @param data - data to sign (if detached and
125                                         Flags.REUSE_DIGEST is not specified)
126                         @param flags - ORed combination of Flags consants
127                 """
128                 if not pkey.cansign:
129                         raise ValueError("Specified keypair has no private part")
130                 if cert.pubkey!=pkey:
131                         raise ValueError("Certificate doesn't match public key")
132                 p1=libcrypto.CMS_sign_add1_Signer(self.ptr,cert.cert,pkey.ptr,
133                         md.digest,flags)
134                 if p1 is None:
135                         raise CMSError("adding signer")
136                 if flags & Flags.REUSE_DIGEST==0:
137                         if data is not None:
138                                 b=Membio(data)
139                                 biodata=b.bio
140                         else:
141                                 biodata=None
142                         res= libcrypto.CMS_final(self.ptr,biodata,None,flags)
143                         if res<=0:
144                                 raise CMSError
145         def verify(self,store,flags,data=None):
146                 """
147                 Verifies signature under CMS message using trusted cert store
148
149                 @param store -  X509Store object with trusted certs
150                 @param flags - OR-ed combination of flag consants
151                 @param data - message data, if messge has detached signature
152                 @returns True if signature valid, False otherwise
153                 """
154                 bio=None
155                 if data!=None:
156                         b=Membio(data)
157                         bio=b.bio
158                 res=libcrypto.CMS_verify(self.ptr,None,store.store,bio,None,flags)
159                 return res>0
160         @property       
161         def signers(self,store=None):
162                 """
163                 Return list of signer's certificates
164                 """
165                 raise NotImplementedError
166         @property
167         def data(self):
168                 """
169                 Returns signed data if present in the message
170                 """
171                 b=Membio()
172                 if not libcrypto.CMS_verify(self.ptr,None,None,None,b.bio,Flags.NO_VERIFY):
173                         raise CMSError("extract data")
174                 return str(b)
175         def addcert(self,cert):
176                 """
177                 Adds a certificate (probably intermediate CA) to the SignedData
178                 structure
179                 """
180                 raise NotImplementedError
181         def addcrl(self,crl):
182                 """
183                 Adds a CRL to the signed data structure
184                 """
185                 raise NotImplementedError
186         @property
187         def certs(self):
188                 """
189                 List of the certificates contained in the structure
190                 """
191                 raise NotImplementedError
192         @property
193         def crls(self):
194                 """
195                 List of the CRLs contained in the structure
196                 """
197                 raise NotImplementedError
198
199 class EnvelopedData(CMSBase):
200         @staticmethod
201         def create(recipients,data,cipher,flags=0):
202                 """
203                 Creates and encrypts message
204                 @param recipients - list of X509 objects
205                 @param data - contents of the message
206                 @param cipher - CipherType object
207                 @param flags - flag
208                 """
209                 # Need to be able to handle OPENSSL stacks
210                 raise NotImplementedError       
211         def decrypt(self,pkey,cert,flags=0):
212                 """
213                 Decrypts message
214                 @param pkey - private key to decrypt
215                 @param cert - certificate of this private key (to find
216                         neccessary RecipientInfo
217                 @param flags - flags
218                 @returns - decrypted data
219                 """
220                 if not pkey.cansign:
221                         raise ValueError("Specified keypair has no private part")
222                 if pkey != cert.pubkey:
223                         raise ValueError("Certificate doesn't match private key")
224                 b=Membio()
225                 res=libcrypto.CMS_decrypt(self.ptr,pkey.ptr,cert.ccert,None,b.bio,flags)
226                 if res<=0:
227                         raise CMSError("decrypting CMS")
228                 return str(b)
229
230 class EncryptedData(CMSBase):
231         @staticmethod
232         def create(data,cipher,key,flags=0):
233                 """
234                 Creates an EncryptedData message.
235                 @param data data to encrypt
236                 @param cipher cipher.CipherType object represening required
237                                 cipher type
238                 @param key - byte array used as simmetic key
239                 @param flags - OR-ed combination of Flags constant
240                 """
241                 b=Membio(data)
242                 ptr=libcrypto.CMS_EncryptedData_encrypt(b.bio,cipher.cipher_type,key,len(key),flags)
243                 if ptr is None:
244                         raise CMSError("encrypt data")
245                 return EncryptedData(ptr)
246         def decrypt(self,key,flags=0):
247                 """
248                 Decrypts encrypted data message
249                 @param key - symmetic key to decrypt
250                 @param flags - OR-ed combination of Flags constant
251                 """
252                 b=Membio()
253                 if libcrypto.CMS_EncryptedData_decrypt(self.ptr,key,len(key),None,
254                         b.bio,flags)<=0:
255                                 raise CMSError("decrypt data")
256                 return str(b)
257
258                 
259
260 libcrypto.CMS_verify.restype=c_int
261 libcrypto.CMS_verify.argtypes=(c_void_p,c_void_p,c_void_p,c_void_p,c_void_p,c_int)