2 Implements operations with CMS EnvelopedData and SignedData messages
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)
8 Each of these objects contains create() static method which is used to
9 create it from raw data and neccessary certificates.
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
20 # Check for neccesary functionality in libcrypto
21 # LibreSSL fails this check
23 if not hasattr(libcrypto,"CMS_decrypt"):
24 raise OSError("libcrypto lacks CMS functionality. Try using different libcrypto")
26 class CMSError(LibCryptoError):
28 Exception which is raised when error occurs
34 Constants for flags passed to the CMS methods.
41 NO_SIGS = NO_CONTENT_VERIFY | NO_ATTR_VERIFY
43 NO_SIGNER_CERT_VERIFY = 0x20
56 DEBUG_DECRYPT = 0x20000
58 def CMS(data, format="PEM"):
60 Parses CMS data and returns either SignedData or EnvelopedData
65 ptr = libcrypto.PEM_read_bio_CMS(bio.bio, None, None, None)
67 ptr = libcrypto.d2i_CMS_bio(bio.bio, 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)
78 raise NotImplementedError("cannot handle "+typeoid.shortname())
80 class CMSBase(object):
82 Common ancessor for all CMS types.
83 Implements serializatio/deserialization
85 def __init__(self, ptr=None):
89 Serialize in DER format
92 if not libcrypto.i2d_CMS_bio(bio.bio, self.ptr):
93 raise CMSError("writing CMS to DER")
98 Serialize in PEM format
101 if not libcrypto.PEM_write_bio_CMS(bio.bio, self.ptr):
102 raise CMSError("writing CMS to PEM")
106 #pylint: disable=R0921
107 class SignedData(CMSBase):
109 Represents signed message (signeddata CMS type)
112 def create(data, cert, pkey, flags=Flags.BINARY, certs=None):
114 Creates SignedData message by signing data with pkey and
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
124 raise ValueError("Specified keypair has no private part")
125 if cert.pubkey != pkey:
126 raise ValueError("Certificate doesn't match public key")
128 if certs is not None and len(certs) > 0:
129 certstack = StackOfX509(certs).ptr
132 ptr = libcrypto.CMS_sign(cert.cert, pkey.key, certstack, bio.bio, flags)
134 raise CMSError("signing message")
135 return SignedData(ptr)
136 def sign(self, cert, pkey, digest_type=None, data=None, flags=Flags.BINARY):
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
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:
160 res = libcrypto.CMS_final(self.ptr, biodata, None, flags)
162 raise CMSError("Cannot finalize CMS")
163 def verify(self, store, flags, data=None, certs=None):
165 Verifies signature under CMS message using trusted cert store
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
177 bio_obj = Membio(data)
179 if certs is not None and len(certs) > 0:
180 certstack = StackOfX509(certs)
183 res = libcrypto.CMS_verify(self.ptr, certstack, store.store, bio,
190 Return list of signer's certificates
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)
200 Returns signed data if present in the message
203 if not libcrypto.CMS_verify(self.ptr, None, None, None, bio.bio,
205 raise CMSError("extract data")
208 def addcert(self, cert):
210 Adds a certificate (probably intermediate CA) to the SignedData
213 if libcrypto.CMS_add1_cert(self.ptr, cert.cert) <= 0:
214 raise CMSError("Cannot add cert")
215 def addcrl(self, crl):
217 Adds a CRL to the signed data structure
219 raise NotImplementedError
223 List of the certificates contained in the structure
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)
232 List of the CRLs contained in the structure
234 raise NotImplementedError
236 class EnvelopedData(CMSBase):
238 Represents EnvelopedData CMS, i.e. message encrypted with session
239 keys, encrypted with recipient's public keys
242 def create(recipients, data, cipher, flags=0):
244 Creates and encrypts message
245 @param recipients - list of X509 objects
246 @param data - contents of the message
247 @param cipher - CipherType object
250 recp = StackOfX509(recipients)
252 cms_ptr = libcrypto.CMS_encrypt(recp.ptr, bio.bio, cipher.cipher,
255 raise CMSError("encrypt EnvelopedData")
256 return EnvelopedData(cms_ptr)
258 def decrypt(self, pkey, cert, flags=0):
261 @param pkey - private key to decrypt
262 @param cert - certificate of this private key (to find
263 neccessary RecipientInfo
265 @returns - decrypted data
268 raise ValueError("Specified keypair has no private part")
269 if pkey != cert.pubkey:
270 raise ValueError("Certificate doesn't match private key")
272 res = libcrypto.CMS_decrypt(self.ptr, pkey.key, cert.cert, None,
275 raise CMSError("decrypting CMS")
278 class EncryptedData(CMSBase):
280 Represents encrypted data CMS structure, i.e. encrypted
281 with symmetric key, shared by sender and recepient.
284 def create(data, cipher, key, flags=0):
286 Creates an EncryptedData message.
287 @param data data to encrypt
288 @param cipher cipher.CipherType object represening required
290 @param key - byte array used as simmetic key
291 @param flags - OR-ed combination of Flags constant
294 ptr = libcrypto.CMS_EncryptedData_encrypt(bio.bio, cipher.cipher,
295 key, len(key), flags)
297 raise CMSError("encrypt data")
298 return EncryptedData(ptr)
300 def decrypt(self, key, flags=0):
302 Decrypts encrypted data message
303 @param key - symmetic key to decrypt
304 @param flags - OR-ed combination of Flags constant
307 if libcrypto.CMS_EncryptedData_decrypt(self.ptr, key, len(key), None,
308 bio.bio, flags) <= 0:
309 raise CMSError("decrypt data")
312 __all__ = ['CMS', 'CMSError', 'Flags', 'SignedData', 'EnvelopedData',
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,
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,
341 libcrypto.CMS_verify.restype = c_int
342 libcrypto.CMS_verify.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p,
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),
351 libcrypto.PEM_write_bio_CMS.restype = c_int
352 libcrypto.PEM_write_bio_CMS.argtypes = (c_void_p, c_void_p)